Compare commits

..

12 Commits

Author SHA1 Message Date
Peter Li c0d923cbed macos inputs 2026-09-18 16:30:35 -07:00
Peterino2 85acfb5b67 Implement Linux software context menus on Wayland 2026-09-18 15:32:04 -07:00
Peterino2 0d2c8eedf9 Add a pinned 16px Roboto bitmap and tiny Linux software canvas 2026-09-18 15:21:15 -07:00
Peterino2 b0da270a67 Implement Plasma app menu export, activation and local accelerators 2026-09-18 14:33:40 -07:00
Peterino2 fdbc99a274 Vendor KDE appmenu protocol and runtime D-Bus export declarations 2026-09-18 14:24:34 -07:00
Peterino2 c1c2de8c4b Harden input ordering, portal activation and portable Linux builds 2026-09-18 14:12:08 -07:00
Peterino2 5623376284 Implement Wayland input and Linux desktop services with isolated fixtures 2026-09-18 14:01:02 -07:00
Peterino2 fa3959e221 Vendor pinned XKB and D-Bus declarations and desktop protocols 2026-09-18 13:36:53 -07:00
Peterino2 7a7a3493d6 Preserve READY ordering after successful native initialization
Reserve the lifecycle event before startup and publish it first only after startup succeeds. This keeps initial macOS and Windows preferences behind READY while failed Wayland initialization emits no READY. Verified 18 core tests, Plasma native lifecycle/pixel tests, and Windows compilation.
2026-09-18 13:19:55 -07:00
Peterino2 685985e9cd Implement Plasma Wayland window and EGL foundation
Add runtime-loaded Wayland/EGL, asynchronous xdg-shell windows, KWin server decorations, app identity, scaling metrics, frame synchronization, remapping and POSIX file URIs. Keep unsupported input and desktop-service capabilities false.

Add pinned optional Nix tooling, portable ELF auditing, and native lifecycle/startup/disconnect tests. Verified Plasma/Mesa GL 4.6 pixel checks, hide/show and cancellation lifetimes, 17 core tests, Windows compilation, and x86-64 glibc 2.35 release output without desktop link dependencies.
2026-09-18 13:17:50 -07:00
Peterino2 afed52d567 Vendor pinned Linux ABI headers and Wayland protocol bindings
Record the Plasma-first backend plan and provide checksum-verified maintainer refresh tooling. Ordinary builds will consume checked-in declarations and generated marshalling code. Verified hashes and byte-identical regeneration with wayland-scanner 1.24.0.
2026-09-18 12:54:17 -07:00
Peter Li 8a1f8fbd28 new header parser and generator 2026-09-18 11:29:02 -07:00
150 changed files with 48716 additions and 468 deletions

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
// MIT License
//
// Copyright (c) 2022 Peter Li
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

103
README.md
View File

@ -9,11 +9,57 @@ 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 # zig-out/lib/libuhdk.dylib and headers
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
@ -41,7 +87,7 @@ exe.root_module.addImport("uhdk", uhdk.module("uhdk"));
```
`@import("uhdk")` provides translated public C declarations, including the bundled
OpenGL headers, and links the library and required system frameworks. The
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.
@ -49,10 +95,24 @@ must arrange its runtime location/rpath.
## C interface
- [uhdk.h](include/uhdk.h): desktop API, lifecycle, ownership, and threading contracts.
- [uhdk_opengl.h](include/uhdk_opengl.h): GLAD 2 OpenGL 3.3 core declarations and per-context loader table.
- [uhdk_native.h](include/uhdk_native.h): optional platform interoperability.
- [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.
@ -72,9 +132,42 @@ 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` includes `uhdk_profile.h`: scope, frame, thread-name and named allocation/
`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

View File

@ -2,7 +2,7 @@ const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
if (target.result.os.tag != .macos and target.result.os.tag != .windows) @panic("Uhdk supports macOS and Windows.");
if (target.result.os.tag != .macos and target.result.os.tag != .windows and target.result.os.tag != .linux) @panic("Uhdk supports macOS, Windows and Linux.");
const optimize = b.standardOptimizeOption(.{});
const shared = b.option(bool, "shared", "Build libuhdk as a shared library instead of static") orelse false;
const tracy = b.option(bool, "tracy", "Emit Tracy 0.14.1 on-demand hooks; consumer must link the client") orelse false;
@ -24,13 +24,16 @@ pub fn build(b: *std.Build) void {
const check_step = b.step("check", "Compile library and core tests without running them");
check_step.dependOn(&lib.step);
check_step.dependOn(&unit.step);
if (target.result.os.tag == .linux) @import("src/linux/build.zig").addTests(b, lib, target, optimize, check_step);
const unit_run = b.addRunArtifact(unit);
b.step("test", "Run core library tests").dependOn(&unit_run.step);
const public_c = b.addTranslateC(.{
.root_source_file = b.path("include/uhdk_opengl.h"),
.root_source_file = b.path("include/uhdk.h"),
.target = target,
.optimize = optimize,
});
public_c.defineCMacro("UHDK_ENABLE_OPENGL", "1");
public_c.defineCMacro("UHDK_ENABLE_NATIVE", "1");
public_c.addIncludePath(b.path("include"));
if (shared) public_c.defineCMacro("UHDK_SHARED", "1");
const public_module = b.addModule("uhdk", .{
@ -77,6 +80,8 @@ fn createLibraryNamed(b: *std.Build, target: std.Build.ResolvedTarget, optimize:
module.linkFramework("Cocoa", .{});
module.linkFramework("OpenGL", .{});
module.linkFramework("CoreServices", .{});
} else if (target.result.os.tag == .linux) {
@import("src/linux/build.zig").configure(b, module, target, optimize);
} else {
module.addCSourceFile(.{ .file = b.path("src/windows/native.c"), .flags = &.{ "-std=c11", "-Wall", "-Wextra", "-Werror", "-DUNICODE", "-D_UNICODE" } });
for ([_][]const u8{ "user32", "gdi32", "opengl32", "shell32", "ole32", "uuid", "imm32", "comctl32", "shlwapi", "advapi32" }) |name| module.linkSystemLibrary(name, .{});

View File

@ -4,5 +4,5 @@
.version = "0.1.0",
.minimum_zig_version = "0.16.0",
.dependencies = .{},
.paths = .{ "build.zig", "build.zig.zon", "src", "include", "README.md" },
.paths = .{ "build.zig", "build.zig.zon", "src", "include", "README.md", "shell.nix" },
}

View File

@ -2,7 +2,8 @@
## GLAD 2.0.8
`uhdk_opengl.h` includes the generated `glad/gl.h`: OpenGL 3.3 core, no
`uhdk.h` with `UHDK_ENABLE_OPENGL` includes the generated `glad/gl.h`:
OpenGL 3.3 core, no
extensions, header-only output, explicit per-context tables (`--mx`), and no
platform loader. `src/glad.c` compiles its implementation into libuhdk. Applications
load a `GladGLContext` through their adapter for `uhdk_gl_context_get_proc`.
@ -27,7 +28,7 @@ No generator or Python installation is required to build Uhdk.
## Original Khronos headers
Retained unmodified for consumers choosing their own loader. Do not combine
`GL/glcorearb.h` with `glad/gl.h` or `uhdk_opengl.h` in a translation unit.
`GL/glcorearb.h` with `glad/gl.h` or `UHDK_ENABLE_OPENGL` in a translation unit.
License notices are preserved in each file.
- `include/GL/glcorearb.h`

View File

@ -2,13 +2,20 @@
#define UHDK_H_INCLUDED
/*
* Uhdk desktop platform API — v0.1, macOS and Windows backends.
* Uhdk desktop platform API — v0.1, macOS, Windows and Linux backends.
*
* Implementation: Zig. ABI: C. Header baseline: C99 (also usable from C++).
* No libc runtime objects or allocation contract.
* <stdint.h> supplies types only; this header requires no libc function calls.
* Native OS frameworks may themselves depend on system runtimes.
*
* Header configuration (define before the first include):
* - UHDK_ENABLE_OPENGL: include bundled GLAD types and per-context loader API.
* - UHDK_ENABLE_NATIVE: declare the experimental borrowed native-handle API.
* - UHDK_PROFILE_BACKEND: quoted consumer instrumentation adapter header.
* Presence enables each feature flag; leave it undefined to disable it.
* These select declarations only, not library backend/build features.
*
* Naming:
* - Types: uhdk_type; functions: uhdk_object_verb; constants: UHDK_CATEGORY_VALUE.
* - create/destroy allocate and dispose unique objects; retain/release manage
@ -27,8 +34,10 @@
* - NULL is allowed for a span's pointer only when its length is zero.
* - Input spans/descriptors are copied before return unless documented otherwise.
* - No allocator ownership crosses the ABI. Release objects through Uhdk.
* - Event spans are borrowed until the next poll or app destruction. Copy spans
* or retain files/menus to keep them. Each poll returns an immutable batch.
* - Event batches and spans are borrowed until the next valid poll on the
* application thread, or app destruction (whichever comes first). Copy spans
* or retain files/menus to keep them. Every such poll resets storage, including empty/error polls.
* Rendering does not reset storage. Each poll returns an immutable batch.
* - create/run/destroy belong to OS main. run invokes one application entry on
* a second thread. All other calls belong to that application thread except
* app_wake, which is thread-safe. Native work is queued to the main thread.
@ -44,11 +53,33 @@
* except documented buffer-size queries.
* - The application owns rendering, document state, file encoding and layout.
* Uhdk provides optional OpenGL 3.3 core context management. Other renderers
* may attach through experimental uhdk_native.h; drawing remains app-owned.
* may attach through the experimental native-handle API; drawing remains app-owned.
*/
#include <stdint.h>
#include "uhdk_profile.h"
/* Optional, consumer-owned instrumentation; no exported UHDK ABI or dependency.
* Define UHDK_PROFILE_BACKEND to a quoted adapter header before including uhdk.h.
* That header implements the macros below. Without it, arguments are not evaluated.
* Names (zone, thread, pool) must be string literals or have static lifetime.
* C zones: UHDK_PROFILE_BEGIN(token, "name"); ... UHDK_PROFILE_END(token);
* End on the same thread, in reverse nesting order, including early exits.
* C++ may use UHDK_PROFILE_SCOPE("name") for automatic scope exit.
* Memory: report successful non-null allocations, then free BEFORE releasing
* storage. A successful resize is free + alloc; failed allocations emit nothing.
* A pointer must use the same pool name for allocation and free. These hooks do
* not allocate/free memory themselves, intercept malloc, or transfer ownership.
*/
#ifdef UHDK_PROFILE_BACKEND
#include UHDK_PROFILE_BACKEND
#else
#define UHDK_PROFILE_BEGIN(token, name) ((void)0)
#define UHDK_PROFILE_END(token) ((void)0)
#define UHDK_PROFILE_SCOPE(name) ((void)0)
#define UHDK_PROFILE_FRAME() ((void)0)
#define UHDK_PROFILE_THREAD(name) ((void)0)
#define UHDK_PROFILE_ALLOC(ptr, size, pool) ((void)0)
#define UHDK_PROFILE_FREE(ptr, pool) ((void)0)
#endif
#ifndef UHDK_API
#if defined(_WIN32) && defined(UHDK_SHARED)
@ -185,7 +216,7 @@ typedef struct uhdk_event_list {
} uhdk_event_list;
#define UHDK_WAIT_FOREVER UINT64_MAX
typedef void(UHDK_CALL *uhdk_app_main_fn)(uhdk_app *, void *user);
typedef void(UHDK_CALL *uhdk_app_main_fn)(uhdk_app *app, void *user);
typedef struct uhdk_app_desc {
uint32_t struct_size;
uint32_t abi_version;
@ -196,27 +227,37 @@ typedef struct uhdk_app_desc {
} uhdk_app_desc;
UHDK_API uint32_t UHDK_CALL uhdk_get_abi_version(void);
UHDK_API uhdk_status UHDK_CALL uhdk_app_create(const uhdk_app_desc *, uhdk_app **out);
UHDK_API uhdk_status UHDK_CALL uhdk_app_run(uhdk_app *, uhdk_app_main_fn, void *user); /* Once. */
UHDK_API uhdk_status UHDK_CALL uhdk_app_create(const uhdk_app_desc *desc, uhdk_app **out);
UHDK_API uhdk_status UHDK_CALL uhdk_app_run(
uhdk_app *app,
uhdk_app_main_fn main_fn,
void *user
); /* Once. */
/* Zero timeout does not wait; finite timeouts use monotonic nanoseconds.
* STOPPED is delivered once; later polls return UNAVAILABLE. */
UHDK_API uhdk_status UHDK_CALL uhdk_app_poll_events(uhdk_app *, uint64_t timeout_ns,
uhdk_event_list *out);
UHDK_API void UHDK_CALL uhdk_app_quit(uhdk_app *); /* Accept/request shutdown. */
UHDK_API void UHDK_CALL uhdk_app_wake(uhdk_app *); /* Thread-safe, coalesced WAKE event. */
UHDK_API uhdk_status UHDK_CALL uhdk_app_poll_events(
uhdk_app *app,
uint64_t timeout_ns,
uhdk_event_list *out
);
UHDK_API void UHDK_CALL uhdk_app_quit(uhdk_app *app); /* Accept/request shutdown. */
UHDK_API void UHDK_CALL uhdk_app_wake(uhdk_app *app); /* Thread-safe, coalesced WAKE event. */
/* Monotonic nanoseconds since app creation, in the same clock as event.time_ns. */
UHDK_API uint64_t UHDK_CALL uhdk_app_get_time_ns(uhdk_app *);
UHDK_API uint64_t UHDK_CALL uhdk_app_get_time_ns(uhdk_app *app);
/* One pending deadline per app. Replaces any previous deadline; 0 cancels it.
* A past deadline queues WAKE at the next dispatch. On expiry the deadline is
* cleared before delivering WAKE. Explicit wakes may coalesce with expiry but
* do not cancel a future deadline. No event is delivered inline. App code maintains
* multiple timers by scheduling its earliest deadline. No real-time guarantee.
*/
UHDK_API uhdk_status UHDK_CALL uhdk_app_set_wake_deadline(uhdk_app *, uint64_t deadline_ns);
UHDK_API void UHDK_CALL uhdk_app_destroy(uhdk_app *); /* After run; releases child objects. */
UHDK_API uhdk_backend UHDK_CALL uhdk_app_get_backend(uhdk_app *);
UHDK_API uhdk_bool UHDK_CALL uhdk_app_has_capability(uhdk_app *, uhdk_capability);
UHDK_API uhdk_status UHDK_CALL uhdk_request_cancel(uhdk_app *, uhdk_request_id);
UHDK_API uhdk_status UHDK_CALL uhdk_app_set_wake_deadline(uhdk_app *app, uint64_t deadline_ns);
UHDK_API void UHDK_CALL uhdk_app_destroy(uhdk_app *app); /* After run; releases child objects. */
/* Linux: AUTO/false before successful startup; thereafter the selected backend
* and implemented, available mechanisms. Capabilities do not guarantee success
* for a particular operation. Other platforms retain their fixed backend. */
UHDK_API uhdk_backend UHDK_CALL uhdk_app_get_backend(uhdk_app *app);
UHDK_API uhdk_bool UHDK_CALL uhdk_app_has_capability(uhdk_app *app, uhdk_capability capability);
UHDK_API uhdk_status UHDK_CALL uhdk_request_cancel(uhdk_app *app, uhdk_request_id request_id);
/* Application-thread CPU execution time in nanoseconds, not elapsed wall time.
* Bracket user update/input processing with begin_tick/end_tick, AFTER polling
@ -250,9 +291,9 @@ typedef struct uhdk_app_stats {
float frames_per_second;
} uhdk_app_stats;
UHDK_API uhdk_status UHDK_CALL uhdk_app_begin_tick(uhdk_app *);
UHDK_API uhdk_status UHDK_CALL uhdk_app_end_tick(uhdk_app *);
UHDK_API uhdk_status UHDK_CALL uhdk_app_get_stats(uhdk_app *, uhdk_app_stats *out);
UHDK_API uhdk_status UHDK_CALL uhdk_app_begin_tick(uhdk_app *app);
UHDK_API uhdk_status UHDK_CALL uhdk_app_end_tick(uhdk_app *app);
UHDK_API uhdk_status UHDK_CALL uhdk_app_get_stats(uhdk_app *app, uhdk_app_stats *out);
/* Logical coordinates: top-left origin, x right, y down. Window geometry uses
* content-area units. Framebuffer dimensions are pixels; use reported sizes,
@ -283,6 +324,26 @@ typedef struct uhdk_window_desc {
uhdk_window *parent; /* Optional; must outlive child. */
} uhdk_window_desc;
/* Observation bits say which independent window facts are known. A clear bit
* means unknown, not false. Support depends on the backend/protocol version. */
typedef uint32_t uhdk_window_observation_flags;
enum {
UHDK_WINDOW_OBSERVE_MINIMIZED = 1u << 0,
UHDK_WINDOW_OBSERVE_RESIZING = 1u << 1,
UHDK_WINDOW_OBSERVE_OCCLUDED = 1u << 2,
UHDK_WINDOW_OBSERVE_SUSPENDED = 1u << 3
};
/* WINDOW_METRICS is the resize/state hook; compare snapshots for transitions.
* State describes geometry (normal/maximized/fullscreen), independently of
* minimized. Wayland does not report minimization; its observation bit is clear.
* visible is show/hide visibility, not an occlusion or minimization guarantee;
* on Wayland it is application intent. occluded is an optional rendering hint,
* not permission to stop processing events. suspended is a separate compositor
* hint that content is not ordinarily repainted, not proof of occlusion or
* minimization. Resizing is an advisory interactive size/move-loop
* hint, not a guaranteed transaction around every size change.
* Async size/state completion acknowledges submission; geometry arrives here. */
typedef struct uhdk_window_metrics {
uint32_t struct_size;
uint32_t framebuffer_width;
@ -292,27 +353,53 @@ typedef struct uhdk_window_metrics {
float scale;
uhdk_bool focused;
uhdk_bool visible;
uhdk_window_observation_flags observations;
uhdk_bool minimized;
uhdk_bool resizing;
uhdk_bool occluded;
uhdk_bool suspended;
} uhdk_window_metrics;
UHDK_API uhdk_status UHDK_CALL uhdk_window_create_async(uhdk_app *, const uhdk_window_desc *,
uhdk_request_id *out);
UHDK_API void UHDK_CALL uhdk_window_destroy(uhdk_window *);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_title_async(uhdk_window *, uhdk_text,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_size_async(uhdk_window *, uhdk_size,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_state_async(uhdk_window *, uhdk_window_state,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_visible_async(uhdk_window *, uhdk_bool,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL
uhdk_window_request_focus_async(uhdk_window *, uhdk_request_id *out); /* OS may decline. */
UHDK_API uhdk_status UHDK_CALL uhdk_window_get_metrics(uhdk_window *, uhdk_window_metrics *out);
UHDK_API void UHDK_CALL uhdk_window_invalidate(uhdk_window *); /* Coalesced redraw event. */
UHDK_API uhdk_status UHDK_CALL uhdk_window_create_async(
uhdk_app *app,
const uhdk_window_desc *desc,
uhdk_request_id *out
);
UHDK_API void UHDK_CALL uhdk_window_destroy(uhdk_window *window);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_title_async(
uhdk_window *window,
uhdk_text title,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_size_async(
uhdk_window *window,
uhdk_size size,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_state_async(
uhdk_window *window,
uhdk_window_state state,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_visible_async(
uhdk_window *window,
uhdk_bool visible,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_window_request_focus_async(
uhdk_window *window,
uhdk_request_id *out
); /* OS may decline. */
UHDK_API uhdk_status UHDK_CALL uhdk_window_get_metrics(
uhdk_window *window,
uhdk_window_metrics *out
);
UHDK_API void UHDK_CALL uhdk_window_invalidate(uhdk_window *window); /* Coalesced redraw event. */
/* Optional OpenGL context management (application/render thread).
* Include <uhdk_opengl.h> for bundled Khronos GL types, constants and PFNGL*
* function-pointer types. This header alone does not introduce GL names.
* Define UHDK_ENABLE_OPENGL before including this header for bundled Khronos
* GL types, constants, and PFNGL* function-pointer types. Without that flag,
* no GL names are introduced.
*
* Creation requires UHDK_WINDOW_OPENGL and guarantees desktop OpenGL >= 3.3,
* core profile, with GLSL >= 3.30. macOS selects its 4.1 core profile because
@ -341,28 +428,40 @@ UHDK_API void UHDK_CALL uhdk_window_invalidate(uhdk_window *); /* Coalesced redr
* type before calling. GL uses stdcall on Win32; Uhdk exports still use C ABI. */
typedef void(UHDK_GL_CALL *uhdk_gl_proc)(void);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_create_async(uhdk_window *, uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_begin_frame(uhdk_gl_context *);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_end_frame(uhdk_gl_context *);
UHDK_API void UHDK_CALL uhdk_gl_context_destroy(uhdk_gl_context *);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_make_current(uhdk_gl_context *);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_clear_current(uhdk_app *);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_create_async(
uhdk_window *window,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_begin_frame(uhdk_gl_context *context);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_end_frame(uhdk_gl_context *context);
UHDK_API void UHDK_CALL uhdk_gl_context_destroy(uhdk_gl_context *context);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_make_current(uhdk_gl_context *context);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_clear_current(uhdk_app *app);
/* Actual negotiated version, not the requested minimum. */
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_get_version(uhdk_gl_context *, uint32_t *out_major,
uint32_t *out_minor);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_swap_buffers(uhdk_gl_context *);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_get_version(
uhdk_gl_context *context,
uint32_t *out_major,
uint32_t *out_minor
);
/* Wayland returns UNAVAILABLE while hidden or awaiting mapping configuration. */
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_swap_buffers(uhdk_gl_context *context);
/* Explicitly set 0 (unsynchronized) or 1 (vsync); other values are invalid.
* No initial interval is guaranteed. Unsupported control returns UNSUPPORTED;
* compositor/driver policy may override presentation timing. */
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_set_swap_interval(uhdk_gl_context *,
int32_t interval);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_set_swap_interval(
uhdk_gl_context *context,
int32_t interval
);
/* Requires this context current. Accepts pointer+length ASCII GL symbol names.
* Missing symbols return NOT_FOUND. Pointers are valid until context destruction
* and must only be used with that context current. A non-NULL pointer alone does
* not prove extension/version support; check GL version/extension availability.
* Loads both platform-library exports and driver-provided GL entry points. */
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_get_proc(uhdk_gl_context *, uhdk_text name,
uhdk_gl_proc *out);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_get_proc(
uhdk_gl_context *context,
uhdk_text name,
uhdk_gl_proc *out
);
/* Keyboard events are not text. Physical key = USB HID keyboard-page usage;
* 0 means unknown. Logical key = Unicode scalar, or UHDK_KEY_* below.
@ -448,12 +547,18 @@ enum {
UHDK_POINTER_NORMAL = 0,
UHDK_POINTER_CAPTURE = 1
};
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_cursor_shape_async(uhdk_window *, uhdk_cursor_shape,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_cursor_shape_async(
uhdk_window *window,
uhdk_cursor_shape shape,
uhdk_request_id *out
);
/* Capture delivers selection-drag events outside the window. Focus loss or OS
* cancellation restores NORMAL and emits POINTER_MODE_CHANGED. */
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_pointer_mode_async(uhdk_window *, uhdk_pointer_mode,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_pointer_mode_async(
uhdk_window *window,
uhdk_pointer_mode mode,
uhdk_request_id *out
);
/* Text offsets/lengths are UTF-8 byte counts, always at scalar boundaries.
* surrounding_text is a bounded snapshot; document_offset locates it in the
@ -462,6 +567,11 @@ UHDK_API uhdk_status UHDK_CALL uhdk_window_set_pointer_mode_async(uhdk_window *,
* revision; do not discard a burst just because its first edit changed revision.
* acknowledged_input_id is the last applied text/edit input ID. Snapshots behind
* outstanding native edits are ignored; publish again after consuming the batch.
* On macOS, a semantic edit invalidates the native snapshot. Subsequent text
* input waits for an acknowledging snapshot, including when an edit is ignored.
* Acknowledge asynchronous edits (such as paste) only once their result is applied.
* Disabling text input discards deferred input. Raw key IDs are observational;
* acknowledge applied text/edit events, not raw keys that may precede them.
* Commit/preedit events carry text + an optional replacement range. A preedit
* updates temporary composition, never the permanent document by itself.
*/
@ -476,11 +586,19 @@ typedef struct uhdk_text_state {
uint64_t selection_len;
uhdk_rect caret; /* Window-local logical coordinates. */
} uhdk_text_state;
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_text_input_async(uhdk_window *, uhdk_bool enabled,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_text_state(uhdk_window *, const uhdk_text_state *);
UHDK_API uhdk_status UHDK_CALL uhdk_window_cancel_composition_async(uhdk_window *,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_text_input_async(
uhdk_window *window,
uhdk_bool enabled,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_text_state(
uhdk_window *window,
const uhdk_text_state *state
);
UHDK_API uhdk_status UHDK_CALL uhdk_window_cancel_composition_async(
uhdk_window *window,
uhdk_request_id *out
);
/* File references preserve native names, including non-UTF-8 POSIX paths.
* Display names are UTF-8. URI/native-path export is explicit and owned by the
@ -500,15 +618,25 @@ enum {
UHDK_FILE_URI = 1,
UHDK_FILE_NATIVE_PATH = 2
};
UHDK_API uhdk_status UHDK_CALL uhdk_file_create_from_native_path(uhdk_app *, uhdk_bytes path,
uhdk_file **out);
UHDK_API uhdk_status UHDK_CALL uhdk_file_create_from_uri(uhdk_app *, uhdk_text uri,
uhdk_file **out);
UHDK_API void UHDK_CALL uhdk_file_retain(uhdk_file *);
UHDK_API void UHDK_CALL uhdk_file_release(uhdk_file *);
UHDK_API uhdk_status UHDK_CALL uhdk_file_copy_representation(uhdk_file *, uhdk_file_representation,
uint8_t *buffer, uint64_t capacity,
uint64_t *out_len);
UHDK_API uhdk_status UHDK_CALL uhdk_file_create_from_native_path(
uhdk_app *app,
uhdk_bytes path,
uhdk_file **out
);
UHDK_API uhdk_status UHDK_CALL uhdk_file_create_from_uri(
uhdk_app *app,
uhdk_text uri,
uhdk_file **out
);
UHDK_API void UHDK_CALL uhdk_file_retain(uhdk_file *file);
UHDK_API void UHDK_CALL uhdk_file_release(uhdk_file *file);
UHDK_API uhdk_status UHDK_CALL uhdk_file_copy_representation(
uhdk_file *file,
uhdk_file_representation representation,
uint8_t *buffer,
uint64_t capacity,
uint64_t *out_len
);
typedef uint32_t uhdk_file_type;
enum {
@ -534,11 +662,17 @@ typedef struct uhdk_file_info {
* New POSIX files use mode 0600. No power-loss durability or conflict detection promise.
* No silent truncate fallback. Cancellation before rename leaves the target intact.
*/
UHDK_API uhdk_status UHDK_CALL uhdk_file_read_all_async(uhdk_file *, uint64_t max_bytes,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_file_write_all_async(uhdk_file *, uhdk_bytes,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_file_get_info_async(uhdk_file *, uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_file_read_all_async(
uhdk_file *file,
uint64_t max_bytes,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_file_write_all_async(
uhdk_file *file,
uhdk_bytes bytes,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_file_get_info_async(uhdk_file *file, uhdk_request_id *out);
typedef uint32_t uhdk_dialog_kind;
enum {
@ -564,29 +698,40 @@ typedef struct uhdk_file_dialog_desc {
const uhdk_file_filter *filters;
uint64_t filters_len;
} uhdk_file_dialog_desc;
UHDK_API uhdk_status UHDK_CALL uhdk_file_dialog_show_async(uhdk_app *,
const uhdk_file_dialog_desc *,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_file_dialog_show_async(
uhdk_app *app,
const uhdk_file_dialog_desc *desc,
uhdk_request_id *out
);
/* Plain-text clipboard only. Writes copy UTF-8 before returning; empty text is
* valid. Reads negotiate native text formats internally and complete with text
* borrowed until the next event poll. NOT_FOUND means no text format.
* borrowed for the event batch lifetime. NOT_FOUND means no text format.
* max_bytes bounds the UTF-8 result; excess returns BUFFER_TOO_SMALL, never a
* truncated string. A read remains async even when a backend can read instantly.
*/
UHDK_API uhdk_status UHDK_CALL uhdk_clipboard_write_text_async(uhdk_app *, uhdk_text,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_clipboard_read_text_async(uhdk_app *, uint64_t max_bytes,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_clipboard_write_text_async(
uhdk_app *app,
uhdk_text text,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_clipboard_read_text_async(
uhdk_app *app,
uint64_t max_bytes,
uhdk_request_id *out
);
/* Incoming file drops only. When enabled, Uhdk negotiates copy-only file drops
* and emits DROP_FILES with borrowed file references plus the drop position.
* Retain references to use after the next poll. Directory references are allowed;
* Retain references to use beyond the event batch lifetime. Directory references are allowed;
* the application decides whether to open them. No source deletion, outgoing
* drag, data offers, or application-visible drop-completion transaction.
*/
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_drop_enabled_async(uhdk_window *, uhdk_bool,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_drop_enabled_async(
uhdk_window *window,
uhdk_bool enabled,
uhdk_request_id *out
);
/* Menus are immutable snapshots. Command IDs are application-assigned, nonzero
* and unique within a menu tree. Shortcuts use logical keys + modifiers.
@ -623,26 +768,49 @@ typedef struct uhdk_menu_item {
uhdk_menu *submenu; /* Retained by the created parent menu. */
} uhdk_menu_item;
UHDK_API uhdk_status UHDK_CALL uhdk_menu_create(uhdk_app *, const uhdk_menu_item *, uint64_t len,
uhdk_menu **out);
UHDK_API uhdk_status UHDK_CALL uhdk_menu_set_item_flags_async(uhdk_menu *, uint64_t command_id,
uhdk_menu_flags,
uhdk_request_id *out);
UHDK_API void UHDK_CALL uhdk_menu_retain(uhdk_menu *);
UHDK_API void UHDK_CALL uhdk_menu_release(uhdk_menu *);
UHDK_API uhdk_status UHDK_CALL uhdk_app_set_menu_async(uhdk_app *, uhdk_menu *,
uhdk_request_id *out); /* NULL clears. */
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_menu_async(uhdk_window *, uhdk_menu *,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_menu_popup_async(uhdk_menu *, uhdk_window *, uhdk_point,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_app_set_icon_async(uhdk_app *, const uhdk_image *,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_menu_create(
uhdk_app *app,
const uhdk_menu_item *items,
uint64_t len,
uhdk_menu **out
);
UHDK_API uhdk_status UHDK_CALL uhdk_menu_set_item_flags_async(
uhdk_menu *menu,
uint64_t command_id,
uhdk_menu_flags flags,
uhdk_request_id *out
);
UHDK_API void UHDK_CALL uhdk_menu_retain(uhdk_menu *menu);
UHDK_API void UHDK_CALL uhdk_menu_release(uhdk_menu *menu);
UHDK_API uhdk_status UHDK_CALL uhdk_app_set_menu_async(
uhdk_app *app,
uhdk_menu *menu,
uhdk_request_id *out
); /* NULL clears. */
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_menu_async(
uhdk_window *window,
uhdk_menu *menu,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_menu_popup_async(
uhdk_menu *menu,
uhdk_window *window,
uhdk_point position,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_app_set_icon_async(
uhdk_app *app,
const uhdk_image *image,
uhdk_request_id *out
);
/* Successful shell completion means handed off to the OS, not that a target
* application finished loading. URLs are not shell command strings. */
UHDK_API uhdk_status UHDK_CALL uhdk_shell_open_url_async(uhdk_app *, uhdk_text url,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_shell_open_url_async(
uhdk_app *app,
uhdk_text url,
uhdk_request_id *out
);
typedef uint32_t uhdk_change_flags;
enum {
@ -663,7 +831,7 @@ enum {
* as remove/create; no rename pairing is promised. RESCAN_REQUIRED means inspect
* current state. Pending notifications are suppressed after destroy; already borrowed batches remain valid. */
UHDK_API uhdk_status UHDK_CALL uhdk_watch_create_async(uhdk_file *root, uhdk_request_id *out);
UHDK_API void UHDK_CALL uhdk_watch_destroy(uhdk_watch *);
UHDK_API void UHDK_CALL uhdk_watch_destroy(uhdk_watch *watch);
typedef uint32_t uhdk_appearance;
enum {
@ -678,7 +846,7 @@ typedef struct uhdk_preferences {
uhdk_bool reduced_motion;
uhdk_bool high_contrast;
} uhdk_preferences;
UHDK_API uhdk_status UHDK_CALL uhdk_app_get_preferences(uhdk_app *, uhdk_preferences *out);
UHDK_API uhdk_status UHDK_CALL uhdk_app_get_preferences(uhdk_app *app, uhdk_preferences *out);
/* Event payloads. Window is NULL for app-wide events. time_ns is monotonic time
* relative to app creation, not wall time. Do not persist event pointers.
@ -729,6 +897,19 @@ enum {
UHDK_REQUEST_FILE_INFO
};
/* Semantic requests, not edits performed by UHDK. The application owns text,
* grapheme/word boundaries, bidi/layout, selection, clipboard actions and history.
* LEFT/RIGHT are visual; BACKWARD/FORWARD follow logical document order.
* LINE uses visual lines; LEFT_END/RIGHT_END explicitly select their visual edge.
* LINE_START/END follow writing direction; PARAGRAPH uses document boundaries.
* PAGE moves the caret by a viewport; SCROLL moves the viewport without the caret.
* Deletion first removes the selection; otherwise it removes the indicated unit.
* NEWLINE requests a paragraph separator; LINE_BREAK requests a soft line break.
* TAB/BACKTAB may insert text or traverse focus, as decided by the application.
* CANCEL is delivered only after native composition handling declines/finishes it.
* Native default gestures vary: macOS uses AppKit selectors, Windows/Wayland
* use Ctrl-based editing defaults. Commands without customary platform gestures
* remain application-bindable intents, not invented global shortcuts. */
typedef uint32_t uhdk_edit_command;
enum {
UHDK_EDIT_COPY = 1,
@ -750,61 +931,65 @@ enum {
UHDK_EDIT_WORD_LEFT,
UHDK_EDIT_WORD_RIGHT,
UHDK_EDIT_NEWLINE,
UHDK_EDIT_TAB
UHDK_EDIT_TAB,
UHDK_EDIT_BACKTAB,
UHDK_EDIT_LINE_BREAK,
UHDK_EDIT_CANCEL,
UHDK_EDIT_DELETE_WORD_BACKWARD,
UHDK_EDIT_DELETE_WORD_FORWARD,
UHDK_EDIT_DELETE_LINE_START,
UHDK_EDIT_DELETE_LINE_END,
UHDK_EDIT_DELETE_PARAGRAPH_START,
UHDK_EDIT_DELETE_PARAGRAPH_END,
UHDK_EDIT_MOVE_BACKWARD,
UHDK_EDIT_MOVE_FORWARD,
UHDK_EDIT_WORD_BACKWARD,
UHDK_EDIT_WORD_FORWARD,
UHDK_EDIT_PARAGRAPH_START,
UHDK_EDIT_PARAGRAPH_END,
UHDK_EDIT_PAGE_UP,
UHDK_EDIT_PAGE_DOWN,
UHDK_EDIT_SCROLL_PAGE_UP,
UHDK_EDIT_SCROLL_PAGE_DOWN,
UHDK_EDIT_SCROLL_DOCUMENT_START,
UHDK_EDIT_SCROLL_DOCUMENT_END,
UHDK_EDIT_LINE_LEFT_END,
UHDK_EDIT_LINE_RIGHT_END
};
typedef struct uhdk_request_result {
uhdk_request_id id;
uhdk_request_kind kind;
uhdk_status status;
uhdk_text diagnostic; /* Optional; for humans, never parse for logic. */
union {
uhdk_file_list files; /* FILE_DIALOG */
uhdk_text text; /* CLIPBOARD_TEXT */
uhdk_window *window;
uhdk_gl_context *gl_context;
uhdk_watch *watch;
uhdk_bytes bytes;
uhdk_file_info info;
} value; /* Only valid on UHDK_STATUS_OK; SHELL/COMMAND/FILE_WRITE/MENU_POPUP have no payload. */
} uhdk_request_result;
struct uhdk_event {
uint32_t struct_size;
uhdk_event_type type;
uint64_t time_ns;
uint64_t input_id; /* Correlates raw key and native edit/text; zero for non-input. */
uhdk_window *window;
union {
uhdk_bool active;
uhdk_file_list files;
uhdk_window_metrics metrics;
uhdk_preferences preferences;
struct {
/* Event data is a pointer to the named payload type selected by type.
* All payloads are immutable and naturally aligned. data_size is the payload
* size in bytes; check it before accessing fields introduced by newer versions.
* Payload-free events have NULL data and data_size == 0. Unknown types may be
* ignored. Event envelopes have a fixed stride within each returned batch.
*/
typedef struct uhdk_key_event {
uint32_t physical_key;
uhdk_key logical_key;
uhdk_modifier_flags modifiers;
uhdk_bool pressed;
uhdk_bool repeat;
} key;
struct {
} uhdk_key_event;
typedef struct uhdk_pointer_event {
uhdk_point position;
uhdk_point delta; /* delta uses logical units. */
uhdk_pointer_button button;
uhdk_modifier_flags modifiers;
uhdk_bool pressed;
uint32_t click_count;
} pointer;
uhdk_pointer_mode pointer_mode;
struct {
} uhdk_pointer_event;
typedef struct uhdk_scroll_event {
uhdk_point position;
uhdk_point delta; /* Positive scroll = toward right/bottom. */
uhdk_scroll_unit unit;
uhdk_scroll_phase phase;
uhdk_modifier_flags modifiers;
uhdk_bool momentum;
} scroll;
struct {
} uhdk_scroll_event;
typedef struct uhdk_text_event {
uhdk_text text;
uint64_t revision;
uhdk_bool has_replacement;
@ -813,26 +998,78 @@ struct uhdk_event {
uint64_t replacement_len;
uint64_t selection_start;
uint64_t selection_len; /* Within preedit text. */
} text;
struct {
} uhdk_text_event;
typedef struct uhdk_edit_event {
uhdk_edit_command command;
uhdk_bool extend_selection;
} edit;
struct {
uhdk_bool extend_selection; /* Only meaningful for caret movement. */
} uhdk_edit_event;
typedef struct uhdk_drop_files_event {
uhdk_file_list files;
uhdk_point position;
} drop_files;
struct {
} uhdk_drop_files_event;
typedef struct uhdk_menu_event {
uint64_t command_id;
} menu;
struct {
} uhdk_menu_event;
typedef struct uhdk_watch_event {
uhdk_watch *watch;
uhdk_change_flags flags;
uhdk_status status; /* Non-OK: watch failure; rescan/recreate as needed. */
uhdk_file *file; /* Nullable when only the watched root is known. */
} watch;
uhdk_request_result request;
} data;
} uhdk_watch_event;
typedef struct uhdk_activation_event {
uhdk_bool active;
} uhdk_activation_event;
typedef struct uhdk_pointer_mode_event {
uhdk_pointer_mode mode;
} uhdk_pointer_mode_event;
typedef struct uhdk_window_result {
uhdk_window *window;
} uhdk_window_result;
typedef struct uhdk_gl_context_result {
uhdk_gl_context *gl_context;
} uhdk_gl_context_result;
typedef struct uhdk_watch_result {
uhdk_watch *watch;
} uhdk_watch_result;
/* Request data mapping (only valid on UHDK_STATUS_OK):
* FILE_DIALOG: uhdk_file_list; CLIPBOARD_TEXT: uhdk_text;
* WINDOW: uhdk_window_result; GL_CONTEXT: uhdk_gl_context_result;
* WATCH: uhdk_watch_result; FILE_READ: uhdk_bytes; FILE_INFO: uhdk_file_info.
* SHELL, COMMAND, FILE_WRITE, MENU_POPUP and failures have NULL/0 data.
*/
typedef struct uhdk_request_result {
uhdk_request_id id;
uhdk_request_kind kind;
uhdk_status status;
uhdk_text diagnostic; /* Optional; for humans, never parse for logic. */
const void *data;
uint64_t data_size;
} uhdk_request_result;
/* Event data mapping:
* APP_ACTIVATION: uhdk_activation_event; OPEN_FILES: uhdk_file_list;
* WINDOW_METRICS: uhdk_window_metrics; PREFERENCES_CHANGED: uhdk_preferences;
* KEY: uhdk_key_event; POINTER_ENTER/LEAVE/MOVE/BUTTON: uhdk_pointer_event;
* POINTER_MODE_CHANGED: uhdk_pointer_mode_event; SCROLL: uhdk_scroll_event;
* TEXT_COMMIT/PREEDIT: uhdk_text_event; EDIT_COMMAND: uhdk_edit_event;
* DROP_FILES: uhdk_drop_files_event; MENU_COMMAND: uhdk_menu_event;
* WATCH: uhdk_watch_event; REQUEST_DONE: uhdk_request_result.
* All other events have NULL/0 data.
*/
struct uhdk_event {
uint32_t struct_size;
uhdk_event_type type;
uint64_t time_ns;
uint64_t input_id; /* Correlates raw key and native edit/text; zero for non-input. */
uhdk_window *window;
const void *data;
uint64_t data_size;
};
/* Lifecycle policy: close/quit requests are advisory. Destroy the window or call
@ -852,7 +1089,43 @@ struct uhdk_event {
* semantic trees, notifications, persistent access bookmarks and streaming data.
*/
#ifdef UHDK_ENABLE_NATIVE
/* Experimental platform escape hatch; native handles may require OS headers. */
/* Platform escape hatch. Returned handles are borrowed and must not be released.
* macOS: primary=NSWindow*, secondary=NSView*; Windows: primary=HWND.
* Wayland: primary=wl_display*, secondary=wl_surface*.
* X11: primary=Display*, value=XID. Lifetime ends with the Uhdk window.
* With UHDK_WINDOW_OPENGL, Uhdk owns drawable setup; do not replace its native
* view/surface or pixel format. Otherwise the external renderer owns setup.
* Borrowing a handle does not change platform threading rules: AppKit window
* and view operations still belong on the macOS main thread.
*/
typedef struct uhdk_native_window {
uint32_t struct_size;
uhdk_backend backend;
void *primary;
void *secondary;
uint64_t value;
} uhdk_native_window;
UHDK_API uhdk_status UHDK_CALL uhdk_window_get_native(uhdk_window *window, uhdk_native_window *out);
#endif /* UHDK_ENABLE_NATIVE */
#ifdef __cplusplus
} /* extern "C" */
#endif
#ifdef UHDK_ENABLE_OPENGL
/* GLAD 2: OpenGL 3.3 core, no extensions, explicit per-context function table.
* Do not combine with platform GL headers or GL/glcorearb.h.
* The loader implementation is linked into libuhdk; consumers must not define
* GLAD_GL_IMPLEMENTATION when linking that library.
* Load a GladGLContext via gladLoadGLContextUserPtr and an adapter calling
* uhdk_gl_context_get_proc, inside a begin_frame/end_frame bracket. Keep one
* table per context and use it only while that context is current.
*/
#if defined(UHDK_SHARED)
#define GLAD_API_CALL_EXPORT
#endif
#include "glad/gl.h"
#endif /* UHDK_ENABLE_OPENGL */
#endif /* UHDK_H_INCLUDED */

View File

@ -1,33 +0,0 @@
#ifndef UHDK_NATIVE_H_INCLUDED
#define UHDK_NATIVE_H_INCLUDED
/* Optional experimental platform escape hatch; outside the v0.1 core contract.
* Native handles may require OS headers. Not needed for Uhdk OpenGL rendering.
*/
#include "uhdk.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Platform escape hatch. Returned handles are borrowed and must not be released.
* macOS: primary=NSWindow*, secondary=NSView*; Windows: primary=HWND.
* Wayland: primary=wl_display*, secondary=wl_surface*.
* X11: primary=Display*, value=XID. Lifetime ends with the Uhdk window.
* With UHDK_WINDOW_OPENGL, Uhdk owns drawable setup; do not replace its native
* view/surface or pixel format. Otherwise the external renderer owns setup.
* Borrowing a handle does not change platform threading rules: AppKit window
* and view operations still belong on the macOS main thread.
*/
typedef struct uhdk_native_window {
uint32_t struct_size;
uhdk_backend backend;
void *primary;
void *secondary;
uint64_t value;
} uhdk_native_window;
UHDK_API uhdk_status UHDK_CALL uhdk_window_get_native(uhdk_window *, uhdk_native_window *out);
#ifdef __cplusplus
}
#endif
#endif /* UHDK_NATIVE_H_INCLUDED */

View File

@ -1,19 +0,0 @@
#ifndef UHDK_OPENGL_H_INCLUDED
#define UHDK_OPENGL_H_INCLUDED
#include "uhdk.h"
/* GLAD 2: OpenGL 3.3 core, no extensions, explicit per-context function table.
* Do not combine with platform GL headers or GL/glcorearb.h.
* The loader implementation is linked into libuhdk; consumers must not define
* GLAD_GL_IMPLEMENTATION when linking that library.
* Load a GladGLContext via gladLoadGLContextUserPtr and an adapter calling
* uhdk_gl_context_get_proc, inside a begin_frame/end_frame bracket. Keep one
* table per context and use it only while that context is current.
*/
#if defined(UHDK_SHARED)
#define GLAD_API_CALL_EXPORT
#endif
#include "glad/gl.h"
#endif /* UHDK_OPENGL_H_INCLUDED */

View File

@ -1,28 +0,0 @@
#ifndef UHDK_PROFILE_H_INCLUDED
#define UHDK_PROFILE_H_INCLUDED
/* Optional, consumer-owned instrumentation; no exported UHDK ABI or dependency.
* Define UHDK_PROFILE_BACKEND to a quoted adapter header before including uhdk.h.
* That header implements the macros below. Without it, arguments are not evaluated.
* Names (zone, thread, pool) must be string literals or have static lifetime.
* C zones: UHDK_PROFILE_BEGIN(token, "name"); ... UHDK_PROFILE_END(token);
* End on the same thread, in reverse nesting order, including early exits.
* C++ may use UHDK_PROFILE_SCOPE("name") for automatic scope exit.
* Memory: report successful non-null allocations, then free BEFORE releasing
* storage. A successful resize is free + alloc; failed allocations emit nothing.
* A pointer must use the same pool name for allocation and free. These hooks do
* not allocate/free memory themselves, intercept malloc, or transfer ownership.
*/
#ifdef UHDK_PROFILE_BACKEND
#include UHDK_PROFILE_BACKEND
#else
#define UHDK_PROFILE_BEGIN(token, name) ((void)0)
#define UHDK_PROFILE_END(token) ((void)0)
#define UHDK_PROFILE_SCOPE(name) ((void)0)
#define UHDK_PROFILE_FRAME() ((void)0)
#define UHDK_PROFILE_THREAD(name) ((void)0)
#define UHDK_PROFILE_ALLOC(ptr, size, pool) ((void)0)
#define UHDK_PROFILE_FREE(ptr, pool) ((void)0)
#endif
#endif

19
shell.nix Normal file
View File

@ -0,0 +1,19 @@
{
pkgs ? import ./src/linux/nix/pkgs.nix,
}:
pkgs.mkShell {
packages = [
(pkgs.python3.withPackages (python: [ python.dbus-next python.pillow ]))
pkgs.dbus
pkgs.wayland-scanner
pkgs.clang-tools
];
# NixOS does not put desktop libraries in the conventional loader search path.
# This affects running an unwrapped development executable, not compilation.
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
pkgs.wayland
pkgs.libglvnd
pkgs.libxkbcommon
pkgs.dbus.lib
];
}

View File

@ -6,6 +6,7 @@ const abi = @import("abi.zig");
const Event = @import("Event.zig");
const Request = @import("Request.zig");
const Resource = @import("Resource.zig");
const arenas = @import("arenas.zig");
const Application = @This();
gpa: std.mem.Allocator,
@ -24,8 +25,9 @@ io_tail: ?*Request = null,
pending_head: ?*Event = null,
pending_tail: ?*Event = null,
published: ?*Event = null,
events: std.ArrayList(c.uhdk_event) = .empty,
scratch: arenas.BumpArena,
name: []u8,
application_id: []const u8 = "",
epoch: u64,
deadline: u64 = 0,
next_id: u64 = 1,
@ -47,7 +49,9 @@ pub fn create(gpa: std.mem.Allocator, io: std.Io, name: []const u8) !*Applicatio
const self = try gpa.create(Application);
errdefer gpa.destroy(self);
const owned_name = try gpa.dupe(u8, name);
self.* = .{ .gpa = gpa, .io = io, .name = owned_name, .epoch = runtime.now(io) };
errdefer gpa.free(owned_name);
const scratch = try arenas.BumpArena.init(gpa, io);
self.* = .{ .gpa = gpa, .io = io, .name = owned_name, .scratch = scratch, .epoch = runtime.now(io) };
return self;
}
pub fn lock(self: *Application) void {

View File

@ -8,5 +8,5 @@ const Event = @This();
next: ?*Event = null,
arena: abi.Arena,
gpa: std.mem.Allocator,
event: c.uhdk_event = std.mem.zeroes(c.uhdk_event),
event: c.z_event = std.mem.zeroes(c.z_event),
pins: std.ArrayList(*Resource) = .empty,

60
src/EventPayload.zig Normal file
View File

@ -0,0 +1,60 @@
//! Convert private producer unions to immutable, arena-owned public payloads.
const std = @import("std");
const c = @import("native");
const abi = @import("abi.zig");
const Data = struct { ptr: ?*const anyopaque = null, size: u64 = 0 };
fn store(comptime T: type, allocator: std.mem.Allocator, value: T) !Data {
const ptr = try allocator.create(T);
ptr.* = value;
return .{ .ptr = ptr, .size = @sizeOf(T) };
}
fn files(allocator: std.mem.Allocator, value: c.uhdk_file_list) !c.uhdk_file_list {
if (value.len == 0) return .{ .ptr = null, .len = 0 };
const ptr = try allocator.dupe(?*c.uhdk_file, value.ptr[0..@intCast(value.len)]);
return .{ .ptr = ptr.ptr, .len = ptr.len };
}
fn result(allocator: std.mem.Allocator, source: c.z_request_result) !c.uhdk_request_result {
const data: Data = if (source.status != abi.OK) .{} else switch (source.kind) {
c.UHDK_REQUEST_FILE_DIALOG => try store(c.uhdk_file_list, allocator, try files(allocator, source.value.files)),
c.UHDK_REQUEST_CLIPBOARD_TEXT => try store(c.uhdk_text, allocator, try abi.copyText(allocator, source.value.text)),
c.UHDK_REQUEST_FILE_READ => try store(c.uhdk_bytes, allocator, try abi.copyBytes(allocator, source.value.bytes)),
c.UHDK_REQUEST_FILE_INFO => try store(c.uhdk_file_info, allocator, source.value.info),
c.UHDK_REQUEST_WINDOW => try store(c.uhdk_window_result, allocator, .{ .window = source.value.window }),
c.UHDK_REQUEST_GL_CONTEXT => try store(c.uhdk_gl_context_result, allocator, .{ .gl_context = source.value.gl_context }),
c.UHDK_REQUEST_WATCH => try store(c.uhdk_watch_result, allocator, .{ .watch = source.value.watch }),
else => .{},
};
return .{ .id = source.id, .kind = source.kind, .status = source.status, .diagnostic = try abi.copyText(allocator, source.diagnostic), .data = data.ptr, .data_size = data.size };
}
pub fn pack(allocator: std.mem.Allocator, source: c.z_event) !c.uhdk_event {
const data: Data = switch (source.type) {
c.UHDK_EVENT_APP_ACTIVATION => try store(c.uhdk_activation_event, allocator, .{ .active = source.data.active }),
c.UHDK_EVENT_OPEN_FILES => try store(c.uhdk_file_list, allocator, try files(allocator, source.data.files)),
c.UHDK_EVENT_WINDOW_METRICS => try store(c.uhdk_window_metrics, allocator, source.data.metrics),
c.UHDK_EVENT_PREFERENCES_CHANGED => try store(c.uhdk_preferences, allocator, source.data.preferences),
c.UHDK_EVENT_KEY => try store(c.uhdk_key_event, allocator, source.data.key),
c.UHDK_EVENT_POINTER_ENTER, c.UHDK_EVENT_POINTER_LEAVE, c.UHDK_EVENT_POINTER_MOVE, c.UHDK_EVENT_POINTER_BUTTON => try store(c.uhdk_pointer_event, allocator, source.data.pointer),
c.UHDK_EVENT_POINTER_MODE_CHANGED => try store(c.uhdk_pointer_mode_event, allocator, .{ .mode = source.data.pointer_mode }),
c.UHDK_EVENT_SCROLL => try store(c.uhdk_scroll_event, allocator, source.data.scroll),
c.UHDK_EVENT_TEXT_COMMIT, c.UHDK_EVENT_TEXT_PREEDIT => data: {
var text = source.data.text;
text.text = try abi.copyText(allocator, text.text);
break :data try store(c.uhdk_text_event, allocator, text);
},
c.UHDK_EVENT_EDIT_COMMAND => try store(c.uhdk_edit_event, allocator, source.data.edit),
c.UHDK_EVENT_DROP_FILES => data: {
var drop = source.data.drop_files;
drop.files = try files(allocator, drop.files);
break :data try store(c.uhdk_drop_files_event, allocator, drop);
},
c.UHDK_EVENT_MENU_COMMAND => try store(c.uhdk_menu_event, allocator, source.data.menu),
c.UHDK_EVENT_WATCH => try store(c.uhdk_watch_event, allocator, source.data.watch),
c.UHDK_EVENT_REQUEST_DONE => try store(c.uhdk_request_result, allocator, try result(allocator, source.data.request)),
else => .{},
};
return .{ .struct_size = @sizeOf(c.uhdk_event), .type = source.type, .time_ns = source.time_ns, .input_id = source.input_id, .window = source.window, .data = data.ptr, .data_size = data.size };
}

View File

@ -16,5 +16,6 @@ node: *Event,
id: u64 = 0,
kind: u32 = c.UHDK_REQUEST_COMMAND,
cancelled: bool = false,
native_deferred: bool = false,
pins: std.ArrayList(*Resource) = .empty,
file_operation: ?FileOperation = null,

View File

@ -1,7 +1,11 @@
//! C ABI entry points for app.
const std = @import("std");
const profile = @import("profile");
const backend_kind = if (@import("builtin").os.tag == .windows) c.UHDK_BACKEND_WINDOWS else c.UHDK_BACKEND_MACOS;
const backend_kind = switch (@import("builtin").os.tag) {
.windows => c.UHDK_BACKEND_WINDOWS,
.linux => c.UHDK_BACKEND_WAYLAND,
else => c.UHDK_BACKEND_MACOS,
};
const c = @import("native");
const runtime = @import("../runtime.zig");
const abi = @import("../abi.zig");
@ -31,6 +35,14 @@ pub export fn uhdk_app_create(desc: [*c]const c.uhdk_app_desc, out: [*c]?*c.uhdk
gpa.destroy(backend);
return abi.OOM;
};
application.application_id = gpa.dupe(u8, abi.textBytes(desc.*.application_id)) catch {
application.scratch.deinit();
gpa.free(application.name);
gpa.destroy(application);
backend.deinit();
gpa.destroy(backend);
return abi.OOM;
};
application.io_backend = backend;
singleton = true;
out.* = runtime.toAppHandle(application);
@ -47,9 +59,13 @@ pub export fn uhdk_app_run(
const application = runtime.fromAppHandle(handle);
if (application.has_run) return abi.UNAVAILABLE;
profile.thread("UHDK platform");
runtime.simpleEvent(application, c.UHDK_EVENT_READY);
const ready = runtime.prepareReady(application) catch return abi.OOM;
const status = c.z_native_start(handle, abi.textSpan(application.name));
if (status != abi.OK) return status;
if (status != abi.OK) {
runtime.destroyEvent(ready);
return status;
}
runtime.publishReady(application, ready);
application.entry = entry;
application.user = user;
application.running = true;
@ -100,7 +116,7 @@ pub export fn uhdk_app_poll_events(
defer poll_zone.end();
runtime.destroyEvents(application.published);
application.published = null;
application.events.clearRetainingCapacity();
application.scratch.reset();
out.* = .{ .ptr = null, .len = 0 };
application.lock();
defer application.unlock();
@ -151,19 +167,30 @@ pub export fn uhdk_app_poll_events(
while (cursor) |node| : (cursor = node.next) {
count += 1;
}
application.events.ensureTotalCapacity(application.gpa, count) catch return abi.OOM;
const allocator = application.scratch.allocator();
const events = allocator.alloc(c.uhdk_event, count) catch return abi.OOM;
var len: usize = 0;
cursor = application.pending_head;
// Do not consume nodes until the entire batch has been converted. On OOM,
// the queue (and all terminal request completions) remains retryable.
while (cursor) |node| : (cursor = node.next) {
const event = node.event;
if (event.window != null and !runtime.fromObjectHandle(event.window).alive) continue;
if (event.type == c.UHDK_EVENT_WATCH and !runtime.fromObjectHandle(event.data.watch.watch).alive) continue;
events[len] = @import("../EventPayload.zig").pack(allocator, event) catch return abi.OOM;
len += 1;
}
application.published = application.pending_head;
application.pending_head = null;
application.pending_tail = null;
cursor = application.published;
while (cursor) |node| : (cursor = node.next) {
const event = node.event;
if (event.window != null and !runtime.fromObjectHandle(event.window).alive) continue;
if (event.type == c.UHDK_EVENT_WATCH and !runtime.fromObjectHandle(event.data.watch.watch).alive) continue;
application.events.appendAssumeCapacity(event);
if (event.type == c.UHDK_EVENT_STOPPED) application.stopped_seen = true;
// Payload bytes now live in scratch. Keep only resource pins and nodes
// until the next poll; their private producer spans are no longer read.
_ = node.arena.reset(.free_all);
if (node.event.type == c.UHDK_EVENT_STOPPED) application.stopped_seen = true;
}
out.* = .{ .ptr = application.events.items.ptr, .len = application.events.items.len };
out.* = .{ .ptr = if (len == 0) null else events.ptr, .len = len };
application.stats.event_count +|= out.*.len;
return abi.OK;
}
@ -224,11 +251,13 @@ pub export fn uhdk_app_set_wake_deadline(handle: ?*c.uhdk_app, deadline: u64) ab
return abi.OK;
}
pub export fn uhdk_app_get_backend(_: ?*c.uhdk_app) c.uhdk_backend {
pub export fn uhdk_app_get_backend(handle: ?*c.uhdk_app) c.uhdk_backend {
if (@import("builtin").os.tag == .linux) return @import("../linux/native.zig").backend(handle);
return backend_kind;
}
pub export fn uhdk_app_has_capability(_: ?*c.uhdk_app, cap: c.uhdk_capability) c.uhdk_bool {
pub export fn uhdk_app_has_capability(handle: ?*c.uhdk_app, cap: c.uhdk_capability) c.uhdk_bool {
if (@import("builtin").os.tag == .linux) return @import("../linux/native.zig").capability(handle, cap);
return switch (cap) {
c.UHDK_CAP_FILE_DIALOGS, c.UHDK_CAP_CLIPBOARD, c.UHDK_CAP_DROP_FILES, c.UHDK_CAP_APP_MENU, c.UHDK_CAP_CONTEXT_MENU, c.UHDK_CAP_APP_ICON, c.UHDK_CAP_POINTER_CAPTURE, c.UHDK_CAP_TEXT_INPUT, c.UHDK_CAP_OPENGL => 1,
else => 0,
@ -259,7 +288,7 @@ pub export fn uhdk_app_destroy(handle: ?*c.uhdk_app) void {
std.debug.assert(c.z_native_is_main_thread() != 0 and !application.running);
runtime.destroyEvents(application.published);
runtime.destroyEvents(application.pending_head);
application.events.deinit(application.gpa);
application.scratch.deinit();
var cursor = application.objects;
while (cursor) |object| {
cursor = object.next;
@ -269,6 +298,7 @@ pub export fn uhdk_app_destroy(handle: ?*c.uhdk_app) void {
}
const gpa = application.gpa;
gpa.free(application.name);
gpa.free(application.application_id);
if (application.io_backend) |backend| {
backend.deinit();
gpa.destroy(backend);

520
src/arenas.zig Normal file
View File

@ -0,0 +1,520 @@
//! UHDK-owned scratch arena and supporting paged storage.
//! Derived from RibtechEngine lib/p2 at 39697f9afba2933f92361586bf8802c22dfbe709.
//! Maintained as part of UHDK; this is not a vendored dependency.
const std = @import("std");
pub const PoolStatistics = struct {
statName: []const u8,
instanceCount: usize,
memoryReserved: usize,
memoryUsed: usize,
};
// mutex based, fast bump-only allocator
pub const BumpArena = struct {
const PageSize = 8192 * 2;
const SmallAllocPages = PagedVectorAdvanced([PageSize]u8, 1);
smallBumpOffset: usize = 0,
pageIndex: usize = 0,
allocatedBytes: usize = 0,
allocCount: usize = 0,
backing: std.mem.Allocator,
io_context: std.Io,
small: SmallAllocPages,
large: std.heap.ArenaAllocator,
mutex: std.Io.Mutex,
pub var vtable: std.mem.Allocator.VTable = .{
.alloc = alloc,
.free = free,
.resize = resize,
.remap = std.mem.Allocator.noRemap,
};
pub fn usage(self: *@This()) usize {
return self.allocatedBytes;
}
pub fn getStatistics(self: *@This()) PoolStatistics {
return .{
.statName = "BumpArena",
.instanceCount = self.allocCount,
.memoryReserved = self.small.pages.items.len * PageSize +
self.small.pages.capacity * @sizeOf(usize) +
self.large.queryCapacity(),
.memoryUsed = self.allocatedBytes,
};
}
pub fn init(backing: std.mem.Allocator, io_context: std.Io) !@This() {
return .{
.backing = backing,
.io_context = io_context,
.small = try SmallAllocPages.init(backing),
.large = std.heap.ArenaAllocator.init(backing),
.mutex = .init,
};
}
pub fn io(self: *const @This()) std.Io {
return self.io_context;
}
pub fn deinit(self: *@This()) void {
self.mutex.lockUncancelable(self.io());
defer self.mutex.unlock(self.io());
self.small.deinit(self.backing);
self.large.deinit();
}
pub fn alloc(ctx: *anyopaque, len: usize, ptr_align: std.mem.Alignment, ret_addr: usize) ?[*]u8 {
const self: *@This() = @ptrCast(@alignCast(ctx));
self.mutex.lockUncancelable(self.io());
defer self.mutex.unlock(self.io());
const alignment = ptr_align.toByteUnits();
const result: [*]u8 = if (len > 4096 or alignment > 8) result: {
break :result self.largeAllocator().rawAlloc(len, ptr_align, ret_addr) orelse return null;
} else result: {
var offset = std.mem.alignForward(usize, self.smallBumpOffset, alignment);
if (offset + len > PageSize) {
// The first page exists at init; materialize its logical entry
// before advancing. append is transactional on allocation failure.
if (self.small.pages.items[0].len == 0) self.small.pages.items[0].len = 1;
self.small.append(self.backing, undefined) catch return null;
self.pageIndex += 1;
offset = 0;
}
const memory = self.small.getMutable(self.pageIndex)[offset .. offset + len];
self.smallBumpOffset = offset + len;
break :result memory.ptr;
};
self.allocatedBytes += len;
self.allocCount += 1;
return result;
}
inline fn largeAllocator(self: *@This()) std.mem.Allocator {
return self.large.allocator();
}
pub fn free(ctx: *anyopaque, buf: []u8, buf_align: std.mem.Alignment, ret_addr: usize) void {
// it's a bump allocator, it does nothing on free.
_ = ctx;
_ = buf;
_ = buf_align;
_ = ret_addr;
}
// do not support resize in place.
pub fn resize(ctx: *anyopaque, buf: []u8, buf_align: std.mem.Alignment, new_len: usize, ret_addr: usize) bool {
_ = ctx;
_ = buf;
_ = buf_align;
_ = new_len;
_ = ret_addr;
return false;
}
pub fn allocator(self: *@This()) std.mem.Allocator {
return .{ .ptr = self, .vtable = &vtable };
}
pub fn reset(self: *@This()) void {
self.mutex.lockUncancelable(self.io());
defer self.mutex.unlock(self.io());
_ = self.large.reset(.free_all);
self.small.reset(self.backing);
self.smallBumpOffset = 0;
self.pageIndex = 0;
self.allocatedBytes = 0;
self.allocCount = 0;
}
};
test "bump arena initialization" {
var arena = try BumpArena.init(std.testing.allocator, std.testing.io);
defer arena.deinit();
}
test "reset clears usage" {
var arena = try BumpArena.init(std.testing.allocator, std.testing.io);
defer arena.deinit();
const allocator = arena.allocator();
const mem = allocator.alloc(u8, 128) catch unreachable;
defer _ = mem;
try std.testing.expect(arena.usage() >= 128);
arena.reset();
try std.testing.expectEqual(@as(usize, 0), arena.usage());
}
// it's like an normal vector/ arraylist except it never invalidates old pointers.
// and never causes re-allocations. (excepting in the control block, which is just a vector of things)
//
// It is implemented with a control block which which contains a list of pages.
// these pages contain the actual memory of the vector.
//
// it has a growth factor, which can be configured.
//
// use cases:
//
// - this is highly performant for cases where
// - YOU WANT generally fast insertion time
// - YOU WANT fast random access time
// - YOU WANT cache-coherent and efficient linear iteration
// - YOU WANT to retain pointers across operations
// - YOU WANT to avoid reallocations (eg. if the data set is remarkably big)
// - YOU KNOW that you have linear growth factors and exponential growth is overkill.
//
// great for:
// - event logging.
// - output buffering.
// - recording timelines.
pub fn PagedVectorAdvanced(comptime T: type, comptime growth: usize) type {
return struct {
const Page = struct {
data: [growth]T align(8) = undefined,
len: u32 = 0,
};
pages: std.ArrayListUnmanaged(*Page) = .empty,
currentPageId: usize = 0,
head: *T,
pub fn init(allocator: std.mem.Allocator) !@This() {
var rv = @This(){
.head = undefined,
};
const page = try allocator.create(Page);
errdefer allocator.destroy(page);
page.* = .{};
try rv.pages.append(allocator, page);
rv.head = &rv.pages.items[0].data[0];
return rv;
}
pub fn capacity(self: @This()) usize {
return self.pages.items.len * growth;
}
pub fn len(self: @This()) usize {
return (self.currentPageId) * growth + self.currentPage().len;
}
pub fn getStatistics(self: *const @This()) PoolStatistics {
return .{
.statName = "PagedVector",
.instanceCount = self.len(),
.memoryReserved = self.pages.items.len * @sizeOf(Page) +
self.pages.capacity * @sizeOf(*Page),
.memoryUsed = self.len() * @sizeOf(T),
};
}
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
for (self.pages.items) |page| {
allocator.destroy(page);
}
self.pages.deinit(allocator);
}
fn maybeExpand(self: *@This(), allocator: std.mem.Allocator) !void {
if (self.currentPageId == self.pages.items.len - 1) {
const page = try allocator.create(Page);
errdefer allocator.destroy(page);
page.* = .{};
try self.pages.append(allocator, page);
self.currentPageId = self.pages.items.len - 1;
} else {
self.currentPageId += 1;
}
}
// Retain pages; no live element survives reset.
pub fn reset(self: *@This(), allocator: std.mem.Allocator) void {
_ = allocator;
for (self.pages.items) |page| page.len = 0;
self.currentPageId = 0;
self.head = &self.pages.items[0].data[0];
}
pub fn append(self: *@This(), allocator: std.mem.Allocator, value: T) !void {
if (self.currentPage().len == growth) try self.maybeExpand(allocator);
const page = self.currentPage();
self.head = &page.data[page.len];
self.head.* = value;
page.len += 1;
}
pub fn appendSlice(self: *@This(), allocator: std.mem.Allocator, slice: []const T) !void {
for (slice) |value| {
try self.append(allocator, value);
}
}
pub fn getMutable(self: *@This(), index: usize) *T {
const pageIndex = std.math.divFloor(usize, index, growth) catch unreachable;
return &self.pages.items[pageIndex].data[index % growth];
}
pub fn get(self: @This(), index: usize) *const T {
const pageIndex = std.math.divFloor(usize, index, growth) catch unreachable;
return &self.pages.items[pageIndex].data[index % growth];
}
// swaps the element at the index with the element currently pointed to by head
// then pops back the head, shrinking length by 1
//
// Efficient removal of an element at an index but does not preserve order.
pub fn swapRemove(self: *@This(), index: usize) void {
const ptr = self.getMutable(index);
ptr.* = self.head.*;
_ = try self.pop();
}
fn currentPage(self: @This()) *Page {
return self.pages.items[self.currentPageId];
}
// // retrieves a value, shrinking length by 1
pub fn pop(self: *@This()) !T {
const rv = self.head.*;
var page = self.currentPage();
page.len -= 1;
if (page.len < 1) {
page.len = 0;
self.currentPageId -= 1;
}
self.head = &self.currentPage().data[self.currentPage().len - 1];
return rv;
}
};
}
pub fn PagedVectorUnmanaged(comptime T: type) type {
return PagedVectorAdvanced(T, 1024);
}
pub fn PagedVector(comptime T: type) type {
return struct {
const VectorType = PagedVectorAdvanced(T, 1024);
vector: VectorType,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) !@This() {
return .{
.vector = try VectorType.init(allocator),
.allocator = allocator,
};
}
pub fn deinit(self: *@This()) void {
self.vector.deinit(self.allocator);
}
pub fn appendSlice(self: *@This(), slice: []const T) !void {
try self.vector.appendSlice(self.allocator, slice);
}
pub fn append(self: *@This(), value: T) !void {
try self.vector.append(self.allocator, value);
}
pub fn appendAndGet(self: *@This(), value: T) !*T {
const index = self.len();
try self.append(value);
return self.getMutable(index);
}
pub fn get(self: *const @This(), index: usize) *const T {
return self.vector.get(index);
}
pub fn getMutable(self: *@This(), index: usize) *T {
return self.vector.getMutable(index);
}
pub fn len(self: @This()) usize {
return self.vector.len();
}
pub fn capacity(self: @This()) usize {
return self.vector.capacity();
}
pub fn getStatistics(self: *const @This()) PoolStatistics {
return self.vector.getStatistics();
}
pub fn swapRemove(self: *@This(), index: usize) void {
self.vector.swapRemove(index);
}
pub fn reset(self: *@This()) void {
self.vector.reset(self.allocator);
}
// // retrieves a value, shrinking length by 1
pub fn pop(self: *@This()) !T {
return try self.vector.pop();
}
};
}
test "simple loading multiple pages." {
const TestStruct = struct {
lmao: usize,
};
const allocator = std.testing.allocator;
var vector = try PagedVectorAdvanced(TestStruct, 1024).init(std.testing.allocator);
defer vector.deinit(allocator);
for (0..128) |j| {
try vector.append(allocator, .{ .lmao = j });
}
const ptr = vector.get(0);
const mutable = vector.getMutable(127);
for (0..4096) |i| {
try vector.append(allocator, .{ .lmao = i });
}
// testing that pointers are not invalidated
std.debug.assert(ptr == vector.get(0));
std.debug.assert(mutable == vector.getMutable(127));
std.debug.assert(vector.getMutable(4092) == vector.getMutable(4092));
// 4096 + 128 = 4224 = 4 pages + 128 extra entries
std.debug.assert(vector.pages.items.len == 5);
std.debug.assert(vector.capacity() == 1024 * 5);
std.debug.assert(vector.len() == 128 + 1024 * 4);
// 4092 - 3072 = offset of 1020 in the third page.
const ptr4092 = vector.getMutable(4092);
std.debug.assert(vector.getMutable(4092) == &vector.pages.items[3].data[1020]);
// lets try adding 1024 elements and see if pointers are still preserved.
for (0..1024) |_| {
try vector.append(allocator, .{ .lmao = 0 });
}
std.debug.assert(vector.getMutable(4092) == ptr4092);
std.debug.assert(vector.get(0).lmao == 0);
std.debug.assert(vector.get(1).lmao == 1);
std.debug.assert(vector.get(2).lmao == 2);
// testing the pop() function
std.debug.assert(vector.len() == 128 + 1024 * 5);
_ = try vector.pop();
_ = try vector.pop();
_ = try vector.pop();
// testing the swapRemove Functionality
std.debug.assert(vector.len() == 128 + 1024 * 5 - 3);
vector.swapRemove(1);
std.debug.assert(vector.len() == 128 + 1024 * 5 - 4);
for (0..1024) |_| {
vector.swapRemove(0);
}
std.debug.assert(vector.len() == 128 + 1024 * 5 - 4 - 1024);
}
test "managed version" {
const allocator = std.testing.allocator;
var vec = try PagedVector(struct { lmao: u64 }).init(allocator);
defer vec.deinit();
for (0..8192) |i| {
try vec.append(.{ .lmao = i });
}
std.debug.assert(vec.vector.pages.items.len == 8);
}
test "reset retains pages and clears length" {
const allocator = std.testing.allocator;
var vec = try PagedVectorAdvanced(u32, 4).init(allocator);
defer vec.deinit(allocator);
for (0..10) |i| {
try vec.append(allocator, @intCast(i));
}
try std.testing.expect(vec.pages.items.len > 1);
vec.reset(allocator);
try std.testing.expectEqual(@as(usize, 3), vec.pages.items.len);
try std.testing.expectEqual(@as(usize, 0), vec.len());
try vec.append(allocator, 42);
try std.testing.expectEqual(@as(u32, 42), vec.get(0).*);
}
fn exercise(gpa: std.mem.Allocator) !void {
var arena = try BumpArena.init(gpa, std.testing.io);
defer arena.deinit();
const a = arena.allocator();
const first = try a.alloc(u8, 4096);
@memset(first, 17);
_ = try a.alloc(u8, 4096);
_ = try a.alloc(u8, 4096);
_ = try a.alloc(u8, 4093);
// Alignment rounding takes this allocation across the first page boundary.
const aligned = try a.create(u64);
aligned.* = 0x12345678;
try std.testing.expectEqual(@as(usize, 0), @intFromPtr(aligned) % @alignOf(u64));
const OverAligned = struct { value: u8 align(256) };
const over = try a.create(OverAligned);
over.* = .{ .value = 42 };
try std.testing.expectEqual(@as(usize, 0), @intFromPtr(over) % 256);
const large = try a.alloc(u8, 20000);
@memset(large, 29);
for (first) |v| try std.testing.expectEqual(@as(u8, 17), v);
try std.testing.expectEqual(@as(u64, 0x12345678), aligned.*);
arena.reset();
try std.testing.expectEqual(@as(usize, 0), arena.usage());
const reused = try a.alloc(u8, 4096);
try std.testing.expectEqual(@intFromPtr(first.ptr), @intFromPtr(reused.ptr));
for (0..12) |_| _ = try a.alloc(u8, 4096);
arena.reset();
for (0..12) |_| _ = try a.alloc(u8, 4096);
}
test "bump arena alignment page rollover reuse and all allocation failures" {
try std.testing.checkAllAllocationFailures(std.testing.allocator, exercise, .{});
}
test "bump arena failed page allocation can be retried" {
var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});
var arena = try BumpArena.init(failing.allocator(), std.testing.io);
defer arena.deinit();
const a = arena.allocator();
for (0..4) |_| _ = try a.alloc(u8, 4096);
const before = arena.usage();
failing.fail_index = failing.alloc_index;
try std.testing.expectError(error.OutOfMemory, a.alloc(u8, 4096));
try std.testing.expectEqual(before, arena.usage());
failing.fail_index = std.math.maxInt(usize);
const memory = try a.alloc(u8, 4096);
@memset(memory, 7);
try std.testing.expectEqual(before + 4096, arena.usage());
}

View File

@ -4,7 +4,7 @@ const c = @import("native");
const abi = @import("abi.zig");
const runtime = @import("runtime.zig");
export fn z_emit(handle: ?*c.uhdk_app, event: [*c]const c.uhdk_event) void {
export fn z_emit(handle: ?*c.uhdk_app, event: [*c]const c.z_event) void {
runtime.emit(handle, event);
}
@ -12,7 +12,7 @@ export fn z_complete(
handle: ?*c.uhdk_app,
id: u64,
status: abi.Status,
result: [*c]const c.uhdk_request_result,
result: [*c]const c.z_request_result,
) void {
runtime.complete(handle, id, status, result);
}
@ -38,7 +38,7 @@ export fn z_metrics(handle: ?*c.uhdk_window, value: [*c]const c.uhdk_window_metr
object.app.lock();
object.payload.window.metrics = value.*;
object.app.unlock();
var event = std.mem.zeroes(c.uhdk_event);
var event = std.mem.zeroes(c.z_event);
event.type = c.UHDK_EVENT_WINDOW_METRICS;
event.window = handle;
event.data.metrics = value.*;
@ -50,7 +50,7 @@ export fn z_preferences(handle: ?*c.uhdk_app, value: [*c]const c.uhdk_preference
application.lock();
application.preferences = value.*;
application.unlock();
var event = std.mem.zeroes(c.uhdk_event);
var event = std.mem.zeroes(c.z_event);
event.type = c.UHDK_EVENT_PREFERENCES_CHANGED;
event.data.preferences = value.*;
c.z_emit(handle, &event);

View File

@ -7,7 +7,7 @@ const abi = @import("abi.zig");
fn destroyApplication(application: *Application) void {
runtime.destroyEvents(application.pending_head);
runtime.destroyEvents(application.published);
application.events.deinit(application.gpa);
application.scratch.deinit();
const gpa = application.gpa;
gpa.free(application.name);
application.* = undefined;
@ -37,7 +37,7 @@ test "publication copies text and preserves batches across ordinary calls" {
const application = try Application.create(std.testing.allocator, std.testing.io, "events");
defer destroyApplication(application);
var source = [_]u8{ 'h', 'i' };
var event = std.mem.zeroes(c.uhdk_event);
var event = std.mem.zeroes(c.z_event);
event.type = c.UHDK_EVENT_TEXT_COMMIT;
event.data.text.text = .{ .ptr = &source, .len = source.len };
runtime.emit(runtime.toAppHandle(application), &event);
@ -99,3 +99,123 @@ test "tick statistics exclude blocked time" {
try std.testing.expectEqual(@as(u64, 1), stats.tick_count);
try std.testing.expect(stats.tick_time_ns < wall_time / 2);
}
test "public payload scratch lifetime empty polls and resource pins" {
const app_api = @import("api/app.zig");
const application = try Application.create(std.testing.allocator, std.testing.io, "payloads");
defer destroyApplication(application);
application.running = true;
application.worker_id = std.Thread.getCurrentId();
const handle = runtime.toAppHandle(application);
const file = try runtime.createObject(application, c.Z_KIND_FILE);
defer runtime.release(file);
var handles = [_]?*c.uhdk_file{@ptrCast(file)};
var event = std.mem.zeroes(c.z_event);
event.type = c.UHDK_EVENT_OPEN_FILES;
event.data.files = .{ .ptr = &handles, .len = 1 };
runtime.emit(handle, &event);
try std.testing.expectEqual(@as(usize, 2), file.refs);
var batch: c.uhdk_event_list = undefined;
try std.testing.expectEqual(@as(abi.Status, abi.OK), app_api.uhdk_app_poll_events(handle, 0, &batch));
try std.testing.expectEqual(@as(u64, 1), batch.len);
try std.testing.expectEqual(@as(u64, @sizeOf(c.uhdk_file_list)), batch.ptr[0].data_size);
const payload: *const c.uhdk_file_list = @ptrCast(@alignCast(batch.ptr[0].data.?));
try std.testing.expectEqual(@intFromPtr(file), @intFromPtr(payload.ptr[0].?));
try std.testing.expectEqual(@as(usize, 2), file.refs);
// Invalid calls must not reset a borrow. Valid empty polls must release it.
try std.testing.expectEqual(@as(abi.Status, abi.INVALID), app_api.uhdk_app_poll_events(handle, 0, null));
try std.testing.expectEqual(@as(u64, 1), payload.len);
try std.testing.expectEqual(@as(abi.Status, abi.OK), app_api.uhdk_app_poll_events(handle, 0, &batch));
try std.testing.expectEqual(@as(u64, 0), batch.len);
try std.testing.expectEqual(@as(usize, 1), file.refs);
try std.testing.expectEqual(@as(usize, 0), application.scratch.usage());
}
test "poll allocation failure keeps queued terminal completion retryable" {
const app_api = @import("api/app.zig");
var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});
const application = try Application.create(failing.allocator(), std.testing.io, "retry");
defer destroyApplication(application);
application.running = true;
application.worker_id = std.Thread.getCurrentId();
const handle = runtime.toAppHandle(application);
var bytes = [_]u8{42} ** 20000;
var event = std.mem.zeroes(c.z_event);
event.type = c.UHDK_EVENT_REQUEST_DONE;
event.data.request = .{ .id = 123, .kind = c.UHDK_REQUEST_FILE_READ, .status = abi.OK, .diagnostic = .{ .ptr = null, .len = 0 }, .value = .{ .bytes = .{ .ptr = &bytes, .len = bytes.len } } };
runtime.emit(handle, &event);
@memset(&bytes, 0);
failing.fail_index = failing.alloc_index;
var batch: c.uhdk_event_list = undefined;
try std.testing.expectEqual(@as(abi.Status, abi.OOM), app_api.uhdk_app_poll_events(handle, 0, &batch));
try std.testing.expectEqual(@as(u64, 0), batch.len);
try std.testing.expect(application.pending_head != null);
failing.fail_index = std.math.maxInt(usize);
try std.testing.expectEqual(@as(abi.Status, abi.OK), app_api.uhdk_app_poll_events(handle, 0, &batch));
try std.testing.expectEqual(@as(u64, 1), batch.len);
const result: *const c.uhdk_request_result = @ptrCast(@alignCast(batch.ptr[0].data.?));
try std.testing.expectEqual(@as(u64, 123), result.id);
const data: *const c.uhdk_bytes = @ptrCast(@alignCast(result.data.?));
try std.testing.expectEqual(@as(u64, bytes.len), data.len);
for (data.ptr[0..@intCast(data.len)]) |b| try std.testing.expectEqual(@as(u8, 42), b);
try std.testing.expect(application.pending_head == null);
// Producer payload storage has been freed; the public copy is in scratch.
try std.testing.expectEqual(@as(usize, 0), application.published.?.arena.queryCapacity());
try std.testing.expectEqual(@as(abi.Status, abi.OK), app_api.uhdk_app_poll_events(handle, 0, &batch));
try std.testing.expectEqual(@as(u64, 0), batch.len);
}
test "READY precedes native startup events without being published on failed startup" {
const application = try Application.create(std.testing.allocator, std.testing.io, "startup");
defer destroyApplication(application);
const failed = try runtime.prepareReady(application);
try std.testing.expect(application.pending_head == null);
runtime.destroyEvent(failed);
const ready = try runtime.prepareReady(application);
runtime.simpleEvent(application, c.UHDK_EVENT_PREFERENCES_CHANGED);
const preferences = application.pending_head.?;
runtime.publishReady(application, ready);
runtime.simpleEvent(application, c.UHDK_EVENT_WAKE);
try std.testing.expectEqual(@as(u32, c.UHDK_EVENT_READY), application.pending_head.?.event.type);
try std.testing.expect(ready.next == preferences);
try std.testing.expect(ready.event.time_ns <= preferences.event.time_ns);
try std.testing.expectEqual(@as(u32, c.UHDK_EVENT_WAKE), preferences.next.?.event.type);
try std.testing.expect(application.pending_tail == preferences.next);
}
test {
_ = @import("linux/WindowState.zig");
}
test "desktop edit defaults preserve modifiers and selection intent" {
const ctrl = c.UHDK_MOD_CONTROL;
const shift = c.UHDK_MOD_SHIFT;
const cases = [_][3]u32{
.{ c.UHDK_KEY_BACKSPACE, ctrl, c.UHDK_EDIT_DELETE_WORD_BACKWARD },
.{ c.UHDK_KEY_DELETE, ctrl, c.UHDK_EDIT_DELETE_WORD_FORWARD },
.{ c.UHDK_KEY_DELETE, shift, c.UHDK_EDIT_CUT },
.{ c.UHDK_KEY_INSERT, ctrl, c.UHDK_EDIT_COPY },
.{ c.UHDK_KEY_INSERT, shift, c.UHDK_EDIT_PASTE },
.{ 'v', ctrl, c.UHDK_EDIT_PASTE },
.{ 'z', ctrl | shift, c.UHDK_EDIT_REDO },
.{ c.UHDK_KEY_UP, ctrl | shift, c.UHDK_EDIT_PARAGRAPH_START },
.{ c.UHDK_KEY_DOWN, ctrl, c.UHDK_EDIT_PARAGRAPH_END },
.{ c.UHDK_KEY_PAGE_UP, shift, c.UHDK_EDIT_PAGE_UP },
.{ c.UHDK_KEY_PAGE_DOWN, 0, c.UHDK_EDIT_PAGE_DOWN },
.{ c.UHDK_KEY_TAB, shift, c.UHDK_EDIT_BACKTAB },
.{ c.UHDK_KEY_ENTER, shift, c.UHDK_EDIT_LINE_BREAK },
.{ c.UHDK_KEY_ESCAPE, 0, c.UHDK_EDIT_CANCEL },
.{ c.UHDK_KEY_TAB, ctrl, 0 },
.{ c.UHDK_KEY_PAGE_DOWN, ctrl, 0 },
.{ 'v', ctrl | shift, 0 },
};
for (cases) |item| {
try std.testing.expectEqual(item[2], c.z_edit_key(item[0], item[1]));
try std.testing.expectEqual(@as(u32, 0), c.z_edit_key(item[0], item[1] | c.UHDK_MOD_ALT));
try std.testing.expectEqual(@as(u32, 0), c.z_edit_key(item[0], item[1] | c.UHDK_MOD_SUPER));
}
try std.testing.expect(c.z_edit_extends_selection(c.UHDK_EDIT_WORD_LEFT, shift) != 0);
try std.testing.expect(c.z_edit_extends_selection(c.UHDK_EDIT_PAGE_DOWN, shift) != 0);
for ([_]u32{ c.UHDK_EDIT_BACKTAB, c.UHDK_EDIT_LINE_BREAK, c.UHDK_EDIT_CUT, c.UHDK_EDIT_DELETE_WORD_FORWARD, c.UHDK_EDIT_REDO }) |command|
try std.testing.expectEqual(@as(c_int, 0), c.z_edit_extends_selection(command, shift));
}

100
src/edit_keys.h Normal file
View File

@ -0,0 +1,100 @@
#ifndef UHDK_PRIVATE_EDIT_KEYS_H
#define UHDK_PRIVATE_EDIT_KEYS_H
#include "uhdk.h"
/* Shared Windows/Wayland defaults. Alt (including AltGr) and Super belong to
* text/layout or desktop shortcuts, never to these Ctrl editing bindings. */
static inline uint32_t z_edit_key(uint32_t key, uint32_t mods) {
int ctrl = (mods & UHDK_MOD_CONTROL) != 0;
int shift = (mods & UHDK_MOD_SHIFT) != 0;
if (mods & (UHDK_MOD_ALT | UHDK_MOD_SUPER))
return 0;
if (ctrl) {
switch (key) {
case 'a':
case 'A':
return shift ? 0 : UHDK_EDIT_SELECT_ALL;
case 'c':
case 'C':
return shift ? 0 : UHDK_EDIT_COPY;
case 'x':
case 'X':
return shift ? 0 : UHDK_EDIT_CUT;
case 'v':
case 'V':
return shift ? 0 : UHDK_EDIT_PASTE;
case 'z':
case 'Z':
return shift ? UHDK_EDIT_REDO : UHDK_EDIT_UNDO;
case 'y':
case 'Y':
return shift ? 0 : UHDK_EDIT_REDO;
default:
break;
}
}
switch (key) {
case UHDK_KEY_BACKSPACE:
return ctrl ? UHDK_EDIT_DELETE_WORD_BACKWARD : UHDK_EDIT_DELETE_BACKWARD;
case UHDK_KEY_DELETE:
return ctrl ? UHDK_EDIT_DELETE_WORD_FORWARD
: shift ? UHDK_EDIT_CUT
: UHDK_EDIT_DELETE_FORWARD;
case UHDK_KEY_INSERT:
return ctrl && !shift ? UHDK_EDIT_COPY : shift && !ctrl ? UHDK_EDIT_PASTE : 0;
case UHDK_KEY_LEFT:
return ctrl ? UHDK_EDIT_WORD_LEFT : UHDK_EDIT_MOVE_LEFT;
case UHDK_KEY_RIGHT:
return ctrl ? UHDK_EDIT_WORD_RIGHT : UHDK_EDIT_MOVE_RIGHT;
case UHDK_KEY_UP:
return ctrl ? UHDK_EDIT_PARAGRAPH_START : UHDK_EDIT_MOVE_UP;
case UHDK_KEY_DOWN:
return ctrl ? UHDK_EDIT_PARAGRAPH_END : UHDK_EDIT_MOVE_DOWN;
case UHDK_KEY_HOME:
return ctrl ? UHDK_EDIT_DOCUMENT_START : UHDK_EDIT_LINE_START;
case UHDK_KEY_END:
return ctrl ? UHDK_EDIT_DOCUMENT_END : UHDK_EDIT_LINE_END;
case UHDK_KEY_PAGE_UP:
return ctrl ? 0 : UHDK_EDIT_PAGE_UP;
case UHDK_KEY_PAGE_DOWN:
return ctrl ? 0 : UHDK_EDIT_PAGE_DOWN;
case UHDK_KEY_ENTER:
return ctrl ? 0 : shift ? UHDK_EDIT_LINE_BREAK : UHDK_EDIT_NEWLINE;
case UHDK_KEY_TAB:
return ctrl ? 0 : shift ? UHDK_EDIT_BACKTAB : UHDK_EDIT_TAB;
case UHDK_KEY_ESCAPE:
return ctrl || shift ? 0 : UHDK_EDIT_CANCEL;
default:
return 0;
}
}
static inline int z_edit_extends_selection(uint32_t command, uint32_t mods) {
if (!(mods & UHDK_MOD_SHIFT))
return 0;
switch (command) {
case UHDK_EDIT_MOVE_LEFT:
case UHDK_EDIT_MOVE_RIGHT:
case UHDK_EDIT_MOVE_UP:
case UHDK_EDIT_MOVE_DOWN:
case UHDK_EDIT_MOVE_BACKWARD:
case UHDK_EDIT_MOVE_FORWARD:
case UHDK_EDIT_WORD_LEFT:
case UHDK_EDIT_WORD_RIGHT:
case UHDK_EDIT_WORD_BACKWARD:
case UHDK_EDIT_WORD_FORWARD:
case UHDK_EDIT_LINE_START:
case UHDK_EDIT_LINE_END:
case UHDK_EDIT_LINE_LEFT_END:
case UHDK_EDIT_LINE_RIGHT_END:
case UHDK_EDIT_DOCUMENT_START:
case UHDK_EDIT_DOCUMENT_END:
case UHDK_EDIT_PARAGRAPH_START:
case UHDK_EDIT_PARAGRAPH_END:
case UHDK_EDIT_PAGE_UP:
case UHDK_EDIT_PAGE_DOWN:
return 1;
default:
return 0;
}
}
#endif

96
src/linux/Bitmap.zig Normal file
View File

@ -0,0 +1,96 @@
//! Fixed 16px menu font and a clipped, opaque software canvas. No runtime font
//! parsing, allocation, GPU work, or dependency on an application's renderer.
const std = @import("std");
const data = @embedFile("font/roboto-16.bin");
fn word(offset: usize) u32 {
return std.mem.readInt(u32, data[offset..][0..4], .little);
}
const count = word(4);
const Glyph = struct { offset: usize, width: u8, height: u8, x: i8, y: i8, advance: u8 };
fn glyph(codepoint: u21) Glyph {
var lo: usize = 0;
var hi: usize = count;
while (lo < hi) {
const mid = (lo + hi) / 2;
const key = word(8 + mid * 16);
if (key < codepoint) lo = mid + 1 else hi = mid;
}
const record = 8 + (if (lo < count and word(8 + lo * 16) == codepoint) lo else count - 1) * 16;
return .{ .offset = word(record + 4), .width = data[record + 8], .height = data[record + 9], .x = @bitCast(data[record + 10]), .y = @bitCast(data[record + 11]), .advance = data[record + 12] };
}
pub fn width(text: []const u8) i32 {
var it = std.unicode.Utf8View.initUnchecked(text).iterator();
var result: i32 = 0;
while (it.nextCodepoint()) |cp| {
result += glyph(cp).advance;
if (result >= 1024) break; // Menus constrain/ellipsize long labels.
}
return result;
}
pub const Canvas = struct {
pixels: []u32,
width: i32,
height: i32,
scale: i32,
pub fn rect(self: Canvas, x: i32, y: i32, w: i32, h: i32, color: u32) void {
const x0 = std.math.clamp(x, 0, self.width) * self.scale;
const y0 = std.math.clamp(y, 0, self.height) * self.scale;
const x1 = std.math.clamp(x + w, 0, self.width) * self.scale;
const y1 = std.math.clamp(y + h, 0, self.height) * self.scale;
if (x1 <= x0 or y1 <= y0) return;
var row = y0;
while (row < y1) : (row += 1) {
const offset: usize = @intCast(row * self.width * self.scale);
@memset(self.pixels[offset + @as(usize, @intCast(x0)) .. offset + @as(usize, @intCast(x1))], color | 0xff000000);
}
}
fn pixel(self: Canvas, x: i32, y: i32, color: u32, alpha: u32) void {
if (x < 0 or y < 0 or x >= self.width or y >= self.height or alpha == 0) return;
const index: usize = @intCast((y * self.width * self.scale + x) * self.scale);
const background = self.pixels[index];
var blended: u32 = 0xff000000;
inline for (.{ 0, 8, 16 }) |shift| {
blended |= ((((color >> shift) & 255) * alpha + ((background >> shift) & 255) * (255 - alpha) + 127) / 255) << shift;
}
self.rect(x, y, 1, 1, blended);
}
fn drawGlyph(self: Canvas, value: Glyph, x: i32, baseline: i32, color: u32) void {
for (0..value.height) |row| for (0..value.width) |column| {
self.pixel(x + value.x + @as(i32, @intCast(column)), baseline + value.y + @as(i32, @intCast(row)), color, data[value.offset + row * value.width + column]);
};
}
pub fn text(self: Canvas, value: []const u8, x: i32, baseline: i32, limit: i32, color: u32) void {
var it = std.unicode.Utf8View.initUnchecked(value).iterator();
var cursor = x;
const ellipsis = glyph(0x2026);
const clipped = width(value) > limit;
while (it.nextCodepoint()) |cp| {
const g = glyph(cp);
if (cursor + g.advance > x + limit - (if (clipped) @as(i32, ellipsis.advance) else 0)) {
if (limit >= ellipsis.advance) self.drawGlyph(ellipsis, cursor, baseline, color);
return;
}
self.drawGlyph(g, cursor, baseline, color);
cursor += g.advance;
}
}
};
test "bitmap bounds, coverage and clipping" {
try std.testing.expectEqualStrings("UF16", data[0..4]);
var previous: u32 = 0;
for (0..count) |i| {
const cp = word(8 + i * 16);
try std.testing.expect(cp > previous);
previous = cp;
const g = glyph(@intCast(cp));
try std.testing.expect(g.offset + @as(usize, g.width) * g.height <= data.len);
}
try std.testing.expectEqual(@as(u32, 0xfffd), previous);
try std.testing.expectEqual(width("<EFBFBD>"), width("漢"));
var pixels: [64 * 32]u32 = @splat(0xff000000);
const canvas: Canvas = .{ .pixels = &pixels, .width = 64, .height = 32, .scale = 1 };
canvas.text("Roboto café", -2, 17, 60, 0xffffff);
try std.testing.expect(!std.mem.allEqual(u32, &pixels, 0xff000000));
canvas.rect(-100, -100, 500, 500, 0x123456);
try std.testing.expect(std.mem.allEqual(u32, &pixels, 0xff123456));
}

95
src/linux/Bus.zig Normal file
View File

@ -0,0 +1,95 @@
//! Small typed D-Bus message builder/parser. No GLib, process spawning, or
//! synchronous method calls. libdbus owns message storage and the transport.
const std = @import("std");
const c = @import("linux_c");
pub const Error = error{OutOfMemory};
pub const Iter = c.DBusMessageIter;
pub const Message = c.DBusMessage;
pub fn method(destination: [*:0]const u8, path: [*:0]const u8, interface: [*:0]const u8, member: [*:0]const u8) Error!*Message {
return c.z_dbus_message_new_method_call.?(destination, path, interface, member) orelse error.OutOfMemory;
}
pub fn append(message: *Message) Iter {
var i: Iter = undefined;
c.z_dbus_message_iter_init_append.?(message, &i);
return i;
}
pub fn parse(message: *Message) Iter {
var i: Iter = std.mem.zeroes(Iter);
_ = c.z_dbus_message_iter_init.?(message, &i);
return i;
}
pub fn basic(i: *Iter, type_: c_int, value: anytype) Error!void {
var v = value;
if (c.z_dbus_message_iter_append_basic.?(i, type_, @ptrCast(&v)) == 0) return error.OutOfMemory;
}
pub fn string(i: *Iter, value: [*:0]const u8) Error!void {
try basic(i, c.DBUS_TYPE_STRING, value);
}
pub fn open(i: *Iter, type_: c_int, signature: ?[*:0]const u8) Error!Iter {
var nested: Iter = std.mem.zeroes(Iter);
if (c.z_dbus_message_iter_open_container.?(i, type_, signature, &nested) == 0) return error.OutOfMemory;
return nested;
}
pub fn close(i: *Iter, nested: *Iter) Error!void {
if (c.z_dbus_message_iter_close_container.?(i, nested) == 0) return error.OutOfMemory;
}
pub fn option(i: *Iter, key: [*:0]const u8, comptime signature: [:0]const u8, value: anytype) Error!void {
var pair = try open(i, c.DBUS_TYPE_DICT_ENTRY, null);
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(i, &pair);
try string(&pair, key);
var payload = try open(&pair, c.DBUS_TYPE_VARIANT, signature);
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&pair, &payload);
try basic(&payload, signature[0], value);
try close(&pair, &payload);
try close(i, &pair);
}
pub fn kind(i: *Iter) c_int {
return c.z_dbus_message_iter_get_arg_type.?(i);
}
pub fn next(i: *Iter) bool {
return c.z_dbus_message_iter_next.?(i) != 0;
}
pub fn child(i: *Iter) Iter {
var result: Iter = std.mem.zeroes(Iter);
c.z_dbus_message_iter_recurse.?(i, &result);
return result;
}
pub fn str(i: *Iter) ?[]const u8 {
if (kind(i) != c.DBUS_TYPE_STRING and kind(i) != c.DBUS_TYPE_OBJECT_PATH) return null;
var value: [*c]const u8 = null;
c.z_dbus_message_iter_get_basic.?(i, @ptrCast(&value));
return if (value == null) null else std.mem.span(value);
}
pub fn uint(i: *Iter) ?u32 {
if (kind(i) != c.DBUS_TYPE_UINT32) return null;
var value: u32 = 0;
c.z_dbus_message_iter_get_basic.?(i, @ptrCast(&value));
return value;
}
pub fn variant(i: *Iter) ?Iter {
return if (kind(i) == c.DBUS_TYPE_VARIANT) child(i) else null;
}
pub fn entry(i: *Iter, key: []const u8) ?Iter {
if (kind(i) != c.DBUS_TYPE_ARRAY) return null;
var entries = child(i);
while (kind(&entries) == c.DBUS_TYPE_DICT_ENTRY) {
var pair = child(&entries);
const name = str(&pair) orelse return null;
if (!next(&pair)) return null;
if (std.mem.eql(u8, name, key)) return pair;
if (!next(&entries)) break;
}
return null;
}
comptime {
std.debug.assert(@sizeOf(c.dbus_uint32_t) == 4);
std.debug.assert(@sizeOf(c.dbus_uint64_t) == 8);
std.debug.assert(@sizeOf(Iter) == if (@sizeOf(usize) == 8) 72 else 56);
}
pub fn int(i: *Iter) ?i32 {
if (kind(i) != c.DBUS_TYPE_INT32) return null;
var value: i32 = 0;
c.z_dbus_message_iter_get_basic.?(i, @ptrCast(&value));
return value;
}

345
src/linux/Data.zig Normal file
View File

@ -0,0 +1,345 @@
//! Core Wayland selection and copy-only URI drops. Transfers keep their offers
//! and sources alive until the nonblocking pipe closes, independent of focus.
const std = @import("std");
const c = @import("linux_c");
const n = @import("native");
const native = @import("native.zig");
const Input = @import("Input.zig");
const Files = @import("Files.zig");
const runtime = @import("../runtime.zig");
const abi = @import("../abi.zig");
const Offer = struct {
next: ?*Offer = null,
proxy: ?*c.struct_wl_data_offer,
refs: usize = 1,
text: ?[*:0]const u8 = null,
uris: bool = false,
actions: u32 = 0,
action: u32 = 0,
};
const Source = struct { next: ?*Source = null, proxy: ?*c.struct_wl_data_source, bytes: []u8, refs: usize = 1 };
const Transfer = struct {
next: ?*Transfer = null,
fd: c_int,
request: u64 = 0,
offer: ?*Offer = null,
source: ?*Source = null,
window: ?*native.Window = null,
position: n.uhdk_point = .{ .x = 0, .y = 0 },
bytes: std.ArrayList(u8) = .empty,
offset: usize = 0,
limit: u64 = 0,
deadline: u64,
};
pub const State = struct {
manager: ?*c.struct_wl_data_device_manager = null,
device: ?*c.struct_wl_data_device = null,
offers: ?*Offer = null,
sources: ?*Source = null,
selection: ?*Offer = null,
drag: ?*Offer = null,
drag_window: ?*native.Window = null,
drag_serial: u32 = 0,
position: n.uhdk_point = .{ .x = 0, .y = 0 },
transfers: ?*Transfer = null,
};
fn b() *native.Backend {
return native.instance.?;
}
fn s() *State {
return &b().data;
}
fn data(comptime T: type, raw: ?*anyopaque) *T {
return @ptrCast(@alignCast(raw.?));
}
pub fn global(registry: ?*c.struct_wl_registry, name: u32, interface: []const u8, version: u32) void {
if (std.mem.eql(u8, interface, "wl_data_device_manager") and version >= 3)
s().manager = @ptrCast(c.wl_registry_bind(registry, name, &c.z_uhdk_wl_data_device_manager_interface, 3));
}
pub fn setup() void {
if (s().manager != null and Input.state().seat != null and s().device == null) {
s().device = c.wl_data_device_manager_get_data_device(s().manager, Input.state().seat);
if (s().device != null) _ = c.wl_data_device_add_listener(s().device, &device_listener, null);
}
native.setCapability(n.UHDK_CAP_CLIPBOARD, s().device != null);
native.setCapability(n.UHDK_CAP_DROP_FILES, s().device != null);
}
fn unref(o: *Offer) void {
o.refs -= 1;
if (o.refs != 0) return;
var link = &s().offers;
while (link.* != o) link = &link.*.?.next;
link.* = o.next;
c.wl_data_offer_destroy(o.proxy);
b().app.gpa.destroy(o);
}
fn offer(_: ?*anyopaque, _: ?*c.struct_wl_data_device, proxy: ?*c.struct_wl_data_offer) callconv(.c) void {
const o = b().app.gpa.create(Offer) catch {
c.wl_data_offer_destroy(proxy);
return;
};
o.* = .{ .next = s().offers, .proxy = proxy };
s().offers = o;
_ = c.wl_data_offer_add_listener(proxy, &offer_listener, o);
}
fn mime(raw: ?*anyopaque, _: ?*c.struct_wl_data_offer, name: [*c]const u8) callconv(.c) void {
const o = data(Offer, raw);
const str = std.mem.span(name);
if (std.mem.eql(u8, str, "text/plain;charset=utf-8")) o.text = "text/plain;charset=utf-8" else if (std.mem.eql(u8, str, "text/plain") and o.text == null) o.text = "text/plain";
if (std.mem.eql(u8, str, "text/uri-list")) o.uris = true;
}
fn actions(raw: ?*anyopaque, _: ?*c.struct_wl_data_offer, value: u32) callconv(.c) void {
data(Offer, raw).actions = value;
}
fn action(raw: ?*anyopaque, _: ?*c.struct_wl_data_offer, value: u32) callconv(.c) void {
data(Offer, raw).action = value;
}
const offer_listener: c.struct_wl_data_offer_listener = .{ .offer = mime, .source_actions = actions, .action = action };
fn lookup(proxy: ?*c.struct_wl_data_offer) ?*Offer {
var o = s().offers;
while (o) |item| : (o = item.next) if (item.proxy == proxy) return item;
return null;
}
fn selection(_: ?*anyopaque, _: ?*c.struct_wl_data_device, proxy: ?*c.struct_wl_data_offer) callconv(.c) void {
if (s().selection) |old| unref(old);
s().selection = lookup(proxy); // Takes the initial offer reference.
}
fn enter(_: ?*anyopaque, _: ?*c.struct_wl_data_device, serial: u32, surface: ?*c.struct_wl_surface, x: i32, y: i32, proxy: ?*c.struct_wl_data_offer) callconv(.c) void {
leave(null, null);
s().drag = lookup(proxy);
s().drag_window = Input.find(surface);
s().drag_serial = serial;
s().position = .{ .x = @as(f32, @floatFromInt(x)) / 256, .y = @as(f32, @floatFromInt(y)) / 256 };
negotiate();
}
fn negotiate() void {
const o = s().drag orelse return;
const accepted = o.uris and s().drag_window != null and s().drag_window.?.drop_enabled;
c.wl_data_offer_accept(o.proxy, s().drag_serial, if (accepted) "text/uri-list" else null);
c.wl_data_offer_set_actions(o.proxy, if (accepted) c.WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY else 0, if (accepted) c.WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY else 0);
}
fn leave(_: ?*anyopaque, _: ?*c.struct_wl_data_device) callconv(.c) void {
if (s().drag) |o| unref(o);
s().drag = null;
s().drag_window = null;
}
fn motion(_: ?*anyopaque, _: ?*c.struct_wl_data_device, _: u32, x: i32, y: i32) callconv(.c) void {
s().position = .{ .x = @as(f32, @floatFromInt(x)) / 256, .y = @as(f32, @floatFromInt(y)) / 256 };
}
fn drop(_: ?*anyopaque, _: ?*c.struct_wl_data_device) callconv(.c) void {
const o = s().drag orelse return;
const w = s().drag_window orelse return;
if (!w.drop_enabled or !o.uris or o.action != c.WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) return;
_ = receive(o, "text/uri-list", 0, 16 * 1024 * 1024, w);
// drop ends the drag session; no leave is required after a successful drop.
leave(null, null);
}
const device_listener: c.struct_wl_data_device_listener = .{ .data_offer = offer, .enter = enter, .leave = leave, .motion = motion, .drop = drop, .selection = selection };
pub fn enable(w: *native.Window, enabled: bool) u32 {
if (s().device == null) return n.UHDK_STATUS_UNSUPPORTED;
w.drop_enabled = enabled;
if (s().drag_window == w) negotiate();
return abi.OK;
}
fn sourceUnref(source: *Source) void {
source.refs -= 1;
if (source.refs != 0) return;
var link = &s().sources;
while (link.* != source) link = &link.*.?.next;
link.* = source.next;
b().app.gpa.free(source.bytes);
b().app.gpa.destroy(source);
}
fn target(_: ?*anyopaque, _: ?*c.struct_wl_data_source, _: [*c]const u8) callconv(.c) void {}
fn send(raw: ?*anyopaque, _: ?*c.struct_wl_data_source, name: [*c]const u8, fd: i32) callconv(.c) void {
const source = data(Source, raw);
const str = std.mem.span(name);
if ((!std.mem.eql(u8, str, "text/plain;charset=utf-8") and !std.mem.eql(u8, str, "text/plain")) or c.z_linux_nonblock(fd) != 0) {
_ = c.close(fd);
return;
}
const transfer = b().app.gpa.create(Transfer) catch {
_ = c.close(fd);
return;
};
source.refs += 1;
transfer.* = .{ .next = s().transfers, .fd = fd, .source = source, .deadline = runtime.now(b().app.io) + 10 * std.time.ns_per_s };
s().transfers = transfer;
}
fn cancelled(raw: ?*anyopaque, proxy: ?*c.struct_wl_data_source) callconv(.c) void {
const source = data(Source, raw);
c.wl_data_source_destroy(proxy);
source.proxy = null;
sourceUnref(source);
}
fn unused(_: ?*anyopaque, _: ?*c.struct_wl_data_source) callconv(.c) void {}
fn unusedAction(_: ?*anyopaque, _: ?*c.struct_wl_data_source, _: u32) callconv(.c) void {}
const source_listener: c.struct_wl_data_source_listener = .{ .target = target, .send = send, .cancelled = cancelled, .dnd_drop_performed = unused, .dnd_finished = unused, .action = unusedAction };
pub fn write(text: n.uhdk_text) u32 {
if (s().device == null) return n.UHDK_STATUS_UNSUPPORTED;
const input = Input.state();
// Selection changes require an actual input serial. Never queue a write to
// hijack an unrelated future keystroke when the app has no input focus.
if (input.key_focus == null or input.serial == 0) return abi.UNAVAILABLE;
const source = b().app.gpa.create(Source) catch return abi.OOM;
const bytes = b().app.gpa.dupe(u8, abi.textBytes(text)) catch {
b().app.gpa.destroy(source);
return abi.OOM;
};
const proxy = c.wl_data_device_manager_create_data_source(s().manager) orelse {
b().app.gpa.free(bytes);
b().app.gpa.destroy(source);
return abi.OOM;
};
source.* = .{ .next = s().sources, .proxy = proxy, .bytes = bytes };
s().sources = source;
_ = c.wl_data_source_add_listener(proxy, &source_listener, source);
c.wl_data_source_offer(proxy, "text/plain;charset=utf-8");
c.wl_data_source_offer(proxy, "text/plain");
c.wl_data_device_set_selection(s().device, proxy, input.serial);
return abi.OK;
}
pub fn read(id: u64, limit: u64) u32 {
if (s().device == null) return n.UHDK_STATUS_UNSUPPORTED;
if (Input.state().key_focus == null) return abi.UNAVAILABLE;
const o = s().selection orelse return n.UHDK_STATUS_NOT_FOUND;
const format = o.text orelse return n.UHDK_STATUS_NOT_FOUND;
return receive(o, format, id, limit, null);
}
fn receive(o: *Offer, format: [*:0]const u8, id: u64, limit: u64, w: ?*native.Window) u32 {
var fds: [2]c_int = undefined;
if (c.z_linux_pipe(&fds) != 0) return n.UHDK_STATUS_OS_ERROR;
const transfer = b().app.gpa.create(Transfer) catch {
_ = c.close(fds[0]);
_ = c.close(fds[1]);
return abi.OOM;
};
o.refs += 1;
transfer.* = .{ .next = s().transfers, .fd = fds[0], .request = id, .offer = o, .window = w, .position = s().position, .limit = limit, .deadline = runtime.now(b().app.io) + 10 * std.time.ns_per_s };
s().transfers = transfer;
c.wl_data_offer_receive(o.proxy, format, fds[1]);
_ = c.close(fds[1]);
return n.Z_DEFERRED;
}
pub fn poll(fds: *std.ArrayList(c.struct_pollfd)) !void {
var t = s().transfers;
while (t) |item| : (t = item.next) try fds.append(b().app.gpa, .{ .fd = item.fd, .events = if (item.source != null) c.POLLOUT else c.POLLIN, .revents = 0 });
}
pub fn tick() i32 {
var t = s().transfers;
while (t) |item| {
t = item.next;
if (runtime.now(b().app.io) >= item.deadline) {
finish(item, abi.UNAVAILABLE);
continue;
}
if (item.source) |source| {
const bytes = source.bytes[item.offset..];
if (bytes.len == 0) {
finish(item, abi.OK);
continue;
}
const amount = c.z_linux_pipe_write(item.fd, bytes.ptr, @min(bytes.len, 65536));
if (amount > 0) item.offset += @intCast(amount) else if (amount < 0 and c.z_linux_errno() != c.EAGAIN and c.z_linux_errno() != c.EINTR) finish(item, abi.UNAVAILABLE);
} else {
var buffer: [65536]u8 = undefined;
const amount = c.z_linux_read(item.fd, &buffer, buffer.len);
if (amount == 0) finish(item, abi.OK) else if (amount > 0) {
const length: usize = @intCast(amount);
if (length > item.limit -| item.bytes.items.len) {
finish(item, n.UHDK_STATUS_BUFFER_TOO_SMALL);
continue;
}
item.bytes.appendSlice(b().app.gpa, buffer[0..length]) catch {
finish(item, abi.OOM);
continue;
};
} else if (c.z_linux_errno() != c.EAGAIN and c.z_linux_errno() != c.EINTR) finish(item, abi.UNAVAILABLE);
}
}
return if (s().transfers != null) 100 else -1;
}
fn finish(t: *Transfer, status: u32) void {
if (t.request != 0) {
var result = std.mem.zeroes(n.z_request_result);
result.value.text = abi.textSpan(t.bytes.items);
n.z_complete(runtime.toAppHandle(b().app), t.request, if (status == abi.OK and !std.unicode.utf8ValidateSlice(t.bytes.items)) abi.INVALID else status, &result);
} else if (t.window) |w| {
if (status == abi.OK and w.drop_enabled and emitDrop(w, t.bytes.items, t.position)) c.wl_data_offer_finish(t.offer.?.proxy);
}
var link = &s().transfers;
while (link.* != t) link = &link.*.?.next;
link.* = t.next;
_ = c.close(t.fd);
if (t.offer) |o| unref(o);
if (t.source) |source| sourceUnref(source);
t.bytes.deinit(b().app.gpa);
b().app.gpa.destroy(t);
}
fn emitDrop(w: *native.Window, bytes: []const u8, position: n.uhdk_point) bool {
var files: std.ArrayList(?*n.uhdk_file) = .empty;
defer {
for (files.items) |file| n.uhdk_file_release(file);
files.deinit(b().app.gpa);
}
var lines = std.mem.splitScalar(u8, bytes, '\n');
while (lines.next()) |line| {
const uri = std.mem.trim(u8, line, "\r");
if (uri.len == 0 or uri[0] == '#') continue;
const path = Files.decode(b().app.gpa, uri) catch return false;
defer b().app.gpa.free(path);
const file = n.z_file_from_url(runtime.toAppHandle(b().app), .{ .ptr = path.ptr, .len = path.len }, null) orelse return false;
files.append(b().app.gpa, file) catch {
n.uhdk_file_release(file);
return false;
};
}
if (files.items.len == 0) return false;
var event = std.mem.zeroes(n.z_event);
event.type = n.UHDK_EVENT_DROP_FILES;
event.data.drop_files = .{ .files = .{ .ptr = files.items.ptr, .len = files.items.len }, .position = position };
Input.send(w, &event);
return true;
}
pub fn cancel(id: u64) bool {
var t = s().transfers;
while (t) |item| : (t = item.next) if (item.request == id) {
finish(item, abi.CANCELLED);
return true;
};
return false;
}
pub fn forget(w: *native.Window) void {
if (s().drag_window == w) {
s().drag_window = null;
negotiate();
}
var t = s().transfers;
while (t) |item| {
t = item.next;
if (item.window == w) {
item.window = null;
finish(item, abi.CANCELLED);
}
}
}
pub fn releaseSeat() void {
while (s().transfers) |t| finish(t, abi.CANCELLED);
s().selection = null;
s().drag = null;
s().drag_window = null;
while (s().offers) |o| unref(o);
while (s().sources) |source| {
if (source.proxy != null) c.wl_data_source_destroy(source.proxy);
sourceUnref(source);
}
if (s().device != null) c.wl_data_device_release(s().device);
s().device = null;
native.setCapability(n.UHDK_CAP_CLIPBOARD, false);
native.setCapability(n.UHDK_CAP_DROP_FILES, false);
}
pub fn deinit() void {
releaseSeat();
if (s().manager != null) c.wl_data_device_manager_destroy(s().manager);
s().manager = null;
}

188
src/linux/Desktop.zig Normal file
View File

@ -0,0 +1,188 @@
//! Optional Wayland activation and application icons. Activation is always a
//! compositor request, never a promise to bypass focus-stealing policy.
const std = @import("std");
const c = @import("linux_c");
const n = @import("native");
const native = @import("native.zig");
const Input = @import("Input.zig");
const runtime = @import("../runtime.zig");
const abi = @import("../abi.zig");
const Activation = struct { next: ?*Activation = null, id: u64, window: *native.Window, token: ?*c.struct_xdg_activation_token_v1, deadline: u64 };
const Icon = struct { proxy: ?*c.struct_xdg_toplevel_icon_v1, buffer: ?*c.struct_wl_buffer, mapping: *anyopaque, size: usize };
pub const State = struct {
activation: ?*c.struct_xdg_activation_v1 = null,
manager: ?*c.struct_xdg_toplevel_icon_manager_v1 = null,
shm: ?*c.struct_wl_shm = null,
icon: ?Icon = null,
pending: ?*Activation = null,
launch: ?[:0]u8 = null,
};
fn b() *native.Backend {
return native.instance.?;
}
fn s() *State {
return &b().desktop;
}
pub fn global(registry: ?*c.struct_wl_registry, name: u32, interface: []const u8, _: u32) void {
if (std.mem.eql(u8, interface, "xdg_activation_v1")) s().activation = @ptrCast(c.wl_registry_bind(registry, name, &c.z_uhdk_xdg_activation_v1_interface, 1)) else if (std.mem.eql(u8, interface, "xdg_toplevel_icon_manager_v1")) {
s().manager = @ptrCast(c.wl_registry_bind(registry, name, &c.z_uhdk_xdg_toplevel_icon_manager_v1_interface, 1));
if (s().manager != null) _ = c.xdg_toplevel_icon_manager_v1_add_listener(s().manager, &icon_listener, null);
} else if (std.mem.eql(u8, interface, "wl_shm")) {
s().shm = @ptrCast(c.wl_registry_bind(registry, name, &c.z_uhdk_wl_shm_interface, 1));
if (s().shm != null) _ = c.wl_shm_add_listener(s().shm, &shm_listener, null);
}
native.setCapability(n.UHDK_CAP_APP_ICON, s().manager != null and s().shm != null);
}
fn iconSize(_: ?*anyopaque, _: ?*c.struct_xdg_toplevel_icon_manager_v1, _: i32) callconv(.c) void {}
fn iconDone(_: ?*anyopaque, _: ?*c.struct_xdg_toplevel_icon_manager_v1) callconv(.c) void {}
const icon_listener: c.struct_xdg_toplevel_icon_manager_v1_listener = .{ .icon_size = iconSize, .done = iconDone };
fn format(_: ?*anyopaque, _: ?*c.struct_wl_shm, _: u32) callconv(.c) void {}
const shm_listener: c.struct_wl_shm_listener = .{ .format = format };
pub fn initialize() void {
const token = c.getenv("XDG_ACTIVATION_TOKEN");
if (token != null and token[0] != 0) {
s().launch = b().app.gpa.dupeZ(u8, std.mem.span(token)) catch null;
_ = c.unsetenv("XDG_ACTIVATION_TOKEN");
}
}
pub fn focus(w: *native.Window, id: u64) u32 {
if (s().activation == null) return n.UHDK_STATUS_UNSUPPORTED;
if (s().launch) |token| {
c.xdg_activation_v1_activate(s().activation, token, w.surface);
b().app.gpa.free(token);
s().launch = null;
return abi.OK;
}
const input = Input.state();
if (input.serial == 0 or input.key_focus == null or runtime.now(b().app.io) -| input.serial_time > 5 * std.time.ns_per_s) return abi.UNAVAILABLE;
const p = b().app.gpa.create(Activation) catch return abi.OOM;
const token = c.xdg_activation_v1_get_activation_token(s().activation) orelse {
b().app.gpa.destroy(p);
return abi.OOM;
};
p.* = .{ .next = s().pending, .id = id, .window = w, .token = token, .deadline = runtime.now(b().app.io) + 5 * std.time.ns_per_s };
s().pending = p;
_ = c.xdg_activation_token_v1_add_listener(token, &activation_listener, p);
c.xdg_activation_token_v1_set_serial(token, input.serial, input.seat);
c.xdg_activation_token_v1_set_surface(token, input.key_focus.?.surface);
const app_id = b().app.gpa.dupeZ(u8, b().app.application_id) catch {
finish(p, abi.OOM);
return n.Z_DEFERRED;
};
defer b().app.gpa.free(app_id);
c.xdg_activation_token_v1_set_app_id(token, app_id);
c.xdg_activation_token_v1_commit(token);
return n.Z_DEFERRED;
}
fn activated(raw: ?*anyopaque, _: ?*c.struct_xdg_activation_token_v1, token: [*c]const u8) callconv(.c) void {
const p: *Activation = @ptrCast(@alignCast(raw.?));
c.xdg_activation_v1_activate(s().activation, token, p.window.surface);
finish(p, abi.OK);
}
const activation_listener: c.struct_xdg_activation_token_v1_listener = .{ .done = activated };
fn finish(p: *Activation, status: u32) void {
var link = &s().pending;
while (link.* != p) link = &link.*.?.next;
link.* = p.next;
c.xdg_activation_token_v1_destroy(p.token);
n.z_complete(runtime.toAppHandle(b().app), p.id, status, null);
b().app.gpa.destroy(p);
}
pub fn tick() i32 {
var cursor = s().pending;
while (cursor) |p| {
cursor = p.next;
if (runtime.now(b().app.io) >= p.deadline) finish(p, abi.UNAVAILABLE);
}
return if (s().pending != null) 100 else -1;
}
pub fn cancel(id: u64) bool {
var cursor = s().pending;
while (cursor) |p| : (cursor = p.next) if (p.id == id) {
finish(p, abi.CANCELLED);
return true;
};
return false;
}
pub fn forget(w: *native.Window) void {
var cursor = s().pending;
while (cursor) |p| {
cursor = p.next;
if (p.window == w) finish(p, abi.CANCELLED);
}
}
pub fn applyIcon(w: *native.Window) void {
if (s().icon) |icon| c.xdg_toplevel_icon_manager_v1_set_icon(s().manager, w.top, icon.proxy);
}
/// Public pixels are straight RGBA; wl_shm ARGB8888 uses native-endian,
/// premultiplied channels. Pad rectangular images to a transparent square.
pub fn pixels(destination: []u32, side: usize, image: n.uhdk_image) void {
@memset(destination, 0);
const source = abi.bytes(image.pixels);
const x0 = (side - image.width) / 2;
const y0 = (side - image.height) / 2;
for (0..image.height) |y| for (0..image.width) |x| {
const offset = y * @as(usize, @intCast(image.stride_bytes)) + x * 4;
const a: u32 = source[offset + 3];
const r = (@as(u32, source[offset]) * a + 127) / 255;
const g = (@as(u32, source[offset + 1]) * a + 127) / 255;
const blue = (@as(u32, source[offset + 2]) * a + 127) / 255;
destination[(y + y0) * side + x + x0] = a << 24 | r << 16 | g << 8 | blue;
};
}
pub fn setIcon(image: n.uhdk_image) u32 {
if (s().manager == null or s().shm == null) return n.UHDK_STATUS_UNSUPPORTED;
const side = @max(image.width, image.height);
const size: usize = @as(usize, side) * side * 4;
const fd = c.memfd_create("uhdk-icon", c.MFD_CLOEXEC);
if (fd < 0) return n.UHDK_STATUS_OS_ERROR;
defer _ = c.close(fd);
if (c.ftruncate(fd, @intCast(size)) != 0) return n.UHDK_STATUS_OS_ERROR;
const mapping = c.mmap(null, size, c.PROT_READ | c.PROT_WRITE, c.MAP_SHARED, fd, 0);
if (mapping == c.MAP_FAILED) return n.UHDK_STATUS_OS_ERROR;
var retained = false;
defer if (!retained) {
_ = c.munmap(mapping, size);
};
const words: [*]u32 = @ptrCast(@alignCast(mapping.?));
pixels(words[0 .. size / 4], side, image);
const pool = c.wl_shm_create_pool(s().shm, fd, @intCast(size)) orelse return abi.OOM;
defer c.wl_shm_pool_destroy(pool);
const buffer = c.wl_shm_pool_create_buffer(pool, 0, @intCast(side), @intCast(side), @intCast(side * 4), c.WL_SHM_FORMAT_ARGB8888) orelse return abi.OOM;
const proxy = c.xdg_toplevel_icon_manager_v1_create_icon(s().manager) orelse {
c.wl_buffer_destroy(buffer);
return abi.OOM;
};
c.xdg_toplevel_icon_v1_add_buffer(proxy, buffer, 1);
const old = s().icon;
s().icon = .{ .proxy = proxy, .buffer = buffer, .mapping = mapping.?, .size = size };
retained = true;
var cursor = b().windows;
while (cursor) |w| : (cursor = w.next) applyIcon(w);
if (old) |icon| destroyIcon(icon);
return abi.OK;
}
fn destroyIcon(icon: Icon) void {
c.xdg_toplevel_icon_v1_destroy(icon.proxy);
c.wl_buffer_destroy(icon.buffer);
_ = c.munmap(icon.mapping, icon.size);
}
pub fn deinit() void {
while (s().pending) |p| finish(p, abi.CANCELLED);
if (s().icon) |icon| destroyIcon(icon);
if (s().launch) |token| b().app.gpa.free(token);
if (s().manager != null) c.xdg_toplevel_icon_manager_v1_destroy(s().manager);
if (s().shm != null) c.wl_shm_destroy(s().shm);
if (s().activation != null) c.xdg_activation_v1_destroy(s().activation);
}
test "icons premultiply RGBA and pad rectangular images" {
const rgba = [_]u8{ 255, 128, 0, 128, 0, 255, 0, 255 };
var image = std.mem.zeroes(n.uhdk_image);
image.width = 2;
image.height = 1;
image.stride_bytes = 8;
image.pixels = .{ .ptr = &rgba, .len = rgba.len };
var result: [4]u32 = undefined;
pixels(&result, 2, image);
try std.testing.expectEqualSlices(u32, &.{ 0x80804000, 0xff00ff00, 0, 0 }, &result);
}

160
src/linux/Egl.zig Normal file
View File

@ -0,0 +1,160 @@
//! EGL owns presentation resources only. The compositor thread must never wait
//! for a frame lock: drivers may wait for Wayland events during presentation.
const std = @import("std");
const c = @import("linux_c");
const n = @import("native");
const abi = @import("../abi.zig");
const native = @import("native.zig");
pub const Context = struct {
window: *native.Window,
native: ?*c.struct_wl_egl_window,
surface: c.EGLSurface,
context: c.EGLContext,
};
threadlocal var frame: ?*Context = null;
pub fn initialize(b: *native.Backend) void {
if (c.z_linux_egl_available() == 0) return;
const display = c.z_eglGetPlatformDisplay.?(c.EGL_PLATFORM_WAYLAND_KHR, b.display, null);
if (display == null) return;
var major: c.EGLint = 0;
var minor: c.EGLint = 0;
if (c.z_eglInitialize.?(display, &major, &minor) == c.EGL_FALSE) return;
if (major < 1 or (major == 1 and minor < 5)) {
_ = c.z_eglTerminate.?(display);
return;
}
const attributes = [_]c.EGLint{ c.EGL_SURFACE_TYPE, c.EGL_WINDOW_BIT, c.EGL_RENDERABLE_TYPE, c.EGL_OPENGL_BIT, c.EGL_RED_SIZE, 8, c.EGL_GREEN_SIZE, 8, c.EGL_BLUE_SIZE, 8, c.EGL_ALPHA_SIZE, 8, c.EGL_NONE };
var config: c.EGLConfig = null;
var count: c.EGLint = 0;
if (c.z_eglChooseConfig.?(display, &attributes, &config, 1, &count) == c.EGL_FALSE or count == 0) {
_ = c.z_eglTerminate.?(display);
return;
}
b.egl_display = display;
b.egl_config = config;
}
pub fn finish(b: *native.Backend) void {
if (b.egl_display != null) {
_ = c.z_eglTerminate.?(b.egl_display);
_ = c.z_eglReleaseThread.?();
b.egl_display = null;
}
}
pub fn create(b: *native.Backend, w: *native.Window, object: ?*anyopaque, result: [*c]n.z_request_result) u32 {
if (b.egl_display == null or w.flags & n.UHDK_WINDOW_OPENGL == 0) return n.UHDK_STATUS_UNSUPPORTED;
if (w.egl != null) return n.UHDK_STATUS_BUSY;
if (c.z_eglBindAPI.?(c.EGL_OPENGL_API) == c.EGL_FALSE) return n.UHDK_STATUS_OS_ERROR;
const attrs = [_]c.EGLint{ c.EGL_CONTEXT_MAJOR_VERSION, 3, c.EGL_CONTEXT_MINOR_VERSION, 3, c.EGL_CONTEXT_OPENGL_PROFILE_MASK, c.EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, c.EGL_NONE };
const context = c.z_eglCreateContext.?(b.egl_display, b.egl_config, null, &attrs);
if (context == null) return n.UHDK_STATUS_UNSUPPORTED;
const win = c.z_wl_egl_window_create.?(w.surface, @intCast(w.metrics.framebuffer_width), @intCast(w.metrics.framebuffer_height));
if (win == null) {
_ = c.z_eglDestroyContext.?(b.egl_display, context);
return abi.OOM;
}
const surface = c.z_eglCreateWindowSurface.?(b.egl_display, b.egl_config, @ptrCast(win), null);
if (surface == null) {
c.z_wl_egl_window_destroy.?(win);
_ = c.z_eglDestroyContext.?(b.egl_display, context);
return n.UHDK_STATUS_OS_ERROR;
}
const ctx = b.app.gpa.create(Context) catch {
_ = c.z_eglDestroySurface.?(b.egl_display, surface);
c.z_wl_egl_window_destroy.?(win);
_ = c.z_eglDestroyContext.?(b.egl_display, context);
return abi.OOM;
};
ctx.* = .{ .window = w, .native = win, .surface = surface, .context = context };
w.egl = ctx;
n.z_object_set_native(object, ctx);
result.*.value.gl_context = @ptrCast(object);
return abi.OK;
}
pub fn destroy(ctx: *Context) void {
const b = native.instance.?;
_ = c.z_eglDestroySurface.?(b.egl_display, ctx.surface);
_ = c.z_eglDestroyContext.?(b.egl_display, ctx.context);
c.z_wl_egl_window_destroy.?(ctx.native);
ctx.window.egl = null;
b.app.gpa.destroy(ctx);
}
pub fn resetSurface(ctx: *Context) void {
const b = native.instance.?;
// An unmapped surface may never receive the driver's outstanding frame
// callback. Drop its EGL surface before hiding so remapping cannot wait
// forever in the next swap; the GL context and application resources survive.
if (ctx.surface != null) _ = c.z_eglDestroySurface.?(b.egl_display, ctx.surface);
ctx.surface = c.z_eglCreateWindowSurface.?(b.egl_display, b.egl_config, ctx.native, null);
}
fn makeCurrent(b: *native.Backend, ctx: *Context) bool {
return ctx.surface != null and c.z_eglBindAPI.?(c.EGL_OPENGL_API) != c.EGL_FALSE and
c.z_eglMakeCurrent.?(b.egl_display, ctx.surface, ctx.surface, ctx.context) != c.EGL_FALSE;
}
pub fn call(ptr: ?*anyopaque, op: u32, out: ?*anyopaque) u32 {
const b = native.instance orelse return abi.UNAVAILABLE;
if (b.egl_display == null) return n.UHDK_STATUS_UNSUPPORTED;
if (op == 4 or op == 8) {
if (op == 8 and frame != null) return n.UHDK_STATUS_BUSY;
return if (c.z_eglMakeCurrent.?(b.egl_display, null, null, null) != c.EGL_FALSE) abi.OK else n.UHDK_STATUS_OS_ERROR;
}
const ctx: *Context = @ptrCast(@alignCast(ptr orelse return abi.INVALID));
if (op == 1) {
if (frame != null) return n.UHDK_STATUS_BUSY;
if (b.failed.load(.acquire)) return abi.UNAVAILABLE;
ctx.window.mutex.lockUncancelable(b.app.io);
if (!makeCurrent(b, ctx)) {
ctx.window.mutex.unlock(b.app.io);
return n.UHDK_STATUS_OS_ERROR;
}
frame = ctx;
return abi.OK;
}
if (frame != ctx) return n.UHDK_STATUS_BUSY;
switch (op) {
2 => {
// Keep ownership on failure so a caller cannot destroy a current context.
if (c.z_eglMakeCurrent.?(b.egl_display, null, null, null) == c.EGL_FALSE) return n.UHDK_STATUS_OS_ERROR;
frame = null;
ctx.window.mutex.unlock(b.app.io);
n.z_native_wake();
return abi.OK;
},
3 => return if (makeCurrent(b, ctx)) abi.OK else n.UHDK_STATUS_OS_ERROR,
5 => {
if (c.z_eglGetCurrentContext.?() != ctx.context) return n.UHDK_STATUS_BUSY;
const address = c.z_eglGetProcAddress.?("glGetIntegerv") orelse return n.UHDK_STATUS_NOT_FOUND;
const get_integer: *const fn (c_uint, *c_int) callconv(.c) void = @ptrCast(address);
var major: c_int = 0;
var minor: c_int = 0;
get_integer(0x821B, &major); // GL_MAJOR_VERSION
get_integer(0x821C, &minor); // GL_MINOR_VERSION
const version: *[2]u32 = @ptrCast(@alignCast(out.?));
version.* = .{ @intCast(major), @intCast(minor) };
},
6 => {
if (b.failed.load(.acquire)) return abi.UNAVAILABLE;
if (!ctx.window.visible or !ctx.window.configured) return abi.UNAVAILABLE;
if (c.z_eglGetCurrentContext.?() != ctx.context) return n.UHDK_STATUS_BUSY;
if (c.z_eglSwapBuffers.?(b.egl_display, ctx.surface) == c.EGL_FALSE) return n.UHDK_STATUS_OS_ERROR;
},
7 => {
if (c.z_eglGetCurrentContext.?() != ctx.context) return n.UHDK_STATUS_BUSY;
const interval: *const i32 = @ptrCast(@alignCast(out.?));
if (c.z_eglSwapInterval.?(b.egl_display, interval.*) == c.EGL_FALSE) return n.UHDK_STATUS_OS_ERROR;
},
else => return abi.INVALID,
}
return abi.OK;
}
pub fn proc(ptr: ?*anyopaque, name: n.uhdk_text, out: [*c]n.uhdk_gl_proc) u32 {
const ctx: *Context = @ptrCast(@alignCast(ptr orelse return abi.INVALID));
if (frame != ctx or c.z_eglGetCurrentContext.?() != ctx.context) return n.UHDK_STATUS_BUSY;
const allocator = ctx.window.owner.gpa;
const terminated = allocator.dupeZ(u8, abi.textBytes(name)) catch return abi.OOM;
defer allocator.free(terminated);
const address = c.z_eglGetProcAddress.?(terminated) orelse return n.UHDK_STATUS_NOT_FOUND;
out.* = @ptrCast(address);
return abi.OK;
}

66
src/linux/Files.zig Normal file
View File

@ -0,0 +1,66 @@
//! Local file URIs preserve arbitrary POSIX filename bytes.
const std = @import("std");
const n = @import("native");
const abi = @import("../abi.zig");
const runtime = @import("../runtime.zig");
pub fn decode(allocator: std.mem.Allocator, uri: []const u8) ![]u8 {
const encoded = if (std.mem.startsWith(u8, uri, "file:///")) uri[7..] else if (std.mem.startsWith(u8, uri, "file://localhost/")) uri[16..] else return error.Invalid;
var result: std.Io.Writer.Allocating = .init(allocator);
errdefer result.deinit();
var i: usize = 0;
while (i < encoded.len) : (i += 1) {
var byte = encoded[i];
if (byte == '%') {
if (encoded.len - i < 3) return error.Invalid;
if (!std.ascii.isHex(encoded[i + 1]) or !std.ascii.isHex(encoded[i + 2])) return error.Invalid;
byte = std.fmt.parseInt(u8, encoded[i + 1 ..][0..2], 16) catch return error.Invalid;
i += 2;
} else if (byte == '?' or byte == '#') return error.Invalid;
if (byte == 0) return error.Invalid;
try result.writer.writeByte(byte);
}
return result.toOwnedSlice();
}
fn encode(allocator: std.mem.Allocator, path: []const u8) ![]u8 {
var result: std.Io.Writer.Allocating = .init(allocator);
errdefer result.deinit();
try result.writer.writeAll("file://");
for (path) |byte| {
if (std.ascii.isAlphanumeric(byte) or std.mem.findScalar(u8, "/-._~", byte) != null) {
try result.writer.writeByte(byte);
} else try result.writer.print("%{X:0>2}", .{byte});
}
return result.toOwnedSlice();
}
pub fn init(file: ?*n.uhdk_file, path: n.uhdk_bytes, uri: n.uhdk_text, grant: ?*anyopaque) u32 {
if (grant != null) return n.UHDK_STATUS_UNSUPPORTED;
const object = runtime.fromObjectHandle(file);
var arena: std.heap.ArenaAllocator = .init(object.app.gpa);
defer arena.deinit();
const allocator = arena.allocator();
const bytes = if (path.len != 0) abi.bytes(path) else decode(allocator, abi.textBytes(uri)) catch |err| return if (err == error.Invalid) abi.INVALID else abi.OOM;
if (bytes.len == 0 or bytes[0] != '/' or std.mem.findScalar(u8, bytes, 0) != null) return abi.INVALID;
const encoded = encode(allocator, bytes) catch return abi.OOM;
const basename = std.fs.path.basename(bytes);
// Display text is lossy only when the native byte name isn't UTF-8.
const display = if (std.unicode.utf8ValidateSlice(basename)) basename else "(non-UTF-8 filename)";
return n.z_file_init(file, .{ .ptr = bytes.ptr, .len = bytes.len }, abi.textSpan(encoded), abi.textSpan(display), null);
}
test "file URI preserves POSIX bytes and rejects remote, NUL, malformed escapes" {
const allocator = std.testing.allocator;
const original = "/tmp/a b%#\xff";
const uri = try encode(allocator, original);
defer allocator.free(uri);
try std.testing.expectEqualStrings("file:///tmp/a%20b%25%23%FF", uri);
const path = try decode(allocator, uri);
defer allocator.free(path);
try std.testing.expectEqualStrings(original, path);
for ([_][]const u8{ "https://host/file", "file://host/path", "file:///a%", "file:///a%XX", "file:///a%00", "file:///a%+1", "file:///a#fragment" }) |bad| {
try std.testing.expectError(error.Invalid, decode(allocator, bad));
}
const local = try decode(allocator, "file://localhost/tmp/a");
defer allocator.free(local);
try std.testing.expectEqualStrings("/tmp/a", local);
}

557
src/linux/Input.zig Normal file
View File

@ -0,0 +1,557 @@
//! One active Wayland seat. All mutable input state belongs to the native thread.
const std = @import("std");
const c = @import("linux_c");
const n = @import("native");
const native = @import("native.zig");
const runtime = @import("../runtime.zig");
const abi = @import("../abi.zig");
const Popup = @import("Popup.zig");
const KdeAppMenu = @import("KdeAppMenu.zig");
const Keyboard = @import("Keyboard.zig");
const Data = @import("Data.zig");
const Text = @import("Text.zig");
const Key = struct {
code: u32,
down: bool,
repeated: bool,
sym: u32,
modifiers: u32,
text: [256]u8 = undefined,
len: usize = 0,
};
pub const State = struct {
seat: ?*c.struct_wl_seat = null,
seat_name: u32 = 0,
keyboard: ?*c.struct_wl_keyboard = null,
pointer: ?*c.struct_wl_pointer = null,
cursor_manager: ?*c.struct_wp_cursor_shape_manager_v1 = null,
cursor_device: ?*c.struct_wp_cursor_shape_device_v1 = null,
text_manager: ?*c.struct_zwp_text_input_manager_v3 = null,
text_input: ?*c.struct_zwp_text_input_v3 = null,
text_focus: ?*native.Window = null,
text_serial: u32 = 0,
key_focus: ?*native.Window = null,
pointer_focus: ?*native.Window = null,
context: ?*c.struct_xkb_context = null,
keymap: ?*c.struct_xkb_keymap = null,
state: ?*c.struct_xkb_state = null,
compose_table: ?*c.struct_xkb_compose_table = null,
compose: ?*c.struct_xkb_compose_state = null,
modifiers: u32 = 0,
active: bool = false,
pending_key: ?Key = null,
key_sync: ?*c.struct_wl_callback = null,
serial: u32 = 0,
serial_time: u64 = 0,
popup_serial: u32 = 0,
popup_time: u64 = 0,
popup_window: ?*native.Window = null,
popup_buttons: u32 = 0,
pointer_serial: u32 = 0,
next_id: u64 = 0,
position: n.uhdk_point = .{ .x = 0, .y = 0 },
buttons: u32 = 0,
captured: bool = false,
last_click_time: u64 = 0,
last_click_button: u32 = 0,
last_click_position: n.uhdk_point = .{ .x = 0, .y = 0 },
click_count: u32 = 0,
repeat_key: ?u32 = null,
repeat_rate: u32 = 0,
repeat_delay: u32 = 600,
repeat_at: u64 = 0,
held: [256]bool = @splat(false),
menu_keys: [256]bool = @splat(false),
axis: n.uhdk_point = .{ .x = 0, .y = 0 },
wheel: n.uhdk_point = .{ .x = 0, .y = 0 },
axis_source: u32 = c.WL_POINTER_AXIS_SOURCE_WHEEL,
axis_stop: bool = false,
scrolling: bool = false,
};
fn b() *native.Backend {
return native.instance.?;
}
pub fn state() *State {
return &b().input;
}
pub fn find(surface: ?*c.struct_wl_surface) ?*native.Window {
var item = b().windows;
while (item) |w| : (item = w.next) if (w.surface == surface) return w;
return null;
}
pub fn send(w: *native.Window, event: *n.z_event) void {
event.window = w.handle;
n.z_emit(runtime.toAppHandle(w.owner), event);
}
pub fn id() u64 {
state().next_id += 1;
return state().next_id;
}
pub fn provenance(serial: u32) void {
state().serial = serial;
state().serial_time = runtime.now(b().app.io);
}
pub fn initialize() void {
const s = state();
if (c.z_linux_xkb_available() != 0) {
s.context = c.z_xkb_context_new.?(c.XKB_CONTEXT_NO_FLAGS);
if (s.context != null) {
var locale: [*c]const u8 = c.getenv("LC_ALL");
if (locale == null or locale[0] == 0) locale = c.getenv("LC_CTYPE");
if (locale == null or locale[0] == 0) locale = c.getenv("LANG");
if (locale == null or locale[0] == 0) locale = "C.UTF-8";
s.compose_table = c.z_xkb_compose_table_new_from_locale.?(s.context, locale, c.XKB_COMPOSE_COMPILE_NO_FLAGS);
if (s.compose_table != null) s.compose = c.z_xkb_compose_state_new.?(s.compose_table, c.XKB_COMPOSE_STATE_NO_FLAGS);
}
}
}
pub fn global(registry: ?*c.struct_wl_registry, name: u32, interface: []const u8, version: u32) void {
const s = state();
if (std.mem.eql(u8, interface, "wl_seat") and s.seat == null and version >= 5) {
s.seat = @ptrCast(c.wl_registry_bind(registry, name, &c.z_uhdk_wl_seat_interface, @min(version, 8)));
s.seat_name = name;
if (s.seat != null) _ = c.wl_seat_add_listener(s.seat, &seat_listener, null);
} else if (std.mem.eql(u8, interface, "wp_cursor_shape_manager_v1")) {
s.cursor_manager = @ptrCast(c.wl_registry_bind(registry, name, &c.z_uhdk_wp_cursor_shape_manager_v1_interface, 1));
} else if (std.mem.eql(u8, interface, "zwp_text_input_manager_v3")) {
s.text_manager = @ptrCast(c.wl_registry_bind(registry, name, &c.z_uhdk_zwp_text_input_manager_v3_interface, 1));
}
}
pub fn setup() void {
const s = state();
if (s.pointer != null and s.cursor_manager != null and s.cursor_device == null)
s.cursor_device = c.wp_cursor_shape_manager_v1_get_pointer(s.cursor_manager, s.pointer);
if (s.seat != null and s.text_manager != null and s.text_input == null) {
s.text_input = c.zwp_text_input_manager_v3_get_text_input(s.text_manager, s.seat);
if (s.text_input != null) Text.listen(s.text_input);
}
Data.setup();
Popup.capability();
native.setCapability(n.UHDK_CAP_POINTER_CAPTURE, s.pointer != null);
native.setCapability(n.UHDK_CAP_TEXT_INPUT, s.context != null and s.keyboard != null);
}
fn seatCapabilities(_: ?*anyopaque, seat: ?*c.struct_wl_seat, caps: u32) callconv(.c) void {
const s = state();
if (caps & c.WL_SEAT_CAPABILITY_KEYBOARD != 0 and s.keyboard == null) {
s.keyboard = c.wl_seat_get_keyboard(seat);
if (s.keyboard != null) _ = c.wl_keyboard_add_listener(s.keyboard, &keyboard_listener, null);
} else if (caps & c.WL_SEAT_CAPABILITY_KEYBOARD == 0 and s.keyboard != null) {
leaveKeyboard();
c.wl_keyboard_release(s.keyboard);
s.keyboard = null;
}
if (caps & c.WL_SEAT_CAPABILITY_POINTER != 0 and s.pointer == null) {
s.pointer = c.wl_seat_get_pointer(seat);
if (s.pointer != null) _ = c.wl_pointer_add_listener(s.pointer, &pointer_listener, null);
} else if (caps & c.WL_SEAT_CAPABILITY_POINTER == 0 and s.pointer != null) {
leavePointer();
if (s.cursor_device != null) c.wp_cursor_shape_device_v1_destroy(s.cursor_device);
s.cursor_device = null;
c.wl_pointer_release(s.pointer);
s.pointer = null;
}
setup();
}
fn seatName(_: ?*anyopaque, _: ?*c.struct_wl_seat, _: [*c]const u8) callconv(.c) void {}
const seat_listener: c.struct_wl_seat_listener = .{ .capabilities = seatCapabilities, .name = seatName };
fn keymap(_: ?*anyopaque, _: ?*c.struct_wl_keyboard, format: u32, fd: i32, size: u32) callconv(.c) void {
flushKey();
defer _ = c.close(fd);
const s = state();
s.repeat_key = null;
if (s.compose != null) c.z_xkb_compose_state_reset.?(s.compose);
if (s.key_focus) |w| Text.cancel(w);
if (s.state != null) c.z_xkb_state_unref.?(s.state);
if (s.keymap != null) c.z_xkb_keymap_unref.?(s.keymap);
s.state = null;
s.keymap = null;
if (s.context == null or format != c.WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 or size == 0 or size > 16 * 1024 * 1024) return;
const mapped = c.mmap(null, size, c.PROT_READ, c.MAP_PRIVATE, fd, 0);
if (mapped == c.MAP_FAILED) return;
defer _ = c.munmap(mapped, size);
const bytes: [*]const u8 = @ptrCast(mapped.?);
if (bytes[size - 1] != 0) return;
s.keymap = c.z_xkb_keymap_new_from_string.?(s.context, @ptrCast(mapped), c.XKB_KEYMAP_FORMAT_TEXT_V1, c.XKB_KEYMAP_COMPILE_NO_FLAGS);
if (s.keymap != null) s.state = c.z_xkb_state_new.?(s.keymap);
}
fn keyEnter(_: ?*anyopaque, _: ?*c.struct_wl_keyboard, _: u32, surface: ?*c.struct_wl_surface, keys: [*c]c.struct_wl_array) callconv(.c) void {
leaveKeyboard();
const s = state();
s.key_focus = Popup.owner(surface) orelse find(surface);
const suppressed = s.menu_keys;
s.menu_keys = @splat(false);
if (s.state != null and keys.*.size != 0) {
const held: [*]const u32 = @ptrCast(@alignCast(keys.*.data));
for (held[0 .. keys.*.size / 4]) |code| {
if (code < s.held.len) {
s.held[code] = true;
s.menu_keys[code] = suppressed[code];
}
}
}
}
fn keyLeave(_: ?*anyopaque, _: ?*c.struct_wl_keyboard, _: u32, _: ?*c.struct_wl_surface) callconv(.c) void {
leaveKeyboard();
}
fn updateModifiers() void {
const s = state();
s.modifiers = 0;
if (s.state == null) return;
const names = [_][*:0]const u8{ "Shift", "Control", "Mod1", "Mod4", "Lock", "Mod2" };
for (names, 0..) |name, index| if (c.z_xkb_state_mod_name_is_active.?(s.state, name, c.XKB_STATE_MODS_EFFECTIVE) > 0) {
s.modifiers |= @as(u32, 1) << @intCast(index);
};
}
fn modifiers(_: ?*anyopaque, _: ?*c.struct_wl_keyboard, _: u32, depressed: u32, latched: u32, locked: u32, group: u32) callconv(.c) void {
const s = state();
if (s.state != null) _ = c.z_xkb_state_update_mask.?(s.state, depressed, latched, locked, 0, 0, group);
updateModifiers();
flushKey();
}
fn key(_: ?*anyopaque, _: ?*c.struct_wl_keyboard, serial: u32, _: u32, code: u32, pressed: u32) callconv(.c) void {
const s = state();
flushKey();
if (code > 0xffff) return;
provenance(serial);
if (pressed != 0 and !Popup.active()) popupTrigger(s.key_focus, serial);
if (code < s.held.len) s.held[code] = pressed != 0;
// The compositor supplies the authoritative modifier/group mask. Running
// XKB key actions locally as well would apply latch/lock actions twice.
s.pending_key = prepareKey(code, pressed != 0, false);
// wl_keyboard has no frame event. A sync boundary lets the following
// authoritative modifiers event supply the public post-event flags even
// when the key and modifier messages arrive in separate socket reads.
if (s.key_sync == null) {
s.key_sync = c.wl_display_sync(b().display);
if (s.key_sync != null) _ = c.wl_callback_add_listener(s.key_sync, &key_sync_listener, null) else flushKey();
}
if (pressed != 0 and s.repeat_rate > 0 and s.keymap != null and c.z_xkb_keymap_key_repeats.?(s.keymap, code + 8) != 0) {
s.repeat_key = code;
s.repeat_at = runtime.now(b().app.io) + @as(u64, s.repeat_delay) * std.time.ns_per_ms;
} else if (s.repeat_key == code) s.repeat_key = null;
}
fn prepareKey(code: u32, down: bool, repeated: bool) Key {
const s = state();
var value: Key = .{ .code = code, .down = down, .repeated = repeated, .sym = if (s.state != null) c.z_xkb_state_key_get_one_sym.?(s.state, code + 8) else 0, .modifiers = s.modifiers };
if (down and s.state != null) {
const length = c.z_xkb_state_key_get_utf8.?(s.state, code + 8, &value.text, value.text.len);
if (length > 0 and length < value.text.len) value.len = @intCast(length);
}
return value;
}
fn deliverKey(code: u32, down: bool, repeated: bool) void {
emitKey(prepareKey(code, down, repeated));
}
fn emitKey(value: Key) void {
const s = state();
const w = s.key_focus orelse return;
var event = std.mem.zeroes(n.z_event);
event.type = n.UHDK_EVENT_KEY;
event.input_id = id();
event.data.key = .{ .physical_key = Keyboard.physical(value.code), .logical_key = if (s.state != null) Keyboard.logical(value.sym) else 0, .modifiers = s.modifiers, .pressed = @intFromBool(value.down), .repeat = @intFromBool(value.repeated) };
if (value.code < s.menu_keys.len) {
if (s.menu_keys[value.code]) {
if (!value.down) s.menu_keys[value.code] = false else if (Popup.active()) Popup.key(event.data.key.logical_key, true, s.serial);
return; // Consume the release/repeats even after flags or focus change.
}
if (Popup.active()) {
s.menu_keys[value.code] = value.down;
Popup.key(event.data.key.logical_key, value.down, s.serial);
return;
}
if (value.down and !value.repeated and !w.text.composing and KdeAppMenu.shortcut(w, event.data.key.logical_key, value.modifiers, event.input_id)) {
s.menu_keys[value.code] = true;
return;
}
}
if (Popup.active()) return;
send(w, &event);
// Resolve symbols/text before consuming a latched modifier, while reporting
// the compositor's post-event flags in the observational raw event.
if (value.down and w.text.enabled) Text.key(w, value.sym, event.data.key.logical_key, event.input_id, value.modifiers, value.text[0..value.len]);
}
pub fn flushKey() void {
if (state().pending_key) |value| {
state().pending_key = null;
emitKey(value);
}
}
fn keySynced(_: ?*anyopaque, callback: ?*c.struct_wl_callback, _: u32) callconv(.c) void {
c.wl_callback_destroy(callback);
state().key_sync = null;
flushKey();
}
const key_sync_listener: c.struct_wl_callback_listener = .{ .done = keySynced };
fn repeatInfo(_: ?*anyopaque, _: ?*c.struct_wl_keyboard, rate: i32, delay: i32) callconv(.c) void {
state().repeat_rate = @intCast(std.math.clamp(rate, 0, 1000));
state().repeat_delay = @intCast(@max(0, delay));
if (rate <= 0) state().repeat_key = null;
}
const keyboard_listener: c.struct_wl_keyboard_listener = .{ .keymap = keymap, .enter = keyEnter, .leave = keyLeave, .key = key, .modifiers = modifiers, .repeat_info = repeatInfo };
pub fn tick() i32 {
const s = state();
const active = s.key_focus != null;
if (active != s.active) {
s.active = active;
var event = std.mem.zeroes(n.z_event);
event.type = n.UHDK_EVENT_APP_ACTIVATION;
event.data.active = @intFromBool(active);
n.z_emit(runtime.toAppHandle(b().app), &event);
}
if (s.pending_key != null) return -1;
if (s.repeat_key) |code| {
const now = runtime.now(b().app.io);
if (now >= s.repeat_at) {
deliverKey(code, true, true);
s.repeat_at = now + std.time.ns_per_s / @max(1, s.repeat_rate);
}
return @intCast(@min(1000, (s.repeat_at -| now) / std.time.ns_per_ms + 1));
}
return -1;
}
fn leaveKeyboard() void {
flushKey();
const s = state();
s.modifiers = 0;
s.repeat_key = null;
if (s.key_focus) |w| {
if (!Popup.active()) clearPopupTrigger(w);
for (&s.held, 0..) |*held, code| if (held.*) {
held.* = false;
if (!s.menu_keys[code]) deliverKey(@intCast(code), false, false);
};
Text.cancel(w);
}
s.key_focus = null;
// Preserve consumed held keys until the next enter supplies its held-key set.
s.modifiers = 0;
s.serial = 0;
if (s.state != null) _ = c.z_xkb_state_update_mask.?(s.state, 0, 0, 0, 0, 0, 0);
normal();
}
fn pointerEvent(kind: u32, button_: u32, pressed: bool, delta: n.uhdk_point) void {
if (Popup.active()) return;
const s = state();
const w = s.pointer_focus orelse return;
var e = std.mem.zeroes(n.z_event);
e.type = kind;
e.input_id = id();
e.data.pointer = .{ .position = s.position, .delta = delta, .button = button_, .pressed = @intFromBool(pressed), .modifiers = s.modifiers, .click_count = if (pressed) s.click_count else 0 };
send(w, &e);
}
fn pointerEnter(_: ?*anyopaque, _: ?*c.struct_wl_pointer, serial: u32, surface: ?*c.struct_wl_surface, x: i32, y: i32) callconv(.c) void {
leavePointer();
const s = state();
s.pointer_focus = find(surface);
s.pointer_serial = serial;
s.position = .{ .x = @as(f32, @floatFromInt(x)) / 256, .y = @as(f32, @floatFromInt(y)) / 256 };
if (Popup.pointerEnter(surface, s.position.x, s.position.y, serial)) return;
pointerEvent(n.UHDK_EVENT_POINTER_ENTER, 0, false, .{ .x = 0, .y = 0 });
if (s.pointer_focus) |w| _ = cursor(w, w.cursor);
}
fn pointerLeave(_: ?*anyopaque, _: ?*c.struct_wl_pointer, _: u32, _: ?*c.struct_wl_surface) callconv(.c) void {
leavePointer();
}
fn leavePointer() void {
Popup.pointerLeave();
const s = state();
normal();
pointerEvent(n.UHDK_EVENT_POINTER_LEAVE, 0, false, .{ .x = 0, .y = 0 });
s.pointer_focus = null;
s.buttons = 0;
s.pointer_serial = 0;
s.scrolling = false;
s.axis = .{ .x = 0, .y = 0 };
s.wheel = .{ .x = 0, .y = 0 };
s.axis_stop = false;
s.last_click_button = 0;
}
fn motion(_: ?*anyopaque, _: ?*c.struct_wl_pointer, _: u32, x: i32, y: i32) callconv(.c) void {
const s = state();
const previous = s.position;
s.position = .{ .x = @as(f32, @floatFromInt(x)) / 256, .y = @as(f32, @floatFromInt(y)) / 256 };
if (Popup.active()) {
Popup.motion(s.position.x, s.position.y);
return;
}
pointerEvent(n.UHDK_EVENT_POINTER_MOVE, 0, false, .{ .x = s.position.x - previous.x, .y = s.position.y - previous.y });
}
fn button(_: ?*anyopaque, _: ?*c.struct_wl_pointer, serial: u32, _: u32, code: u32, pressed: u32) callconv(.c) void {
const s = state();
provenance(serial);
const mapped: u32 = switch (code) {
0x110 => n.UHDK_BUTTON_PRIMARY,
0x111 => n.UHDK_BUTTON_SECONDARY,
0x112 => n.UHDK_BUTTON_MIDDLE,
0x113, 0x116 => n.UHDK_BUTTON_BACK,
0x114, 0x115 => n.UHDK_BUTTON_FORWARD,
else => 0,
};
if (mapped == 0) return;
const mask = @as(u32, 1) << @intCast(mapped);
if (Popup.active()) {
if (pressed != 0) s.popup_buttons |= mask else s.popup_buttons &= ~mask;
Popup.button(code, pressed != 0, serial);
return;
}
if (pressed == 0 and s.popup_buttons & mask != 0) {
s.popup_buttons &= ~mask;
return;
}
if (pressed != 0) popupTrigger(s.pointer_focus, serial);
if (pressed != 0) {
s.buttons |= mask;
const now = runtime.now(b().app.io);
const dx = s.position.x - s.last_click_position.x;
const dy = s.position.y - s.last_click_position.y;
s.click_count = if (s.last_click_button == mapped and now -| s.last_click_time <= 500 * std.time.ns_per_ms and dx * dx + dy * dy <= 25) @min(3, s.click_count + 1) else 1;
s.last_click_time = now;
s.last_click_button = mapped;
s.last_click_position = s.position;
} else s.buttons &= ~mask;
pointerEvent(n.UHDK_EVENT_POINTER_BUTTON, mapped, pressed != 0, .{ .x = 0, .y = 0 });
if (s.buttons == 0) normal();
}
fn axis(_: ?*anyopaque, _: ?*c.struct_wl_pointer, _: u32, which: u32, value: i32) callconv(.c) void {
const v: f32 = @as(f32, @floatFromInt(value)) / 256;
if (which == c.WL_POINTER_AXIS_VERTICAL_SCROLL) state().axis.y += v else state().axis.x += v;
}
fn axisSource(_: ?*anyopaque, _: ?*c.struct_wl_pointer, source: u32) callconv(.c) void {
state().axis_source = source;
}
fn axisStop(_: ?*anyopaque, _: ?*c.struct_wl_pointer, _: u32, _: u32) callconv(.c) void {
state().axis_stop = true;
}
fn axisDiscrete(_: ?*anyopaque, _: ?*c.struct_wl_pointer, which: u32, value: i32) callconv(.c) void {
if (c.wl_pointer_get_version(state().pointer) >= 8) return;
if (which == c.WL_POINTER_AXIS_VERTICAL_SCROLL) state().wheel.y += @floatFromInt(value) else state().wheel.x += @floatFromInt(value);
}
fn axis120(_: ?*anyopaque, _: ?*c.struct_wl_pointer, which: u32, value: i32) callconv(.c) void {
if (which == c.WL_POINTER_AXIS_VERTICAL_SCROLL) state().wheel.y += @as(f32, @floatFromInt(value)) / 120 else state().wheel.x += @as(f32, @floatFromInt(value)) / 120;
}
fn frame(_: ?*anyopaque, _: ?*c.struct_wl_pointer) callconv(.c) void {
const s = state();
defer {
s.axis = .{ .x = 0, .y = 0 };
s.wheel = .{ .x = 0, .y = 0 };
s.axis_stop = false;
}
if (Popup.active()) {
const delta = if (s.wheel.y != 0) s.wheel.y * 26 else s.axis.y;
if (delta != 0) Popup.scroll(delta);
return;
}
const w = s.pointer_focus orelse return;
const discrete = s.axis_source == c.WL_POINTER_AXIS_SOURCE_WHEEL or s.axis_source == c.WL_POINTER_AXIS_SOURCE_WHEEL_TILT;
const has_wheel = s.wheel.x != 0 or s.wheel.y != 0;
if (s.axis.x == 0 and s.axis.y == 0 and !has_wheel and !s.axis_stop) return;
var e = std.mem.zeroes(n.z_event);
e.type = n.UHDK_EVENT_SCROLL;
e.input_id = id();
e.data.scroll = .{ .position = s.position, .delta = if (has_wheel) s.wheel else s.axis, .unit = if (has_wheel) n.UHDK_SCROLL_LINES else n.UHDK_SCROLL_LOGICAL_PIXELS, .phase = if (discrete) n.UHDK_SCROLL_DISCRETE else if (s.axis_stop) n.UHDK_SCROLL_END else if (!s.scrolling) n.UHDK_SCROLL_BEGIN else n.UHDK_SCROLL_UPDATE, .modifiers = s.modifiers, .momentum = 0 };
s.scrolling = !discrete and !s.axis_stop;
send(w, &e);
}
const pointer_listener: c.struct_wl_pointer_listener = .{ .enter = pointerEnter, .leave = pointerLeave, .motion = motion, .button = button, .axis = axis, .frame = frame, .axis_source = axisSource, .axis_stop = axisStop, .axis_discrete = axisDiscrete, .axis_value120 = axis120, .axis_relative_direction = null };
pub fn cursor(w: *native.Window, shape: u32) u32 {
w.cursor = shape;
const s = state();
if (shape != n.UHDK_CURSOR_HIDDEN and s.cursor_device == null) return n.UHDK_STATUS_UNSUPPORTED;
if (s.pointer_focus != w) return abi.OK;
if (shape == n.UHDK_CURSOR_HIDDEN) {
c.wl_pointer_set_cursor(s.pointer, s.pointer_serial, null, 0, 0);
return abi.OK;
}
if (s.cursor_device == null) return n.UHDK_STATUS_UNSUPPORTED;
const shapes = [_]u32{ c.WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT, c.WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT, c.WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR, c.WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER, c.WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE, c.WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE, c.WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE, c.WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE, c.WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE, c.WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED, c.WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT };
c.wp_cursor_shape_device_v1_set_shape(s.cursor_device, s.pointer_serial, shapes[shape]);
return abi.OK;
}
pub fn capture(w: *native.Window, enabled: bool) u32 {
const s = state();
if (enabled and (s.pointer_focus != w or s.buttons == 0)) return abi.UNAVAILABLE;
if (!enabled) {
normal();
return abi.OK;
}
s.captured = true;
var e = std.mem.zeroes(n.z_event);
e.type = n.UHDK_EVENT_POINTER_MODE_CHANGED;
e.data.pointer_mode = n.UHDK_POINTER_CAPTURE;
send(w, &e);
return abi.OK;
}
fn normal() void {
const s = state();
if (!s.captured) return;
s.captured = false;
if (s.pointer_focus) |w| {
var e = std.mem.zeroes(n.z_event);
e.type = n.UHDK_EVENT_POINTER_MODE_CHANGED;
e.data.pointer_mode = n.UHDK_POINTER_NORMAL;
send(w, &e);
}
}
fn popupTrigger(w: ?*native.Window, serial: u32) void {
const s = state();
s.popup_window = w;
s.popup_serial = serial;
s.popup_time = runtime.now(b().app.io);
}
pub fn beginPopup() void {
normal();
state().buttons = 0;
state().scrolling = false;
}
pub fn clearPopupTrigger(w: *native.Window) void {
if (state().popup_window == w) {
state().popup_window = null;
state().popup_serial = 0;
}
}
pub fn forget(w: *native.Window) void {
clearPopupTrigger(w);
if (state().key_focus == w) leaveKeyboard();
if (state().pointer_focus == w) leavePointer();
Text.forget(w);
}
pub fn remove(name: u32) void {
if (name == state().seat_name) releaseSeat();
}
fn releaseSeat() void {
Popup.deinit();
state().popup_serial = 0;
state().popup_window = null;
Data.releaseSeat();
const s = state();
if (s.key_sync != null) c.wl_callback_destroy(s.key_sync);
s.key_sync = null;
leaveKeyboard();
leavePointer();
if (s.text_focus) |w| Text.forget(w);
if (s.text_input != null) c.zwp_text_input_v3_destroy(s.text_input);
if (s.cursor_device != null) c.wp_cursor_shape_device_v1_destroy(s.cursor_device);
if (s.keyboard != null) c.wl_keyboard_release(s.keyboard);
if (s.pointer != null) c.wl_pointer_release(s.pointer);
if (s.seat != null) c.wl_seat_release(s.seat);
s.text_input = null;
s.cursor_device = null;
s.keyboard = null;
s.pointer = null;
s.seat = null;
setup();
}
pub fn deinit() void {
const s = state();
releaseSeat();
if (s.text_manager != null) c.zwp_text_input_manager_v3_destroy(s.text_manager);
if (s.cursor_manager != null) c.wp_cursor_shape_manager_v1_destroy(s.cursor_manager);
if (s.compose != null) c.z_xkb_compose_state_unref.?(s.compose);
if (s.compose_table != null) c.z_xkb_compose_table_unref.?(s.compose_table);
if (s.state != null) c.z_xkb_state_unref.?(s.state);
if (s.keymap != null) c.z_xkb_keymap_unref.?(s.keymap);
if (s.context != null) c.z_xkb_context_unref.?(s.context);
}

465
src/linux/KdeAppMenu.zig Normal file
View File

@ -0,0 +1,465 @@
//! Optional Plasma appmenu adapter. The session bus transport is shared with
//! portals; no toolkit, registrar, X11 window ID, or consumer process is required.
//! All exports and menu references are owned by the native thread.
const std = @import("std");
const c = @import("linux_c");
const n = @import("native");
const native = @import("native.zig");
const runtime = @import("../runtime.zig");
const Resource = @import("../Resource.zig");
const abi = @import("../abi.zig");
const Bus = @import("Bus.zig");
const Portal = @import("Portal.zig");
const Menu = @import("Menu.zig");
const Input = @import("Input.zig");
const iface = "com.canonical.dbusmenu";
const prefix = "/org/uhdk/menu/";
const properties = [_][:0]const u8{ "type", "label", "enabled", "visible", "children-display", "toggle-type", "toggle-state", "shortcut" };
const Node = struct {
item: ?*const n.uhdk_menu_item = null,
parent: usize = 0,
first: usize = 0,
next: usize = 0,
label: [:0]const u8 = "",
flags: u32 = 0,
};
pub const Export = struct {
next: ?*Export = null, // Temporary transaction list, never an object registry.
window: *native.Window,
root: *Resource,
arena: std.heap.ArenaAllocator,
nodes: std.ArrayList(Node) = .empty,
path: [:0]const u8,
proxy: ?*c.struct_org_kde_kwin_appmenu = null,
};
pub const State = struct {
manager: ?*c.struct_org_kde_kwin_appmenu_manager = null,
global_name: u32 = 0,
sequence: u64 = 0,
app_menu: ?*Resource = null,
};
fn b() *native.Backend {
return native.instance.?;
}
fn s() *State {
return &b().menus;
}
fn same(a: []const u8, z: []const u8) bool {
return std.mem.eql(u8, a, z);
}
pub fn global(registry: ?*c.struct_wl_registry, name: u32, interface: []const u8, version: u32) void {
if (!same(interface, "org_kde_kwin_appmenu_manager") or s().manager != null) return;
s().manager = @ptrCast(c.wl_registry_bind(registry, name, &c.z_uhdk_org_kde_kwin_appmenu_manager_interface, @min(version, 2)));
s().global_name = name;
ready();
}
pub fn remove(name: u32) void {
if (name != s().global_name) return;
disconnected();
destroyManager();
}
fn destroyManager() void {
if (s().manager) |manager| {
if (c.org_kde_kwin_appmenu_manager_get_version(manager) >= 2) c.org_kde_kwin_appmenu_manager_release(manager) else c.org_kde_kwin_appmenu_manager_destroy(manager);
}
s().manager = null;
s().global_name = 0;
}
pub fn ready() void {
native.setCapability(n.UHDK_CAP_APP_MENU, s().manager != null and b().portal.ready);
var cursor = b().windows;
while (cursor) |w| : (cursor = w.next) if (w.menu_export) |e| associate(e);
}
fn associate(e: *Export) void {
if (e.proxy != null or s().manager == null or !b().portal.ready) return;
// The export is already reachable by process() before advertising it.
e.proxy = c.org_kde_kwin_appmenu_manager_create(s().manager, e.window.surface) orelse return;
c.org_kde_kwin_appmenu_set_address(e.proxy, b().portal.unique.?.ptr, e.path.ptr);
}
pub fn disconnected() void {
native.setCapability(n.UHDK_CAP_APP_MENU, false);
var cursor = b().windows;
while (cursor) |w| : (cursor = w.next) if (w.menu_export) |e| unassociate(e);
}
fn unassociate(e: *Export) void {
if (e.proxy != null) c.org_kde_kwin_appmenu_release(e.proxy);
e.proxy = null;
}
fn destroy(e: *Export) void {
unassociate(e);
runtime.release(e.root);
e.arena.deinit();
b().app.gpa.destroy(e);
}
fn add(e: *Export, menu: *Resource, parent: usize) !void {
var previous: usize = 0;
for (menu.payload.menu.items) |*item| {
// Bound work and D-Bus message size even for repeatedly shared subtrees.
if (e.nodes.items.len >= 65536) return error.OutOfMemory;
const index = e.nodes.items.len;
const allocator = e.arena.allocator();
// D-BusMenu uses '_' as a mnemonic marker; UHDK labels are literal text.
var label: std.ArrayList(u8) = .empty;
for (abi.textBytes(item.label)) |byte| {
try label.append(allocator, byte);
if (byte == '_') try label.append(allocator, '_');
}
try label.append(allocator, 0);
try e.nodes.append(allocator, .{ .item = item, .parent = parent, .label = label.items[0 .. label.items.len - 1 :0], .flags = item.flags });
if (previous == 0) e.nodes.items[parent].first = index else e.nodes.items[previous].next = index;
previous = index;
if (item.submenu) |child| try add(e, @ptrCast(@alignCast(child)), index);
}
}
fn create(w: *native.Window, root: *Resource) !*Export {
const e = try b().app.gpa.create(Export);
errdefer b().app.gpa.destroy(e);
s().sequence += 1;
e.* = .{ .window = w, .root = root, .arena = .init(b().app.gpa), .path = "" };
errdefer e.arena.deinit();
e.path = try std.fmt.allocPrintSentinel(e.arena.allocator(), prefix ++ "{d}", .{s().sequence}, 0);
try e.nodes.append(e.arena.allocator(), .{});
try add(e, root, 0);
runtime.retain(root);
return e;
}
fn replace(w: *native.Window, e: ?*Export) void {
const old = w.menu_export;
w.menu_export = e;
if (old) |value| destroy(value);
if (e) |value| associate(value);
}
pub fn inherit(w: *native.Window) !void {
if (s().app_menu) |root| replace(w, try create(w, root));
}
pub fn attach(w: ?*native.Window, menu: ?*anyopaque) u32 {
const root: ?*Resource = if (menu) |p| @ptrCast(@alignCast(p)) else null;
if (root != null) {
if (s().manager == null) return n.UHDK_STATUS_UNSUPPORTED;
if (b().portal.connection == null) return abi.UNAVAILABLE;
}
if (w) |window| {
const effective = root orelse s().app_menu;
const e = if (effective) |r| create(window, r) catch return abi.OOM else null;
if (root) |r| runtime.retain(r);
if (window.menu_override) |r| runtime.release(r);
window.menu_override = root;
replace(window, e);
} else {
// Prepare every replacement before committing; OOM leaves attachments intact.
var pending: ?*Export = null;
defer while (pending) |e| {
pending = e.next;
destroy(e);
};
if (root) |r| {
var cursor = b().windows;
while (cursor) |window| : (cursor = window.next) if (window.menu_override == null) {
const e = create(window, r) catch return abi.OOM;
e.next = pending;
pending = e;
};
runtime.retain(r);
}
if (s().app_menu) |r| runtime.release(r);
s().app_menu = root;
if (root == null) {
var cursor = b().windows;
while (cursor) |window| : (cursor = window.next) if (window.menu_override == null) {
replace(window, null);
};
}
while (pending) |e| {
pending = e.next;
e.next = null;
replace(e.window, e);
}
}
return abi.OK;
}
pub fn forget(w: *native.Window) void {
replace(w, null);
if (w.menu_override) |root| runtime.release(root);
w.menu_override = null;
}
pub fn deinit() void {
if (s().app_menu) |root| runtime.release(root);
s().app_menu = null;
destroyManager();
}
fn enabled(e: *Export, index: usize) bool {
var i = index;
while (i != 0) : (i = e.nodes.items[i].parent) if (e.nodes.items[i].item.?.flags & n.UHDK_MENU_DISABLED != 0) return false;
return true;
}
fn activate(e: *Export, index: usize, input_id: u64) void {
const item = e.nodes.items[index].item orelse return;
if (!enabled(e, index) or item.flags & n.UHDK_MENU_SEPARATOR != 0 or item.submenu != null) return;
Menu.activate(e.window, item, input_id);
}
fn folded(key: u32) u32 {
return if (key >= 'A' and key <= 'Z') key + ('a' - 'A') else key;
}
pub fn shortcut(w: *native.Window, key: u32, mods: u32, input_id: u64) bool {
const e = w.menu_export orelse return false;
if (key == 0) return false;
const mask: u32 = n.UHDK_MOD_SHIFT | n.UHDK_MOD_CONTROL | n.UHDK_MOD_ALT | n.UHDK_MOD_SUPER;
for (e.nodes.items[1..], 1..) |node, i| {
const item = node.item.?;
if (item.submenu != null or item.flags & n.UHDK_MENU_SEPARATOR != 0 or !enabled(e, i)) continue;
if (folded(item.shortcut_key) == folded(key) and item.shortcut_modifiers & mask == mods & mask) {
activate(e, i, input_id);
return true;
}
}
return false;
}
fn nodeId(e: *Export, value: i32) ?usize {
return if (value >= 0 and value < e.nodes.items.len) @intCast(value) else null;
}
fn variant(i: *Bus.Iter, comptime sig: [:0]const u8, value: anytype) !void {
var v = try Bus.open(i, c.DBUS_TYPE_VARIANT, sig);
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(i, &v);
try Bus.basic(&v, sig[0], value);
try Bus.close(i, &v);
}
fn property(e: *Export, index: usize, name: []const u8, out: *Bus.Iter) !bool {
const node = e.nodes.items[index];
const item = node.item;
const flags = if (item) |v| v.flags else 0;
if (same(name, "type")) try variant(out, "s", @as([*:0]const u8, if (flags & n.UHDK_MENU_SEPARATOR != 0) "separator" else "standard")) else if (same(name, "label")) try variant(out, "s", node.label.ptr) else if (same(name, "enabled")) try variant(out, "b", @as(u32, @intFromBool(enabled(e, index)))) else if (same(name, "visible")) try variant(out, "b", @as(u32, 1)) else if (same(name, "children-display")) try variant(out, "s", @as([*:0]const u8, if (index == 0 or (item != null and item.?.submenu != null)) "submenu" else "")) else if (same(name, "toggle-type")) try variant(out, "s", @as([*:0]const u8, if (flags & n.UHDK_MENU_CHECKED != 0) "checkmark" else "")) else if (same(name, "toggle-state")) try variant(out, "i", @as(i32, if (flags & n.UHDK_MENU_CHECKED != 0) 1 else 0)) else if (same(name, "shortcut")) {
var v = try Bus.open(out, c.DBUS_TYPE_VARIANT, "aas");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(out, &v);
var arrays = try Bus.open(&v, c.DBUS_TYPE_ARRAY, "as");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&v, &arrays);
var buffer: [32]u8 = undefined;
if (item) |value| if (Menu.keyName(value.shortcut_key, &buffer)) |key_name| {
var keys = try Bus.open(&arrays, c.DBUS_TYPE_ARRAY, "s");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&arrays, &keys);
inline for (.{ .{ n.UHDK_MOD_CONTROL, "Control" }, .{ n.UHDK_MOD_ALT, "Alt" }, .{ n.UHDK_MOD_SHIFT, "Shift" }, .{ n.UHDK_MOD_SUPER, "Super" } }) |mod| {
if (value.shortcut_modifiers & mod[0] != 0) try Bus.string(&keys, mod[1]);
}
try Bus.string(&keys, key_name.ptr);
try Bus.close(&arrays, &keys);
};
try Bus.close(&v, &arrays);
try Bus.close(out, &v);
} else return false;
return true;
}
fn wanted(filter: ?Bus.Iter, name: []const u8) bool {
var iter = filter orelse return true;
if (Bus.kind(&iter) == c.DBUS_TYPE_INVALID) return true;
while (Bus.str(&iter)) |value| {
if (same(value, name)) return true;
if (!Bus.next(&iter)) break;
}
return false;
}
fn props(e: *Export, index: usize, filter: ?Bus.Iter, out: *Bus.Iter) !void {
var dict = try Bus.open(out, c.DBUS_TYPE_ARRAY, "{sv}");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(out, &dict);
for (properties) |name| if (wanted(filter, name)) {
var pair = try Bus.open(&dict, c.DBUS_TYPE_DICT_ENTRY, null);
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&dict, &pair);
try Bus.string(&pair, name.ptr);
_ = try property(e, index, name, &pair);
try Bus.close(&dict, &pair);
};
try Bus.close(out, &dict);
}
fn layout(e: *Export, index: usize, depth: i32, filter: Bus.Iter, out: *Bus.Iter) Bus.Error!void {
var tuple = try Bus.open(out, c.DBUS_TYPE_STRUCT, null);
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(out, &tuple);
try Bus.basic(&tuple, c.DBUS_TYPE_INT32, @as(i32, @intCast(index)));
try props(e, index, filter, &tuple);
var children = try Bus.open(&tuple, c.DBUS_TYPE_ARRAY, "v");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&tuple, &children);
if (depth != 0) {
var cursor = e.nodes.items[index].first;
while (cursor != 0) : (cursor = e.nodes.items[cursor].next) {
var v = try Bus.open(&children, c.DBUS_TYPE_VARIANT, "(ia{sv}av)");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&children, &v);
try layout(e, cursor, if (depth < 0) -1 else depth - 1, filter, &v);
try Bus.close(&children, &v);
}
}
try Bus.close(&tuple, &children);
try Bus.close(out, &tuple);
}
fn groupItem(e: *Export, index: usize, filter: ?Bus.Iter, out: *Bus.Iter) !void {
var tuple = try Bus.open(out, c.DBUS_TYPE_STRUCT, null);
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(out, &tuple);
try Bus.basic(&tuple, c.DBUS_TYPE_INT32, @as(i32, @intCast(index)));
try props(e, index, filter, &tuple);
try Bus.close(out, &tuple);
}
pub fn updateFlags() u32 {
var cursor = b().windows;
while (cursor) |w| : (cursor = w.next) if (w.menu_export) |e| {
changed(e) catch return abi.OOM;
};
return abi.OK;
}
fn changed(e: *Export) !void {
var dirty = false;
for (e.nodes.items[1..]) |node| if (node.flags != node.item.?.flags) {
dirty = true;
break;
};
if (!dirty) return;
if (b().portal.ready and e.proxy != null) {
const message = c.z_dbus_message_new_signal.?(e.path.ptr, iface, "ItemsPropertiesUpdated") orelse return error.OutOfMemory;
defer c.z_dbus_message_unref.?(message);
var out = Bus.append(message);
var updates = try Bus.open(&out, c.DBUS_TYPE_ARRAY, "(ia{sv})");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&out, &updates);
// Include descendants: a disabled ancestor also disables their activation.
for (e.nodes.items[1..], 1..) |_, i| try groupItem(e, i, null, &updates);
try Bus.close(&out, &updates);
var removed = try Bus.open(&out, c.DBUS_TYPE_ARRAY, "(ias)");
try Bus.close(&out, &removed);
_ = try Portal.send(message);
}
for (e.nodes.items[1..]) |*node| node.flags = node.item.?.flags;
}
fn fail(message: *Bus.Message, name: [*:0]const u8) !void {
if (c.z_dbus_message_get_no_reply.?(message) != 0) return;
const reply = c.z_dbus_message_new_error.?(message, name, "Menu request could not be handled") orelse return error.OutOfMemory;
defer c.z_dbus_message_unref.?(reply);
_ = try Portal.send(reply);
}
const invalid = "org.freedesktop.DBus.Error.InvalidArgs";
const missing = "com.canonical.dbusmenu.Error.InvalidMenuItem";
fn callIs(message: *Bus.Message, interface: []const u8, member: []const u8) bool {
const a = c.z_dbus_message_get_interface.?(message);
const z = c.z_dbus_message_get_member.?(message);
return a != null and z != null and same(std.mem.span(a), interface) and same(std.mem.span(z), member);
}
fn signature(message: *Bus.Message, sig: [*:0]const u8) bool {
return c.z_dbus_message_has_signature.?(message, sig) != 0;
}
fn globalProperty(name: []const u8, out: *Bus.Iter) !bool {
if (same(name, "Version")) try variant(out, "u", @as(u32, 3)) else if (same(name, "TextDirection")) try variant(out, "s", @as([*:0]const u8, "ltr")) else if (same(name, "Status")) try variant(out, "s", @as([*:0]const u8, "normal")) else if (same(name, "IconThemePath")) {
var v = try Bus.open(out, c.DBUS_TYPE_VARIANT, "as");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(out, &v);
var paths = try Bus.open(&v, c.DBUS_TYPE_ARRAY, "s");
try Bus.close(&v, &paths);
try Bus.close(out, &v);
} else return false;
return true;
}
pub fn process(message: *Bus.Message) !bool {
if (c.z_dbus_message_get_type.?(message) != c.DBUS_MESSAGE_TYPE_METHOD_CALL) return false;
const path = c.z_dbus_message_get_path.?(message);
if (path == null or !std.mem.startsWith(u8, std.mem.span(path), prefix)) return false;
var found: ?*Export = null;
var cursor = b().windows;
while (cursor) |w| : (cursor = w.next) if (w.menu_export) |e| {
if (same(e.path, std.mem.span(path))) {
found = e;
break;
}
};
if (found) |e| try dispatch(e, message) else try fail(message, "org.freedesktop.DBus.Error.UnknownObject");
return true;
}
fn dispatch(e: *Export, message: *Bus.Message) !void {
const reply = c.z_dbus_message_new_method_return.?(message) orelse return error.OutOfMemory;
defer c.z_dbus_message_unref.?(reply);
var out = Bus.append(reply);
var args = Bus.parse(message);
if (callIs(message, "org.freedesktop.DBus.Introspectable", "Introspect")) {
if (!signature(message, "")) return fail(message, invalid);
try Bus.string(&out, @embedFile("dbusmenu.xml"));
} else if (callIs(message, "org.freedesktop.DBus.Peer", "Ping")) {
if (!signature(message, "")) return fail(message, invalid);
} else if (callIs(message, "org.freedesktop.DBus.Properties", "Get") or callIs(message, "org.freedesktop.DBus.Properties", "GetAll")) {
const single = callIs(message, "org.freedesktop.DBus.Properties", "Get");
if (!signature(message, if (single) "ss" else "s")) return fail(message, invalid);
if (!same(Bus.str(&args).?, iface)) return fail(message, "org.freedesktop.DBus.Error.UnknownInterface");
if (single) {
_ = Bus.next(&args);
if (!try globalProperty(Bus.str(&args).?, &out)) return fail(message, "org.freedesktop.DBus.Error.UnknownProperty");
} else {
var dict = try Bus.open(&out, c.DBUS_TYPE_ARRAY, "{sv}");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&out, &dict);
for ([_][:0]const u8{ "Version", "TextDirection", "Status", "IconThemePath" }) |name| {
var pair = try Bus.open(&dict, c.DBUS_TYPE_DICT_ENTRY, null);
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&dict, &pair);
try Bus.string(&pair, name.ptr);
_ = try globalProperty(name, &pair);
try Bus.close(&dict, &pair);
}
try Bus.close(&out, &dict);
}
} else if (callIs(message, iface, "GetLayout")) {
if (!signature(message, "iias")) return fail(message, invalid);
const index = nodeId(e, Bus.int(&args).?) orelse return fail(message, missing);
_ = Bus.next(&args);
const depth = Bus.int(&args).?;
if (depth < -1) return fail(message, invalid);
_ = Bus.next(&args);
try Bus.basic(&out, c.DBUS_TYPE_UINT32, @as(u32, 1));
try layout(e, index, depth, Bus.child(&args), &out);
} else if (callIs(message, iface, "GetGroupProperties")) {
if (!signature(message, "aias")) return fail(message, invalid);
var ids = Bus.child(&args);
_ = Bus.next(&args);
const filter = Bus.child(&args);
var items = try Bus.open(&out, c.DBUS_TYPE_ARRAY, "(ia{sv})");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&out, &items);
if (Bus.kind(&ids) == c.DBUS_TYPE_INVALID) {
for (e.nodes.items, 0..) |_, i| try groupItem(e, i, filter, &items);
} else while (Bus.int(&ids)) |value| {
if (nodeId(e, value)) |i| try groupItem(e, i, filter, &items);
if (!Bus.next(&ids)) break;
}
try Bus.close(&out, &items);
} else if (callIs(message, iface, "GetProperty")) {
if (!signature(message, "is")) return fail(message, invalid);
const index = nodeId(e, Bus.int(&args).?) orelse return fail(message, missing);
_ = Bus.next(&args);
if (!try property(e, index, Bus.str(&args).?, &out)) return fail(message, "org.freedesktop.DBus.Error.UnknownProperty");
} else if (callIs(message, iface, "Event")) {
if (!signature(message, "isvu")) return fail(message, invalid);
const index = nodeId(e, Bus.int(&args).?) orelse return fail(message, missing);
_ = Bus.next(&args);
if (same(Bus.str(&args).?, "clicked")) activate(e, index, Input.id());
} else if (callIs(message, iface, "AboutToShow")) {
if (!signature(message, "i")) return fail(message, invalid);
_ = nodeId(e, Bus.int(&args).?) orelse return fail(message, missing);
try Bus.basic(&out, c.DBUS_TYPE_BOOLEAN, @as(u32, 0));
} else if (callIs(message, iface, "EventGroup") or callIs(message, iface, "AboutToShowGroup")) {
const events = callIs(message, iface, "EventGroup");
if (!signature(message, if (events) "a(isvu)" else "ai")) return fail(message, invalid);
var ids = Bus.child(&args);
if (!events) {
var empty = try Bus.open(&out, c.DBUS_TYPE_ARRAY, "i");
try Bus.close(&out, &empty);
}
var errors = try Bus.open(&out, c.DBUS_TYPE_ARRAY, "i");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&out, &errors);
var valid_count: usize = 0;
var error_count: usize = 0;
while (Bus.kind(&ids) != c.DBUS_TYPE_INVALID) {
var value = if (events) Bus.child(&ids) else ids;
const number = Bus.int(&value).?;
if (nodeId(e, number)) |index| {
valid_count += 1;
if (events) {
_ = Bus.next(&value);
if (same(Bus.str(&value).?, "clicked")) activate(e, index, Input.id());
}
} else {
error_count += 1;
try Bus.basic(&errors, c.DBUS_TYPE_INT32, number);
}
if (!Bus.next(&ids)) break;
}
try Bus.close(&out, &errors);
if (valid_count == 0 and error_count != 0) return fail(message, missing);
} else return fail(message, "org.freedesktop.DBus.Error.UnknownMethod");
if (c.z_dbus_message_get_no_reply.?(message) == 0) _ = try Portal.send(reply);
}

61
src/linux/Keyboard.zig Normal file
View File

@ -0,0 +1,61 @@
const std = @import("std");
const c = @import("linux_c");
const n = @import("native");
/// evdev keyboard codes to USB HID keyboard-page usages. Values describe
/// physical positions, independently of the compositor-supplied XKB layout.
pub fn physical(code: u32) u32 {
const map = comptime blk: {
var t = [_]u16{0} ** 256;
const pairs = [_][2]u16{
.{ 1, 41 }, .{ 12, 45 }, .{ 13, 46 }, .{ 14, 42 }, .{ 15, 43 }, .{ 26, 47 }, .{ 27, 48 }, .{ 28, 40 }, .{ 29, 224 },
.{ 39, 51 }, .{ 40, 52 }, .{ 41, 53 }, .{ 42, 225 }, .{ 43, 49 }, .{ 51, 54 }, .{ 52, 55 }, .{ 53, 56 }, .{ 54, 229 },
.{ 55, 85 }, .{ 56, 226 }, .{ 57, 44 }, .{ 58, 57 }, .{ 69, 83 }, .{ 70, 71 }, .{ 71, 95 }, .{ 72, 96 }, .{ 73, 97 },
.{ 74, 86 }, .{ 75, 92 }, .{ 76, 93 }, .{ 77, 94 }, .{ 78, 87 }, .{ 79, 89 }, .{ 80, 90 }, .{ 81, 91 }, .{ 82, 98 },
.{ 83, 99 }, .{ 86, 100 }, .{ 87, 68 }, .{ 88, 69 }, .{ 96, 88 }, .{ 97, 228 }, .{ 98, 84 }, .{ 99, 70 }, .{ 100, 230 },
.{ 102, 74 }, .{ 103, 82 }, .{ 104, 75 }, .{ 105, 80 }, .{ 106, 79 }, .{ 107, 77 }, .{ 108, 81 }, .{ 109, 78 }, .{ 110, 73 },
.{ 111, 76 }, .{ 119, 72 }, .{ 125, 227 }, .{ 126, 231 }, .{ 127, 101 },
};
for (pairs) |p| t[p[0]] = p[1];
for (0..10) |i| t[2 + i] = 30 + i;
for (0..10) |i| t[59 + i] = 58 + i;
for (0..12) |i| t[183 + i] = 104 + i;
for ("qwertyuiop", 16..) |letter, code_| t[code_] = letter - 'a' + 4;
for ("asdfghjkl", 30..) |letter, code_| t[code_] = letter - 'a' + 4;
for ("zxcvbnm", 44..) |letter, code_| t[code_] = letter - 'a' + 4;
break :blk t;
};
return if (code < map.len) map[code] else 0;
}
pub fn logical(sym: u32) u32 {
return switch (sym) {
c.XKB_KEY_Escape => n.UHDK_KEY_ESCAPE,
c.XKB_KEY_Return, c.XKB_KEY_KP_Enter => n.UHDK_KEY_ENTER,
c.XKB_KEY_Tab, c.XKB_KEY_ISO_Left_Tab => n.UHDK_KEY_TAB,
c.XKB_KEY_BackSpace => n.UHDK_KEY_BACKSPACE,
c.XKB_KEY_Delete, c.XKB_KEY_KP_Delete => n.UHDK_KEY_DELETE,
c.XKB_KEY_Insert, c.XKB_KEY_KP_Insert => n.UHDK_KEY_INSERT,
c.XKB_KEY_Left, c.XKB_KEY_KP_Left => n.UHDK_KEY_LEFT,
c.XKB_KEY_Right, c.XKB_KEY_KP_Right => n.UHDK_KEY_RIGHT,
c.XKB_KEY_Up, c.XKB_KEY_KP_Up => n.UHDK_KEY_UP,
c.XKB_KEY_Down, c.XKB_KEY_KP_Down => n.UHDK_KEY_DOWN,
c.XKB_KEY_Home, c.XKB_KEY_KP_Home => n.UHDK_KEY_HOME,
c.XKB_KEY_End, c.XKB_KEY_KP_End => n.UHDK_KEY_END,
c.XKB_KEY_Page_Up, c.XKB_KEY_KP_Page_Up => n.UHDK_KEY_PAGE_UP,
c.XKB_KEY_Page_Down, c.XKB_KEY_KP_Page_Down => n.UHDK_KEY_PAGE_DOWN,
c.XKB_KEY_F1...c.XKB_KEY_F24 => @as(u32, n.UHDK_KEY_F1) + sym - c.XKB_KEY_F1,
else => c.z_xkb_keysym_to_utf32.?(sym),
};
}
pub fn edit(key: u32, mods: u32) u32 {
return n.z_edit_key(key, mods);
}
test "physical positions and editor shortcuts" {
try std.testing.expectEqual(@as(u32, 4), physical(30));
try std.testing.expectEqual(@as(u32, 29), physical(44));
try std.testing.expectEqual(@as(u32, 0), physical(500));
try std.testing.expectEqual(@as(u32, n.UHDK_EDIT_COPY), edit('c', n.UHDK_MOD_CONTROL));
try std.testing.expectEqual(@as(u32, 0), edit('c', n.UHDK_MOD_CONTROL | n.UHDK_MOD_ALT));
try std.testing.expectEqual(@as(u32, n.UHDK_EDIT_DOCUMENT_START), edit(n.UHDK_KEY_HOME, n.UHDK_MOD_CONTROL));
}

61
src/linux/Menu.zig Normal file
View File

@ -0,0 +1,61 @@
//! Linux menu role dispatch and shortcut names shared by desktop exports/popups.
const std = @import("std");
const n = @import("native");
const native = @import("native.zig");
const runtime = @import("../runtime.zig");
const Input = @import("Input.zig");
const Text = @import("Text.zig");
pub fn activate(window: *native.Window, item: *const n.uhdk_menu_item, input_id: u64) void {
var event = std.mem.zeroes(n.z_event);
if (item.role == n.UHDK_MENU_ROLE_QUIT) {
event.type = n.UHDK_EVENT_QUIT_REQUESTED;
n.z_emit(runtime.toAppHandle(window.owner), &event);
} else if (item.role >= n.UHDK_MENU_ROLE_COPY and item.role <= n.UHDK_MENU_ROLE_REDO) {
Text.editCommand(window, @as(u32, n.UHDK_EDIT_COPY) + item.role - n.UHDK_MENU_ROLE_COPY, 0, input_id);
} else {
event.type = n.UHDK_EVENT_MENU_COMMAND;
event.data.menu.command_id = item.command_id;
Input.send(window, &event);
}
}
pub fn keyName(key: u32, buffer: *[32]u8) ?[:0]const u8 {
if (key >= n.UHDK_KEY_F1 and key < n.UHDK_KEY_F1 + 24) return std.fmt.bufPrintZ(buffer, "F{d}", .{key - n.UHDK_KEY_F1 + 1}) catch unreachable;
switch (key) {
n.UHDK_KEY_ESCAPE => return "Escape",
n.UHDK_KEY_ENTER => return "Return",
n.UHDK_KEY_TAB => return "Tab",
n.UHDK_KEY_BACKSPACE => return "BackSpace",
n.UHDK_KEY_DELETE => return "Delete",
n.UHDK_KEY_INSERT => return "Insert",
n.UHDK_KEY_LEFT => return "Left",
n.UHDK_KEY_RIGHT => return "Right",
n.UHDK_KEY_UP => return "Up",
n.UHDK_KEY_DOWN => return "Down",
n.UHDK_KEY_HOME => return "Home",
n.UHDK_KEY_END => return "End",
n.UHDK_KEY_PAGE_UP => return "Page_Up",
n.UHDK_KEY_PAGE_DOWN => return "Page_Down",
' ' => return "space",
else => {},
}
if (key < 0x21 or key > 0x10ffff) return null;
const k = if (key >= 'a' and key <= 'z') key - ('a' - 'A') else key;
const len = std.unicode.utf8Encode(@intCast(k), buffer) catch return null;
buffer[len] = 0;
return buffer[0..len :0];
}
pub fn shortcut(item: n.uhdk_menu_item, buffer: *[96]u8) []const u8 {
var key_buffer: [32]u8 = undefined;
const key = keyName(item.shortcut_key, &key_buffer) orelse return "";
var end: usize = 0;
inline for (.{ .{ n.UHDK_MOD_CONTROL, "Ctrl+" }, .{ n.UHDK_MOD_ALT, "Alt+" }, .{ n.UHDK_MOD_SHIFT, "Shift+" }, .{ n.UHDK_MOD_SUPER, "Super+" } }) |mod| {
if (item.shortcut_modifiers & mod[0] != 0) {
@memcpy(buffer[end..][0..mod[1].len], mod[1]);
end += mod[1].len;
}
}
@memcpy(buffer[end..][0..key.len], key);
end += key.len;
return buffer[0..end];
}

464
src/linux/Popup.zig Normal file
View File

@ -0,0 +1,464 @@
//! Small software-rendered xdg_popup menus. One active request owns a stack of
//! panels, with two wl_shm buffers per panel. Everything runs on the native thread.
const std = @import("std");
const c = @import("linux_c");
const n = @import("native");
const native = @import("native.zig");
const runtime = @import("../runtime.zig");
const Resource = @import("../Resource.zig");
const abi = @import("../abi.zig");
const Input = @import("Input.zig");
const Menu = @import("Menu.zig");
const Bitmap = @import("Bitmap.zig");
const Buffer = struct { proxy: ?*c.struct_wl_buffer = null, mapping: ?*anyopaque = null, size: usize = 0, busy: bool = false };
const Panel = struct {
parent: ?*Panel,
menu: *Resource,
surface: ?*c.struct_wl_surface = null,
xdg: ?*c.struct_xdg_surface = null,
popup: ?*c.struct_xdg_popup = null,
buffers: [2]Buffer = .{ .{}, .{} },
width: i32 = 180,
height: i32 = 32,
scale: i32 = 1,
pending_width: i32 = 180,
pending_height: i32 = 32,
total: i32 = 8,
scroll: i32 = 0,
selected: ?usize = null,
pressed: ?usize = null,
configured: bool = false,
dirty: bool = true,
deadline: u64 = 0,
};
pub const State = struct {
window: ?*native.Window = null,
root: ?*Resource = null,
top: ?*Panel = null,
pointer: ?*Panel = null,
request: u64 = 0,
serial: u32 = 0,
hover_at: u64 = 0,
};
fn b() *native.Backend {
return native.instance.?;
}
fn s() *State {
return &b().popup;
}
pub fn active() bool {
return s().request != 0;
}
pub fn capability() void {
native.setCapability(n.UHDK_CAP_CONTEXT_MENU, b().desktop.shm != null and Input.state().seat != null);
}
fn panel(raw: ?*anyopaque) *Panel {
return @ptrCast(@alignCast(raw.?));
}
fn rowHeight(item: n.uhdk_menu_item) i32 {
return if (item.flags & n.UHDK_MENU_SEPARATOR != 0) 9 else 26;
}
fn usable(item: n.uhdk_menu_item) bool {
return item.flags & (n.UHDK_MENU_DISABLED | n.UHDK_MENU_SEPARATOR) == 0;
}
fn rowY(p: *Panel, index: usize) i32 {
var y: i32 = 4;
for (p.menu.payload.menu.items[0..index]) |item| y += rowHeight(item);
return y - p.scroll;
}
fn lookup(surface: ?*c.struct_wl_surface) ?*Panel {
var cursor = s().top;
while (cursor) |p| : (cursor = p.parent) if (p.surface == surface) return p;
return null;
}
pub fn owner(surface: ?*c.struct_wl_surface) ?*native.Window {
return if (lookup(surface) != null) s().window else null;
}
fn freeBuffer(buf: *Buffer) void {
if (buf.proxy != null) c.wl_buffer_destroy(buf.proxy);
if (buf.mapping) |mapping| _ = c.munmap(mapping, buf.size);
buf.* = .{};
}
fn destroy(p: *Panel) void {
if (s().pointer == p) s().pointer = null;
if (p.popup != null) c.xdg_popup_destroy(p.popup);
if (p.xdg != null) c.xdg_surface_destroy(p.xdg);
if (p.surface != null) c.wl_surface_destroy(p.surface);
for (&p.buffers) |*buf| freeBuffer(buf);
b().app.gpa.destroy(p);
}
fn closeChildren(p: *Panel) void {
while (s().top != p) {
const top = s().top orelse return;
s().top = top.parent;
destroy(top);
}
}
fn finish(status: u32) void {
if (!active()) return;
const request_id = s().request;
while (s().top) |top| {
s().top = top.parent;
destroy(top);
}
const root = s().root.?;
s().* = .{};
runtime.release(root);
n.z_complete(runtime.toAppHandle(b().app), request_id, status, null);
}
pub fn forget(w: *native.Window) void {
if (s().window == w) finish(abi.CANCELLED);
}
pub fn cancel(id: u64) bool {
if (s().request != id or !active()) return false;
finish(abi.CANCELLED);
return true;
}
pub fn deinit() void {
finish(abi.CANCELLED);
}
pub fn request(cmd: *const n.z_command, id: u64) u32 {
if (active()) return n.UHDK_STATUS_BUSY;
if (b().desktop.shm == null or Input.state().seat == null) return n.UHDK_STATUS_UNSUPPORTED;
const w: *native.Window = @ptrCast(@alignCast(n.z_object_native(cmd.other) orelse return abi.UNAVAILABLE));
const input = Input.state();
if (!w.visible or !w.configured or input.popup_window != w or input.popup_serial == 0 or runtime.now(b().app.io) -| input.popup_time > 5 * std.time.ns_per_s) return abi.UNAVAILABLE;
const root: *Resource = @ptrCast(@alignCast(cmd.target.?));
if (root.payload.menu.items.len == 0) return abi.CANCELLED;
s().window = w;
s().root = root;
s().serial = input.popup_serial;
runtime.retain(root);
const point: n.uhdk_point = .{ .x = std.math.clamp(cmd.point.x, 0, @as(f32, @floatFromInt(@max(0, w.width - 1)))), .y = std.math.clamp(cmd.point.y, 0, @as(f32, @floatFromInt(@max(0, w.height - 1)))) };
const p = create(root, null, @intFromFloat(point.x), @intFromFloat(point.y), 1) catch {
runtime.release(root);
s().* = .{};
return abi.OOM;
};
s().top = p;
s().request = id;
Input.beginPopup();
input.popup_serial = 0; // A root grab consumes its triggering action.
return n.Z_DEFERRED;
}
fn create(menu: *Resource, parent: ?*Panel, x: i32, y: i32, anchor_height: i32) !*Panel {
const p = try b().app.gpa.create(Panel);
p.* = .{ .parent = parent, .menu = menu, .scale = @intCast(std.math.clamp((s().window.?.scale + 119) / 120, 1, 4)), .deadline = runtime.now(b().app.io) + 5 * std.time.ns_per_s };
errdefer destroy(p);
for (menu.payload.menu.items) |item| {
var buffer: [96]u8 = undefined;
p.width = @max(p.width, 60 + Bitmap.width(abi.textBytes(item.label)) + Bitmap.width(Menu.shortcut(item, &buffer)));
p.total += rowHeight(item);
}
p.width = @min(p.width, 480);
p.height = @min(p.total, 480);
p.pending_width = p.width;
p.pending_height = p.height;
p.surface = c.wl_compositor_create_surface(b().compositor) orelse return error.OutOfMemory;
if (c.wl_surface_add_listener(p.surface, &surface_listener, p) != 0) return error.OutOfMemory;
p.xdg = c.xdg_wm_base_get_xdg_surface(b().shell, p.surface) orelse return error.OutOfMemory;
if (c.xdg_surface_add_listener(p.xdg, &xdg_listener, p) != 0) return error.OutOfMemory;
const pos = c.xdg_wm_base_create_positioner(b().shell) orelse return error.OutOfMemory;
defer c.xdg_positioner_destroy(pos);
c.xdg_positioner_set_size(pos, p.width, p.height);
c.xdg_positioner_set_anchor_rect(pos, x, y, 1, @max(1, anchor_height));
c.xdg_positioner_set_anchor(pos, if (parent != null) c.XDG_POSITIONER_ANCHOR_TOP_RIGHT else c.XDG_POSITIONER_ANCHOR_BOTTOM_LEFT);
c.xdg_positioner_set_gravity(pos, c.XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
c.xdg_positioner_set_constraint_adjustment(pos, c.XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | c.XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | c.XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y | c.XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | c.XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y);
p.popup = c.xdg_surface_get_popup(p.xdg, if (parent) |v| v.xdg else s().window.?.xdg, pos) orelse return error.OutOfMemory;
if (c.xdg_popup_add_listener(p.popup, &popup_listener, p) != 0) return error.OutOfMemory;
c.xdg_popup_grab(p.popup, Input.state().seat, s().serial);
c.wl_surface_commit(p.surface);
return p;
}
fn configure(raw: ?*anyopaque, _: ?*c.struct_xdg_popup, _: i32, _: i32, width: i32, height: i32) callconv(.c) void {
const p = panel(raw);
if (width < 1 or height < 1 or width > 1024 or height > 1024) {
finish(n.UHDK_STATUS_OS_ERROR);
return;
}
// Geometry belongs to the next xdg_surface.configure, which can arrive in
// another socket read. Do not render/commit it before acknowledging that.
p.pending_width = width;
p.pending_height = height;
p.configured = false;
p.deadline = runtime.now(b().app.io) + 5 * std.time.ns_per_s;
}
fn configured(raw: ?*anyopaque, _: ?*c.struct_xdg_surface, serial: u32) callconv(.c) void {
const p = panel(raw);
c.xdg_surface_ack_configure(p.xdg, serial);
if (p.width != p.pending_width or p.height != p.pending_height) {
for (&p.buffers) |*buf| freeBuffer(buf);
p.width = p.pending_width;
p.height = p.pending_height;
p.scroll = std.math.clamp(p.scroll, 0, @max(0, p.total - p.height));
}
p.configured = true;
p.deadline = 0;
p.dirty = true;
render(p) catch finish(abi.OOM);
}
fn done(raw: ?*anyopaque, _: ?*c.struct_xdg_popup) callconv(.c) void {
const p = panel(raw);
if (p.parent) |parent| {
closeChildren(parent);
s().hover_at = 0;
} else finish(abi.CANCELLED);
}
fn repositioned(_: ?*anyopaque, _: ?*c.struct_xdg_popup, _: u32) callconv(.c) void {}
const popup_listener: c.struct_xdg_popup_listener = .{ .configure = configure, .popup_done = done, .repositioned = repositioned };
const xdg_listener: c.struct_xdg_surface_listener = .{ .configure = configured };
fn enter(_: ?*anyopaque, _: ?*c.struct_wl_surface, _: ?*c.struct_wl_output) callconv(.c) void {}
fn scale(raw: ?*anyopaque, _: ?*c.struct_wl_surface, value: i32) callconv(.c) void {
const p = panel(raw);
const next = std.math.clamp(value, 1, 4);
if (next == p.scale) return;
p.scale = next;
for (&p.buffers) |*buf| freeBuffer(buf);
p.dirty = true;
}
fn transform(_: ?*anyopaque, _: ?*c.struct_wl_surface, _: u32) callconv(.c) void {}
const surface_listener: c.struct_wl_surface_listener = .{ .enter = enter, .leave = enter, .preferred_buffer_scale = scale, .preferred_buffer_transform = transform };
fn released(raw: ?*anyopaque, proxy: ?*c.struct_wl_buffer) callconv(.c) void {
const p = panel(raw);
for (&p.buffers) |*buf| if (buf.proxy == proxy) {
buf.busy = false;
};
}
const buffer_listener: c.struct_wl_buffer_listener = .{ .release = released };
fn allocateBuffer(p: *Panel, buf: *Buffer) !void {
const size: usize = @intCast(p.width * p.height * p.scale * p.scale * 4);
const fd = c.memfd_create("uhdk-menu", c.MFD_CLOEXEC);
if (fd < 0) return error.OutOfMemory;
defer _ = c.close(fd);
if (c.ftruncate(fd, @intCast(size)) != 0) return error.OutOfMemory;
const mapping = c.mmap(null, size, c.PROT_READ | c.PROT_WRITE, c.MAP_SHARED, fd, 0);
if (mapping == c.MAP_FAILED) return error.OutOfMemory;
buf.mapping = mapping;
buf.size = size;
errdefer freeBuffer(buf);
const pool = c.wl_shm_create_pool(b().desktop.shm, fd, @intCast(size)) orelse return error.OutOfMemory;
defer c.wl_shm_pool_destroy(pool);
buf.proxy = c.wl_shm_pool_create_buffer(pool, 0, p.width * p.scale, p.height * p.scale, p.width * p.scale * 4, c.WL_SHM_FORMAT_ARGB8888) orelse return error.OutOfMemory;
if (c.wl_buffer_add_listener(buf.proxy, &buffer_listener, p) != 0) return error.OutOfMemory;
}
fn render(p: *Panel) !void {
if (!p.configured or !p.dirty) return;
const buf: *Buffer = for (&p.buffers) |*value| {
if (!value.busy) break value;
} else return;
if (buf.proxy == null) try allocateBuffer(p, buf);
const words: [*]u32 = @ptrCast(@alignCast(buf.mapping.?));
const canvas: Bitmap.Canvas = .{ .pixels = words[0 .. buf.size / 4], .width = p.width, .height = p.height, .scale = p.scale };
paint(canvas, p.menu.payload.menu.items, p.selected, p.scroll, b().portal.preferences.appearance == n.UHDK_APPEARANCE_LIGHT);
c.wl_surface_set_buffer_scale(p.surface, p.scale);
c.xdg_surface_set_window_geometry(p.xdg, 0, 0, p.width, p.height);
c.wl_surface_attach(p.surface, buf.proxy, 0, 0);
c.wl_surface_damage_buffer(p.surface, 0, 0, p.width * p.scale, p.height * p.scale);
c.wl_surface_commit(p.surface);
buf.busy = true;
p.dirty = false;
}
pub fn paint(canvas: Bitmap.Canvas, items: []const n.uhdk_menu_item, selected: ?usize, scroll_offset: i32, light: bool) void {
const background: u32 = if (light) 0xf6f6f6 else 0x292c30;
const foreground: u32 = if (light) 0x202124 else 0xf1f2f3;
const border: u32 = if (light) 0xc4c7ca else 0x53585e;
canvas.rect(0, 0, canvas.width, canvas.height, background);
var y: i32 = 4 - scroll_offset;
for (items, 0..) |item, i| {
const height = rowHeight(item);
defer y += height;
if (y + height <= 4 or y >= canvas.height - 4) continue;
if (item.flags & n.UHDK_MENU_SEPARATOR != 0) {
canvas.rect(8, y + 4, canvas.width - 16, 1, border);
continue;
}
const color: u32 = if (item.flags & n.UHDK_MENU_DISABLED != 0) (if (light) @as(u32, 0x85888c) else 0x8d939a) else if (selected == i) 0xffffff else foreground;
if (selected == i and usable(item)) canvas.rect(3, y, canvas.width - 6, height, 0x2969b0);
if (item.flags & n.UHDK_MENU_CHECKED != 0) {
for (0..4) |j| canvas.rect(10 + @as(i32, @intCast(j)), y + 12 + @as(i32, @intCast(j)), 2, 2, color);
for (0..7) |j| canvas.rect(13 + @as(i32, @intCast(j)), y + 15 - @as(i32, @intCast(j)), 2, 2, color);
}
var storage: [96]u8 = undefined;
const shortcut = Menu.shortcut(item, &storage);
const shortcut_width = @min(Bitmap.width(shortcut), @max(0, @divTrunc(canvas.width, 2) - 30));
const label_width = @max(0, canvas.width - 52 - (if (shortcut.len != 0) shortcut_width + 18 else 0));
canvas.text(abi.textBytes(item.label), 28, y + 18, label_width, color);
if (shortcut.len != 0) canvas.text(shortcut, canvas.width - 22 - shortcut_width, y + 18, shortcut_width, color);
if (item.submenu != null) for (0..5) |j| {
const offset: i32 = @intCast(j);
canvas.rect(canvas.width - 13 + offset, y + 8 + offset, 1, 9 - 2 * offset, color);
};
}
// Padding masks partially scrolled rows and keeps the border intact.
canvas.rect(0, 0, canvas.width, 4, background);
canvas.rect(0, canvas.height - 4, canvas.width, 4, background);
canvas.rect(0, 0, canvas.width, 1, border);
canvas.rect(0, canvas.height - 1, canvas.width, 1, border);
canvas.rect(0, 0, 1, canvas.height, border);
canvas.rect(canvas.width - 1, 0, 1, canvas.height, border);
if (scroll_offset > 0) canvas.rect(@divTrunc(canvas.width, 2) - 5, 1, 10, 2, foreground);
if (y > canvas.height - 4) canvas.rect(@divTrunc(canvas.width, 2) - 5, canvas.height - 3, 10, 2, foreground);
}
fn choose(p: *Panel, index: ?usize) void {
if (p.selected == index) return;
closeChildren(p);
p.selected = index;
p.pressed = null;
p.dirty = true;
s().hover_at = if (index != null and p.menu.payload.menu.items[index.?].submenu != null) runtime.now(b().app.io) + 200 * std.time.ns_per_ms else 0;
}
fn hit(p: *Panel, x: f32, y: f32) ?usize {
if (x < 3 or x >= @as(f32, @floatFromInt(p.width - 3)) or y < 4 or y >= @as(f32, @floatFromInt(p.height - 4))) return null;
var top: i32 = 4 - p.scroll;
for (p.menu.payload.menu.items, 0..) |item, i| {
const bottom = top + rowHeight(item);
if (y >= @as(f32, @floatFromInt(top)) and y < @as(f32, @floatFromInt(bottom))) return if (usable(item)) i else null;
top = bottom;
}
return null;
}
pub fn pointerEnter(surface: ?*c.struct_wl_surface, x: f32, y: f32, serial: u32) bool {
s().pointer = lookup(surface);
const p = s().pointer orelse return false;
const input = Input.state();
if (input.cursor_device != null) c.wp_cursor_shape_device_v1_set_shape(input.cursor_device, serial, c.WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
choose(p, hit(p, x, y));
return true;
}
pub fn pointerLeave() void {
s().pointer = null;
s().hover_at = 0;
}
pub fn motion(x: f32, y: f32) void {
if (s().pointer) |p| choose(p, hit(p, x, y));
}
pub fn button(code: u32, down: bool, serial: u32) void {
if (down) s().serial = serial;
const p = s().pointer orelse {
if (down) finish(abi.CANCELLED);
return;
};
if (code != 0x110) return;
if (down) p.pressed = p.selected else if (p.pressed != null and p.pressed == p.selected) select(p);
}
fn openChild(p: *Panel) void {
s().hover_at = 0;
const index = p.selected orelse return;
const item = p.menu.payload.menu.items[index];
if (!usable(item) or item.submenu == null) return;
closeChildren(p);
const menu: *Resource = @ptrCast(@alignCast(item.submenu.?));
if (menu.payload.menu.items.len == 0) return;
const y = std.math.clamp(rowY(p, index), 0, p.height - 1);
const child = create(menu, p, p.width - 1, y, @min(26, p.height - y)) catch {
finish(abi.OOM);
return;
};
s().top = child;
}
fn select(p: *Panel) void {
const index = p.selected orelse return;
const item = &p.menu.payload.menu.items[index];
if (!usable(item.*)) return;
if (item.submenu != null) {
openChild(p);
return;
}
Menu.activate(s().window.?, item, Input.id());
finish(abi.OK); // Command event must precede request completion.
}
fn reveal(p: *Panel) void {
const index = p.selected orelse return;
const y = rowY(p, index);
if (y < 4) p.scroll -= 4 - y;
if (y + 26 > p.height - 4) p.scroll += y + 26 - (p.height - 4);
p.scroll = std.math.clamp(p.scroll, 0, @max(0, p.total - p.height));
p.dirty = true;
}
fn move(p: *Panel, forward: bool) void {
const items = p.menu.payload.menu.items;
if (items.len == 0) return;
var index = p.selected orelse (if (forward) items.len - 1 else 0);
for (0..items.len) |_| {
index = if (forward) (index + 1) % items.len else (index + items.len - 1) % items.len;
if (usable(items[index])) {
choose(p, index);
s().hover_at = 0;
reveal(p);
return;
}
}
}
pub fn key(logical: u32, down: bool, serial: u32) void {
if (!down) return;
const p = s().top orelse return;
s().serial = serial;
switch (logical) {
n.UHDK_KEY_ESCAPE => finish(abi.CANCELLED),
n.UHDK_KEY_UP => move(p, false),
n.UHDK_KEY_DOWN, n.UHDK_KEY_TAB => move(p, true),
n.UHDK_KEY_HOME => {
choose(p, null);
move(p, true);
},
n.UHDK_KEY_END => {
choose(p, null);
move(p, false);
},
n.UHDK_KEY_RIGHT => {
openChild(p);
if (s().top != p and s().top != null) move(s().top.?, true);
},
n.UHDK_KEY_LEFT => if (p.parent) |parent| {
closeChildren(parent);
s().hover_at = 0;
},
n.UHDK_KEY_ENTER, ' ' => select(p),
else => {},
}
}
pub fn scroll(delta: f32) void {
const p = s().pointer orelse return;
closeChildren(p);
p.scroll = std.math.clamp(p.scroll + @as(i32, @intFromFloat(std.math.clamp(delta, -480, 480))), 0, @max(0, p.total - p.height));
p.selected = null;
p.pressed = null;
p.dirty = true;
s().hover_at = 0;
}
pub fn flags() void {
var cursor = s().top;
while (cursor) |p| : (cursor = p.parent) {
p.dirty = true;
if (p.selected) |index| if (!usable(p.menu.payload.menu.items[index])) {
closeChildren(p);
p.selected = null;
p.pressed = null;
};
}
}
pub fn tick() i32 {
if (!active()) return -1;
const now = runtime.now(b().app.io);
if (s().hover_at != 0 and now >= s().hover_at) {
if (s().pointer) |p| openChild(p);
s().hover_at = 0;
}
var cursor = s().top;
var timeout: i32 = if (s().hover_at != 0) 25 else -1;
while (cursor) |p| : (cursor = p.parent) {
if (!p.configured) {
if (now >= p.deadline) {
finish(abi.UNAVAILABLE);
return -1;
}
timeout = 100;
}
render(p) catch {
finish(abi.OOM);
return -1;
};
}
return timeout;
}

539
src/linux/Portal.zig Normal file
View File

@ -0,0 +1,539 @@
//! Freedesktop portal frontend over a private, asynchronously pumped D-Bus
//! connection. The session chooses its backend (including KDE's native dialogs).
const std = @import("std");
const c = @import("linux_c");
const n = @import("native");
const native = @import("native.zig");
const KdeAppMenu = @import("KdeAppMenu.zig");
const Bus = @import("Bus.zig");
const Files = @import("Files.zig");
const Input = @import("Input.zig");
const runtime = @import("../runtime.zig");
const abi = @import("../abi.zig");
const desktop = "org.freedesktop.portal.Desktop";
const path = "/org/freedesktop/portal/desktop";
const chooser = "org.freedesktop.portal.FileChooser";
const settings = "org.freedesktop.portal.Settings";
const dbus = "org.freedesktop.DBus";
const dbus_path = "/org/freedesktop/DBus";
const Pending = struct {
next: ?*Pending = null,
id: u64,
command: ?*const n.z_command,
serial: u32 = 0,
replied: bool = false,
cancelled: bool = false,
handle: [:0]u8,
token: [:0]u8,
exported: ?*c.struct_zxdg_exported_v2 = null,
activation: ?*c.struct_xdg_activation_token_v1 = null,
deadline: u64,
};
pub const State = struct {
connection: ?*c.DBusConnection = null,
unique: ?[:0]u8 = null,
owner: ?[:0]u8 = null,
hello: u32 = 0,
matches: [3]u32 = @splat(0),
ready: bool = false,
version_serial: u32 = 0,
settings_serial: u32 = 0,
chooser_version: u32 = 0,
startup_deadline: u64 = 0,
pending: ?*Pending = null,
exporter: ?*c.struct_zxdg_exporter_v2 = null,
preferences: n.uhdk_preferences = .{ .struct_size = @sizeOf(n.uhdk_preferences), .appearance = n.UHDK_APPEARANCE_UNKNOWN, .reduced_motion = 0, .high_contrast = 0 },
};
fn b() *native.Backend {
return native.instance.?;
}
fn s() *State {
return &b().portal;
}
pub fn send(message: *Bus.Message) !u32 {
var serial: u32 = 0;
if (c.z_dbus_connection_send.?(s().connection, message, &serial) == 0) return error.OutOfMemory;
return serial;
}
pub fn global(registry: ?*c.struct_wl_registry, name: u32, interface: []const u8, _: u32) void {
if (std.mem.eql(u8, interface, "zxdg_exporter_v2")) s().exporter = @ptrCast(c.wl_registry_bind(registry, name, &c.z_uhdk_zxdg_exporter_v2_interface, 1));
}
pub fn initialize() void {
if (c.z_linux_dbus_available() == 0 or c.z_dbus_threads_init_default.?() == 0) return;
// Avoid libdbus autolaunch/process spawning. Desktop sessions provide this
// address; absence disables portals without affecting windows or input.
const address = c.getenv("DBUS_SESSION_BUS_ADDRESS");
if (address == null or !std.mem.startsWith(u8, std.mem.span(address), "unix:")) return;
s().connection = c.z_dbus_connection_open_private.?(address, null) orelse return;
c.z_dbus_connection_set_exit_on_disconnect.?(s().connection, 0);
s().startup_deadline = runtime.now(b().app.io) + 5 * std.time.ns_per_s;
const message = Bus.method(dbus, dbus_path, dbus, "Hello") catch {
disconnect();
return;
};
defer c.z_dbus_message_unref.?(message);
s().hello = send(message) catch {
disconnect();
return;
};
}
fn addMatches() !void {
const rules = [_][*:0]const u8{
"type='signal',sender='org.freedesktop.portal.Desktop',interface='org.freedesktop.portal.Request',member='Response'",
"type='signal',sender='org.freedesktop.portal.Desktop',interface='org.freedesktop.portal.Settings',member='SettingChanged',path='/org/freedesktop/portal/desktop'",
"type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.freedesktop.portal.Desktop'",
};
for (rules, 0..) |rule, i| {
const message = try Bus.method(dbus, dbus_path, dbus, "AddMatch");
defer c.z_dbus_message_unref.?(message);
var args = Bus.append(message);
try Bus.string(&args, rule);
s().matches[i] = try send(message);
}
}
fn discover() !void {
const version = try Bus.method(desktop, path, "org.freedesktop.DBus.Properties", "Get");
defer c.z_dbus_message_unref.?(version);
var args = Bus.append(version);
try Bus.string(&args, chooser);
try Bus.string(&args, "version");
s().version_serial = try send(version);
const read = try Bus.method(desktop, path, settings, "ReadAll");
defer c.z_dbus_message_unref.?(read);
args = Bus.append(read);
var namespaces = try Bus.open(&args, c.DBUS_TYPE_ARRAY, "s");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&args, &namespaces);
try Bus.string(&namespaces, "org.freedesktop.appearance");
try Bus.close(&args, &namespaces);
s().settings_serial = try send(read);
}
pub fn poll(fds: *std.ArrayList(c.struct_pollfd)) !void {
const connection = s().connection orelse return;
var fd: c_int = -1;
if (c.z_dbus_connection_get_unix_fd.?(connection, &fd) != 0)
try fds.append(b().app.gpa, .{ .fd = fd, .events = @as(c_short, c.POLLIN) | @as(c_short, if (c.z_dbus_connection_has_messages_to_send.?(connection) != 0) c.POLLOUT else 0), .revents = 0 });
}
pub fn tick() i32 {
const connection = s().connection orelse return -1;
if (c.z_dbus_connection_read_write.?(connection, 0) == 0) {
disconnect();
return -1;
}
// Bound dispatch work so an active bus cannot starve Wayland or app commands.
for (0..64) |_| {
const message = c.z_dbus_connection_pop_message.?(connection) orelse break;
defer c.z_dbus_message_unref.?(message);
process(message) catch {
disconnect();
return -1;
};
if (s().connection == null) return -1;
} else return 0;
const now = runtime.now(b().app.io);
if (!s().ready and now >= s().startup_deadline) {
disconnect();
return -1;
}
var cursor = s().pending;
while (cursor) |pending| {
cursor = pending.next;
if (pending.deadline != 0 and now >= pending.deadline) {
closeRequest(pending.handle);
complete(pending, abi.UNAVAILABLE, null);
destroy(pending);
}
}
return if (s().pending != null or !s().ready) 100 else -1;
}
fn setOwner(message: *Bus.Message) !void {
const sender = c.z_dbus_message_get_sender.?(message);
if (sender == null) return;
if (s().owner) |old| b().app.gpa.free(old);
s().owner = try b().app.gpa.dupeZ(u8, std.mem.span(sender));
}
fn process(message: *Bus.Message) !void {
if (try KdeAppMenu.process(message)) return;
const serial = c.z_dbus_message_get_reply_serial.?(message);
const failed = c.z_dbus_message_get_type.?(message) == c.DBUS_MESSAGE_TYPE_ERROR;
var args = Bus.parse(message);
if (serial != 0 and serial == s().hello) {
s().hello = 0;
if (failed or c.z_dbus_message_has_signature.?(message, "s") == 0) {
disconnect();
return;
}
s().unique = try b().app.gpa.dupeZ(u8, Bus.str(&args).?);
try addMatches();
return;
}
for (&s().matches) |*match| if (serial != 0 and serial == match.*) {
if (failed) {
disconnect();
return;
}
match.* = 0;
if (std.mem.allEqual(u32, &s().matches, 0)) {
s().ready = true;
KdeAppMenu.ready();
try discover();
}
return;
};
if (serial != 0 and serial == s().version_serial) {
s().version_serial = 0;
if (!failed) {
var value = Bus.variant(&args) orelse return;
s().chooser_version = Bus.uint(&value) orelse 0;
try setOwner(message);
native.setCapability(n.UHDK_CAP_FILE_DIALOGS, s().chooser_version >= 3);
}
return;
}
if (serial != 0 and serial == s().settings_serial) {
s().settings_serial = 0;
if (!failed and c.z_dbus_message_has_signature.?(message, "a{sa{sv}}") != 0) {
try setOwner(message);
var appearance = Bus.entry(&args, "org.freedesktop.appearance") orelse return;
inline for (.{ "color-scheme", "contrast", "reduced-motion" }) |key| {
if (Bus.entry(&appearance, key)) |raw| {
var v = raw;
if (Bus.variant(&v)) |inner| {
var value = inner;
preference(key, Bus.uint(&value) orelse 0);
}
}
}
}
return;
}
if (c.z_dbus_message_is_signal.?(message, dbus, "NameOwnerChanged") != 0 and c.z_dbus_message_has_signature.?(message, "sss") != 0) {
const sender = c.z_dbus_message_get_sender.?(message);
if (sender == null or !std.mem.eql(u8, std.mem.span(sender), dbus)) return;
if (!std.mem.eql(u8, Bus.str(&args).?, desktop)) return;
_ = Bus.next(&args);
const old = Bus.str(&args).?;
_ = Bus.next(&args);
const new = Bus.str(&args).?;
if (old.len != 0) {
while (s().pending) |p| {
complete(p, abi.UNAVAILABLE, null);
destroy(p);
}
s().chooser_version = 0;
native.setCapability(n.UHDK_CAP_FILE_DIALOGS, false);
if (s().owner) |owner| b().app.gpa.free(owner);
s().owner = null;
preference("color-scheme", 0);
preference("contrast", 0);
preference("reduced-motion", 0);
}
if (new.len != 0) {
if (s().owner) |owner| b().app.gpa.free(owner);
s().owner = try b().app.gpa.dupeZ(u8, new);
try discover();
}
return;
}
var cursor = s().pending;
while (cursor) |pending| : (cursor = pending.next) {
if (serial != 0 and serial == pending.serial) {
pending.replied = true;
if (failed) {
complete(pending, errorStatus(message), null);
destroy(pending);
return;
}
if (c.z_dbus_message_has_signature.?(message, "o") == 0) {
complete(pending, n.UHDK_STATUS_OS_ERROR, null);
destroy(pending);
return;
}
const handle = try b().app.gpa.dupeZ(u8, Bus.str(&args).?);
b().app.gpa.free(pending.handle);
pending.handle = handle;
if (pending.id == 0) {
if (pending.cancelled) closeRequest(pending.handle);
destroy(pending);
} else pending.deadline = 0; // Human interaction has no artificial timeout.
return;
}
}
// Match rules are installed on the bus, and sender validation also protects
// against unrelated unicast signals sent directly to our private connection.
const sender = c.z_dbus_message_get_sender.?(message);
if (sender == null or s().owner == null or !std.mem.eql(u8, std.mem.span(sender), s().owner.?)) return;
if (c.z_dbus_message_is_signal.?(message, settings, "SettingChanged") != 0 and c.z_dbus_message_has_signature.?(message, "ssv") != 0) {
if (!std.mem.eql(u8, Bus.str(&args).?, "org.freedesktop.appearance")) return;
_ = Bus.next(&args);
const key = Bus.str(&args).?;
_ = Bus.next(&args);
var value = Bus.variant(&args).?;
preference(key, Bus.uint(&value) orelse 0);
} else if (c.z_dbus_message_is_signal.?(message, "org.freedesktop.portal.Request", "Response") != 0 and c.z_dbus_message_has_signature.?(message, "ua{sv}") != 0) {
const response_path = c.z_dbus_message_get_path.?(message);
if (response_path == null) return;
cursor = s().pending;
while (cursor) |pending| : (cursor = pending.next) if (std.mem.eql(u8, pending.handle, std.mem.span(response_path))) {
const code = Bus.uint(&args).?;
_ = Bus.next(&args);
if (pending.id != 0) {
if (code != 0) complete(pending, if (code == 1) abi.CANCELLED else n.UHDK_STATUS_OS_ERROR, null) else if (pending.command.?.op == n.Z_URL) complete(pending, abi.OK, null) else responseFiles(pending, &args);
}
if (pending.replied) destroy(pending);
return;
};
}
}
fn errorStatus(message: *Bus.Message) u32 {
const name = c.z_dbus_message_get_error_name.?(message);
if (name != null and (std.mem.endsWith(u8, std.mem.span(name), ".NotFound") or std.mem.endsWith(u8, std.mem.span(name), ".ServiceUnknown") or std.mem.endsWith(u8, std.mem.span(name), ".UnknownMethod"))) return n.UHDK_STATUS_UNSUPPORTED;
return n.UHDK_STATUS_OS_ERROR;
}
fn preference(key: []const u8, value: u32) void {
const old = s().preferences;
if (std.mem.eql(u8, key, "color-scheme")) s().preferences.appearance = switch (value) {
1 => n.UHDK_APPEARANCE_DARK,
2 => n.UHDK_APPEARANCE_LIGHT,
else => n.UHDK_APPEARANCE_UNKNOWN,
} else if (std.mem.eql(u8, key, "contrast")) s().preferences.high_contrast = @intFromBool(value == 1) else if (std.mem.eql(u8, key, "reduced-motion")) s().preferences.reduced_motion = @intFromBool(value == 1);
if (!std.meta.eql(old, s().preferences)) n.z_preferences(runtime.toAppHandle(b().app), &s().preferences);
}
pub fn request(cmd: *const n.z_command, id: u64) u32 {
if (!s().ready or s().connection == null) return abi.UNAVAILABLE;
if (cmd.op == n.Z_DIALOG and s().chooser_version < 3) return n.UHDK_STATUS_UNSUPPORTED;
const p = allocate(cmd, id) catch return abi.OOM;
p.next = s().pending;
s().pending = p;
if (cmd.op == n.Z_DIALOG and cmd.dialog.parent != null and s().exporter != null) {
const w: *native.Window = @ptrCast(@alignCast(n.z_object_native(cmd.dialog.parent) orelse {
destroy(p);
return abi.UNAVAILABLE;
}));
p.exported = c.zxdg_exporter_v2_export_toplevel(s().exporter, w.surface) orelse {
destroy(p);
return abi.OOM;
};
_ = c.zxdg_exported_v2_add_listener(p.exported, &export_listener, p);
} else if (cmd.op == n.Z_URL and b().desktop.activation != null and Input.state().key_focus != null and
Input.state().serial != 0 and runtime.now(b().app.io) -| Input.state().serial_time <= 5 * std.time.ns_per_s)
{
p.activation = c.xdg_activation_v1_get_activation_token(b().desktop.activation) orelse {
destroy(p);
return abi.OOM;
};
_ = c.xdg_activation_token_v1_add_listener(p.activation, &activation_listener, p);
c.xdg_activation_token_v1_set_serial(p.activation, Input.state().serial, Input.state().seat);
c.xdg_activation_token_v1_set_surface(p.activation, Input.state().key_focus.?.surface);
c.xdg_activation_token_v1_commit(p.activation);
} else submit(p, "", null) catch {
destroy(p);
return abi.OOM;
};
return n.Z_DEFERRED;
}
fn allocate(cmd: *const n.z_command, id: u64) !*Pending {
const gpa = b().app.gpa;
const p = try gpa.create(Pending);
errdefer gpa.destroy(p);
const token = try std.fmt.allocPrintSentinel(gpa, "uhdk_{x}", .{id}, 0);
errdefer gpa.free(token);
const unique = s().unique.?;
const name = try gpa.dupe(u8, unique[1..]);
defer gpa.free(name);
std.mem.replaceScalar(u8, name, '.', '_');
p.* = .{ .id = id, .command = cmd, .token = token, .handle = try std.fmt.allocPrintSentinel(gpa, path ++ "/request/{s}/{s}", .{ name, token }, 0), .deadline = runtime.now(b().app.io) + 30 * std.time.ns_per_s };
return p;
}
fn exported(raw: ?*anyopaque, _: ?*c.struct_zxdg_exported_v2, handle: [*c]const u8) callconv(.c) void {
const p: *Pending = @ptrCast(@alignCast(raw.?));
const parent = std.fmt.allocPrintSentinel(b().app.gpa, "wayland:{s}", .{std.mem.span(handle)}, 0) catch {
complete(p, abi.OOM, null);
destroy(p);
return;
};
defer b().app.gpa.free(parent);
submit(p, parent, null) catch {
complete(p, abi.OOM, null);
destroy(p);
};
}
const export_listener: c.struct_zxdg_exported_v2_listener = .{ .handle = exported };
fn activated(raw: ?*anyopaque, _: ?*c.struct_xdg_activation_token_v1, token: [*c]const u8) callconv(.c) void {
const p: *Pending = @ptrCast(@alignCast(raw.?));
submit(p, "", token) catch {
complete(p, abi.OOM, null);
destroy(p);
};
}
const activation_listener: c.struct_xdg_activation_token_v1_listener = .{ .done = activated };
fn submit(p: *Pending, parent: [*:0]const u8, activation_token: ?[*:0]const u8) !void {
const cmd = p.command.?;
const dialog = cmd.op == n.Z_DIALOG;
const message = try Bus.method(desktop, path, if (dialog) chooser else "org.freedesktop.portal.OpenURI", if (!dialog) "OpenURI" else if (cmd.dialog.kind == n.UHDK_DIALOG_SAVE_FILE) "SaveFile" else "OpenFile");
defer c.z_dbus_message_unref.?(message);
var arena: std.heap.ArenaAllocator = .init(b().app.gpa);
defer arena.deinit();
const gpa = arena.allocator();
var args = Bus.append(message);
try Bus.string(&args, parent);
try Bus.string(&args, try gpa.dupeZ(u8, abi.textBytes(if (dialog) cmd.dialog.title else cmd.text)));
var options = try Bus.open(&args, c.DBUS_TYPE_ARRAY, "{sv}");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&args, &options);
try Bus.option(&options, "handle_token", "s", p.token.ptr);
if (activation_token) |token| try Bus.option(&options, "activation_token", "s", token);
if (dialog) {
try Bus.option(&options, "modal", "b", @as(u32, @intFromBool(cmd.dialog.parent != null)));
if (cmd.dialog.kind != n.UHDK_DIALOG_SAVE_FILE) {
try Bus.option(&options, "multiple", "b", @as(u32, @intFromBool(cmd.dialog.kind == n.UHDK_DIALOG_OPEN_FILES)));
try Bus.option(&options, "directory", "b", @as(u32, @intFromBool(cmd.dialog.kind == n.UHDK_DIALOG_OPEN_DIRECTORY)));
}
if (cmd.dialog.accept_label.len != 0) try Bus.option(&options, "accept_label", "s", (try gpa.dupeZ(u8, abi.textBytes(cmd.dialog.accept_label))).ptr);
if (cmd.dialog.suggested_name.len != 0 and cmd.dialog.kind == n.UHDK_DIALOG_SAVE_FILE) try Bus.option(&options, "current_name", "s", (try gpa.dupeZ(u8, abi.textBytes(cmd.dialog.suggested_name))).ptr);
if (cmd.dialog.initial_directory != null) {
const directory = abi.bytes(n.z_file_path(cmd.dialog.initial_directory));
var entry = try Bus.open(&options, c.DBUS_TYPE_DICT_ENTRY, null);
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&options, &entry);
try Bus.string(&entry, "current_folder");
var variant = try Bus.open(&entry, c.DBUS_TYPE_VARIANT, "ay");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&entry, &variant);
var bytes = try Bus.open(&variant, c.DBUS_TYPE_ARRAY, "y");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&variant, &bytes);
for (directory) |byte| try Bus.basic(&bytes, c.DBUS_TYPE_BYTE, byte);
try Bus.basic(&bytes, c.DBUS_TYPE_BYTE, @as(u8, 0));
try Bus.close(&variant, &bytes);
try Bus.close(&entry, &variant);
try Bus.close(&options, &entry);
}
if (cmd.dialog.filters_len != 0) try filters(&options, cmd.dialog, gpa);
}
try Bus.close(&args, &options);
p.serial = try send(message);
}
fn filters(options: *Bus.Iter, desc: n.uhdk_file_dialog_desc, gpa: std.mem.Allocator) !void {
var entry = try Bus.open(options, c.DBUS_TYPE_DICT_ENTRY, null);
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(options, &entry);
try Bus.string(&entry, "filters");
var variant = try Bus.open(&entry, c.DBUS_TYPE_VARIANT, "a(sa(us))");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&entry, &variant);
var list = try Bus.open(&variant, c.DBUS_TYPE_ARRAY, "(sa(us))");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&variant, &list);
for (desc.filters[0..@intCast(desc.filters_len)]) |filter| {
var pair = try Bus.open(&list, c.DBUS_TYPE_STRUCT, null);
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&list, &pair);
try Bus.string(&pair, try gpa.dupeZ(u8, abi.textBytes(filter.label)));
var patterns = try Bus.open(&pair, c.DBUS_TYPE_ARRAY, "(us)");
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&pair, &patterns);
for (0..@max(1, filter.extensions.len)) |index| {
const pattern: [:0]const u8 = if (filter.extensions.len == 0) "*" else try std.fmt.allocPrintSentinel(gpa, "*.{s}", .{abi.textBytes(filter.extensions.ptr[index])}, 0);
var rule = try Bus.open(&patterns, c.DBUS_TYPE_STRUCT, null);
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&patterns, &rule);
try Bus.basic(&rule, c.DBUS_TYPE_UINT32, @as(u32, 0));
try Bus.string(&rule, pattern);
try Bus.close(&patterns, &rule);
}
try Bus.close(&pair, &patterns);
try Bus.close(&list, &pair);
}
try Bus.close(&variant, &list);
try Bus.close(&entry, &variant);
try Bus.close(options, &entry);
}
fn responseFiles(p: *Pending, args: *Bus.Iter) void {
var raw = Bus.entry(args, "uris") orelse {
complete(p, n.UHDK_STATUS_OS_ERROR, null);
return;
};
var value = Bus.variant(&raw) orelse {
complete(p, n.UHDK_STATUS_OS_ERROR, null);
return;
};
if (Bus.kind(&value) != c.DBUS_TYPE_ARRAY) {
complete(p, n.UHDK_STATUS_OS_ERROR, null);
return;
}
var strings = Bus.child(&value);
var files: std.ArrayList(?*n.uhdk_file) = .empty;
defer {
for (files.items) |file| n.uhdk_file_release(file);
files.deinit(b().app.gpa);
}
while (Bus.kind(&strings) != c.DBUS_TYPE_INVALID) {
const uri = Bus.str(&strings) orelse {
complete(p, n.UHDK_STATUS_OS_ERROR, null);
return;
};
const bytes = Files.decode(b().app.gpa, uri) catch |err| {
complete(p, if (err == error.OutOfMemory) abi.OOM else n.UHDK_STATUS_UNSUPPORTED, null);
return;
};
defer b().app.gpa.free(bytes);
const file = n.z_file_from_url(runtime.toAppHandle(b().app), .{ .ptr = bytes.ptr, .len = bytes.len }, null) orelse {
complete(p, abi.OOM, null);
return;
};
files.append(b().app.gpa, file) catch {
n.uhdk_file_release(file);
complete(p, abi.OOM, null);
return;
};
if (!Bus.next(&strings)) break;
}
var result = std.mem.zeroes(n.z_request_result);
result.value.files = .{ .ptr = files.items.ptr, .len = files.items.len };
complete(p, if (files.items.len == 0) abi.CANCELLED else abi.OK, &result);
}
fn complete(p: *Pending, status: u32, result: ?*n.z_request_result) void {
if (p.id != 0) n.z_complete(runtime.toAppHandle(b().app), p.id, status, result);
p.id = 0;
p.command = null;
}
fn closeRequest(handle: [*:0]const u8) void {
if (s().connection == null) return;
const message = Bus.method(desktop, handle, "org.freedesktop.portal.Request", "Close") catch return;
defer c.z_dbus_message_unref.?(message);
c.z_dbus_message_set_no_reply.?(message, 1);
_ = send(message) catch {};
}
pub fn cancel(id: u64) bool {
var cursor = s().pending;
while (cursor) |p| : (cursor = p.next) if (p.id == id) {
if (p.serial != 0) closeRequest(p.handle);
p.cancelled = true;
complete(p, abi.CANCELLED, null);
if (p.serial == 0 or p.replied) destroy(p);
return true;
};
return false;
}
fn destroy(p: *Pending) void {
var link = &s().pending;
while (link.* != p) link = &link.*.?.next;
link.* = p.next;
if (p.exported != null) c.zxdg_exported_v2_destroy(p.exported);
if (p.activation != null) c.xdg_activation_token_v1_destroy(p.activation);
b().app.gpa.free(p.handle);
b().app.gpa.free(p.token);
b().app.gpa.destroy(p);
}
fn disconnect() void {
KdeAppMenu.disconnected();
while (s().pending) |p| {
complete(p, abi.UNAVAILABLE, null);
destroy(p);
}
if (s().connection) |connection| {
c.z_dbus_connection_close.?(connection);
c.z_dbus_connection_unref.?(connection);
}
s().connection = null;
s().ready = false;
native.setCapability(n.UHDK_CAP_FILE_DIALOGS, false);
}
pub fn deinit() void {
var cursor = s().pending;
while (cursor) |p| : (cursor = p.next) closeRequest(p.handle);
disconnect();
if (s().unique) |unique| b().app.gpa.free(unique);
if (s().owner) |owner| b().app.gpa.free(owner);
if (s().exporter != null) c.zxdg_exporter_v2_destroy(s().exporter);
}

264
src/linux/README.md Normal file
View File

@ -0,0 +1,264 @@
# Linux Wayland preview
The first implementation targets KDE Plasma Wayland using standard protocols.
It implements window creation/destruction, titles, parent relationships, size
constraints, maximize/fullscreen/minimize requests, show/hide, close requests,
logical/pixel metrics, EGL desktop OpenGL, and local POSIX file references.
Keyboard/pointer input, cursor shapes, XKB composition, text-input-v3 IME,
clipboard, incoming file drops, portal dialogs/URLs/preferences, activation,
application icons, inotify file watches, Plasma app/window menu export, and
software-rendered context menus are also implemented.
A cursor-theme fallback, client-side window decorations, and X11
remain unsupported. Protocol tests cover IME batching; a live Fcitx/IBus CJK
session and other desktops still need qualification. Missing optional protocols
or libraries disable their features without a desktop-name whitelist.
## Build and run
Normal builds require only Zig 0.16.0 on a glibc Linux target:
```sh
zig build
zig build check
zig build test
zig build -Dshared=true
```
These commands do not download dependencies or run Python, a protocol scanner,
pkg-config, Nix, or an external C compiler. Zig compiles the checked-in protocol
C files and translates the private headers at build time.
At runtime, UHDK opens `libwayland-client.so.0`. OpenGL also needs
`libwayland-egl.so.1`, `libEGL.so.1` and a working system graphics driver.
Keyboard layout/text processing needs `libxkbcommon.so.0`. Portal services need
`libdbus-1.so.3`, a session bus, and the desktop portal frontend/backend.
Missing EGL disables the OpenGL capability without preventing window creation.
No desktop libraries are linked or bundled. EGL 1.5 and desktop GL 3.3 core are
required for graphics; there is no OpenGL ES fallback. A non-null capability
means the mechanism is available, not that every context creation must succeed.
`AUTO` selects Wayland; explicit X11 requests return `UNSUPPORTED`. Before a
successful run, the Linux backend query returns `AUTO` and all capabilities are
false. Startup discovers the registry before publishing `READY`. The protocol
handshake and initial window configure have five-second deadlines. Display
failure requests shutdown and cancels pending work; it never switches backends.
## NixOS development
NixOS does not normally expose every desktop library through the conventional
runtime linker search path. The optional package-owned shell supplies the pinned
scanner, Python (including dbus-next for fixtures), dbus-daemon, clang-format
and runtime library paths:
```sh
nix-shell
# Run a native application or the native tests from this shell.
zig build test-linux-native
```
Zig 0.16.0 should already be on PATH. The shell is optional for compilation.
Its nixpkgs revision and source hash live in `nix/pkgs.nix`. It also supports
maintainer [header/protocol refreshes](vendor/README.md). Running packaged apps
on NixOS should use the application's normal Nix wrapper and graphics setup.
UHDK first loads library SONAMEs, then checks the stable system profiles
`/run/current-system/sw/lib` and `/run/opengl-driver/lib`; it does not search
arbitrary Nix store directories or embed store paths.
## Portable executables
An executable built for the native NixOS target can name a Nix store loader.
For distribution on conventional glibc systems, build the application explicitly
for the chosen baseline, for example:
```sh
zig build -Dtarget=x86_64-linux-gnu.2.35 -Dcpu=baseline -Doptimize=ReleaseSafe
python3 src/linux/tools/audit-elf.py /path/to/application
```
The audit checks the executable's ELF interpreter, runtime dependencies and
required glibc symbol versions. This is not a substitute for running the artifact
on each supported distribution/driver. The current acceptance environment is
NixOS with Plasma 6.5.6 and Mesa 25.2.6; other systems are not runtime-qualified.
## Window and GL semantics
- A decorated window requires xdg-decoration server-side mode. A compositor that
lacks it returns `UNSUPPORTED`; a libdecor adapter is future work. Undecorated
windows remain possible. No desktop-name checks or Qt/KDE linkage are used.
- The application ID is copied from `uhdk_app_desc` and sent as the xdg-toplevel
app ID. Applications own their desktop-file and icon installation.
- A window is created hidden after its first configure. Showing requests mapping;
the first successful buffer swap supplies its content. `visible` is the
application's show/hide intent, not proof that the compositor displays it.
- Size/state commands acknowledge submission. Confirmed geometry and focus arrive
through metrics. Wayland cannot report minimized state, force focus, or directly
undo minimization. Metrics expose known resizing and (xdg-shell v6+) suspension
observations. Suspension is a rendering hint, not minimized/occluded state;
those two observation bits remain unset. Advertised unsupported window-manager
requests complete with `UNSUPPORTED`; older protocol versions remain best effort.
Activation requests use a launch token or a recent focused
input serial; the compositor can decline to focus the window.
- Fractional scale uses fractional-scale-v1 plus viewporter; otherwise preferred
integer buffer scale is used when wl_surface v6 is available. Older compositors
without those mechanisms currently use scale 1. Fractional/mixed-DPI behavior
still needs runtime validation.
- Native requests and protocol dispatch run on the OS main thread. GL work runs
on the application thread. Main-thread drawable updates use a nonblocking lock
attempt; the dispatcher never waits behind an in-progress swap. End-frame wakes
it to retry deferred updates.
- Hiding replaces the EGL presentation surface so an outstanding frame callback
cannot hang remapping. The GL context and its application-owned resources
survive. Swapping a hidden/unconfigured window returns `UNAVAILABLE`.
- Every GL call belongs inside begin/end-frame. Clear/make-current are supported
within that bracket. Actual GL version reporting is queried in the active
context. Window framebuffer dimensions describe the presentation target;
applications continue to own their viewport and rendering resources.
## Input and desktop services
- One active seat owns keyboard, pointer, and clipboard state. The keyboard uses
the compositor's XKB layout/masks, USB HID physical usages, client-side repeat,
and locale-aware compose tables. Raw events use the post-event modifier mask;
text lookup retains the pre-event state for latched modifiers. A Wayland sync
boundary flushes keys without modifier changes. Focus loss releases tracked
keys and stops repeat.
- Text input uses text-input-v3 when available and local XKB composition for
forwarded keys. IME batches retain their surrounding-text revision and UTF-8
offsets until `done`. Surrounding text is cropped to the protocol's 4000-byte
limit; selections too large to fit disable surrounding-text edits for that
snapshot. Ctrl editing shortcuts produce native edit events, including word
deletion, paragraph/page navigation, Backtab, Shift+Enter, and menu-independent
clipboard actions. Shift extends only movement; Alt/Super combinations are left
to text/layout or desktop handling. Document/selection-changing commands retire
old IME snapshot serials, and an acknowledged snapshot publishes the new basis.
Escape first cancels composition before becoming an application action.
- Pointer motion, buttons, wheel/touchpad frames and cursor-shape-v1 are supported.
Capture acknowledges Wayland's implicit button grab during a selection drag;
requesting it without a held button/focused pointer returns `UNAVAILABLE`.
The last button release or focus loss restores normal mode. There is no pointer
lock, relative motion, touch, tablet, or themed-cursor fallback yet.
- Clipboard writes require keyboard focus and a triggering input serial. They
never wait for an unrelated future key. Reads negotiate UTF-8/plain text,
enforce the caller's byte limit without truncation, and reject invalid UTF-8.
Transfers use nonblocking reads/writes with ten-second deadlines and preserve
source/offer ownership across cancellation and selection changes. Incoming
drops accept local `text/uri-list` files using copy-only negotiation, bounded
to 16 MiB. Remote/KIO URIs are not local file references.
- File dialogs and URL opening use `org.freedesktop.portal.Desktop`, never KDE's
backend service directly. Dialogs export their parent through xdg-foreign-v2,
support filters, initial directories, suggested names and multiple selection.
A private D-Bus connection subscribes before submission, correlates early
responses, closes cancelled requests, and handles portal-owner changes.
Portal discovery is asynchronous, so its capability can appear after `READY`.
Connection/method setup is bounded; human interaction has no timeout. URL
handoff includes an activation token when recent focused input permits it.
- Portal Settings supplies appearance, contrast and reduced-motion preferences
and live changes. Unknown/unavailable values retain neutral defaults. See the
[Settings contract](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Settings.html)
and [request lifecycle](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Request.html).
- Icons use xdg-toplevel-icon with square, premultiplied ARGB shared-memory
buffers; rectangular public images receive transparent padding. App IDs and
installed desktop entries still govern normal desktop application identity.
- Plasma app/window menus use `com.canonical.dbusmenu` on the session bus and
KDE's `org_kde_kwin_appmenu_manager` surface association. `APP_MENU` appears
after bus discovery when the compositor offers that protocol; it does not
promise a visible panel widget. Configure Plasma's Global Menu widget or an
application-menu titlebar button to display the export. No Qt or libdbusmenu
dependency is required. Other desktops can use this adapter if they implement
the same protocols; otherwise the menu capability remains false.
- An app menu supplies each window's default. A window menu overrides it;
clearing the override restores the default. New windows inherit the app menu.
Attachments retain the menu tree after callers release their references.
Each replacement has a new object path; stale calls cannot activate a new
menu. Enabled/checked changes are signalled to consumers, and disabled parents
block their descendants. Labels are literal UTF-8, with mnemonic markers
escaped. The current API has no separate checkable/radio or RTL setting.
- Menu accelerators use logical keys and Shift/Control/Alt/Super, ignoring lock
flags and ASCII letter case. They dispatch menu/edit/quit events before raw
keys or text; matching key releases and repeats are consumed. They remain
active without a menu consumer. COPY through REDO roles emit edit commands for
the associated window; QUIT requests application quit. Ordinary commands keep
their full 64-bit IDs. Menu export is bounded to 65,535 item occurrences per
window. Session-bus loss removes exports/capability but keeps local shortcuts;
reconnecting to a replaced session bus requires restarting the application.
Restarting just the menu consumer does not require reattachment.
- Context menus use an internal software renderer and `xdg_popup`, with a
checked-in 16px Roboto bitmap (about 24 KB). They need `wl_shm` and a seat,
independently of KDE appmenu export or a session bus. No Qt/GTK, runtime font
library, image decoder, or application GL context is used for menu drawing.
`CONTEXT_MENU` reports protocol availability; opening also requires a visible,
configured parent and a recent button/key press from that window. Call the
popup API while handling that input event; missing provenance returns
`UNAVAILABLE`. One popup request can be active; another returns `BUSY`.
- Context menus display labels, shortcut hints, checked/disabled items and
separators. Pointer hover or Right opens submenus, Left closes a submenu,
Up/Down/Home/End navigate, Enter/Space select, and Escape dismisses. Tall menus
scroll with the wheel or keyboard. Positioners let the compositor adjust menus
at screen edges. Popup input is consumed; activation emits its menu/edit/quit
event before successful request completion. Dismissal, cancellation, parent
hiding/destruction and seat removal complete as `CANCELLED`.
- This small menu has its own light/dark palette, selected from portal appearance
preferences when available. It does not reproduce arbitrary KDE widget themes
or expose an accessibility tree. The bitmap covers printable ASCII, Latin-1
and selected punctuation/arrows, with a replacement glyph for other text; no
shaping or bidi is implemented. Output scales enlarge the 16px bitmap instead
of rerasterizing it. Font provenance, license and maintainer regeneration
instructions are in [font/README.md](font/README.md). The renderer and font stay
entirely inside the Linux backend.
- File watches use inotify. A document watch follows its named path through
atomic replacement by observing its parent. Directory watches observe immediate
children. Queue overflow requests a rescan; lost roots are invalidated. Each
watch owns an inotify descriptor and is subject to the host's instance limits.
## Validation
From the package directory:
```sh
zig build check # also compile the native harness
zig build test # non-graphical core and file URI tests
zig build test-linux-native # opens windows in the active session
zig build linux-test-bin
python3 src/linux/tests/startup.py zig-out/bin/linux-native-tests
python3 src/linux/tests/disconnect.py zig-out/bin/linux-native-tests
# In the optional nix-shell, or with dbus-next/dbus-daemon installed:
python3 src/linux/tests/wayland_fixture.py zig-out/bin/linux-service-tests
python3 src/linux/tests/editing_fixture.py zig-out/bin/linux-service-tests
python3 src/linux/tests/window_state_fixture.py zig-out/bin/linux-service-tests
python3 src/linux/tests/portal_fixture.py zig-out/bin/linux-service-tests
python3 src/linux/tests/menu_fixture.py zig-out/bin/linux-service-tests
python3 src/linux/tests/popup_fixture.py zig-out/bin/linux-service-tests
# Opens a supplied app that exports a Quit or Quit … item, then activates that item:
python3 src/linux/tests/menu_live.py /path/to/menu-enabled-app
zig-out/bin/linux-service-tests desktop # live portal discovery/icons/file watches
python3 src/linux/tools/protocols.py --check
python3 src/linux/tools/bitmap-font.py --check
```
The native harness exercises repeated application/window lifetimes, cancellation,
shutdown with queued creation, show/hide/remapping, resize, GL context recreation,
actual GL version, pixel readback and byte-preserving file URIs. The startup test
uses isolated sockets for absent, disconnected and unresponsive servers. The
runtime-disconnect test proxies and closes only its own client connection; it
does not stop or modify the desktop compositor.
The service fixtures own their compositor socket and D-Bus daemon. They exercise
keys/repeat/dead-key composition, IME revisions/deletion/preedit, scrolling,
clipboard size limits/cancellation/invalid UTF-8/early reader exit, URI drops,
activation, portal open/save options, live settings, response-before-reply,
cancellation with a late alternate handle, parent destruction and portal restart.
The popup fixture checks shared-memory pixels, configure/scale handling,
press-serial grabs, nested teardown order, disabled items, pointer/keyboard
selection, scrolling, live flags, cancellation, outside clicks, parent destruction
and input suppression across focus restoration. It saves a rendered menu to
`/tmp/uhdk-popup.ppm` for inspection. Live right-click behavior still needs a
manual compositor check; the fixture does not establish that by itself.
The menu fixture covers D-BusMenu layout/properties/errors, state signals, window
overrides/defaults, retained trees, stale paths, consumer restarts, 64-bit command
IDs, role dispatch, shortcut consumption, missing protocol/bus and bus loss. The live menu check verifies KWin
association and D-Bus round trips, not visual panel/titlebar presentation. They never open a real chooser
or browser or inject input into the user's desktop. The live service mode checks
portal discovery, icon submission and atomic-save watch behavior without touching
the user's clipboard. These fixtures supplement live desktop testing; they do
not establish compatibility with every compositor or input-method engine.

364
src/linux/Text.zig Normal file
View File

@ -0,0 +1,364 @@
//! IME batches are applied at done, using the snapshot associated with its
//! commit serial. Forwarded wl_keyboard keys use local XKB composition.
const std = @import("std");
const c = @import("linux_c");
const n = @import("native");
const abi = @import("../abi.zig");
const native = @import("native.zig");
const Input = @import("Input.zig");
const Keyboard = @import("Keyboard.zig");
const Sent = struct {
next: ?*Sent = null,
serial: u32,
revision: u64,
bytes: [:0]u8,
offset: usize,
start: usize,
len: usize,
};
pub const State = struct {
enabled: bool = false,
composing: bool = false,
ime_composing: bool = false,
snapshot: n.uhdk_text_state = std.mem.zeroes(n.uhdk_text_state),
surrounding: []u8 = &.{},
last_edit: u64 = 0,
sent: ?*Sent = null,
sent_count: usize = 0,
dirty: bool = true,
pending_preedit: ?[]u8 = null,
pending_commit: ?[]u8 = null,
cursor_begin: i32 = 0,
cursor_end: i32 = 0,
before: u32 = 0,
after: u32 = 0,
};
pub fn listen(input: ?*c.struct_zwp_text_input_v3) void {
_ = c.zwp_text_input_v3_add_listener(input, &listener, null);
}
fn commit() u32 {
const s = Input.state();
s.text_serial +%= 1;
c.zwp_text_input_v3_commit(s.text_input);
return s.text_serial;
}
fn enter(_: ?*anyopaque, _: ?*c.struct_zwp_text_input_v3, surface: ?*c.struct_wl_surface) callconv(.c) void {
const s = Input.state();
s.text_focus = Input.find(surface);
if (s.text_focus) |w| if (w.text.enabled) {
c.zwp_text_input_v3_enable(s.text_input);
_ = publish(w);
};
}
fn leave(_: ?*anyopaque, _: ?*c.struct_zwp_text_input_v3, _: ?*c.struct_wl_surface) callconv(.c) void {
if (Input.state().text_focus) |w| {
end(w);
clearPending(w);
clearSent(w);
}
Input.state().text_focus = null;
}
fn own(w: *native.Window, destination: *?[]u8, text: [*c]const u8) void {
if (destination.*) |old| w.owner.gpa.free(old);
const bytes = if (text == null) "" else std.mem.span(text);
destination.* = w.owner.gpa.dupe(u8, bytes) catch null;
}
fn preedit(_: ?*anyopaque, _: ?*c.struct_zwp_text_input_v3, text: [*c]const u8, first: i32, last: i32) callconv(.c) void {
const w = Input.state().text_focus orelse return;
own(w, &w.text.pending_preedit, text);
w.text.cursor_begin = first;
w.text.cursor_end = last;
}
fn committed(_: ?*anyopaque, _: ?*c.struct_zwp_text_input_v3, text: [*c]const u8) callconv(.c) void {
const w = Input.state().text_focus orelse return;
own(w, &w.text.pending_commit, text);
}
fn deleted(_: ?*anyopaque, _: ?*c.struct_zwp_text_input_v3, before: u32, after: u32) callconv(.c) void {
const w = Input.state().text_focus orelse return;
w.text.before = before;
w.text.after = after;
}
fn done(_: ?*anyopaque, _: ?*c.struct_zwp_text_input_v3, serial: u32) callconv(.c) void {
Input.flushKey();
const w = Input.state().text_focus orelse return;
const t = &w.text;
defer clearPending(w);
if (!t.enabled) return;
var sent = t.sent;
while (sent) |value| {
if (value.serial == serial) break;
sent = value.next;
}
// A cleared serial belongs to a cancelled/disabled input session.
const basis = sent orelse return;
if (t.pending_commit) |bytes| if (!std.unicode.utf8ValidateSlice(bytes)) return;
end(w);
if (t.pending_commit != null or t.before != 0 or t.after != 0) {
var event = std.mem.zeroes(n.z_event);
event.type = n.UHDK_EVENT_TEXT_COMMIT;
event.input_id = Input.id();
event.data.text.text = abi.textSpan(t.pending_commit orelse "");
event.data.text.revision = basis.revision;
if (t.before != 0 or t.after != 0) {
if (t.before > basis.start or t.after > basis.bytes.len - basis.start - basis.len) return;
const start = basis.start - t.before;
const length = t.before + basis.len + t.after;
if (!abi.boundary(basis.bytes, start) or !abi.boundary(basis.bytes, start + length)) return;
event.data.text.has_replacement = 1;
event.data.text.replacement_start = basis.offset + start;
event.data.text.replacement_len = length;
}
t.last_edit = event.input_id;
Input.send(w, &event);
}
if (t.pending_preedit) |bytes| {
if (bytes.len != 0 and std.unicode.utf8ValidateSlice(bytes)) {
var event = std.mem.zeroes(n.z_event);
event.type = n.UHDK_EVENT_TEXT_PREEDIT;
event.input_id = Input.id();
event.data.text.text = abi.textSpan(bytes);
event.data.text.revision = basis.revision;
const lo: usize = @intCast(@max(0, @min(t.cursor_begin, t.cursor_end)));
const hi: usize = @intCast(@max(0, @max(t.cursor_begin, t.cursor_end)));
if (lo <= hi and hi <= bytes.len and abi.boundary(bytes, lo) and abi.boundary(bytes, hi)) {
event.data.text.selection_start = lo;
event.data.text.selection_len = hi - lo;
}
t.composing = true;
t.ime_composing = true;
t.last_edit = event.input_id;
Input.send(w, &event);
}
}
// Keep the acknowledged basis: more than one done can share its serial.
while (t.sent != basis) {
const old = t.sent.?;
t.sent = old.next;
w.owner.gpa.free(old.bytes);
w.owner.gpa.destroy(old);
t.sent_count -= 1;
}
// Never send pending state in response to an older serial. A later matching
// done or an acknowledged application basis will release the throttle.
if (serial == Input.state().text_serial and t.dirty and t.snapshot.acknowledged_input_id >= t.last_edit) _ = publish(w);
}
const listener: c.struct_zwp_text_input_v3_listener = .{ .enter = enter, .leave = leave, .preedit_string = preedit, .commit_string = committed, .delete_surrounding_text = deleted, .done = done };
pub fn crop(bytes: []const u8, start: usize, length: usize) ?struct { start: usize, end: usize } {
if (length > 4000 or std.mem.findScalar(u8, bytes, 0) != null) return null;
var lo = start -| ((4000 - length) / 2);
while (lo < start and !abi.boundary(bytes, lo)) lo += 1;
var hi = @min(bytes.len, lo + 4000);
while (hi > start + length and !abi.boundary(bytes, hi)) hi -= 1;
if (hi < start + length) return null;
return .{ .start = lo, .end = hi };
}
fn publish(w: *native.Window) u32 {
const s = Input.state();
const t = &w.text;
if (s.text_input == null or s.text_focus != w or !t.enabled) return abi.OK;
if (t.snapshot.acknowledged_input_id < t.last_edit) {
t.dirty = true;
return abi.OK;
}
// Bound unacknowledged snapshots without discarding snapshots a late IME
// batch may still refer to. Keep the newest application state for later.
if (t.sent_count >= 32) {
t.dirty = true;
return abi.OK;
}
const range = crop(t.surrounding, @intCast(t.snapshot.selection_start), @intCast(t.snapshot.selection_len));
const sent = w.owner.gpa.create(Sent) catch return abi.OOM;
const bytes = w.owner.gpa.dupeZ(u8, if (range) |r| t.surrounding[r.start..r.end] else "") catch {
w.owner.gpa.destroy(sent);
return abi.OOM;
};
sent.* = .{ .serial = s.text_serial +% 1, .revision = t.snapshot.revision, .bytes = bytes, .offset = if (range) |r| r.start else 0, .start = if (range) |r| @intCast(t.snapshot.selection_start - r.start) else 0, .len = if (range != null) @intCast(t.snapshot.selection_len) else 0 };
c.zwp_text_input_v3_set_surrounding_text(s.text_input, bytes, @intCast(sent.start + sent.len), @intCast(sent.start));
const caret = t.snapshot.caret;
c.zwp_text_input_v3_set_cursor_rectangle(s.text_input, @intFromFloat(std.math.clamp(caret.x, -1000000, 1000000)), @intFromFloat(std.math.clamp(caret.y, -1000000, 1000000)), @intFromFloat(@min(caret.width, 1000000)), @intFromFloat(@min(caret.height, 1000000)));
c.zwp_text_input_v3_set_content_type(s.text_input, c.ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE, c.ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL);
c.zwp_text_input_v3_set_text_change_cause(s.text_input, if (t.snapshot.acknowledged_input_id == t.last_edit) c.ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD else c.ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_OTHER);
_ = commit();
var link = &t.sent;
while (link.*) |item| link = &item.next;
link.* = sent;
t.sent_count += 1;
t.dirty = false;
return abi.OK;
}
pub fn snapshot(w: *native.Window, value: n.uhdk_text_state) u32 {
const t = &w.text;
if (value.acknowledged_input_id < t.last_edit) return abi.OK;
const bytes = abi.textBytes(value.surrounding_text);
const same = t.snapshot.revision == value.revision and t.snapshot.selection_start == value.selection_start and t.snapshot.selection_len == value.selection_len and
std.meta.eql(t.snapshot.caret, value.caret) and std.mem.eql(u8, t.surrounding, bytes);
const copy = w.owner.gpa.dupe(u8, bytes) catch return abi.OOM;
w.owner.gpa.free(t.surrounding);
t.surrounding = copy;
t.snapshot = value;
t.snapshot.surrounding_text = abi.textSpan(copy);
if (same and !t.dirty) return abi.OK;
t.dirty = true;
return publish(w);
}
pub fn enable(w: *native.Window, enabled: bool) u32 {
const t = &w.text;
if (enabled and Input.state().context == null) return n.UHDK_STATUS_UNSUPPORTED;
if (t.enabled == enabled) return abi.OK;
end(w);
clearPending(w);
clearSent(w);
if (Input.state().compose != null) c.z_xkb_compose_state_reset.?(Input.state().compose);
t.enabled = enabled;
const s = Input.state();
if (s.text_input != null and s.text_focus == w) {
if (enabled) {
c.zwp_text_input_v3_enable(s.text_input);
return publish(w);
}
c.zwp_text_input_v3_disable(s.text_input);
_ = commit();
}
return abi.OK;
}
fn end(w: *native.Window) void {
if (w.text.composing) endAt(w, Input.id());
}
fn endAt(w: *native.Window, input_id: u64) void {
const t = &w.text;
if (!t.composing) return;
var e = std.mem.zeroes(n.z_event);
e.type = n.UHDK_EVENT_TEXT_COMPOSITION_END;
e.input_id = input_id;
t.last_edit = input_id;
Input.send(w, &e);
t.composing = false;
t.ime_composing = false;
}
fn clearPending(w: *native.Window) void {
const t = &w.text;
if (t.pending_preedit) |p| w.owner.gpa.free(p);
if (t.pending_commit) |p| w.owner.gpa.free(p);
t.pending_preedit = null;
t.pending_commit = null;
t.before = 0;
t.after = 0;
}
fn clearSent(w: *native.Window) void {
while (w.text.sent) |item| {
w.text.sent = item.next;
w.owner.gpa.free(item.bytes);
w.owner.gpa.destroy(item);
}
w.text.sent_count = 0;
w.text.dirty = true;
}
pub fn cancel(w: *native.Window) void {
end(w);
clearPending(w);
clearSent(w);
const s = Input.state();
if (s.compose != null) c.z_xkb_compose_state_reset.?(s.compose);
if (s.text_input != null and s.text_focus == w and w.text.enabled) {
c.zwp_text_input_v3_disable(s.text_input);
_ = commit();
c.zwp_text_input_v3_enable(s.text_input);
_ = publish(w);
}
}
pub fn forget(w: *native.Window) void {
if (Input.state().text_focus == w) {
_ = enable(w, false);
Input.state().text_focus = null;
}
end(w);
clearPending(w);
clearSent(w);
}
pub fn deinit(w: *native.Window) void {
forget(w);
w.owner.gpa.free(w.text.surrounding);
}
pub fn editCommand(w: *native.Window, command: u32, modifiers: u32, input_id: u64) void {
if (w.text.ime_composing) return;
// A mutation or movement makes old surrounding-text ranges unsafe. Copy
// leaves the document/selection untouched, so delayed IME batches may retain
// their previously published basis. New state is published only on app ack.
if (command != n.UHDK_EDIT_COPY and command != n.UHDK_EDIT_CANCEL) {
clearPending(w);
clearSent(w);
}
var event = std.mem.zeroes(n.z_event);
event.type = n.UHDK_EVENT_EDIT_COMMAND;
event.input_id = input_id;
event.data.edit = .{ .command = command, .extend_selection = @intCast(n.z_edit_extends_selection(command, modifiers)) };
w.text.last_edit = input_id;
Input.send(w, &event);
}
pub fn key(w: *native.Window, sym: u32, logical: u32, input_id: u64, modifiers: u32, plain: []const u8) void {
const s = Input.state();
const t = &w.text;
if (s.state == null) return;
if (t.ime_composing) {
if (Keyboard.edit(logical, modifiers) == n.UHDK_EDIT_CANCEL) cancel(w);
return;
}
const edit = Keyboard.edit(logical, modifiers);
if (edit != 0) {
// Escape cancels an XKB compose sequence before becoming an app action.
if (edit == n.UHDK_EDIT_CANCEL and t.composing) {
if (s.compose != null) c.z_xkb_compose_state_reset.?(s.compose);
endAt(w, input_id);
return;
}
if (s.compose != null) c.z_xkb_compose_state_reset.?(s.compose);
endAt(w, input_id);
editCommand(w, edit, modifiers, input_id);
return;
}
if (modifiers & (n.UHDK_MOD_CONTROL | n.UHDK_MOD_ALT | n.UHDK_MOD_SUPER) != 0) return;
var buffer: [256]u8 = undefined;
var length: c_int = @intCast(plain.len);
@memcpy(buffer[0..plain.len], plain);
if (s.compose != null) {
_ = c.z_xkb_compose_state_feed.?(s.compose, sym);
switch (c.z_xkb_compose_state_get_status.?(s.compose)) {
c.XKB_COMPOSE_COMPOSING => {
t.composing = true;
return;
},
c.XKB_COMPOSE_CANCELLED => {
c.z_xkb_compose_state_reset.?(s.compose);
endAt(w, input_id);
return;
},
c.XKB_COMPOSE_COMPOSED => {
length = c.z_xkb_compose_state_get_utf8.?(s.compose, &buffer, buffer.len);
c.z_xkb_compose_state_reset.?(s.compose);
endAt(w, input_id);
},
else => {},
}
}
if (length <= 0 or length >= buffer.len or buffer[0] < 32 or buffer[0] == 127) return;
var e = std.mem.zeroes(n.z_event);
e.type = n.UHDK_EVENT_TEXT_COMMIT;
e.input_id = input_id;
e.data.text.text = abi.textSpan(buffer[0..@intCast(length)]);
e.data.text.revision = t.snapshot.revision;
t.last_edit = input_id;
Input.send(w, &e);
}
test "surrounding crop preserves UTF-8 and complete selections" {
var bytes: [8000]u8 = undefined;
for (0..4000) |i| {
bytes[i * 2] = 0xc3;
bytes[i * 2 + 1] = 0xa9;
}
const range = crop(&bytes, 3998, 4).?;
try std.testing.expect(range.start % 2 == 0 and range.end % 2 == 0 and range.end - range.start <= 4000);
try std.testing.expect(range.start <= 3998 and range.end >= 4002);
try std.testing.expect(crop(&bytes, 0, 4002) == null);
}

130
src/linux/Watch.zig Normal file
View File

@ -0,0 +1,130 @@
//! inotify watches observe a document's parent so atomic replacement keeps the
//! watch attached to the name. Directory watches cover immediate children only.
const std = @import("std");
const c = @import("linux_c");
const n = @import("native");
const native = @import("native.zig");
const runtime = @import("../runtime.zig");
const abi = @import("../abi.zig");
pub const Watch = struct {
next: ?*Watch = null,
handle: ?*n.uhdk_watch,
root: ?*n.uhdk_file,
fd: c_int,
directory: []u8,
name: []u8,
};
fn b() *native.Backend {
return native.instance.?;
}
pub fn create(cmd: *const n.z_command, result: *n.z_request_result) u32 {
const root: ?*n.uhdk_file = @ptrCast(cmd.target);
const path = b().app.gpa.dupeZ(u8, abi.bytes(n.z_file_path(root))) catch return abi.OOM;
defer b().app.gpa.free(path);
const kind = c.z_linux_is_directory(path);
if (kind < 0 and c.z_linux_errno() != c.ENOENT) return n.UHDK_STATUS_OS_ERROR;
const directory = b().app.gpa.dupeZ(u8, if (kind == 1) path else std.fs.path.dirname(path) orelse "/") catch return abi.OOM;
defer b().app.gpa.free(directory);
const fd = c.inotify_init1(c.IN_NONBLOCK | c.IN_CLOEXEC);
if (fd < 0) return n.UHDK_STATUS_OS_ERROR;
const mask = c.IN_CREATE | c.IN_DELETE | c.IN_MOVED_FROM | c.IN_MOVED_TO | c.IN_MODIFY | c.IN_ATTRIB | c.IN_DELETE_SELF | c.IN_MOVE_SELF | c.IN_ONLYDIR;
if (c.inotify_add_watch(fd, directory, mask) < 0) {
_ = c.close(fd);
return n.UHDK_STATUS_OS_ERROR;
}
const w = allocate(fd, root, @ptrCast(cmd.other), directory, if (kind == 1) "" else std.fs.path.basename(path)) catch {
_ = c.close(fd);
return abi.OOM;
};
w.next = b().watches;
b().watches = w;
n.z_object_set_native(cmd.other, w);
result.value.watch = w.handle;
return abi.OK;
}
fn allocate(fd: c_int, root: ?*n.uhdk_file, handle: ?*n.uhdk_watch, directory: []const u8, name: []const u8) !*Watch {
const gpa = b().app.gpa;
const w = try gpa.create(Watch);
errdefer gpa.destroy(w);
const dir = try gpa.dupe(u8, directory);
errdefer gpa.free(dir);
w.* = .{ .fd = fd, .root = root, .handle = handle, .directory = dir, .name = try gpa.dupe(u8, name) };
return w;
}
pub fn poll(fds: *std.ArrayList(c.struct_pollfd)) !void {
var w = b().watches;
while (w) |item| : (w = item.next) if (item.fd >= 0) try fds.append(b().app.gpa, .{ .fd = item.fd, .events = c.POLLIN, .revents = 0 });
}
pub fn flags(mask: u32) u32 {
var value: u32 = 0;
if (mask & (c.IN_CREATE | c.IN_MOVED_TO) != 0) value |= n.UHDK_CHANGE_CREATED;
if (mask & (c.IN_DELETE | c.IN_MOVED_FROM) != 0) value |= n.UHDK_CHANGE_REMOVED;
if (mask & c.IN_MODIFY != 0) value |= n.UHDK_CHANGE_CONTENT;
if (mask & c.IN_ATTRIB != 0) value |= n.UHDK_CHANGE_METADATA;
if (mask & (c.IN_MOVED_TO | c.IN_MOVED_FROM) != 0) value |= n.UHDK_CHANGE_RENAMED;
if (mask & c.IN_Q_OVERFLOW != 0) value |= n.UHDK_CHANGE_RESCAN_REQUIRED;
if (mask & (c.IN_DELETE_SELF | c.IN_MOVE_SELF | c.IN_IGNORED | c.IN_UNMOUNT) != 0) value |= n.UHDK_CHANGE_ROOT_INVALIDATED;
return value;
}
fn emit(w: *Watch, changes: u32, name: []const u8, status: u32) void {
var file = w.root;
if (w.name.len == 0 and name.len != 0) {
const path = std.fs.path.join(b().app.gpa, &.{ w.directory, name }) catch null;
if (path) |p| {
defer b().app.gpa.free(p);
file = n.z_file_from_url(runtime.toAppHandle(b().app), .{ .ptr = p.ptr, .len = p.len }, null);
} else file = null;
}
defer if (file != null and file != w.root) n.uhdk_file_release(file);
var event = std.mem.zeroes(n.z_event);
event.type = n.UHDK_EVENT_WATCH;
event.data.watch = .{ .watch = w.handle, .flags = changes, .status = status, .file = file };
n.z_emit(runtime.toAppHandle(b().app), &event);
}
pub fn tick() void {
var cursor = b().watches;
while (cursor) |w| : (cursor = w.next) {
if (w.fd < 0) continue;
var buffer: [65536]u8 align(@alignOf(c.struct_inotify_event)) = undefined;
const count = c.z_linux_read(w.fd, &buffer, buffer.len);
if (count < 0) {
if (c.z_linux_errno() != c.EAGAIN and c.z_linux_errno() != c.EINTR) {
emit(w, n.UHDK_CHANGE_ROOT_INVALIDATED, "", n.UHDK_STATUS_OS_ERROR);
_ = c.close(w.fd);
w.fd = -1;
}
continue;
}
var offset: usize = 0;
while (offset + @sizeOf(c.struct_inotify_event) <= @as(usize, @intCast(count))) {
const event: *const c.struct_inotify_event = @ptrCast(@alignCast(&buffer[offset]));
const start = offset + @sizeOf(c.struct_inotify_event);
offset = start + event.len;
if (offset > @as(usize, @intCast(count))) break;
const bytes = buffer[start..offset];
const name = bytes[0 .. std.mem.findScalar(u8, bytes, 0) orelse bytes.len];
const changes = flags(event.mask);
const root_event = changes & (n.UHDK_CHANGE_ROOT_INVALIDATED | n.UHDK_CHANGE_RESCAN_REQUIRED) != 0;
if (changes != 0 and (root_event or w.name.len == 0 or std.mem.eql(u8, name, w.name))) emit(w, changes, if (root_event) "" else name, abi.OK);
if (changes & n.UHDK_CHANGE_ROOT_INVALIDATED != 0) {
_ = c.close(w.fd);
w.fd = -1;
break;
}
}
}
}
pub fn destroy(w: *Watch) void {
var link = &b().watches;
while (link.* != w) link = &link.*.?.next;
link.* = w.next;
if (w.fd >= 0) _ = c.close(w.fd);
b().app.gpa.free(w.directory);
b().app.gpa.free(w.name);
b().app.gpa.destroy(w);
}
test "watch replacement is remove/create, overflow and invalidation stay distinct" {
try std.testing.expectEqual(@as(u32, n.UHDK_CHANGE_CREATED | n.UHDK_CHANGE_RENAMED), flags(c.IN_MOVED_TO));
try std.testing.expectEqual(@as(u32, n.UHDK_CHANGE_RESCAN_REQUIRED), flags(c.IN_Q_OVERFLOW));
try std.testing.expectEqual(@as(u32, n.UHDK_CHANGE_ROOT_INVALIDATED), flags(c.IN_IGNORED));
}

60
src/linux/WindowState.zig Normal file
View File

@ -0,0 +1,60 @@
//! xdg-shell state values from protocols/xml/xdg-shell.xml. Kept separate from
//! Wayland objects so version/unknown-state behavior can be tested on any host.
const std = @import("std");
const n = @import("native");
pub const Observation = struct {
geometry: u32 = n.UHDK_WINDOW_NORMAL,
focused: bool = false,
resizing: bool = false,
suspended: bool = false,
known: u32 = n.UHDK_WINDOW_OBSERVE_RESIZING,
};
pub fn observe(version: u32, states: []const u32) Observation {
var result: Observation = .{};
if (version >= 6) result.known |= n.UHDK_WINDOW_OBSERVE_SUSPENDED;
for (states) |state| switch (state) {
1 => if (result.geometry != n.UHDK_WINDOW_FULLSCREEN) {
result.geometry = n.UHDK_WINDOW_MAXIMIZED;
},
2 => result.geometry = n.UHDK_WINDOW_FULLSCREEN,
3 => result.resizing = true,
4 => result.focused = true,
9 => result.suspended = version >= 6,
else => {},
};
return result;
}
pub const Capabilities = struct {
// Before v5 there is no advertisement; preserve best-effort requests.
minimize: bool = true,
maximize: bool = true,
fullscreen: bool = true,
pub fn announced(values: []const u32) Capabilities {
var result: Capabilities = .{ .minimize = false, .maximize = false, .fullscreen = false };
for (values) |value| switch (value) {
2 => result.maximize = true,
3 => result.fullscreen = true,
4 => result.minimize = true,
else => {},
};
return result;
}
};
test "Wayland observations do not invent minimization or occlusion" {
const old = observe(5, &.{ 1, 2, 3, 4, 9, 999 });
try std.testing.expectEqual(@as(u32, n.UHDK_WINDOW_FULLSCREEN), old.geometry);
try std.testing.expect(old.focused and old.resizing and !old.suspended);
try std.testing.expectEqual(@as(u32, n.UHDK_WINDOW_OBSERVE_RESIZING), old.known);
const modern = observe(6, &.{ 9, 2, 1 });
try std.testing.expect(modern.suspended and !modern.resizing);
try std.testing.expectEqual(@as(u32, n.UHDK_WINDOW_FULLSCREEN), modern.geometry);
try std.testing.expectEqual(@as(u32, n.UHDK_WINDOW_OBSERVE_RESIZING | n.UHDK_WINDOW_OBSERVE_SUSPENDED), modern.known);
const resumed = observe(6, &.{});
try std.testing.expect(!resumed.suspended and !resumed.focused and !resumed.resizing);
try std.testing.expectEqual(@as(u32, n.UHDK_WINDOW_NORMAL), resumed.geometry);
const caps = Capabilities.announced(&.{ 2, 999 });
try std.testing.expect(caps.maximize and !caps.minimize and !caps.fullscreen);
try std.testing.expect(!Capabilities.announced(&.{}).maximize);
}

54
src/linux/bindings.h Normal file
View File

@ -0,0 +1,54 @@
#ifndef UHDK_LINUX_BINDINGS_H
#define UHDK_LINUX_BINDINGS_H
#define _GNU_SOURCE
#include <errno.h>
#include <poll.h>
#include <sys/eventfd.h>
#include <sys/inotify.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include "wayland-client-core.h"
#include "wayland-egl-core.h"
#define EGL_NO_X11
#include "EGL/egl.h"
#include "EGL/eglext.h"
#include "private-symbols.h"
/* Runtime imports are checked against upstream declarations in libraries.c.
* typeof(function) in translated Zig debug info can create unwanted link-time
* references, so the translated header uses explicit function-pointer types. */
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-compose.h>
#include "xkb-imports.h"
#include <dbus/dbus.h>
#include "dbus-imports.h"
#include "function-pointers.h"
#include "redirects.h"
#include "wayland-client-protocol.h"
#include "xdg-shell.h"
#include "xdg-decoration-unstable-v1.h"
#include "viewporter.h"
#include "fractional-scale-v1.h"
#include "cursor-shape-v1.h"
#include "text-input-unstable-v3.h"
#include "xdg-activation-v1.h"
#include "xdg-foreign-unstable-v2.h"
#include "xdg-toplevel-icon-v1.h"
#include "appmenu.h"
int z_linux_libraries_open(void);
int z_linux_egl_available(void);
int z_linux_xkb_available(void);
int z_linux_dbus_available(void);
int z_linux_is_directory(const char *);
int z_linux_nonblock(int);
int z_linux_pipe(int[2]);
void z_linux_libraries_close(void);
int z_linux_errno(void);
int z_linux_poll(struct pollfd *, nfds_t, int);
ssize_t z_linux_read(int, void *, size_t);
ssize_t z_linux_write(int, const void *, size_t);
ssize_t z_linux_pipe_write(int, const void *, size_t);
int z_linux_is_main_thread(void);
#endif

47
src/linux/build.zig Normal file
View File

@ -0,0 +1,47 @@
const std = @import("std");
pub fn configure(b: *std.Build, module: *std.Build.Module, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) void {
const bindings = b.addTranslateC(.{
.root_source_file = b.path("src/linux/bindings.h"),
.target = target,
.optimize = optimize,
});
for ([_][]const u8{ "src/linux", "src/linux/vendor/wayland", "src/linux/vendor", "src/linux/vendor/dbus", "src/linux/vendor/khronos", "src/linux/protocols/generated" }) |path| {
bindings.addIncludePath(b.path(path));
module.addIncludePath(b.path(path));
}
module.addImport("linux_c", bindings.createModule());
module.addCSourceFile(.{ .file = b.path("src/linux/libraries.c"), .flags = &.{ "-std=gnu11", "-Wall", "-Wextra", "-Werror", "-fvisibility=hidden" } });
for ([_][]const u8{ "wayland", "xdg-shell", "xdg-decoration-unstable-v1", "viewporter", "fractional-scale-v1", "cursor-shape-v1", "tablet-v2", "text-input-unstable-v3", "xdg-activation-v1", "xdg-foreign-unstable-v2", "xdg-toplevel-icon-v1", "appmenu" }) |name| {
module.addCSourceFile(.{
.file = b.path(b.fmt("src/linux/protocols/generated/{s}.c", .{name})),
.flags = &.{ "-std=c99", "-include", b.path("src/linux/protocols/generated/private-symbols.h").getPath(b) },
});
}
module.linkSystemLibrary("dl", .{});
}
pub fn addTests(b: *std.Build, lib: *std.Build.Step.Compile, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, check: *std.Build.Step) void {
const test_module = b.createModule(.{ .target = target, .optimize = optimize, .link_libc = true });
test_module.addIncludePath(b.path("include"));
test_module.addCSourceFile(.{ .file = b.path("src/linux/tests/native.c"), .flags = &.{ "-std=c99", "-Wall", "-Wextra", "-Werror" } });
test_module.linkLibrary(lib);
const exe = b.addExecutable(.{ .name = "linux-native-tests", .root_module = test_module });
check.dependOn(&exe.step);
b.step("test-linux-native", "Exercise Wayland/EGL lifecycle in the active desktop session").dependOn(&b.addRunArtifact(exe).step);
const install = b.addInstallArtifact(exe, .{});
b.step("linux-test-bin", "Install the Linux native test harnesses").dependOn(&install.step);
addServiceTests(b, lib, target, optimize, check);
}
// Kept separate from automatic native smoke: fixture tests supply an isolated
// compositor/session bus, and the live mode explicitly checks installed portals.
pub fn addServiceTests(b: *std.Build, lib: *std.Build.Step.Compile, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, check: *std.Build.Step) void {
const module = b.createModule(.{ .target = target, .optimize = optimize, .link_libc = true });
module.addIncludePath(b.path("include"));
module.addCSourceFile(.{ .file = b.path("src/linux/tests/services.c"), .flags = &.{ "-std=c99", "-Wall", "-Wextra", "-Werror" } });
module.linkLibrary(lib);
const exe = b.addExecutable(.{ .name = "linux-service-tests", .root_module = module });
check.dependOn(&exe.step);
b.top_level_steps.get("linux-test-bin").?.step.dependOn(&b.addInstallArtifact(exe, .{}).step);
}

77
src/linux/dbus-imports.h Normal file
View File

@ -0,0 +1,77 @@
/* Runtime ABI pointers, type-checked against upstream in libraries.c. */
#ifndef UHDK_DBUS_IMPORTS_H
#define UHDK_DBUS_IMPORTS_H
extern DBusConnection *(*z_dbus_connection_open_private)(const char *address, DBusError *error);
extern void (*z_dbus_connection_set_exit_on_disconnect)(
DBusConnection *connection,
dbus_bool_t exit_on_disconnect
);
extern void (*z_dbus_connection_close)(DBusConnection *connection);
extern void (*z_dbus_connection_unref)(DBusConnection *connection);
extern dbus_bool_t (*z_dbus_connection_get_is_connected)(DBusConnection *connection);
extern dbus_bool_t (*z_dbus_connection_get_unix_fd)(DBusConnection *connection, int *fd);
extern dbus_bool_t (*z_dbus_connection_has_messages_to_send)(DBusConnection *connection);
extern dbus_bool_t (*z_dbus_connection_read_write)(
DBusConnection *connection,
int timeout_milliseconds
);
extern DBusMessage *(*z_dbus_connection_pop_message)(DBusConnection *connection);
extern dbus_bool_t (*z_dbus_connection_send)(
DBusConnection *connection,
DBusMessage *message,
dbus_uint32_t *client_serial
);
extern DBusMessage *(*z_dbus_message_new_method_call)(
const char *bus_name,
const char *path,
const char *iface,
const char *method
);
extern void (*z_dbus_message_unref)(DBusMessage *message);
extern int (*z_dbus_message_get_type)(DBusMessage *message);
extern dbus_uint32_t (*z_dbus_message_get_reply_serial)(DBusMessage *message);
extern const char *(*z_dbus_message_get_path)(DBusMessage *message);
extern const char *(*z_dbus_message_get_sender)(DBusMessage *message);
extern const char *(*z_dbus_message_get_error_name)(DBusMessage *message);
extern dbus_bool_t (*z_dbus_message_is_signal)(
DBusMessage *message,
const char *iface,
const char *signal_name
);
extern dbus_bool_t (*z_dbus_message_has_signature)(DBusMessage *message, const char *signature);
extern void (*z_dbus_message_set_no_reply)(DBusMessage *message, dbus_bool_t no_reply);
extern dbus_bool_t (*z_dbus_message_iter_init)(DBusMessage *message, DBusMessageIter *iter);
extern void (*z_dbus_message_iter_init_append)(DBusMessage *message, DBusMessageIter *iter);
extern int (*z_dbus_message_iter_get_arg_type)(DBusMessageIter *iter);
extern dbus_bool_t (*z_dbus_message_iter_next)(DBusMessageIter *iter);
extern void (*z_dbus_message_iter_recurse)(DBusMessageIter *iter, DBusMessageIter *sub);
extern void (*z_dbus_message_iter_get_basic)(DBusMessageIter *iter, void *value);
extern dbus_bool_t (*z_dbus_message_iter_append_basic)(
DBusMessageIter *iter,
int type,
const void *value
);
extern dbus_bool_t (*z_dbus_message_iter_open_container)(
DBusMessageIter *iter,
int type,
const char *contained_signature,
DBusMessageIter *sub
);
extern dbus_bool_t (*z_dbus_message_iter_close_container)(
DBusMessageIter *iter,
DBusMessageIter *sub
);
extern void (*z_dbus_message_iter_abandon_container_if_open)(
DBusMessageIter *iter,
DBusMessageIter *sub
);
extern dbus_bool_t (*z_dbus_threads_init_default)(void);
extern DBusMessage *(*z_dbus_message_new_method_return)(DBusMessage *message);
extern DBusMessage
*(*z_dbus_message_new_error)(DBusMessage *message, const char *name, const char *text);
extern DBusMessage
*(*z_dbus_message_new_signal)(const char *path, const char *iface, const char *name);
extern const char *(*z_dbus_message_get_interface)(DBusMessage *message);
extern const char *(*z_dbus_message_get_member)(DBusMessage *message);
extern dbus_bool_t (*z_dbus_message_get_no_reply)(DBusMessage *message);
#endif

72
src/linux/dbusmenu.xml Normal file
View File

@ -0,0 +1,72 @@
<!-- UHDK D-BusMenu exporter introspection. -->
<node>
<interface name="com.canonical.dbusmenu">
<property name="Version" type="u" access="read" />
<property name="TextDirection" type="s" access="read" />
<property name="Status" type="s" access="read" />
<property name="IconThemePath" type="as" access="read" />
<method name="GetLayout">
<arg type="i" direction="in" />
<arg type="i" direction="in" />
<arg type="as" direction="in" />
<arg type="u" direction="out" />
<arg type="(ia{sv}av)" direction="out" />
</method>
<method name="GetGroupProperties">
<arg type="ai" direction="in" />
<arg type="as" direction="in" />
<arg type="a(ia{sv})" direction="out" />
</method>
<method name="GetProperty">
<arg type="i" direction="in" />
<arg type="s" direction="in" />
<arg type="v" direction="out" />
</method>
<method name="Event">
<arg type="i" direction="in" />
<arg type="s" direction="in" />
<arg type="v" direction="in" />
<arg type="u" direction="in" />
</method>
<method name="EventGroup">
<arg type="a(isvu)" direction="in" />
<arg type="ai" direction="out" />
</method>
<method name="AboutToShow">
<arg type="i" direction="in" />
<arg type="b" direction="out" />
</method>
<method name="AboutToShowGroup">
<arg type="ai" direction="in" />
<arg type="ai" direction="out" />
<arg type="ai" direction="out" />
</method>
<signal name="ItemsPropertiesUpdated">
<arg type="a(ia{sv})" />
<arg type="a(ias)" />
</signal>
<signal name="LayoutUpdated">
<arg type="u" />
<arg type="i" />
</signal>
</interface>
<interface name="org.freedesktop.DBus.Introspectable">
<method name="Introspect">
<arg type="s" direction="out" />
</method>
</interface>
<interface name="org.freedesktop.DBus.Peer">
<method name="Ping" />
</interface>
<interface name="org.freedesktop.DBus.Properties">
<method name="Get">
<arg type="s" direction="in" />
<arg type="s" direction="in" />
<arg type="v" direction="out" />
</method>
<method name="GetAll">
<arg type="s" direction="in" />
<arg type="a{sv}" direction="out" />
</method>
</interface>
</node>

93
src/linux/font/OFL.txt Normal file
View File

@ -0,0 +1,93 @@
Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

32
src/linux/font/README.md Normal file
View File

@ -0,0 +1,32 @@
# Linux menu bitmap
`roboto-16.bin` contains Roboto Regular rasterized at 16 pixels, with 8-bit
antialiased coverage. It is embedded into the Linux backend; no font parser,
font discovery, image decoder, or runtime rasterizer is linked into UHDK.
Coverage: printable ASCII, Latin-1, selected punctuation/currency/arrows, and
U+FFFD. Other codepoints use the replacement glyph. There is no shaping, bidi,
kerning, or combining-mark placement. Larger output scales enlarge the bitmap;
they do not rerasterize the font. This is an intentionally fixed, small menu font.
The source is the unmodified Google Fonts Roboto variable TTF, pinned in
`manifest.json`, with weight 400 and width 100. The generated bitmap is a font
derivative distributed under the accompanying SIL Open Font License (`OFL.txt`).
The TTF is downloaded only for maintainer regeneration and is not shipped here.
Binary format, little-endian: `UF16`, u32 glyph count, sorted 16-byte records
(u32 codepoint, u32 absolute pixel offset, u8 width/height, i8 x/y bearing relative
to the baseline, u8 advance, three padding bytes), followed by tightly packed,
row-major grayscale glyph bitmaps.
From the package directory:
```sh
python3 src/linux/tools/bitmap-font.py --check
# Optional nix-shell provides pinned Pillow/FreeType:
python3 src/linux/tools/bitmap-font.py --verify-generation
python3 src/linux/tools/bitmap-font.py --regenerate
```
Source URLs/checksums, rasterizer versions and output checksum are recorded in
`manifest.json`. Normal builds require only the checked-in bitmap and Zig.

View File

@ -0,0 +1,23 @@
{
"revision": "1c627bfa375fc51cf86fabeca4f6e08a95f0aa5c",
"size": 16,
"sources": [
{
"url": "https://raw.githubusercontent.com/google/fonts/1c627bfa375fc51cf86fabeca4f6e08a95f0aa5c/ofl/roboto/Roboto%5Bwdth%2Cwght%5D.ttf",
"sha256": "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134",
"name": "Roboto.ttf"
},
{
"url": "https://raw.githubusercontent.com/google/fonts/1c627bfa375fc51cf86fabeca4f6e08a95f0aa5c/ofl/roboto/OFL.txt",
"sha256": "061402327a96aadb0bfb694a960ed289ecd38d383e396243831ab81feb109c41",
"name": "OFL.txt"
}
],
"rasterizer": {
"pillow": "12.2.0",
"freetype": "2.13.3"
},
"generated": {
"roboto-16.bin": "23bfe858a9ff398b3826d836bb815255ced0e50ac7f695b2d9309a15b32d335e"
}
}

Binary file not shown.

View File

@ -0,0 +1,42 @@
/* Typed runtime imports. libraries.c defines these using upstream typeof, so
* the C compiler checks the Wayland signatures here against the pinned ABI. */
extern struct wl_display *(*z_wl_display_connect)(const char *);
extern void (*z_wl_display_disconnect)(struct wl_display *);
extern int (*z_wl_display_get_fd)(struct wl_display *);
extern int (*z_wl_display_dispatch_pending)(struct wl_display *);
extern int (*z_wl_display_prepare_read)(struct wl_display *);
extern int (*z_wl_display_read_events)(struct wl_display *);
extern void (*z_wl_display_cancel_read)(struct wl_display *);
extern int (*z_wl_display_flush)(struct wl_display *);
extern struct wl_proxy *(*z_wl_proxy_marshal_flags)(
struct wl_proxy *,
uint32_t,
const struct wl_interface *,
uint32_t,
uint32_t,
...
);
extern int (*z_wl_proxy_add_listener)(struct wl_proxy *, void (**)(void), void *);
extern void (*z_wl_proxy_destroy)(struct wl_proxy *);
extern uint32_t (*z_wl_proxy_get_version)(struct wl_proxy *);
extern void (*z_wl_proxy_set_user_data)(struct wl_proxy *, void *);
extern void *(*z_wl_proxy_get_user_data)(struct wl_proxy *);
extern struct wl_egl_window *(*z_wl_egl_window_create)(struct wl_surface *, int, int);
extern void (*z_wl_egl_window_destroy)(struct wl_egl_window *);
extern void (*z_wl_egl_window_resize)(struct wl_egl_window *, int, int, int, int);
extern PFNEGLGETPLATFORMDISPLAYPROC z_eglGetPlatformDisplay;
extern PFNEGLINITIALIZEPROC z_eglInitialize;
extern PFNEGLTERMINATEPROC z_eglTerminate;
extern PFNEGLBINDAPIPROC z_eglBindAPI;
extern PFNEGLCHOOSECONFIGPROC z_eglChooseConfig;
extern PFNEGLCREATECONTEXTPROC z_eglCreateContext;
extern PFNEGLDESTROYCONTEXTPROC z_eglDestroyContext;
extern PFNEGLCREATEWINDOWSURFACEPROC z_eglCreateWindowSurface;
extern PFNEGLDESTROYSURFACEPROC z_eglDestroySurface;
extern PFNEGLMAKECURRENTPROC z_eglMakeCurrent;
extern PFNEGLGETCURRENTCONTEXTPROC z_eglGetCurrentContext;
extern PFNEGLSWAPBUFFERSPROC z_eglSwapBuffers;
extern PFNEGLSWAPINTERVALPROC z_eglSwapInterval;
extern PFNEGLGETPROCADDRESSPROC z_eglGetProcAddress;
extern PFNEGLRELEASETHREADPROC z_eglReleaseThread;

89
src/linux/functions.inc Normal file
View File

@ -0,0 +1,89 @@
Z_FUNCTION(wl_display_connect)
Z_FUNCTION(wl_display_disconnect)
Z_FUNCTION(wl_display_get_fd)
Z_FUNCTION(wl_display_dispatch_pending)
Z_FUNCTION(wl_display_prepare_read)
Z_FUNCTION(wl_display_read_events)
Z_FUNCTION(wl_display_cancel_read)
Z_FUNCTION(wl_display_flush)
Z_FUNCTION(wl_proxy_marshal_flags)
Z_FUNCTION(wl_proxy_add_listener)
Z_FUNCTION(wl_proxy_destroy)
Z_FUNCTION(wl_proxy_get_version)
Z_FUNCTION(wl_proxy_set_user_data)
Z_FUNCTION(wl_proxy_get_user_data)
Z_FUNCTION(wl_egl_window_create)
Z_FUNCTION(wl_egl_window_destroy)
Z_FUNCTION(wl_egl_window_resize)
Z_FUNCTION(eglGetPlatformDisplay)
Z_FUNCTION(eglInitialize)
Z_FUNCTION(eglTerminate)
Z_FUNCTION(eglBindAPI)
Z_FUNCTION(eglChooseConfig)
Z_FUNCTION(eglCreateContext)
Z_FUNCTION(eglDestroyContext)
Z_FUNCTION(eglCreateWindowSurface)
Z_FUNCTION(eglDestroySurface)
Z_FUNCTION(eglMakeCurrent)
Z_FUNCTION(eglGetCurrentContext)
Z_FUNCTION(eglSwapBuffers)
Z_FUNCTION(eglSwapInterval)
Z_FUNCTION(eglGetProcAddress)
Z_FUNCTION(eglReleaseThread)
Z_FUNCTION(xkb_context_new)
Z_FUNCTION(xkb_context_unref)
Z_FUNCTION(xkb_keymap_new_from_string)
Z_FUNCTION(xkb_keymap_unref)
Z_FUNCTION(xkb_keymap_key_repeats)
Z_FUNCTION(xkb_state_new)
Z_FUNCTION(xkb_state_unref)
Z_FUNCTION(xkb_state_update_mask)
Z_FUNCTION(xkb_state_key_get_one_sym)
Z_FUNCTION(xkb_state_key_get_utf8)
Z_FUNCTION(xkb_state_mod_name_is_active)
Z_FUNCTION(xkb_keysym_to_utf32)
Z_FUNCTION(xkb_compose_table_new_from_locale)
Z_FUNCTION(xkb_compose_table_unref)
Z_FUNCTION(xkb_compose_state_new)
Z_FUNCTION(xkb_compose_state_unref)
Z_FUNCTION(xkb_compose_state_feed)
Z_FUNCTION(xkb_compose_state_reset)
Z_FUNCTION(xkb_compose_state_get_status)
Z_FUNCTION(xkb_compose_state_get_utf8)
Z_FUNCTION(dbus_connection_open_private)
Z_FUNCTION(dbus_connection_set_exit_on_disconnect)
Z_FUNCTION(dbus_connection_close)
Z_FUNCTION(dbus_connection_unref)
Z_FUNCTION(dbus_connection_get_is_connected)
Z_FUNCTION(dbus_connection_get_unix_fd)
Z_FUNCTION(dbus_connection_has_messages_to_send)
Z_FUNCTION(dbus_connection_read_write)
Z_FUNCTION(dbus_connection_pop_message)
Z_FUNCTION(dbus_connection_send)
Z_FUNCTION(dbus_message_new_method_call)
Z_FUNCTION(dbus_message_unref)
Z_FUNCTION(dbus_message_get_type)
Z_FUNCTION(dbus_message_get_reply_serial)
Z_FUNCTION(dbus_message_get_path)
Z_FUNCTION(dbus_message_get_sender)
Z_FUNCTION(dbus_message_get_error_name)
Z_FUNCTION(dbus_message_is_signal)
Z_FUNCTION(dbus_message_has_signature)
Z_FUNCTION(dbus_message_set_no_reply)
Z_FUNCTION(dbus_message_iter_init)
Z_FUNCTION(dbus_message_iter_init_append)
Z_FUNCTION(dbus_message_iter_get_arg_type)
Z_FUNCTION(dbus_message_iter_next)
Z_FUNCTION(dbus_message_iter_recurse)
Z_FUNCTION(dbus_message_iter_get_basic)
Z_FUNCTION(dbus_message_iter_append_basic)
Z_FUNCTION(dbus_message_iter_open_container)
Z_FUNCTION(dbus_message_iter_close_container)
Z_FUNCTION(dbus_message_iter_abandon_container_if_open)
Z_FUNCTION(dbus_threads_init_default)
Z_FUNCTION(dbus_message_new_method_return)
Z_FUNCTION(dbus_message_new_error)
Z_FUNCTION(dbus_message_new_signal)
Z_FUNCTION(dbus_message_get_interface)
Z_FUNCTION(dbus_message_get_member)
Z_FUNCTION(dbus_message_get_no_reply)

182
src/linux/libraries.c Normal file
View File

@ -0,0 +1,182 @@
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <pthread.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/stat.h>
#include <unistd.h>
#include "wayland-client-core.h"
#include "wayland-egl-core.h"
#define EGL_NO_X11
#include "EGL/egl.h"
#include "EGL/eglext.h"
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-compose.h>
#include "xkb-imports.h"
#include <dbus/dbus.h>
#include "dbus-imports.h"
#include "function-pointers.h"
#define Z_FUNCTION(name) __typeof__(name) *z_##name;
#include "functions.inc"
#undef Z_FUNCTION
static void *wayland, *wayland_egl, *egl, *xkb, *dbus;
static int have_egl, have_xkb, have_dbus;
static void *open_library(const char *name) {
void *handle = dlopen(name, RTLD_NOW | RTLD_LOCAL);
if (handle)
return handle;
/* Stable system profiles only; never bake a Nix store path into the binary. */
const char *prefixes[] = {"/run/current-system/sw/lib/", "/run/opengl-driver/lib/"};
char path[256];
for (unsigned i = 0; i < sizeof(prefixes) / sizeof(prefixes[0]); ++i) {
snprintf(path, sizeof(path), "%s%s", prefixes[i], name);
handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
if (handle)
return handle;
}
return NULL;
}
void z_linux_libraries_close(void) {
have_egl = have_xkb = have_dbus = 0;
if (dbus)
dlclose(dbus);
dbus = NULL;
if (xkb)
dlclose(xkb);
xkb = NULL;
if (egl)
dlclose(egl);
if (wayland_egl)
dlclose(wayland_egl);
if (wayland)
dlclose(wayland);
wayland = wayland_egl = egl = NULL;
#define Z_FUNCTION(name) z_##name = NULL;
#include "functions.inc"
#undef Z_FUNCTION
}
int z_linux_libraries_open(void) {
wayland = open_library("libwayland-client.so.0");
if (!wayland)
return 0;
wayland_egl = open_library("libwayland-egl.so.1");
egl = open_library("libEGL.so.1");
have_egl = wayland_egl && egl;
xkb = open_library("libxkbcommon.so.0");
have_xkb = xkb != NULL;
dbus = open_library("libdbus-1.so.3");
have_dbus = dbus != NULL;
#define Z_FUNCTION(name) \
do { \
const char *symbol = #name; \
int bus = strncmp(symbol, "dbus_", 5) == 0; \
int keyboard = strncmp(symbol, "xkb_", 4) == 0; \
int graphics = strncmp(symbol, "egl", 3) == 0 || strncmp(symbol, "wl_egl_", 7) == 0; \
void *library = \
bus ? dbus \
: keyboard \
? xkb \
: (strncmp(symbol, "egl", 3) == 0 ? egl : (graphics ? wayland_egl : wayland)); \
if (library) \
z_##name = (__typeof__(z_##name))dlsym(library, symbol); \
if (!z_##name) { \
if (bus) \
have_dbus = 0; \
else if (keyboard) \
have_xkb = 0; \
else if (graphics) \
have_egl = 0; \
else { \
z_linux_libraries_close(); \
return 0; \
} \
} \
} while (0);
#include "functions.inc"
#undef Z_FUNCTION
return 1;
}
int z_linux_egl_available(void) {
return have_egl;
}
int z_linux_errno(void) {
return errno;
}
int z_linux_is_main_thread(void) {
return syscall(SYS_gettid) == getpid();
}
/* Keep libc's optimization/fortify inline implementation on the C side. */
int z_linux_poll(struct pollfd *fds, nfds_t count, int timeout) {
return poll(fds, count, timeout);
}
ssize_t z_linux_read(int fd, void *buffer, size_t count) {
return read(fd, buffer, count);
}
ssize_t z_linux_write(int fd, const void *buffer, size_t count) {
return write(fd, buffer, count);
}
int z_linux_xkb_available(void) {
return have_xkb;
}
int z_linux_nonblock(int fd) {
int flags = fcntl(fd, F_GETFL);
return flags < 0 ? -1 : fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
int z_linux_pipe(int fds[2]) {
if (pipe2(fds, O_CLOEXEC) < 0)
return -1;
if (z_linux_nonblock(fds[0]) == 0)
return 0;
int saved = errno;
close(fds[0]);
close(fds[1]);
errno = saved;
return -1;
}
/* A clipboard reader may close early. Suppress only the SIGPIPE generated by
* this write, preserving the embedding application's signal disposition. */
ssize_t z_linux_pipe_write(int fd, const void *buffer, size_t count) {
sigset_t set, old, pending;
sigemptyset(&set);
sigaddset(&set, SIGPIPE);
if (pthread_sigmask(SIG_BLOCK, &set, &old) != 0) {
errno = EIO;
return -1;
}
sigpending(&pending);
int was_pending = sigismember(&pending, SIGPIPE);
ssize_t result = write(fd, buffer, count);
int saved = errno;
if (result < 0 && saved == EPIPE && !was_pending) {
struct timespec zero = {0, 0};
while (sigtimedwait(&set, NULL, &zero) < 0 && errno == EINTR) {
}
}
pthread_sigmask(SIG_SETMASK, &old, NULL);
errno = saved;
return result;
}
int z_linux_is_directory(const char *path) {
struct stat info;
return stat(path, &info) < 0 ? -1 : S_ISDIR(info.st_mode);
}
int z_linux_dbus_available(void) {
return have_dbus;
}
_Static_assert(sizeof(dbus_uint32_t) == 4 && sizeof(dbus_uint64_t) == 8, "D-Bus integer ABI");
_Static_assert(sizeof(DBusMessageIter) == (sizeof(void *) == 8 ? 72 : 56), "D-Bus iterator ABI");

676
src/linux/native.zig Normal file
View File

@ -0,0 +1,676 @@
//! Wayland platform owner. Protocol dispatch and native commands stay on the OS
//! thread; the application thread holds each window's lock only while rendering.
const std = @import("std");
const c = @import("linux_c");
const n = @import("native");
const abi = @import("../abi.zig");
const runtime = @import("../runtime.zig");
const Application = @import("../Application.zig");
const Egl = @import("Egl.zig");
const Input = @import("Input.zig");
const Desktop = @import("Desktop.zig");
const WindowState = @import("WindowState.zig");
const Portal = @import("Portal.zig");
const Watch = @import("Watch.zig");
const Data = @import("Data.zig");
const Popup = @import("Popup.zig");
const KdeAppMenu = @import("KdeAppMenu.zig");
const Text = @import("Text.zig");
pub const Window = struct {
next: ?*Window = null,
owner: *Application,
handle: ?*n.uhdk_window,
surface: ?*c.struct_wl_surface = null,
xdg: ?*c.struct_xdg_surface = null,
top: ?*c.struct_xdg_toplevel = null,
decoration: ?*c.struct_zxdg_toplevel_decoration_v1 = null,
viewport: ?*c.struct_wp_viewport = null,
fractional: ?*c.struct_wp_fractional_scale_v1 = null,
egl: ?*Egl.Context = null,
mutex: std.Io.Mutex = .init,
request: u64 = 0,
deadline: u64 = 0,
serial: ?u32 = null,
dirty: bool = true,
configured: bool = false,
want_visible: bool = false,
visible: bool = false,
width: i32,
height: i32,
min_width: i32,
min_height: i32,
max_width: i32,
max_height: i32,
scale: u32 = 120,
focused: bool = false,
state: u32 = n.UHDK_WINDOW_NORMAL,
observation: WindowState.Observation = .{},
wm_caps: WindowState.Capabilities = .{},
flags: u32,
cursor: u32 = n.UHDK_CURSOR_ARROW,
text: Text.State = .{},
menu_override: ?*@import("../Resource.zig") = null,
menu_export: ?*KdeAppMenu.Export = null,
drop_enabled: bool = false,
metrics: n.uhdk_window_metrics = std.mem.zeroes(n.uhdk_window_metrics),
};
pub const Backend = struct {
app: *Application,
display: ?*c.struct_wl_display = null,
registry: ?*c.struct_wl_registry = null,
compositor: ?*c.struct_wl_compositor = null,
shell: ?*c.struct_xdg_wm_base = null,
decorations: ?*c.struct_zxdg_decoration_manager_v1 = null,
viewporter: ?*c.struct_wp_viewporter = null,
fractional: ?*c.struct_wp_fractional_scale_manager_v1 = null,
windows: ?*Window = null,
egl_display: c.EGLDisplay = null,
egl_config: c.EGLConfig = null,
input: Input.State = .{},
data: Data.State = .{},
watches: ?*Watch.Watch = null,
portal: Portal.State = .{},
desktop: Desktop.State = .{},
menus: KdeAppMenu.State = .{},
popup: Popup.State = .{},
capabilities: std.atomic.Value(u32) = .init(0),
running: bool = true,
failed: std.atomic.Value(bool) = .init(false),
};
pub var instance: ?*Backend = null;
var wake_fd: std.atomic.Value(c_int) = .init(-1);
pub fn backend(handle: ?*n.uhdk_app) u32 {
if (handle == null) return n.UHDK_BACKEND_AUTO;
const app = runtime.fromAppHandle(handle);
return if (app.has_run) n.UHDK_BACKEND_WAYLAND else n.UHDK_BACKEND_AUTO;
}
pub fn capability(handle: ?*n.uhdk_app, cap: u32) n.uhdk_bool {
if (handle == null) return 0;
const b = instance orelse return 0;
if (b.app != runtime.fromAppHandle(handle) or !b.app.running or b.failed.load(.acquire)) return 0;
return @intFromBool(cap < 32 and b.capabilities.load(.acquire) & (@as(u32, 1) << @intCast(cap)) != 0);
}
pub fn setCapability(cap: u32, available: bool) void {
const bits = @as(u32, 1) << @intCast(cap);
if (available) _ = instance.?.capabilities.fetchOr(bits, .release) else _ = instance.?.capabilities.fetchAnd(~bits, .release);
}
export fn z_native_is_main_thread() c_int {
return c.z_linux_is_main_thread();
}
export fn z_native_start(handle: ?*n.uhdk_app, _: n.uhdk_text) u32 {
const app = runtime.fromAppHandle(handle);
if (c.z_linux_libraries_open() == 0) return abi.UNAVAILABLE;
const b = app.gpa.create(Backend) catch {
c.z_linux_libraries_close();
return abi.OOM;
};
b.* = .{ .app = app };
instance = b;
Input.initialize();
const fd = c.eventfd(0, c.EFD_CLOEXEC | c.EFD_NONBLOCK);
if (fd < 0) {
z_native_finish();
return n.UHDK_STATUS_OS_ERROR;
}
wake_fd.store(fd, .release);
b.display = c.z_wl_display_connect.?(null);
if (b.display == null) {
z_native_finish();
return abi.UNAVAILABLE;
}
b.registry = c.wl_display_get_registry(b.display);
if (b.registry == null) {
z_native_finish();
return abi.OOM;
}
if (c.wl_registry_add_listener(b.registry, &registry_listener, b) != 0 or !initialRegistry(b)) {
z_native_finish();
return n.UHDK_STATUS_OS_ERROR;
}
if (b.compositor == null or b.shell == null) {
z_native_finish();
return n.UHDK_STATUS_UNSUPPORTED;
}
Input.setup();
if (!initialRegistry(b)) {
z_native_finish();
return n.UHDK_STATUS_OS_ERROR;
}
Desktop.initialize();
Portal.initialize();
Egl.initialize(b);
setCapability(n.UHDK_CAP_OPENGL, b.egl_display != null);
return abi.OK;
}
// Startup also uses a bounded read/flush/poll handshake: an unresponsive
// compositor must not trap app_run before the application thread even starts.
fn initialRegistry(b: *Backend) bool {
var done = false;
const callback = c.wl_display_sync(b.display) orelse return false;
defer c.wl_callback_destroy(callback);
const listener: c.struct_wl_callback_listener = .{ .done = registryDone };
if (c.wl_callback_add_listener(callback, &listener, &done) != 0) return false;
const deadline = runtime.now(b.app.io) + 5 * std.time.ns_per_s;
while (!done) {
if (c.z_wl_display_dispatch_pending.?(b.display) < 0) return false;
if (done) return true;
const now = runtime.now(b.app.io);
if (now >= deadline) return false;
if (c.z_wl_display_prepare_read.?(b.display) != 0) continue;
var events: c_short = c.POLLIN;
if (c.z_wl_display_flush.?(b.display) < 0) {
if (c.z_linux_errno() == c.EAGAIN) events |= c.POLLOUT else {
c.z_wl_display_cancel_read.?(b.display);
return false;
}
}
var fd: c.struct_pollfd = .{ .fd = c.z_wl_display_get_fd.?(b.display), .events = events, .revents = 0 };
const ready = c.z_linux_poll(&fd, 1, @intCast(@min(5000, (deadline - now + std.time.ns_per_ms - 1) / std.time.ns_per_ms)));
if (ready > 0 and fd.revents & c.POLLIN != 0) {
if (c.z_wl_display_read_events.?(b.display) < 0) return false;
} else c.z_wl_display_cancel_read.?(b.display);
if (ready < 0 and c.z_linux_errno() != c.EINTR) return false;
if (fd.revents & (c.POLLHUP | c.POLLERR | c.POLLNVAL) != 0) return false;
}
return true;
}
fn registryDone(data: ?*anyopaque, _: ?*c.struct_wl_callback, _: u32) callconv(.c) void {
const done: *bool = @ptrCast(@alignCast(data.?));
done.* = true;
}
export fn z_native_finish() void {
const b = instance orelse return;
std.debug.assert(b.windows == null);
while (b.watches) |w| Watch.destroy(w);
Popup.deinit();
Desktop.deinit();
KdeAppMenu.deinit();
Portal.deinit();
Data.deinit();
Input.deinit();
Egl.finish(b);
if (b.fractional != null) c.wp_fractional_scale_manager_v1_destroy(b.fractional);
if (b.viewporter != null) c.wp_viewporter_destroy(b.viewporter);
if (b.decorations != null) c.zxdg_decoration_manager_v1_destroy(b.decorations);
if (b.shell != null) c.xdg_wm_base_destroy(b.shell);
if (b.compositor != null) c.wl_compositor_destroy(b.compositor);
if (b.registry != null) c.wl_registry_destroy(b.registry);
if (b.display != null) c.z_wl_display_disconnect.?(b.display);
const fd = wake_fd.swap(-1, .acq_rel);
if (fd >= 0) _ = c.close(fd);
const gpa = b.app.gpa;
instance = null;
gpa.destroy(b);
c.z_linux_libraries_close();
}
export fn z_native_wake() void {
const fd = wake_fd.load(.acquire);
if (fd < 0) return;
const one: u64 = 1;
while (c.z_linux_write(fd, &one, @sizeOf(u64)) < 0) {
if (c.z_linux_errno() != c.EINTR) break; // EAGAIN already means readable.
}
}
export fn z_native_again() void {
z_native_wake();
}
export fn z_native_stop() void {
if (instance) |b| b.running = false;
}
export fn z_native_pool_push() ?*anyopaque {
return null;
}
export fn z_native_pool_pop(_: ?*anyopaque) void {}
fn fail(b: *Backend) void {
if (b.failed.swap(true, .acq_rel)) return;
b.app.lock();
b.app.stopping = true;
b.app.unlock();
Popup.deinit();
runtime.simpleEvent(b.app, n.UHDK_EVENT_QUIT_REQUESTED);
}
export fn z_native_run() void {
const b = instance.?;
var fds: std.ArrayList(c.struct_pollfd) = .empty;
defer fds.deinit(b.app.gpa);
while (b.running) {
if (!b.failed.load(.acquire) and c.z_wl_display_dispatch_pending.?(b.display) < 0) fail(b);
n.z_tick(runtime.toAppHandle(b.app));
var cursor = b.windows;
var timeout: c_int = Input.tick();
Watch.tick();
const popup_timeout = Popup.tick();
if (popup_timeout >= 0) timeout = if (timeout < 0) popup_timeout else @min(timeout, popup_timeout);
const desktop_timeout = Desktop.tick();
if (desktop_timeout >= 0) timeout = if (timeout < 0) desktop_timeout else @min(timeout, desktop_timeout);
const portal_timeout = Portal.tick();
if (portal_timeout >= 0) timeout = if (timeout < 0) portal_timeout else @min(timeout, portal_timeout);
const data_timeout = Data.tick();
if (data_timeout >= 0) timeout = if (timeout < 0) data_timeout else @min(timeout, data_timeout);
while (cursor) |w| {
cursor = w.next;
if (w.request != 0) {
timeout = if (timeout < 0) 50 else @min(timeout, 50);
if (runtime.now(b.app.io) >= w.deadline) {
completeCreation(w, abi.UNAVAILABLE);
continue;
}
}
if (!b.failed.load(.acquire)) apply(w);
}
if (!b.running) break;
var reading = false;
var display_events: c_short = c.POLLIN;
if (!b.failed.load(.acquire)) {
if (c.z_wl_display_prepare_read.?(b.display) != 0) continue;
reading = true;
if (c.z_wl_display_flush.?(b.display) < 0) {
if (c.z_linux_errno() == c.EAGAIN) display_events |= c.POLLOUT else {
c.z_wl_display_cancel_read.?(b.display);
fail(b);
continue;
}
}
}
fds.clearRetainingCapacity();
fds.appendSlice(b.app.gpa, &.{
.{ .fd = if (reading) c.z_wl_display_get_fd.?(b.display) else -1, .events = display_events, .revents = 0 },
.{ .fd = wake_fd.load(.acquire), .events = c.POLLIN, .revents = 0 },
}) catch {
if (reading) c.z_wl_display_cancel_read.?(b.display);
fail(b);
continue;
};
Data.poll(&fds) catch {
if (reading) c.z_wl_display_cancel_read.?(b.display);
fail(b);
continue;
};
Watch.poll(&fds) catch {
if (reading) c.z_wl_display_cancel_read.?(b.display);
fail(b);
continue;
};
Portal.poll(&fds) catch {
if (reading) c.z_wl_display_cancel_read.?(b.display);
fail(b);
continue;
};
const ready = c.z_linux_poll(fds.items.ptr, fds.items.len, timeout);
if (reading) {
if (ready > 0 and fds.items[0].revents & c.POLLIN != 0) {
if (c.z_wl_display_read_events.?(b.display) < 0) fail(b);
} else c.z_wl_display_cancel_read.?(b.display);
}
if (ready < 0 and c.z_linux_errno() != c.EINTR) fail(b);
if (fds.items[0].revents & (c.POLLERR | c.POLLHUP | c.POLLNVAL) != 0) fail(b);
if (fds.items[1].revents & c.POLLIN != 0) {
var value: u64 = 0;
_ = c.z_linux_read(fds.items[1].fd, &value, @sizeOf(u64));
}
}
}
fn registryGlobal(data: ?*anyopaque, registry: ?*c.struct_wl_registry, name: u32, interface: [*c]const u8, version: u32) callconv(.c) void {
const b: *Backend = @ptrCast(@alignCast(data.?));
const str = std.mem.span(interface);
Input.global(registry, name, str, version);
Data.global(registry, name, str, version);
Portal.global(registry, name, str, version);
KdeAppMenu.global(registry, name, str, version);
Desktop.global(registry, name, str, version);
Popup.capability();
if (std.mem.eql(u8, str, "wl_compositor") and version >= 4) {
b.compositor = @ptrCast(c.wl_registry_bind(registry, name, &c.z_uhdk_wl_compositor_interface, @min(version, 6)));
} else if (std.mem.eql(u8, str, "xdg_wm_base")) {
b.shell = @ptrCast(c.wl_registry_bind(registry, name, &c.z_uhdk_xdg_wm_base_interface, @min(version, 6)));
if (b.shell != null) _ = c.xdg_wm_base_add_listener(b.shell, &shell_listener, b);
} else if (std.mem.eql(u8, str, "zxdg_decoration_manager_v1")) {
b.decorations = @ptrCast(c.wl_registry_bind(registry, name, &c.z_uhdk_zxdg_decoration_manager_v1_interface, 1));
} else if (std.mem.eql(u8, str, "wp_viewporter")) {
b.viewporter = @ptrCast(c.wl_registry_bind(registry, name, &c.z_uhdk_wp_viewporter_interface, 1));
} else if (std.mem.eql(u8, str, "wp_fractional_scale_manager_v1")) {
b.fractional = @ptrCast(c.wl_registry_bind(registry, name, &c.z_uhdk_wp_fractional_scale_manager_v1_interface, 1));
}
}
fn registryRemove(_: ?*anyopaque, _: ?*c.struct_wl_registry, name: u32) callconv(.c) void {
Input.remove(name);
KdeAppMenu.remove(name);
}
const registry_listener: c.struct_wl_registry_listener = .{ .global = registryGlobal, .global_remove = registryRemove };
fn ping(_: ?*anyopaque, shell: ?*c.struct_xdg_wm_base, serial: u32) callconv(.c) void {
c.xdg_wm_base_pong(shell, serial);
}
const shell_listener: c.struct_xdg_wm_base_listener = .{ .ping = ping };
fn window(data: ?*anyopaque) *Window {
return @ptrCast(@alignCast(data.?));
}
fn configure(data: ?*anyopaque, _: ?*c.struct_xdg_surface, serial: u32) callconv(.c) void {
const w = window(data);
w.serial = serial;
w.dirty = true;
}
const surface_listener: c.struct_xdg_surface_listener = .{ .configure = configure };
fn topConfigure(data: ?*anyopaque, _: ?*c.struct_xdg_toplevel, width: i32, height: i32, states: [*c]c.struct_wl_array) callconv(.c) void {
const w = window(data);
if (width > 0) w.width = width;
if (height > 0) w.height = height;
const values: []const u32 = if (states.*.size == 0) &.{} else @as([*]const u32, @ptrCast(@alignCast(states.*.data)))[0 .. states.*.size / @sizeOf(u32)];
w.observation = WindowState.observe(c.xdg_toplevel_get_version(w.top), values);
w.focused = w.observation.focused;
w.state = w.observation.geometry;
}
fn emit(w: *Window, kind: u32) void {
var event = std.mem.zeroes(n.z_event);
event.type = kind;
event.window = w.handle;
n.z_emit(runtime.toAppHandle(w.owner), &event);
}
fn topClose(data: ?*anyopaque, _: ?*c.struct_xdg_toplevel) callconv(.c) void {
emit(window(data), n.UHDK_EVENT_WINDOW_CLOSE_REQUESTED);
}
fn bounds(_: ?*anyopaque, _: ?*c.struct_xdg_toplevel, _: i32, _: i32) callconv(.c) void {}
fn wmCaps(data: ?*anyopaque, _: ?*c.struct_xdg_toplevel, caps: [*c]c.struct_wl_array) callconv(.c) void {
const values: []const u32 = if (caps.*.size == 0) &.{} else @as([*]const u32, @ptrCast(@alignCast(caps.*.data)))[0 .. caps.*.size / @sizeOf(u32)];
window(data).wm_caps = WindowState.Capabilities.announced(values);
}
const top_listener: c.struct_xdg_toplevel_listener = .{ .configure = topConfigure, .close = topClose, .configure_bounds = bounds, .wm_capabilities = wmCaps };
fn decorationConfigure(data: ?*anyopaque, _: ?*c.struct_zxdg_toplevel_decoration_v1, mode: u32) callconv(.c) void {
const w = window(data);
// Until the client-decoration adapter exists, don't silently create a
// decorated window with no title bar on compositors that decline SSD.
if (mode != c.ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE and w.request != 0) completeCreation(w, n.UHDK_STATUS_UNSUPPORTED);
}
const decoration_listener: c.struct_zxdg_toplevel_decoration_v1_listener = .{ .configure = decorationConfigure };
fn fraction(data: ?*anyopaque, _: ?*c.struct_wp_fractional_scale_v1, scale: u32) callconv(.c) void {
const w = window(data);
if (scale == 0 or scale > 120 * 16) return;
w.scale = scale;
w.dirty = true;
}
const fraction_listener: c.struct_wp_fractional_scale_v1_listener = .{ .preferred_scale = fraction };
fn enter(_: ?*anyopaque, _: ?*c.struct_wl_surface, _: ?*c.struct_wl_output) callconv(.c) void {}
fn bufferScale(data: ?*anyopaque, _: ?*c.struct_wl_surface, scale: i32) callconv(.c) void {
const w = window(data);
if (w.fractional == null and scale > 0 and scale <= 16) {
w.scale = @as(u32, @intCast(scale)) * 120;
w.dirty = true;
}
}
fn bufferTransform(_: ?*anyopaque, _: ?*c.struct_wl_surface, _: u32) callconv(.c) void {}
const wl_surface_listener: c.struct_wl_surface_listener = .{ .enter = enter, .leave = enter, .preferred_buffer_scale = bufferScale, .preferred_buffer_transform = bufferTransform };
fn completeCreation(w: *Window, status: u32) void {
const id = w.request;
if (id == 0) return;
w.request = 0;
const app = runtime.toAppHandle(w.owner);
var result = std.mem.zeroes(n.z_request_result);
result.value.window = w.handle;
if (status != abi.OK) {
n.z_object_set_native(w.handle, null);
destroyWindow(w);
}
n.z_complete(app, id, status, &result);
}
fn apply(w: *Window) void {
if (!w.dirty or !w.mutex.tryLock()) return;
// Never block the display dispatcher behind eglSwapBuffers, which itself
// may be waiting for the dispatcher. end_frame wakes us to retry.
if (w.serial) |serial| {
c.xdg_surface_ack_configure(w.xdg, serial);
w.serial = null;
w.configured = true;
}
if (!w.configured) {
w.mutex.unlock(w.owner.io);
return;
}
const width = @min(w.width, 65536);
const height = @min(w.height, 65536);
const pixel_width: u32 = @intCast(@divTrunc(@as(i64, width) * w.scale + 119, 120));
const pixel_height: u32 = @intCast(@divTrunc(@as(i64, height) * w.scale + 119, 120));
if (w.viewport != null) {
c.wl_surface_set_buffer_scale(w.surface, 1);
c.wp_viewport_set_destination(w.viewport, width, height);
} else c.wl_surface_set_buffer_scale(w.surface, @intCast(w.scale / 120));
c.xdg_surface_set_window_geometry(w.xdg, 0, 0, width, height);
if (w.egl) |ctx| c.z_wl_egl_window_resize.?(ctx.native, @intCast(pixel_width), @intCast(pixel_height), 0, 0);
if (!w.want_visible and w.visible) {
if (w.egl) |ctx| Egl.resetSurface(ctx);
c.wl_surface_attach(w.surface, null, 0, 0);
c.wl_surface_commit(w.surface);
w.configured = false;
}
w.visible = w.want_visible;
w.metrics = .{ .struct_size = @sizeOf(n.uhdk_window_metrics), .framebuffer_width = pixel_width, .framebuffer_height = pixel_height, .logical_size = .{ .width = @floatFromInt(width), .height = @floatFromInt(height) }, .scale = @as(f32, @floatFromInt(w.scale)) / 120, .state = w.state, .focused = @intFromBool(w.focused), .visible = @intFromBool(w.visible), .observations = w.observation.known, .resizing = @intFromBool(w.observation.resizing), .suspended = @intFromBool(w.observation.suspended), .minimized = 0, .occluded = 0 };
const metrics = w.metrics;
w.dirty = false;
w.mutex.unlock(w.owner.io);
n.z_metrics(w.handle, &metrics);
emit(w, n.UHDK_EVENT_WINDOW_REDRAW);
if (w.request != 0) completeCreation(w, abi.OK);
}
fn createWindow(b: *Backend, command: *const n.z_command, id: u64) u32 {
const desc = command.window;
if (desc.flags & n.UHDK_WINDOW_DECORATED != 0 and b.decorations == null) return n.UHDK_STATUS_UNSUPPORTED;
const w = b.app.gpa.create(Window) catch return abi.OOM;
w.* = .{ .owner = b.app, .handle = @ptrCast(command.target), .width = @intFromFloat(@max(1, desc.content_size.width)), .height = @intFromFloat(@max(1, desc.content_size.height)), .min_width = @intFromFloat(desc.min_size.width), .min_height = @intFromFloat(desc.min_size.height), .max_width = @intFromFloat(desc.max_size.width), .max_height = @intFromFloat(desc.max_size.height), .flags = desc.flags };
w.next = b.windows;
b.windows = w;
const status = setupWindow(b, w, desc);
if (status != abi.OK) {
destroyWindow(w);
return status;
}
n.z_object_set_native(command.target, w);
w.request = id;
w.deadline = runtime.now(b.app.io) + 5 * std.time.ns_per_s;
c.wl_surface_commit(w.surface); // Empty initial commit; wait for configure.
return n.Z_DEFERRED;
}
fn setupWindow(b: *Backend, w: *Window, desc: n.uhdk_window_desc) u32 {
const title = b.app.gpa.dupeZ(u8, abi.textBytes(desc.title)) catch return abi.OOM;
defer b.app.gpa.free(title);
const app_id = b.app.gpa.dupeZ(u8, b.app.application_id) catch return abi.OOM;
defer b.app.gpa.free(app_id);
w.surface = c.wl_compositor_create_surface(b.compositor) orelse return abi.OOM;
if (c.wl_surface_add_listener(w.surface, &wl_surface_listener, w) != 0) return abi.OOM;
w.xdg = c.xdg_wm_base_get_xdg_surface(b.shell, w.surface) orelse return abi.OOM;
if (c.xdg_surface_add_listener(w.xdg, &surface_listener, w) != 0) return abi.OOM;
w.top = c.xdg_surface_get_toplevel(w.xdg) orelse return abi.OOM;
if (c.xdg_toplevel_add_listener(w.top, &top_listener, w) != 0) return abi.OOM;
c.xdg_toplevel_set_title(w.top, title);
c.xdg_toplevel_set_app_id(w.top, app_id);
Desktop.applyIcon(w);
KdeAppMenu.inherit(w) catch return abi.OOM;
if (desc.parent) |parent| {
const p: *Window = @ptrCast(@alignCast(n.z_object_native(parent) orelse return abi.UNAVAILABLE));
c.xdg_toplevel_set_parent(w.top, p.top);
}
if (w.flags & n.UHDK_WINDOW_RESIZABLE == 0) {
w.min_width = w.width;
w.min_height = w.height;
w.max_width = w.width;
w.max_height = w.height;
}
if (w.flags & n.UHDK_WINDOW_RESIZABLE != 0) {
w.width = std.math.clamp(w.width, @max(1, w.min_width), if (w.max_width == 0) 65536 else w.max_width);
w.height = std.math.clamp(w.height, @max(1, w.min_height), if (w.max_height == 0) 65536 else w.max_height);
}
setLimits(w);
if (w.flags & n.UHDK_WINDOW_DECORATED != 0) {
w.decoration = c.zxdg_decoration_manager_v1_get_toplevel_decoration(b.decorations, w.top) orelse return abi.OOM;
if (c.zxdg_toplevel_decoration_v1_add_listener(w.decoration, &decoration_listener, w) != 0) return abi.OOM;
c.zxdg_toplevel_decoration_v1_set_mode(w.decoration, c.ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
}
if (b.fractional != null and b.viewporter != null) {
w.viewport = c.wp_viewporter_get_viewport(b.viewporter, w.surface) orelse return abi.OOM;
w.fractional = c.wp_fractional_scale_manager_v1_get_fractional_scale(b.fractional, w.surface) orelse return abi.OOM;
if (c.wp_fractional_scale_v1_add_listener(w.fractional, &fraction_listener, w) != 0) return abi.OOM;
}
return abi.OK;
}
fn setLimits(w: *Window) void {
c.xdg_toplevel_set_min_size(w.top, w.min_width, w.min_height);
c.xdg_toplevel_set_max_size(w.top, w.max_width, w.max_height);
}
fn destroyWindow(w: *Window) void {
const b = instance.?;
std.debug.assert(w.egl == null);
Popup.forget(w);
KdeAppMenu.forget(w);
Desktop.forget(w);
Data.forget(w);
Input.forget(w);
Text.deinit(w);
var link = &b.windows;
while (link.*) |item| {
if (item == w) {
link.* = item.next;
break;
}
link = &item.next;
}
if (w.fractional != null) c.wp_fractional_scale_v1_destroy(w.fractional);
if (w.viewport != null) c.wp_viewport_destroy(w.viewport);
if (w.decoration != null) c.zxdg_toplevel_decoration_v1_destroy(w.decoration);
if (w.top != null) c.xdg_toplevel_destroy(w.top);
if (w.xdg != null) c.xdg_surface_destroy(w.xdg);
if (w.surface != null) c.wl_surface_destroy(w.surface);
w.owner.gpa.destroy(w);
}
export fn z_native_execute(_: ?*n.uhdk_app, raw: [*c]const n.z_command, id: u64, result: [*c]n.z_request_result) u32 {
const b = instance.?;
const cmd: *const n.z_command = &raw.*;
if (cmd.op == n.Z_DESTROY) {
if (n.z_object_native(cmd.target)) |ptr| {
z_native_destroy(n.z_object_kind(cmd.target), ptr);
n.z_object_set_native(cmd.target, null);
}
return abi.OK;
}
if (b.failed.load(.acquire)) return abi.UNAVAILABLE;
if (cmd.op == n.Z_WINDOW) return createWindow(b, cmd, id);
if (cmd.op == n.Z_APP_MENU) return KdeAppMenu.attach(null, cmd.other);
if (cmd.op == n.Z_MENU_FLAGS) {
Popup.flags();
return KdeAppMenu.updateFlags();
}
if (cmd.op == n.Z_POPUP) return Popup.request(cmd, id);
if (cmd.op == n.Z_ICON) return Desktop.setIcon(cmd.image);
if (cmd.op == n.Z_DIALOG or cmd.op == n.Z_URL) return Portal.request(cmd, id);
if (cmd.op == n.Z_WATCH) return Watch.create(cmd, &result.*);
if (cmd.op == n.Z_CLIP_WRITE) return Data.write(cmd.text);
if (cmd.op == n.Z_CLIP_READ) return Data.read(id, cmd.limit);
const ptr = if (cmd.target != null) n.z_object_native(cmd.target) else null;
const w: *Window = if (ptr) |p| @ptrCast(@alignCast(p)) else return n.UHDK_STATUS_UNSUPPORTED;
switch (cmd.op) {
n.Z_WINDOW_MENU => return KdeAppMenu.attach(w, cmd.other),
n.Z_TITLE => {
const title = b.app.gpa.dupeZ(u8, abi.textBytes(cmd.text)) catch return abi.OOM;
defer b.app.gpa.free(title);
c.xdg_toplevel_set_title(w.top, title);
},
n.Z_SIZE => {
if (w.state != n.UHDK_WINDOW_NORMAL) return abi.UNAVAILABLE;
w.width = @intFromFloat(@max(1, cmd.size.width));
w.height = @intFromFloat(@max(1, cmd.size.height));
if (w.flags & n.UHDK_WINDOW_RESIZABLE != 0) {
w.width = std.math.clamp(w.width, @max(1, w.min_width), if (w.max_width == 0) 65536 else w.max_width);
w.height = std.math.clamp(w.height, @max(1, w.min_height), if (w.max_height == 0) 65536 else w.max_height);
}
if (w.flags & n.UHDK_WINDOW_RESIZABLE == 0) {
w.min_width = w.width;
w.max_width = w.width;
w.min_height = w.height;
w.max_height = w.height;
setLimits(w);
}
w.dirty = true;
},
n.Z_STATE => switch (cmd.number) {
n.UHDK_WINDOW_MINIMIZED => {
if (!w.wm_caps.minimize) return n.UHDK_STATUS_UNSUPPORTED;
c.xdg_toplevel_set_minimized(w.top);
},
n.UHDK_WINDOW_MAXIMIZED => {
if (!w.wm_caps.maximize) return n.UHDK_STATUS_UNSUPPORTED;
if (w.wm_caps.fullscreen) c.xdg_toplevel_unset_fullscreen(w.top);
c.xdg_toplevel_set_maximized(w.top);
},
n.UHDK_WINDOW_FULLSCREEN => {
if (!w.wm_caps.fullscreen) return n.UHDK_STATUS_UNSUPPORTED;
c.xdg_toplevel_set_fullscreen(w.top, null);
},
else => {
if (w.wm_caps.fullscreen) c.xdg_toplevel_unset_fullscreen(w.top);
if (w.wm_caps.maximize) c.xdg_toplevel_unset_maximized(w.top);
},
},
n.Z_VISIBLE => {
w.want_visible = cmd.number != 0;
if (!w.want_visible) {
Popup.forget(w);
Input.clearPopupTrigger(w);
}
if (w.want_visible and !w.configured) c.wl_surface_commit(w.surface);
w.dirty = true;
},
n.Z_DROP => return Data.enable(w, cmd.number != 0),
n.Z_CURSOR => return Input.cursor(w, @intCast(cmd.number)),
n.Z_POINTER => return Input.capture(w, cmd.number != 0),
n.Z_TEXT_ENABLE => return Text.enable(w, cmd.number != 0),
n.Z_TEXT_STATE => return Text.snapshot(w, cmd.text_state),
n.Z_TEXT_CANCEL => Text.cancel(w),
n.Z_FOCUS => return Desktop.focus(w, id),
n.Z_REDRAW => emit(w, n.UHDK_EVENT_WINDOW_REDRAW),
n.Z_GL => return Egl.create(b, w, cmd.other, result),
else => return n.UHDK_STATUS_UNSUPPORTED,
}
return abi.OK;
}
export fn z_native_cancel(id: u64) void {
const b = instance orelse return;
if (Popup.cancel(id) or Data.cancel(id) or Portal.cancel(id) or Desktop.cancel(id)) return;
var cursor = b.windows;
while (cursor) |w| : (cursor = w.next) {
if (w.request == id) {
completeCreation(w, abi.CANCELLED);
return;
}
}
}
export fn z_native_destroy(kind: u32, ptr: ?*anyopaque) void {
const p = ptr orelse return;
switch (kind) {
n.Z_KIND_WINDOW => {
const w: *Window = @ptrCast(@alignCast(p));
if (w.request != 0) completeCreation(w, abi.CANCELLED) else destroyWindow(w);
},
n.Z_KIND_WATCH => Watch.destroy(@ptrCast(@alignCast(p))),
n.Z_KIND_GL => Egl.destroy(@ptrCast(@alignCast(p))),
else => {},
}
}
export fn z_native_handles(ptr: ?*anyopaque, out: [*c]n.uhdk_native_window) void {
const w: *Window = @ptrCast(@alignCast(ptr.?));
out.* = .{ .struct_size = @sizeOf(n.uhdk_native_window), .backend = n.UHDK_BACKEND_WAYLAND, .primary = instance.?.display, .secondary = w.surface, .value = 0 };
}
export fn z_native_gl(ptr: ?*anyopaque, op: u32, out: ?*anyopaque) u32 {
return Egl.call(ptr, op, out);
}
export fn z_native_gl_proc(ptr: ?*anyopaque, name: n.uhdk_text, out: [*c]n.uhdk_gl_proc) u32 {
return Egl.proc(ptr, name, out);
}
export fn z_native_file_init(file: ?*n.uhdk_file, path: n.uhdk_bytes, uri: n.uhdk_text, grant: ?*anyopaque) u32 {
return @import("Files.zig").init(file, path, uri, grant);
}

6
src/linux/nix/pkgs.nix Normal file
View File

@ -0,0 +1,6 @@
# Maintainer tools and a runtime library search path for NixOS development.
# Normal Zig builds do not import this file.
import (builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/d6df3513510aa548c83868fd22bfddd0a8c0a0d4.tar.gz";
sha256 = "0k16bbpgr4wnqwddzfb4j0vfaz6hwf48m8wy9qiaf8xw73s6r5mq";
}) { }

View File

@ -0,0 +1,55 @@
/* Generated by wayland-scanner 1.24.0 */
/*
* SPDX-FileCopyrightText: 2017 David Edmundson
*
* SPDX-License-Identifier: MIT
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface org_kde_kwin_appmenu_interface;
extern const struct wl_interface wl_surface_interface;
static const struct wl_interface *appmenu_types[] = {
NULL,
NULL,
&org_kde_kwin_appmenu_interface,
&wl_surface_interface,
};
static const struct wl_message org_kde_kwin_appmenu_manager_requests[] = {
{ "create", "no", appmenu_types + 2 },
{ "release", "2", appmenu_types + 0 },
};
WL_PRIVATE const struct wl_interface org_kde_kwin_appmenu_manager_interface = {
"org_kde_kwin_appmenu_manager", 2,
2, org_kde_kwin_appmenu_manager_requests,
0, NULL,
};
static const struct wl_message org_kde_kwin_appmenu_requests[] = {
{ "set_address", "ss", appmenu_types + 0 },
{ "release", "", appmenu_types + 0 },
};
WL_PRIVATE const struct wl_interface org_kde_kwin_appmenu_interface = {
"org_kde_kwin_appmenu", 2,
2, org_kde_kwin_appmenu_requests,
0, NULL,
};

View File

@ -0,0 +1,203 @@
/* Generated by wayland-scanner 1.24.0 */
#ifndef APPMENU_CLIENT_PROTOCOL_H
#define APPMENU_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_appmenu The appmenu protocol
* @section page_ifaces_appmenu Interfaces
* - @subpage page_iface_org_kde_kwin_appmenu_manager - appmenu dbus address interface
* - @subpage page_iface_org_kde_kwin_appmenu - appmenu dbus address interface
* @section page_copyright_appmenu Copyright
* <pre>
*
* SPDX-FileCopyrightText: 2017 David Edmundson
*
* SPDX-License-Identifier: MIT
* </pre>
*/
struct org_kde_kwin_appmenu;
struct org_kde_kwin_appmenu_manager;
struct wl_surface;
#ifndef ORG_KDE_KWIN_APPMENU_MANAGER_INTERFACE
#define ORG_KDE_KWIN_APPMENU_MANAGER_INTERFACE
/**
* @page page_iface_org_kde_kwin_appmenu_manager org_kde_kwin_appmenu_manager
* @section page_iface_org_kde_kwin_appmenu_manager_desc Description
*
* This interface allows a client to link a window (or wl_surface) to an com.canonical.dbusmenu
* interface registered on DBus.
* @section page_iface_org_kde_kwin_appmenu_manager_api API
* See @ref iface_org_kde_kwin_appmenu_manager.
*/
/**
* @defgroup iface_org_kde_kwin_appmenu_manager The org_kde_kwin_appmenu_manager interface
*
* This interface allows a client to link a window (or wl_surface) to an com.canonical.dbusmenu
* interface registered on DBus.
*/
extern const struct wl_interface org_kde_kwin_appmenu_manager_interface;
#endif
#ifndef ORG_KDE_KWIN_APPMENU_INTERFACE
#define ORG_KDE_KWIN_APPMENU_INTERFACE
/**
* @page page_iface_org_kde_kwin_appmenu org_kde_kwin_appmenu
* @section page_iface_org_kde_kwin_appmenu_desc Description
*
* The DBus service name and object path where the appmenu interface is present
* The object should be registered on the session bus before sending this request.
* If not applicable, clients should remove this object.
* @section page_iface_org_kde_kwin_appmenu_api API
* See @ref iface_org_kde_kwin_appmenu.
*/
/**
* @defgroup iface_org_kde_kwin_appmenu The org_kde_kwin_appmenu interface
*
* The DBus service name and object path where the appmenu interface is present
* The object should be registered on the session bus before sending this request.
* If not applicable, clients should remove this object.
*/
extern const struct wl_interface org_kde_kwin_appmenu_interface;
#endif
#define ORG_KDE_KWIN_APPMENU_MANAGER_CREATE 0
#define ORG_KDE_KWIN_APPMENU_MANAGER_RELEASE 1
/**
* @ingroup iface_org_kde_kwin_appmenu_manager
*/
#define ORG_KDE_KWIN_APPMENU_MANAGER_CREATE_SINCE_VERSION 1
/**
* @ingroup iface_org_kde_kwin_appmenu_manager
*/
#define ORG_KDE_KWIN_APPMENU_MANAGER_RELEASE_SINCE_VERSION 2
/** @ingroup iface_org_kde_kwin_appmenu_manager */
static inline void
org_kde_kwin_appmenu_manager_set_user_data(struct org_kde_kwin_appmenu_manager *org_kde_kwin_appmenu_manager, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) org_kde_kwin_appmenu_manager, user_data);
}
/** @ingroup iface_org_kde_kwin_appmenu_manager */
static inline void *
org_kde_kwin_appmenu_manager_get_user_data(struct org_kde_kwin_appmenu_manager *org_kde_kwin_appmenu_manager)
{
return wl_proxy_get_user_data((struct wl_proxy *) org_kde_kwin_appmenu_manager);
}
static inline uint32_t
org_kde_kwin_appmenu_manager_get_version(struct org_kde_kwin_appmenu_manager *org_kde_kwin_appmenu_manager)
{
return wl_proxy_get_version((struct wl_proxy *) org_kde_kwin_appmenu_manager);
}
/** @ingroup iface_org_kde_kwin_appmenu_manager */
static inline void
org_kde_kwin_appmenu_manager_destroy(struct org_kde_kwin_appmenu_manager *org_kde_kwin_appmenu_manager)
{
wl_proxy_destroy((struct wl_proxy *) org_kde_kwin_appmenu_manager);
}
/**
* @ingroup iface_org_kde_kwin_appmenu_manager
*/
static inline struct org_kde_kwin_appmenu *
org_kde_kwin_appmenu_manager_create(struct org_kde_kwin_appmenu_manager *org_kde_kwin_appmenu_manager, struct wl_surface *surface)
{
struct wl_proxy *id;
id = wl_proxy_marshal_flags((struct wl_proxy *) org_kde_kwin_appmenu_manager,
ORG_KDE_KWIN_APPMENU_MANAGER_CREATE, &org_kde_kwin_appmenu_interface, wl_proxy_get_version((struct wl_proxy *) org_kde_kwin_appmenu_manager), 0, NULL, surface);
return (struct org_kde_kwin_appmenu *) id;
}
/**
* @ingroup iface_org_kde_kwin_appmenu_manager
*/
static inline void
org_kde_kwin_appmenu_manager_release(struct org_kde_kwin_appmenu_manager *org_kde_kwin_appmenu_manager)
{
wl_proxy_marshal_flags((struct wl_proxy *) org_kde_kwin_appmenu_manager,
ORG_KDE_KWIN_APPMENU_MANAGER_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) org_kde_kwin_appmenu_manager), WL_MARSHAL_FLAG_DESTROY);
}
#define ORG_KDE_KWIN_APPMENU_SET_ADDRESS 0
#define ORG_KDE_KWIN_APPMENU_RELEASE 1
/**
* @ingroup iface_org_kde_kwin_appmenu
*/
#define ORG_KDE_KWIN_APPMENU_SET_ADDRESS_SINCE_VERSION 1
/**
* @ingroup iface_org_kde_kwin_appmenu
*/
#define ORG_KDE_KWIN_APPMENU_RELEASE_SINCE_VERSION 1
/** @ingroup iface_org_kde_kwin_appmenu */
static inline void
org_kde_kwin_appmenu_set_user_data(struct org_kde_kwin_appmenu *org_kde_kwin_appmenu, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) org_kde_kwin_appmenu, user_data);
}
/** @ingroup iface_org_kde_kwin_appmenu */
static inline void *
org_kde_kwin_appmenu_get_user_data(struct org_kde_kwin_appmenu *org_kde_kwin_appmenu)
{
return wl_proxy_get_user_data((struct wl_proxy *) org_kde_kwin_appmenu);
}
static inline uint32_t
org_kde_kwin_appmenu_get_version(struct org_kde_kwin_appmenu *org_kde_kwin_appmenu)
{
return wl_proxy_get_version((struct wl_proxy *) org_kde_kwin_appmenu);
}
/** @ingroup iface_org_kde_kwin_appmenu */
static inline void
org_kde_kwin_appmenu_destroy(struct org_kde_kwin_appmenu *org_kde_kwin_appmenu)
{
wl_proxy_destroy((struct wl_proxy *) org_kde_kwin_appmenu);
}
/**
* @ingroup iface_org_kde_kwin_appmenu
*
* Set or update the service name and object path.
* Strings should be formatted in Latin-1 matching the relevant DBus specifications.
*/
static inline void
org_kde_kwin_appmenu_set_address(struct org_kde_kwin_appmenu *org_kde_kwin_appmenu, const char *service_name, const char *object_path)
{
wl_proxy_marshal_flags((struct wl_proxy *) org_kde_kwin_appmenu,
ORG_KDE_KWIN_APPMENU_SET_ADDRESS, NULL, wl_proxy_get_version((struct wl_proxy *) org_kde_kwin_appmenu), 0, service_name, object_path);
}
/**
* @ingroup iface_org_kde_kwin_appmenu
*/
static inline void
org_kde_kwin_appmenu_release(struct org_kde_kwin_appmenu *org_kde_kwin_appmenu)
{
wl_proxy_marshal_flags((struct wl_proxy *) org_kde_kwin_appmenu,
ORG_KDE_KWIN_APPMENU_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) org_kde_kwin_appmenu), WL_MARSHAL_FLAG_DESTROY);
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,75 @@
/* Generated by wayland-scanner 1.24.0 */
/*
* Copyright 2018 The Chromium Authors
* Copyright 2023 Simon Ser
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_pointer_interface;
extern const struct wl_interface wp_cursor_shape_device_v1_interface;
extern const struct wl_interface zwp_tablet_tool_v2_interface;
static const struct wl_interface *cursor_shape_v1_types[] = {
NULL,
NULL,
&wp_cursor_shape_device_v1_interface,
&wl_pointer_interface,
&wp_cursor_shape_device_v1_interface,
&zwp_tablet_tool_v2_interface,
};
static const struct wl_message wp_cursor_shape_manager_v1_requests[] = {
{ "destroy", "", cursor_shape_v1_types + 0 },
{ "get_pointer", "no", cursor_shape_v1_types + 2 },
{ "get_tablet_tool_v2", "no", cursor_shape_v1_types + 4 },
};
WL_PRIVATE const struct wl_interface wp_cursor_shape_manager_v1_interface = {
"wp_cursor_shape_manager_v1", 2,
3, wp_cursor_shape_manager_v1_requests,
0, NULL,
};
static const struct wl_message wp_cursor_shape_device_v1_requests[] = {
{ "destroy", "", cursor_shape_v1_types + 0 },
{ "set_shape", "uu", cursor_shape_v1_types + 0 },
};
WL_PRIVATE const struct wl_interface wp_cursor_shape_device_v1_interface = {
"wp_cursor_shape_device_v1", 2,
2, wp_cursor_shape_device_v1_requests,
0, NULL,
};

View File

@ -0,0 +1,452 @@
/* Generated by wayland-scanner 1.24.0 */
#ifndef CURSOR_SHAPE_V1_CLIENT_PROTOCOL_H
#define CURSOR_SHAPE_V1_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_cursor_shape_v1 The cursor_shape_v1 protocol
* @section page_ifaces_cursor_shape_v1 Interfaces
* - @subpage page_iface_wp_cursor_shape_manager_v1 - cursor shape manager
* - @subpage page_iface_wp_cursor_shape_device_v1 - cursor shape for a device
* @section page_copyright_cursor_shape_v1 Copyright
* <pre>
*
* Copyright 2018 The Chromium Authors
* Copyright 2023 Simon Ser
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* </pre>
*/
struct wl_pointer;
struct wp_cursor_shape_device_v1;
struct wp_cursor_shape_manager_v1;
struct zwp_tablet_tool_v2;
#ifndef WP_CURSOR_SHAPE_MANAGER_V1_INTERFACE
#define WP_CURSOR_SHAPE_MANAGER_V1_INTERFACE
/**
* @page page_iface_wp_cursor_shape_manager_v1 wp_cursor_shape_manager_v1
* @section page_iface_wp_cursor_shape_manager_v1_desc Description
*
* This global offers an alternative, optional way to set cursor images. This
* new way uses enumerated cursors instead of a wl_surface like
* wl_pointer.set_cursor does.
*
* Warning! The protocol described in this file is currently in the testing
* phase. Backward compatible changes may be added together with the
* corresponding interface version bump. Backward incompatible changes can
* only be done by creating a new major version of the extension.
* @section page_iface_wp_cursor_shape_manager_v1_api API
* See @ref iface_wp_cursor_shape_manager_v1.
*/
/**
* @defgroup iface_wp_cursor_shape_manager_v1 The wp_cursor_shape_manager_v1 interface
*
* This global offers an alternative, optional way to set cursor images. This
* new way uses enumerated cursors instead of a wl_surface like
* wl_pointer.set_cursor does.
*
* Warning! The protocol described in this file is currently in the testing
* phase. Backward compatible changes may be added together with the
* corresponding interface version bump. Backward incompatible changes can
* only be done by creating a new major version of the extension.
*/
extern const struct wl_interface wp_cursor_shape_manager_v1_interface;
#endif
#ifndef WP_CURSOR_SHAPE_DEVICE_V1_INTERFACE
#define WP_CURSOR_SHAPE_DEVICE_V1_INTERFACE
/**
* @page page_iface_wp_cursor_shape_device_v1 wp_cursor_shape_device_v1
* @section page_iface_wp_cursor_shape_device_v1_desc Description
*
* This interface allows clients to set the cursor shape.
* @section page_iface_wp_cursor_shape_device_v1_api API
* See @ref iface_wp_cursor_shape_device_v1.
*/
/**
* @defgroup iface_wp_cursor_shape_device_v1 The wp_cursor_shape_device_v1 interface
*
* This interface allows clients to set the cursor shape.
*/
extern const struct wl_interface wp_cursor_shape_device_v1_interface;
#endif
#define WP_CURSOR_SHAPE_MANAGER_V1_DESTROY 0
#define WP_CURSOR_SHAPE_MANAGER_V1_GET_POINTER 1
#define WP_CURSOR_SHAPE_MANAGER_V1_GET_TABLET_TOOL_V2 2
/**
* @ingroup iface_wp_cursor_shape_manager_v1
*/
#define WP_CURSOR_SHAPE_MANAGER_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_wp_cursor_shape_manager_v1
*/
#define WP_CURSOR_SHAPE_MANAGER_V1_GET_POINTER_SINCE_VERSION 1
/**
* @ingroup iface_wp_cursor_shape_manager_v1
*/
#define WP_CURSOR_SHAPE_MANAGER_V1_GET_TABLET_TOOL_V2_SINCE_VERSION 1
/** @ingroup iface_wp_cursor_shape_manager_v1 */
static inline void
wp_cursor_shape_manager_v1_set_user_data(struct wp_cursor_shape_manager_v1 *wp_cursor_shape_manager_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) wp_cursor_shape_manager_v1, user_data);
}
/** @ingroup iface_wp_cursor_shape_manager_v1 */
static inline void *
wp_cursor_shape_manager_v1_get_user_data(struct wp_cursor_shape_manager_v1 *wp_cursor_shape_manager_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) wp_cursor_shape_manager_v1);
}
static inline uint32_t
wp_cursor_shape_manager_v1_get_version(struct wp_cursor_shape_manager_v1 *wp_cursor_shape_manager_v1)
{
return wl_proxy_get_version((struct wl_proxy *) wp_cursor_shape_manager_v1);
}
/**
* @ingroup iface_wp_cursor_shape_manager_v1
*
* Destroy the cursor shape manager.
*/
static inline void
wp_cursor_shape_manager_v1_destroy(struct wp_cursor_shape_manager_v1 *wp_cursor_shape_manager_v1)
{
wl_proxy_marshal_flags((struct wl_proxy *) wp_cursor_shape_manager_v1,
WP_CURSOR_SHAPE_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_cursor_shape_manager_v1), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_wp_cursor_shape_manager_v1
*
* Obtain a wp_cursor_shape_device_v1 for a wl_pointer object.
*
* When the pointer capability is removed from the wl_seat, the
* wp_cursor_shape_device_v1 object becomes inert.
*/
static inline struct wp_cursor_shape_device_v1 *
wp_cursor_shape_manager_v1_get_pointer(struct wp_cursor_shape_manager_v1 *wp_cursor_shape_manager_v1, struct wl_pointer *pointer)
{
struct wl_proxy *cursor_shape_device;
cursor_shape_device = wl_proxy_marshal_flags((struct wl_proxy *) wp_cursor_shape_manager_v1,
WP_CURSOR_SHAPE_MANAGER_V1_GET_POINTER, &wp_cursor_shape_device_v1_interface, wl_proxy_get_version((struct wl_proxy *) wp_cursor_shape_manager_v1), 0, NULL, pointer);
return (struct wp_cursor_shape_device_v1 *) cursor_shape_device;
}
/**
* @ingroup iface_wp_cursor_shape_manager_v1
*
* Obtain a wp_cursor_shape_device_v1 for a zwp_tablet_tool_v2 object.
*
* When the zwp_tablet_tool_v2 is removed, the wp_cursor_shape_device_v1
* object becomes inert.
*/
static inline struct wp_cursor_shape_device_v1 *
wp_cursor_shape_manager_v1_get_tablet_tool_v2(struct wp_cursor_shape_manager_v1 *wp_cursor_shape_manager_v1, struct zwp_tablet_tool_v2 *tablet_tool)
{
struct wl_proxy *cursor_shape_device;
cursor_shape_device = wl_proxy_marshal_flags((struct wl_proxy *) wp_cursor_shape_manager_v1,
WP_CURSOR_SHAPE_MANAGER_V1_GET_TABLET_TOOL_V2, &wp_cursor_shape_device_v1_interface, wl_proxy_get_version((struct wl_proxy *) wp_cursor_shape_manager_v1), 0, NULL, tablet_tool);
return (struct wp_cursor_shape_device_v1 *) cursor_shape_device;
}
#ifndef WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ENUM
#define WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ENUM
/**
* @ingroup iface_wp_cursor_shape_device_v1
* cursor shapes
*
* This enum describes cursor shapes.
*
* The names are taken from the CSS W3C specification:
* https://w3c.github.io/csswg-drafts/css-ui/#cursor
* with a few additions.
*
* Note that there are some groups of cursor shapes that are related:
* The first group is drag-and-drop cursors which are used to indicate
* the selected action during dnd operations. The second group is resize
* cursors which are used to indicate resizing and moving possibilities
* on window borders. It is recommended that the shapes in these groups
* should use visually compatible images and metaphors.
*/
enum wp_cursor_shape_device_v1_shape {
/**
* default cursor
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT = 1,
/**
* a context menu is available for the object under the cursor
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CONTEXT_MENU = 2,
/**
* help is available for the object under the cursor
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP = 3,
/**
* pointer that indicates a link or another interactive element
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER = 4,
/**
* progress indicator
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS = 5,
/**
* program is busy, user should wait
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT = 6,
/**
* a cell or set of cells may be selected
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CELL = 7,
/**
* simple crosshair
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR = 8,
/**
* text may be selected
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT = 9,
/**
* vertical text may be selected
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_VERTICAL_TEXT = 10,
/**
* drag-and-drop: alias of/shortcut to something is to be created
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALIAS = 11,
/**
* drag-and-drop: something is to be copied
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY = 12,
/**
* drag-and-drop: something is to be moved
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE = 13,
/**
* drag-and-drop: the dragged item cannot be dropped at the current cursor location
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP = 14,
/**
* drag-and-drop: the requested action will not be carried out
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED = 15,
/**
* drag-and-drop: something can be grabbed
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB = 16,
/**
* drag-and-drop: something is being grabbed
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING = 17,
/**
* resizing: the east border is to be moved
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE = 18,
/**
* resizing: the north border is to be moved
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE = 19,
/**
* resizing: the north-east corner is to be moved
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE = 20,
/**
* resizing: the north-west corner is to be moved
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE = 21,
/**
* resizing: the south border is to be moved
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE = 22,
/**
* resizing: the south-east corner is to be moved
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE = 23,
/**
* resizing: the south-west corner is to be moved
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE = 24,
/**
* resizing: the west border is to be moved
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE = 25,
/**
* resizing: the east and west borders are to be moved
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE = 26,
/**
* resizing: the north and south borders are to be moved
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE = 27,
/**
* resizing: the north-east and south-west corners are to be moved
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE = 28,
/**
* resizing: the north-west and south-east corners are to be moved
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE = 29,
/**
* resizing: that the item/column can be resized horizontally
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COL_RESIZE = 30,
/**
* resizing: that the item/row can be resized vertically
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ROW_RESIZE = 31,
/**
* something can be scrolled in any direction
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL = 32,
/**
* something can be zoomed in
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN = 33,
/**
* something can be zoomed out
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT = 34,
/**
* drag-and-drop: the user will select which action will be carried out (non-css value)
* @since 2
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK = 35,
/**
* resizing: something can be moved or resized in any direction (non-css value)
* @since 2
*/
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE = 36,
};
/**
* @ingroup iface_wp_cursor_shape_device_v1
*/
#define WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION 2
/**
* @ingroup iface_wp_cursor_shape_device_v1
*/
#define WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE_SINCE_VERSION 2
#endif /* WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ENUM */
#ifndef WP_CURSOR_SHAPE_DEVICE_V1_ERROR_ENUM
#define WP_CURSOR_SHAPE_DEVICE_V1_ERROR_ENUM
enum wp_cursor_shape_device_v1_error {
/**
* the specified shape value is invalid
*/
WP_CURSOR_SHAPE_DEVICE_V1_ERROR_INVALID_SHAPE = 1,
};
#endif /* WP_CURSOR_SHAPE_DEVICE_V1_ERROR_ENUM */
#define WP_CURSOR_SHAPE_DEVICE_V1_DESTROY 0
#define WP_CURSOR_SHAPE_DEVICE_V1_SET_SHAPE 1
/**
* @ingroup iface_wp_cursor_shape_device_v1
*/
#define WP_CURSOR_SHAPE_DEVICE_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_wp_cursor_shape_device_v1
*/
#define WP_CURSOR_SHAPE_DEVICE_V1_SET_SHAPE_SINCE_VERSION 1
/** @ingroup iface_wp_cursor_shape_device_v1 */
static inline void
wp_cursor_shape_device_v1_set_user_data(struct wp_cursor_shape_device_v1 *wp_cursor_shape_device_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) wp_cursor_shape_device_v1, user_data);
}
/** @ingroup iface_wp_cursor_shape_device_v1 */
static inline void *
wp_cursor_shape_device_v1_get_user_data(struct wp_cursor_shape_device_v1 *wp_cursor_shape_device_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) wp_cursor_shape_device_v1);
}
static inline uint32_t
wp_cursor_shape_device_v1_get_version(struct wp_cursor_shape_device_v1 *wp_cursor_shape_device_v1)
{
return wl_proxy_get_version((struct wl_proxy *) wp_cursor_shape_device_v1);
}
/**
* @ingroup iface_wp_cursor_shape_device_v1
*
* Destroy the cursor shape device.
*
* The device cursor shape remains unchanged.
*/
static inline void
wp_cursor_shape_device_v1_destroy(struct wp_cursor_shape_device_v1 *wp_cursor_shape_device_v1)
{
wl_proxy_marshal_flags((struct wl_proxy *) wp_cursor_shape_device_v1,
WP_CURSOR_SHAPE_DEVICE_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_cursor_shape_device_v1), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_wp_cursor_shape_device_v1
*
* Sets the device cursor to the specified shape. The compositor will
* change the cursor image based on the specified shape.
*
* The cursor actually changes only if the input device focus is one of
* the requesting client's surfaces. If any, the previous cursor image
* (surface or shape) is replaced.
*
* The "shape" argument must be a valid enum entry, otherwise the
* invalid_shape protocol error is raised.
*
* This is similar to the wl_pointer.set_cursor and
* zwp_tablet_tool_v2.set_cursor requests, but this request accepts a
* shape instead of contents in the form of a surface. Clients can mix
* set_cursor and set_shape requests.
*
* The serial parameter must match the latest wl_pointer.enter or
* zwp_tablet_tool_v2.proximity_in serial number sent to the client.
* Otherwise the request will be ignored.
*/
static inline void
wp_cursor_shape_device_v1_set_shape(struct wp_cursor_shape_device_v1 *wp_cursor_shape_device_v1, uint32_t serial, uint32_t shape)
{
wl_proxy_marshal_flags((struct wl_proxy *) wp_cursor_shape_device_v1,
WP_CURSOR_SHAPE_DEVICE_V1_SET_SHAPE, NULL, wl_proxy_get_version((struct wl_proxy *) wp_cursor_shape_device_v1), 0, serial, shape);
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,65 @@
/* -*- mode: C; c-file-style: "gnu" -*- */
/* dbus-arch-deps.h Header with architecture/compiler specific information, installed to libdir
*
* Copyright (C) 2003 Red Hat, Inc.
* SPDX-License-Identifier: AFL-2.0 OR GPL-2.0-or-later
*
* Licensed under the Academic Free License version 2.0
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION)
#error "Only <dbus/dbus.h> can be included directly, this file may disappear or change contents."
#endif
#ifndef DBUS_ARCH_DEPS_H
#define DBUS_ARCH_DEPS_H
#include <dbus/dbus-macros.h>
DBUS_BEGIN_DECLS
/* D-Bus no longer supports platforms with no 64-bit integer type. */
#define DBUS_HAVE_INT64 1
_DBUS_GNUC_EXTENSION typedef long long dbus_int64_t;
_DBUS_GNUC_EXTENSION typedef unsigned long long dbus_uint64_t;
#define DBUS_INT64_MODIFIER "ll"
#define DBUS_INT64_CONSTANT(val) (_DBUS_GNUC_EXTENSION val##LL)
#define DBUS_UINT64_CONSTANT(val) (_DBUS_GNUC_EXTENSION val##ULL)
typedef int dbus_int32_t;
typedef unsigned int dbus_uint32_t;
typedef short dbus_int16_t;
typedef unsigned short dbus_uint16_t;
#define DBUS_SIZEOF_VOID_P __SIZEOF_POINTER__
/* This is not really arch-dependent, but it's not worth
* creating an additional generated header just for this
*/
#define DBUS_MAJOR_VERSION 1
#define DBUS_MINOR_VERSION 16
#define DBUS_MICRO_VERSION 2
#define DBUS_VERSION_STRING "1.16.2"
#define DBUS_VERSION ((1 << 16) | (16 << 8) | (2))
DBUS_END_DECLS
#endif /* DBUS_ARCH_DEPS_H */

View File

@ -0,0 +1,74 @@
/* Generated by wayland-scanner 1.24.0 */
/*
* Copyright © 2022 Kenny Levinsen
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface wp_fractional_scale_v1_interface;
static const struct wl_interface *fractional_scale_v1_types[] = {
NULL,
&wp_fractional_scale_v1_interface,
&wl_surface_interface,
};
static const struct wl_message wp_fractional_scale_manager_v1_requests[] = {
{ "destroy", "", fractional_scale_v1_types + 0 },
{ "get_fractional_scale", "no", fractional_scale_v1_types + 1 },
};
WL_PRIVATE const struct wl_interface wp_fractional_scale_manager_v1_interface = {
"wp_fractional_scale_manager_v1", 1,
2, wp_fractional_scale_manager_v1_requests,
0, NULL,
};
static const struct wl_message wp_fractional_scale_v1_requests[] = {
{ "destroy", "", fractional_scale_v1_types + 0 },
};
static const struct wl_message wp_fractional_scale_v1_events[] = {
{ "preferred_scale", "u", fractional_scale_v1_types + 0 },
};
WL_PRIVATE const struct wl_interface wp_fractional_scale_v1_interface = {
"wp_fractional_scale_v1", 1,
1, wp_fractional_scale_v1_requests,
1, wp_fractional_scale_v1_events,
};

View File

@ -0,0 +1,264 @@
/* Generated by wayland-scanner 1.24.0 */
#ifndef FRACTIONAL_SCALE_V1_CLIENT_PROTOCOL_H
#define FRACTIONAL_SCALE_V1_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_fractional_scale_v1 The fractional_scale_v1 protocol
* Protocol for requesting fractional surface scales
*
* @section page_desc_fractional_scale_v1 Description
*
* This protocol allows a compositor to suggest for surfaces to render at
* fractional scales.
*
* A client can submit scaled content by utilizing wp_viewport. This is done by
* creating a wp_viewport object for the surface and setting the destination
* rectangle to the surface size before the scale factor is applied.
*
* The buffer size is calculated by multiplying the surface size by the
* intended scale.
*
* The wl_surface buffer scale should remain set to 1.
*
* If a surface has a surface-local size of 100 px by 50 px and wishes to
* submit buffers with a scale of 1.5, then a buffer of 150px by 75 px should
* be used and the wp_viewport destination rectangle should be 100 px by 50 px.
*
* For toplevel surfaces, the size is rounded halfway away from zero. The
* rounding algorithm for subsurface position and size is not defined.
*
* @section page_ifaces_fractional_scale_v1 Interfaces
* - @subpage page_iface_wp_fractional_scale_manager_v1 - fractional surface scale information
* - @subpage page_iface_wp_fractional_scale_v1 - fractional scale interface to a wl_surface
* @section page_copyright_fractional_scale_v1 Copyright
* <pre>
*
* Copyright © 2022 Kenny Levinsen
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* </pre>
*/
struct wl_surface;
struct wp_fractional_scale_manager_v1;
struct wp_fractional_scale_v1;
#ifndef WP_FRACTIONAL_SCALE_MANAGER_V1_INTERFACE
#define WP_FRACTIONAL_SCALE_MANAGER_V1_INTERFACE
/**
* @page page_iface_wp_fractional_scale_manager_v1 wp_fractional_scale_manager_v1
* @section page_iface_wp_fractional_scale_manager_v1_desc Description
*
* A global interface for requesting surfaces to use fractional scales.
* @section page_iface_wp_fractional_scale_manager_v1_api API
* See @ref iface_wp_fractional_scale_manager_v1.
*/
/**
* @defgroup iface_wp_fractional_scale_manager_v1 The wp_fractional_scale_manager_v1 interface
*
* A global interface for requesting surfaces to use fractional scales.
*/
extern const struct wl_interface wp_fractional_scale_manager_v1_interface;
#endif
#ifndef WP_FRACTIONAL_SCALE_V1_INTERFACE
#define WP_FRACTIONAL_SCALE_V1_INTERFACE
/**
* @page page_iface_wp_fractional_scale_v1 wp_fractional_scale_v1
* @section page_iface_wp_fractional_scale_v1_desc Description
*
* An additional interface to a wl_surface object which allows the compositor
* to inform the client of the preferred scale.
* @section page_iface_wp_fractional_scale_v1_api API
* See @ref iface_wp_fractional_scale_v1.
*/
/**
* @defgroup iface_wp_fractional_scale_v1 The wp_fractional_scale_v1 interface
*
* An additional interface to a wl_surface object which allows the compositor
* to inform the client of the preferred scale.
*/
extern const struct wl_interface wp_fractional_scale_v1_interface;
#endif
#ifndef WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_ENUM
#define WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_ENUM
enum wp_fractional_scale_manager_v1_error {
/**
* the surface already has a fractional_scale object associated
*/
WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_FRACTIONAL_SCALE_EXISTS = 0,
};
#endif /* WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_ENUM */
#define WP_FRACTIONAL_SCALE_MANAGER_V1_DESTROY 0
#define WP_FRACTIONAL_SCALE_MANAGER_V1_GET_FRACTIONAL_SCALE 1
/**
* @ingroup iface_wp_fractional_scale_manager_v1
*/
#define WP_FRACTIONAL_SCALE_MANAGER_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_wp_fractional_scale_manager_v1
*/
#define WP_FRACTIONAL_SCALE_MANAGER_V1_GET_FRACTIONAL_SCALE_SINCE_VERSION 1
/** @ingroup iface_wp_fractional_scale_manager_v1 */
static inline void
wp_fractional_scale_manager_v1_set_user_data(struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) wp_fractional_scale_manager_v1, user_data);
}
/** @ingroup iface_wp_fractional_scale_manager_v1 */
static inline void *
wp_fractional_scale_manager_v1_get_user_data(struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) wp_fractional_scale_manager_v1);
}
static inline uint32_t
wp_fractional_scale_manager_v1_get_version(struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1)
{
return wl_proxy_get_version((struct wl_proxy *) wp_fractional_scale_manager_v1);
}
/**
* @ingroup iface_wp_fractional_scale_manager_v1
*
* Informs the server that the client will not be using this protocol
* object anymore. This does not affect any other objects,
* wp_fractional_scale_v1 objects included.
*/
static inline void
wp_fractional_scale_manager_v1_destroy(struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1)
{
wl_proxy_marshal_flags((struct wl_proxy *) wp_fractional_scale_manager_v1,
WP_FRACTIONAL_SCALE_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_fractional_scale_manager_v1), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_wp_fractional_scale_manager_v1
*
* Create an add-on object for the the wl_surface to let the compositor
* request fractional scales. If the given wl_surface already has a
* wp_fractional_scale_v1 object associated, the fractional_scale_exists
* protocol error is raised.
*/
static inline struct wp_fractional_scale_v1 *
wp_fractional_scale_manager_v1_get_fractional_scale(struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1, struct wl_surface *surface)
{
struct wl_proxy *id;
id = wl_proxy_marshal_flags((struct wl_proxy *) wp_fractional_scale_manager_v1,
WP_FRACTIONAL_SCALE_MANAGER_V1_GET_FRACTIONAL_SCALE, &wp_fractional_scale_v1_interface, wl_proxy_get_version((struct wl_proxy *) wp_fractional_scale_manager_v1), 0, NULL, surface);
return (struct wp_fractional_scale_v1 *) id;
}
/**
* @ingroup iface_wp_fractional_scale_v1
* @struct wp_fractional_scale_v1_listener
*/
struct wp_fractional_scale_v1_listener {
/**
* notify of new preferred scale
*
* Notification of a new preferred scale for this surface that
* the compositor suggests that the client should use.
*
* The sent scale is the numerator of a fraction with a denominator
* of 120.
* @param scale the new preferred scale
*/
void (*preferred_scale)(void *data,
struct wp_fractional_scale_v1 *wp_fractional_scale_v1,
uint32_t scale);
};
/**
* @ingroup iface_wp_fractional_scale_v1
*/
static inline int
wp_fractional_scale_v1_add_listener(struct wp_fractional_scale_v1 *wp_fractional_scale_v1,
const struct wp_fractional_scale_v1_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) wp_fractional_scale_v1,
(void (**)(void)) listener, data);
}
#define WP_FRACTIONAL_SCALE_V1_DESTROY 0
/**
* @ingroup iface_wp_fractional_scale_v1
*/
#define WP_FRACTIONAL_SCALE_V1_PREFERRED_SCALE_SINCE_VERSION 1
/**
* @ingroup iface_wp_fractional_scale_v1
*/
#define WP_FRACTIONAL_SCALE_V1_DESTROY_SINCE_VERSION 1
/** @ingroup iface_wp_fractional_scale_v1 */
static inline void
wp_fractional_scale_v1_set_user_data(struct wp_fractional_scale_v1 *wp_fractional_scale_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) wp_fractional_scale_v1, user_data);
}
/** @ingroup iface_wp_fractional_scale_v1 */
static inline void *
wp_fractional_scale_v1_get_user_data(struct wp_fractional_scale_v1 *wp_fractional_scale_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) wp_fractional_scale_v1);
}
static inline uint32_t
wp_fractional_scale_v1_get_version(struct wp_fractional_scale_v1 *wp_fractional_scale_v1)
{
return wl_proxy_get_version((struct wl_proxy *) wp_fractional_scale_v1);
}
/**
* @ingroup iface_wp_fractional_scale_v1
*
* Destroy the fractional scale object. When this object is destroyed,
* preferred_scale events will no longer be sent.
*/
static inline void
wp_fractional_scale_v1_destroy(struct wp_fractional_scale_v1 *wp_fractional_scale_v1)
{
wl_proxy_marshal_flags((struct wl_proxy *) wp_fractional_scale_v1,
WP_FRACTIONAL_SCALE_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_fractional_scale_v1), WL_MARSHAL_FLAG_DESTROY);
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,58 @@
/* Generated by tools/protocols.py; private protocol descriptors. */
#define org_kde_kwin_appmenu_manager_interface z_uhdk_org_kde_kwin_appmenu_manager_interface
#define org_kde_kwin_appmenu_interface z_uhdk_org_kde_kwin_appmenu_interface
#define wp_cursor_shape_manager_v1_interface z_uhdk_wp_cursor_shape_manager_v1_interface
#define wp_cursor_shape_device_v1_interface z_uhdk_wp_cursor_shape_device_v1_interface
#define wp_fractional_scale_manager_v1_interface z_uhdk_wp_fractional_scale_manager_v1_interface
#define wp_fractional_scale_v1_interface z_uhdk_wp_fractional_scale_v1_interface
#define zwp_tablet_manager_v2_interface z_uhdk_zwp_tablet_manager_v2_interface
#define zwp_tablet_seat_v2_interface z_uhdk_zwp_tablet_seat_v2_interface
#define zwp_tablet_tool_v2_interface z_uhdk_zwp_tablet_tool_v2_interface
#define zwp_tablet_v2_interface z_uhdk_zwp_tablet_v2_interface
#define zwp_tablet_pad_ring_v2_interface z_uhdk_zwp_tablet_pad_ring_v2_interface
#define zwp_tablet_pad_strip_v2_interface z_uhdk_zwp_tablet_pad_strip_v2_interface
#define zwp_tablet_pad_group_v2_interface z_uhdk_zwp_tablet_pad_group_v2_interface
#define zwp_tablet_pad_v2_interface z_uhdk_zwp_tablet_pad_v2_interface
#define zwp_tablet_pad_dial_v2_interface z_uhdk_zwp_tablet_pad_dial_v2_interface
#define zwp_text_input_v3_interface z_uhdk_zwp_text_input_v3_interface
#define zwp_text_input_manager_v3_interface z_uhdk_zwp_text_input_manager_v3_interface
#define wp_viewporter_interface z_uhdk_wp_viewporter_interface
#define wp_viewport_interface z_uhdk_wp_viewport_interface
#define wl_display_interface z_uhdk_wl_display_interface
#define wl_registry_interface z_uhdk_wl_registry_interface
#define wl_callback_interface z_uhdk_wl_callback_interface
#define wl_compositor_interface z_uhdk_wl_compositor_interface
#define wl_shm_pool_interface z_uhdk_wl_shm_pool_interface
#define wl_shm_interface z_uhdk_wl_shm_interface
#define wl_buffer_interface z_uhdk_wl_buffer_interface
#define wl_data_offer_interface z_uhdk_wl_data_offer_interface
#define wl_data_source_interface z_uhdk_wl_data_source_interface
#define wl_data_device_interface z_uhdk_wl_data_device_interface
#define wl_data_device_manager_interface z_uhdk_wl_data_device_manager_interface
#define wl_shell_interface z_uhdk_wl_shell_interface
#define wl_shell_surface_interface z_uhdk_wl_shell_surface_interface
#define wl_surface_interface z_uhdk_wl_surface_interface
#define wl_seat_interface z_uhdk_wl_seat_interface
#define wl_pointer_interface z_uhdk_wl_pointer_interface
#define wl_keyboard_interface z_uhdk_wl_keyboard_interface
#define wl_touch_interface z_uhdk_wl_touch_interface
#define wl_output_interface z_uhdk_wl_output_interface
#define wl_region_interface z_uhdk_wl_region_interface
#define wl_subcompositor_interface z_uhdk_wl_subcompositor_interface
#define wl_subsurface_interface z_uhdk_wl_subsurface_interface
#define wl_fixes_interface z_uhdk_wl_fixes_interface
#define xdg_activation_v1_interface z_uhdk_xdg_activation_v1_interface
#define xdg_activation_token_v1_interface z_uhdk_xdg_activation_token_v1_interface
#define zxdg_decoration_manager_v1_interface z_uhdk_zxdg_decoration_manager_v1_interface
#define zxdg_toplevel_decoration_v1_interface z_uhdk_zxdg_toplevel_decoration_v1_interface
#define zxdg_exporter_v2_interface z_uhdk_zxdg_exporter_v2_interface
#define zxdg_importer_v2_interface z_uhdk_zxdg_importer_v2_interface
#define zxdg_exported_v2_interface z_uhdk_zxdg_exported_v2_interface
#define zxdg_imported_v2_interface z_uhdk_zxdg_imported_v2_interface
#define xdg_wm_base_interface z_uhdk_xdg_wm_base_interface
#define xdg_positioner_interface z_uhdk_xdg_positioner_interface
#define xdg_surface_interface z_uhdk_xdg_surface_interface
#define xdg_toplevel_interface z_uhdk_xdg_toplevel_interface
#define xdg_popup_interface z_uhdk_xdg_popup_interface
#define xdg_toplevel_icon_manager_v1_interface z_uhdk_xdg_toplevel_icon_manager_v1_interface
#define xdg_toplevel_icon_v1_interface z_uhdk_xdg_toplevel_icon_v1_interface

View File

@ -0,0 +1,254 @@
/* Generated by wayland-scanner 1.24.0 */
/*
* Copyright 2014 © Stephen "Lyude" Chandler Paul
* Copyright 2015-2024 © Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_seat_interface;
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface zwp_tablet_pad_dial_v2_interface;
extern const struct wl_interface zwp_tablet_pad_group_v2_interface;
extern const struct wl_interface zwp_tablet_pad_ring_v2_interface;
extern const struct wl_interface zwp_tablet_pad_strip_v2_interface;
extern const struct wl_interface zwp_tablet_pad_v2_interface;
extern const struct wl_interface zwp_tablet_seat_v2_interface;
extern const struct wl_interface zwp_tablet_tool_v2_interface;
extern const struct wl_interface zwp_tablet_v2_interface;
static const struct wl_interface *tablet_v2_types[] = {
NULL,
NULL,
NULL,
&zwp_tablet_seat_v2_interface,
&wl_seat_interface,
&zwp_tablet_v2_interface,
&zwp_tablet_tool_v2_interface,
&zwp_tablet_pad_v2_interface,
NULL,
&wl_surface_interface,
NULL,
NULL,
NULL,
&zwp_tablet_v2_interface,
&wl_surface_interface,
&zwp_tablet_pad_ring_v2_interface,
&zwp_tablet_pad_strip_v2_interface,
&zwp_tablet_pad_dial_v2_interface,
&zwp_tablet_pad_group_v2_interface,
NULL,
&zwp_tablet_v2_interface,
&wl_surface_interface,
NULL,
&wl_surface_interface,
};
static const struct wl_message zwp_tablet_manager_v2_requests[] = {
{ "get_tablet_seat", "no", tablet_v2_types + 3 },
{ "destroy", "", tablet_v2_types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_tablet_manager_v2_interface = {
"zwp_tablet_manager_v2", 2,
2, zwp_tablet_manager_v2_requests,
0, NULL,
};
static const struct wl_message zwp_tablet_seat_v2_requests[] = {
{ "destroy", "", tablet_v2_types + 0 },
};
static const struct wl_message zwp_tablet_seat_v2_events[] = {
{ "tablet_added", "n", tablet_v2_types + 5 },
{ "tool_added", "n", tablet_v2_types + 6 },
{ "pad_added", "n", tablet_v2_types + 7 },
};
WL_PRIVATE const struct wl_interface zwp_tablet_seat_v2_interface = {
"zwp_tablet_seat_v2", 2,
1, zwp_tablet_seat_v2_requests,
3, zwp_tablet_seat_v2_events,
};
static const struct wl_message zwp_tablet_tool_v2_requests[] = {
{ "set_cursor", "u?oii", tablet_v2_types + 8 },
{ "destroy", "", tablet_v2_types + 0 },
};
static const struct wl_message zwp_tablet_tool_v2_events[] = {
{ "type", "u", tablet_v2_types + 0 },
{ "hardware_serial", "uu", tablet_v2_types + 0 },
{ "hardware_id_wacom", "uu", tablet_v2_types + 0 },
{ "capability", "u", tablet_v2_types + 0 },
{ "done", "", tablet_v2_types + 0 },
{ "removed", "", tablet_v2_types + 0 },
{ "proximity_in", "uoo", tablet_v2_types + 12 },
{ "proximity_out", "", tablet_v2_types + 0 },
{ "down", "u", tablet_v2_types + 0 },
{ "up", "", tablet_v2_types + 0 },
{ "motion", "ff", tablet_v2_types + 0 },
{ "pressure", "u", tablet_v2_types + 0 },
{ "distance", "u", tablet_v2_types + 0 },
{ "tilt", "ff", tablet_v2_types + 0 },
{ "rotation", "f", tablet_v2_types + 0 },
{ "slider", "i", tablet_v2_types + 0 },
{ "wheel", "fi", tablet_v2_types + 0 },
{ "button", "uuu", tablet_v2_types + 0 },
{ "frame", "u", tablet_v2_types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_tablet_tool_v2_interface = {
"zwp_tablet_tool_v2", 2,
2, zwp_tablet_tool_v2_requests,
19, zwp_tablet_tool_v2_events,
};
static const struct wl_message zwp_tablet_v2_requests[] = {
{ "destroy", "", tablet_v2_types + 0 },
};
static const struct wl_message zwp_tablet_v2_events[] = {
{ "name", "s", tablet_v2_types + 0 },
{ "id", "uu", tablet_v2_types + 0 },
{ "path", "s", tablet_v2_types + 0 },
{ "done", "", tablet_v2_types + 0 },
{ "removed", "", tablet_v2_types + 0 },
{ "bustype", "2u", tablet_v2_types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_tablet_v2_interface = {
"zwp_tablet_v2", 2,
1, zwp_tablet_v2_requests,
6, zwp_tablet_v2_events,
};
static const struct wl_message zwp_tablet_pad_ring_v2_requests[] = {
{ "set_feedback", "su", tablet_v2_types + 0 },
{ "destroy", "", tablet_v2_types + 0 },
};
static const struct wl_message zwp_tablet_pad_ring_v2_events[] = {
{ "source", "u", tablet_v2_types + 0 },
{ "angle", "f", tablet_v2_types + 0 },
{ "stop", "", tablet_v2_types + 0 },
{ "frame", "u", tablet_v2_types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_tablet_pad_ring_v2_interface = {
"zwp_tablet_pad_ring_v2", 2,
2, zwp_tablet_pad_ring_v2_requests,
4, zwp_tablet_pad_ring_v2_events,
};
static const struct wl_message zwp_tablet_pad_strip_v2_requests[] = {
{ "set_feedback", "su", tablet_v2_types + 0 },
{ "destroy", "", tablet_v2_types + 0 },
};
static const struct wl_message zwp_tablet_pad_strip_v2_events[] = {
{ "source", "u", tablet_v2_types + 0 },
{ "position", "u", tablet_v2_types + 0 },
{ "stop", "", tablet_v2_types + 0 },
{ "frame", "u", tablet_v2_types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_tablet_pad_strip_v2_interface = {
"zwp_tablet_pad_strip_v2", 2,
2, zwp_tablet_pad_strip_v2_requests,
4, zwp_tablet_pad_strip_v2_events,
};
static const struct wl_message zwp_tablet_pad_group_v2_requests[] = {
{ "destroy", "", tablet_v2_types + 0 },
};
static const struct wl_message zwp_tablet_pad_group_v2_events[] = {
{ "buttons", "a", tablet_v2_types + 0 },
{ "ring", "n", tablet_v2_types + 15 },
{ "strip", "n", tablet_v2_types + 16 },
{ "modes", "u", tablet_v2_types + 0 },
{ "done", "", tablet_v2_types + 0 },
{ "mode_switch", "uuu", tablet_v2_types + 0 },
{ "dial", "2n", tablet_v2_types + 17 },
};
WL_PRIVATE const struct wl_interface zwp_tablet_pad_group_v2_interface = {
"zwp_tablet_pad_group_v2", 2,
1, zwp_tablet_pad_group_v2_requests,
7, zwp_tablet_pad_group_v2_events,
};
static const struct wl_message zwp_tablet_pad_v2_requests[] = {
{ "set_feedback", "usu", tablet_v2_types + 0 },
{ "destroy", "", tablet_v2_types + 0 },
};
static const struct wl_message zwp_tablet_pad_v2_events[] = {
{ "group", "n", tablet_v2_types + 18 },
{ "path", "s", tablet_v2_types + 0 },
{ "buttons", "u", tablet_v2_types + 0 },
{ "done", "", tablet_v2_types + 0 },
{ "button", "uuu", tablet_v2_types + 0 },
{ "enter", "uoo", tablet_v2_types + 19 },
{ "leave", "uo", tablet_v2_types + 22 },
{ "removed", "", tablet_v2_types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_tablet_pad_v2_interface = {
"zwp_tablet_pad_v2", 2,
2, zwp_tablet_pad_v2_requests,
8, zwp_tablet_pad_v2_events,
};
static const struct wl_message zwp_tablet_pad_dial_v2_requests[] = {
{ "set_feedback", "su", tablet_v2_types + 0 },
{ "destroy", "", tablet_v2_types + 0 },
};
static const struct wl_message zwp_tablet_pad_dial_v2_events[] = {
{ "delta", "i", tablet_v2_types + 0 },
{ "frame", "u", tablet_v2_types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_tablet_pad_dial_v2_interface = {
"zwp_tablet_pad_dial_v2", 2,
2, zwp_tablet_pad_dial_v2_requests,
2, zwp_tablet_pad_dial_v2_events,
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,97 @@
/* Generated by wayland-scanner 1.24.0 */
/*
* Copyright © 2012, 2013 Intel Corporation
* Copyright © 2015, 2016 Jan Arne Petersen
* Copyright © 2017, 2018 Red Hat, Inc.
* Copyright © 2018 Purism SPC
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that copyright notice and this permission
* notice appear in supporting documentation, and that the name of
* the copyright holders not be used in advertising or publicity
* pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no
* representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied
* warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
* THIS SOFTWARE.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_seat_interface;
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface zwp_text_input_v3_interface;
static const struct wl_interface *text_input_unstable_v3_types[] = {
NULL,
NULL,
NULL,
NULL,
&wl_surface_interface,
&wl_surface_interface,
&zwp_text_input_v3_interface,
&wl_seat_interface,
};
static const struct wl_message zwp_text_input_v3_requests[] = {
{ "destroy", "", text_input_unstable_v3_types + 0 },
{ "enable", "", text_input_unstable_v3_types + 0 },
{ "disable", "", text_input_unstable_v3_types + 0 },
{ "set_surrounding_text", "sii", text_input_unstable_v3_types + 0 },
{ "set_text_change_cause", "u", text_input_unstable_v3_types + 0 },
{ "set_content_type", "uu", text_input_unstable_v3_types + 0 },
{ "set_cursor_rectangle", "iiii", text_input_unstable_v3_types + 0 },
{ "commit", "", text_input_unstable_v3_types + 0 },
};
static const struct wl_message zwp_text_input_v3_events[] = {
{ "enter", "o", text_input_unstable_v3_types + 4 },
{ "leave", "o", text_input_unstable_v3_types + 5 },
{ "preedit_string", "?sii", text_input_unstable_v3_types + 0 },
{ "commit_string", "?s", text_input_unstable_v3_types + 0 },
{ "delete_surrounding_text", "uu", text_input_unstable_v3_types + 0 },
{ "done", "u", text_input_unstable_v3_types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_text_input_v3_interface = {
"zwp_text_input_v3", 1,
8, zwp_text_input_v3_requests,
6, zwp_text_input_v3_events,
};
static const struct wl_message zwp_text_input_manager_v3_requests[] = {
{ "destroy", "", text_input_unstable_v3_types + 0 },
{ "get_text_input", "no", text_input_unstable_v3_types + 6 },
};
WL_PRIVATE const struct wl_interface zwp_text_input_manager_v3_interface = {
"zwp_text_input_manager_v3", 1,
2, zwp_text_input_manager_v3_requests,
0, NULL,
};

View File

@ -0,0 +1,837 @@
/* Generated by wayland-scanner 1.24.0 */
#ifndef TEXT_INPUT_UNSTABLE_V3_CLIENT_PROTOCOL_H
#define TEXT_INPUT_UNSTABLE_V3_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_text_input_unstable_v3 The text_input_unstable_v3 protocol
* Protocol for composing text
*
* @section page_desc_text_input_unstable_v3 Description
*
* This protocol allows compositors to act as input methods and to send text
* to applications. A text input object is used to manage state of what are
* typically text entry fields in the application.
*
* This document adheres to the RFC 2119 when using words like "must",
* "should", "may", etc.
*
* Warning! The protocol described in this file is experimental and
* backward incompatible changes may be made. Backward compatible changes
* may be added together with the corresponding interface version bump.
* Backward incompatible changes are done by bumping the version number in
* the protocol and interface names and resetting the interface version.
* Once the protocol is to be declared stable, the 'z' prefix and the
* version number in the protocol and interface names are removed and the
* interface version number is reset.
*
* @section page_ifaces_text_input_unstable_v3 Interfaces
* - @subpage page_iface_zwp_text_input_v3 - text input
* - @subpage page_iface_zwp_text_input_manager_v3 - text input manager
* @section page_copyright_text_input_unstable_v3 Copyright
* <pre>
*
* Copyright © 2012, 2013 Intel Corporation
* Copyright © 2015, 2016 Jan Arne Petersen
* Copyright © 2017, 2018 Red Hat, Inc.
* Copyright © 2018 Purism SPC
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that copyright notice and this permission
* notice appear in supporting documentation, and that the name of
* the copyright holders not be used in advertising or publicity
* pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no
* representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied
* warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
* THIS SOFTWARE.
* </pre>
*/
struct wl_seat;
struct wl_surface;
struct zwp_text_input_manager_v3;
struct zwp_text_input_v3;
#ifndef ZWP_TEXT_INPUT_V3_INTERFACE
#define ZWP_TEXT_INPUT_V3_INTERFACE
/**
* @page page_iface_zwp_text_input_v3 zwp_text_input_v3
* @section page_iface_zwp_text_input_v3_desc Description
*
* The zwp_text_input_v3 interface represents text input and input methods
* associated with a seat. It provides enter/leave events to follow the
* text input focus for a seat.
*
* Requests are used to enable/disable the text-input object and set
* state information like surrounding and selected text or the content type.
* The information about the entered text is sent to the text-input object
* via the preedit_string and commit_string events.
*
* Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices
* must not point to middle bytes inside a code point: they must either
* point to the first byte of a code point or to the end of the buffer.
* Lengths must be measured between two valid indices.
*
* Focus moving throughout surfaces will result in the emission of
* zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused
* surface must commit zwp_text_input_v3.enable and
* zwp_text_input_v3.disable requests as the keyboard focus moves across
* editable and non-editable elements of the UI. Those two requests are not
* expected to be paired with each other, the compositor must be able to
* handle consecutive series of the same request.
*
* State is sent by the state requests (set_surrounding_text,
* set_content_type and set_cursor_rectangle) and a commit request. After an
* enter event or disable request all state information is invalidated and
* needs to be resent by the client.
* @section page_iface_zwp_text_input_v3_api API
* See @ref iface_zwp_text_input_v3.
*/
/**
* @defgroup iface_zwp_text_input_v3 The zwp_text_input_v3 interface
*
* The zwp_text_input_v3 interface represents text input and input methods
* associated with a seat. It provides enter/leave events to follow the
* text input focus for a seat.
*
* Requests are used to enable/disable the text-input object and set
* state information like surrounding and selected text or the content type.
* The information about the entered text is sent to the text-input object
* via the preedit_string and commit_string events.
*
* Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices
* must not point to middle bytes inside a code point: they must either
* point to the first byte of a code point or to the end of the buffer.
* Lengths must be measured between two valid indices.
*
* Focus moving throughout surfaces will result in the emission of
* zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused
* surface must commit zwp_text_input_v3.enable and
* zwp_text_input_v3.disable requests as the keyboard focus moves across
* editable and non-editable elements of the UI. Those two requests are not
* expected to be paired with each other, the compositor must be able to
* handle consecutive series of the same request.
*
* State is sent by the state requests (set_surrounding_text,
* set_content_type and set_cursor_rectangle) and a commit request. After an
* enter event or disable request all state information is invalidated and
* needs to be resent by the client.
*/
extern const struct wl_interface zwp_text_input_v3_interface;
#endif
#ifndef ZWP_TEXT_INPUT_MANAGER_V3_INTERFACE
#define ZWP_TEXT_INPUT_MANAGER_V3_INTERFACE
/**
* @page page_iface_zwp_text_input_manager_v3 zwp_text_input_manager_v3
* @section page_iface_zwp_text_input_manager_v3_desc Description
*
* A factory for text-input objects. This object is a global singleton.
* @section page_iface_zwp_text_input_manager_v3_api API
* See @ref iface_zwp_text_input_manager_v3.
*/
/**
* @defgroup iface_zwp_text_input_manager_v3 The zwp_text_input_manager_v3 interface
*
* A factory for text-input objects. This object is a global singleton.
*/
extern const struct wl_interface zwp_text_input_manager_v3_interface;
#endif
#ifndef ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM
#define ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM
/**
* @ingroup iface_zwp_text_input_v3
* text change reason
*
* Reason for the change of surrounding text or cursor posision.
*/
enum zwp_text_input_v3_change_cause {
/**
* input method caused the change
*/
ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD = 0,
/**
* something else than the input method caused the change
*/
ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_OTHER = 1,
};
#endif /* ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_ENUM */
#ifndef ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM
#define ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM
/**
* @ingroup iface_zwp_text_input_v3
* content hint
*
* Content hint is a bitmask to allow to modify the behavior of the text
* input.
*/
enum zwp_text_input_v3_content_hint {
/**
* no special behavior
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE = 0x0,
/**
* suggest word completions
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION = 0x1,
/**
* suggest word corrections
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK = 0x2,
/**
* switch to uppercase letters at the start of a sentence
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION = 0x4,
/**
* prefer lowercase letters
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_LOWERCASE = 0x8,
/**
* prefer uppercase letters
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE = 0x10,
/**
* prefer casing for titles and headings (can be language dependent)
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE = 0x20,
/**
* characters should be hidden
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT = 0x40,
/**
* typed text should not be stored
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA = 0x80,
/**
* just Latin characters should be entered
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_LATIN = 0x100,
/**
* the text input is multiline
*/
ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE = 0x200,
};
#endif /* ZWP_TEXT_INPUT_V3_CONTENT_HINT_ENUM */
#ifndef ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM
#define ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM
/**
* @ingroup iface_zwp_text_input_v3
* content purpose
*
* The content purpose allows to specify the primary purpose of a text
* input.
*
* This allows an input method to show special purpose input panels with
* extra characters or to disallow some characters.
*/
enum zwp_text_input_v3_content_purpose {
/**
* default input, allowing all characters
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL = 0,
/**
* allow only alphabetic characters
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ALPHA = 1,
/**
* allow only digits
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS = 2,
/**
* input a number (including decimal separator and sign)
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER = 3,
/**
* input a phone number
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE = 4,
/**
* input an URL
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL = 5,
/**
* input an email address
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL = 6,
/**
* input a name of a person
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME = 7,
/**
* input a password (combine with sensitive_data hint)
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD = 8,
/**
* input is a numeric password (combine with sensitive_data hint)
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN = 9,
/**
* input a date
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATE = 10,
/**
* input a time
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TIME = 11,
/**
* input a date and time
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATETIME = 12,
/**
* input for a terminal
*/
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL = 13,
};
#endif /* ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ENUM */
/**
* @ingroup iface_zwp_text_input_v3
* @struct zwp_text_input_v3_listener
*/
struct zwp_text_input_v3_listener {
/**
* enter event
*
* Notification that this seat's text-input focus is on a certain
* surface.
*
* If client has created multiple text input objects, compositor
* must send this event to all of them.
*
* When the seat has the keyboard capability the text-input focus
* follows the keyboard focus. This event sets the current surface
* for the text-input object.
*/
void (*enter)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
struct wl_surface *surface);
/**
* leave event
*
* Notification that this seat's text-input focus is no longer on
* a certain surface. The client should reset any preedit string
* previously set.
*
* The leave notification clears the current surface. It is sent
* before the enter notification for the new focus. After leave
* event, compositor must ignore requests from any text input
* instances until next enter event.
*
* When the seat has the keyboard capability the text-input focus
* follows the keyboard focus.
*/
void (*leave)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
struct wl_surface *surface);
/**
* pre-edit
*
* Notify when a new composing text (pre-edit) should be set at
* the current cursor position. Any previously set composing text
* must be removed. Any previously existing selected text must be
* removed.
*
* The argument text contains the pre-edit string buffer.
*
* The parameters cursor_begin and cursor_end are counted in bytes
* relative to the beginning of the submitted text buffer. Cursor
* should be hidden when both are equal to -1.
*
* They could be represented by the client as a line if both values
* are the same, or as a text highlight otherwise.
*
* Values set with this event are double-buffered. They must be
* applied and reset to initial on the next zwp_text_input_v3.done
* event.
*
* The initial value of text is an empty string, and cursor_begin,
* cursor_end and cursor_hidden are all 0.
*/
void (*preedit_string)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
const char *text,
int32_t cursor_begin,
int32_t cursor_end);
/**
* text commit
*
* Notify when text should be inserted into the editor widget.
* The text to commit could be either just a single character after
* a key press or the result of some composing (pre-edit).
*
* Values set with this event are double-buffered. They must be
* applied and reset to initial on the next zwp_text_input_v3.done
* event.
*
* The initial value of text is an empty string.
*/
void (*commit_string)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
const char *text);
/**
* delete surrounding text
*
* Notify when the text around the current cursor position should
* be deleted.
*
* Before_length and after_length are the number of bytes before
* and after the current cursor index (excluding the selection) to
* delete.
*
* If a preedit text is present, in effect before_length is counted
* from the beginning of it, and after_length from its end (see
* done event sequence).
*
* Values set with this event are double-buffered. They must be
* applied and reset to initial on the next zwp_text_input_v3.done
* event.
*
* The initial values of both before_length and after_length are 0.
* @param before_length length of text before current cursor position
* @param after_length length of text after current cursor position
*/
void (*delete_surrounding_text)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
uint32_t before_length,
uint32_t after_length);
/**
* apply changes
*
* Instruct the application to apply changes to state requested
* by the preedit_string, commit_string and delete_surrounding_text
* events. The state relating to these events is double-buffered,
* and each one modifies the pending state. This event replaces the
* current state with the pending state.
*
* The application must proceed by evaluating the changes in the
* following order:
*
* 1. Replace existing preedit string with the cursor. 2. Delete
* requested surrounding text. 3. Insert commit string with the
* cursor at its end. 4. Calculate surrounding text to send. 5.
* Insert new preedit text in cursor position. 6. Place cursor
* inside preedit text.
*
* The serial number reflects the last state of the
* zwp_text_input_v3 object known to the compositor. The value of
* the serial argument must be equal to the number of commit
* requests already issued on that object.
*
* When the client receives a done event with a serial different
* than the number of past commit requests, it must proceed with
* evaluating and applying the changes as normal, except it should
* not change the current state of the zwp_text_input_v3 object.
* All pending state requests (set_surrounding_text,
* set_content_type and set_cursor_rectangle) on the
* zwp_text_input_v3 object should be sent and committed after
* receiving a zwp_text_input_v3.done event with a matching serial.
*/
void (*done)(void *data,
struct zwp_text_input_v3 *zwp_text_input_v3,
uint32_t serial);
};
/**
* @ingroup iface_zwp_text_input_v3
*/
static inline int
zwp_text_input_v3_add_listener(struct zwp_text_input_v3 *zwp_text_input_v3,
const struct zwp_text_input_v3_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) zwp_text_input_v3,
(void (**)(void)) listener, data);
}
#define ZWP_TEXT_INPUT_V3_DESTROY 0
#define ZWP_TEXT_INPUT_V3_ENABLE 1
#define ZWP_TEXT_INPUT_V3_DISABLE 2
#define ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT 3
#define ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE 4
#define ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE 5
#define ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE 6
#define ZWP_TEXT_INPUT_V3_COMMIT 7
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_ENTER_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_LEAVE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_PREEDIT_STRING_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_COMMIT_STRING_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_DELETE_SURROUNDING_TEXT_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_DONE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_ENABLE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_DISABLE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_v3
*/
#define ZWP_TEXT_INPUT_V3_COMMIT_SINCE_VERSION 1
/** @ingroup iface_zwp_text_input_v3 */
static inline void
zwp_text_input_v3_set_user_data(struct zwp_text_input_v3 *zwp_text_input_v3, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zwp_text_input_v3, user_data);
}
/** @ingroup iface_zwp_text_input_v3 */
static inline void *
zwp_text_input_v3_get_user_data(struct zwp_text_input_v3 *zwp_text_input_v3)
{
return wl_proxy_get_user_data((struct wl_proxy *) zwp_text_input_v3);
}
static inline uint32_t
zwp_text_input_v3_get_version(struct zwp_text_input_v3 *zwp_text_input_v3)
{
return wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Destroy the wp_text_input object. Also disables all surfaces enabled
* through this wp_text_input object.
*/
static inline void
zwp_text_input_v3_destroy(struct zwp_text_input_v3 *zwp_text_input_v3)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Requests text input on the surface previously obtained from the enter
* event.
*
* This request must be issued every time the focused text input changes
* to a new one, including within the current surface. Use
* zwp_text_input_v3.disable when there is no longer any input focus on
* the current surface.
*
* Clients must not enable more than one text input on the single seat
* and should disable the current text input before enabling the new one.
* Requests to enable a text input when another text input is enabled
* on the same seat must be ignored by compositor.
*
* This request resets all state associated with previous enable, disable,
* set_surrounding_text, set_text_change_cause, set_content_type, and
* set_cursor_rectangle requests, as well as the state associated with
* preedit_string, commit_string, and delete_surrounding_text events.
*
* The set_surrounding_text, set_content_type and set_cursor_rectangle
* requests must follow if the text input supports the necessary
* functionality.
*
* State set with this request is double-buffered. It will get applied on
* the next zwp_text_input_v3.commit request, and stay valid until the
* next committed enable or disable request.
*
* The changes must be applied by the compositor after issuing a
* zwp_text_input_v3.commit request.
*/
static inline void
zwp_text_input_v3_enable(struct zwp_text_input_v3 *zwp_text_input_v3)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_ENABLE, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Explicitly disable text input on the current surface (typically when
* there is no focus on any text entry inside the surface).
*
* State set with this request is double-buffered. It will get applied on
* the next zwp_text_input_v3.commit request.
*/
static inline void
zwp_text_input_v3_disable(struct zwp_text_input_v3 *zwp_text_input_v3)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_DISABLE, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Sets the surrounding plain text around the input, excluding the preedit
* text.
*
* The client should notify the compositor of any changes in any of the
* values carried with this request, including changes caused by handling
* incoming text-input events as well as changes caused by other
* mechanisms like keyboard typing.
*
* If the client is unaware of the text around the cursor, it should not
* issue this request, to signify lack of support to the compositor.
*
* Text is UTF-8 encoded, and should include the cursor position, the
* complete selection and additional characters before and after them.
* There is a maximum length of wayland messages, so text can not be
* longer than 4000 bytes.
*
* Cursor is the byte offset of the cursor within text buffer.
*
* Anchor is the byte offset of the selection anchor within text buffer.
* If there is no selected text, anchor is the same as cursor.
*
* If any preedit text is present, it is replaced with a cursor for the
* purpose of this event.
*
* Values set with this request are double-buffered. They will get applied
* on the next zwp_text_input_v3.commit request, and stay valid until the
* next committed enable or disable request.
*
* The initial state for affected fields is empty, meaning that the text
* input does not support sending surrounding text. If the empty values
* get applied, subsequent attempts to change them may have no effect.
*/
static inline void
zwp_text_input_v3_set_surrounding_text(struct zwp_text_input_v3 *zwp_text_input_v3, const char *text, int32_t cursor, int32_t anchor)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_SET_SURROUNDING_TEXT, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0, text, cursor, anchor);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Tells the compositor why the text surrounding the cursor changed.
*
* Whenever the client detects an external change in text, cursor, or
* anchor posision, it must issue this request to the compositor. This
* request is intended to give the input method a chance to update the
* preedit text in an appropriate way, e.g. by removing it when the user
* starts typing with a keyboard.
*
* cause describes the source of the change.
*
* The value set with this request is double-buffered. It must be applied
* and reset to initial at the next zwp_text_input_v3.commit request.
*
* The initial value of cause is input_method.
*/
static inline void
zwp_text_input_v3_set_text_change_cause(struct zwp_text_input_v3 *zwp_text_input_v3, uint32_t cause)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_SET_TEXT_CHANGE_CAUSE, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0, cause);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Sets the content purpose and content hint. While the purpose is the
* basic purpose of an input field, the hint flags allow to modify some of
* the behavior.
*
* Values set with this request are double-buffered. They will get applied
* on the next zwp_text_input_v3.commit request.
* Subsequent attempts to update them may have no effect. The values
* remain valid until the next committed enable or disable request.
*
* The initial value for hint is none, and the initial value for purpose
* is normal.
*/
static inline void
zwp_text_input_v3_set_content_type(struct zwp_text_input_v3 *zwp_text_input_v3, uint32_t hint, uint32_t purpose)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_SET_CONTENT_TYPE, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0, hint, purpose);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Marks an area around the cursor as a x, y, width, height rectangle in
* surface local coordinates.
*
* Allows the compositor to put a window with word suggestions near the
* cursor, without obstructing the text being input.
*
* If the client is unaware of the position of edited text, it should not
* issue this request, to signify lack of support to the compositor.
*
* Values set with this request are double-buffered. They will get applied
* on the next zwp_text_input_v3.commit request, and stay valid until the
* next committed enable or disable request.
*
* The initial values describing a cursor rectangle are empty. That means
* the text input does not support describing the cursor area. If the
* empty values get applied, subsequent attempts to change them may have
* no effect.
*/
static inline void
zwp_text_input_v3_set_cursor_rectangle(struct zwp_text_input_v3 *zwp_text_input_v3, int32_t x, int32_t y, int32_t width, int32_t height)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_SET_CURSOR_RECTANGLE, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0, x, y, width, height);
}
/**
* @ingroup iface_zwp_text_input_v3
*
* Atomically applies state changes recently sent to the compositor.
*
* The commit request establishes and updates the state of the client, and
* must be issued after any changes to apply them.
*
* Text input state (enabled status, content purpose, content hint,
* surrounding text and change cause, cursor rectangle) is conceptually
* double-buffered within the context of a text input, i.e. between a
* committed enable request and the following committed enable or disable
* request.
*
* Protocol requests modify the pending state, as opposed to the current
* state in use by the input method. A commit request atomically applies
* all pending state, replacing the current state. After commit, the new
* pending state is as documented for each related request.
*
* Requests are applied in the order of arrival.
*
* Neither current nor pending state are modified unless noted otherwise.
*
* The compositor must count the number of commit requests coming from
* each zwp_text_input_v3 object and use the count as the serial in done
* events.
*/
static inline void
zwp_text_input_v3_commit(struct zwp_text_input_v3 *zwp_text_input_v3)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_v3,
ZWP_TEXT_INPUT_V3_COMMIT, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_v3), 0);
}
#define ZWP_TEXT_INPUT_MANAGER_V3_DESTROY 0
#define ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT 1
/**
* @ingroup iface_zwp_text_input_manager_v3
*/
#define ZWP_TEXT_INPUT_MANAGER_V3_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zwp_text_input_manager_v3
*/
#define ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT_SINCE_VERSION 1
/** @ingroup iface_zwp_text_input_manager_v3 */
static inline void
zwp_text_input_manager_v3_set_user_data(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zwp_text_input_manager_v3, user_data);
}
/** @ingroup iface_zwp_text_input_manager_v3 */
static inline void *
zwp_text_input_manager_v3_get_user_data(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
{
return wl_proxy_get_user_data((struct wl_proxy *) zwp_text_input_manager_v3);
}
static inline uint32_t
zwp_text_input_manager_v3_get_version(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
{
return wl_proxy_get_version((struct wl_proxy *) zwp_text_input_manager_v3);
}
/**
* @ingroup iface_zwp_text_input_manager_v3
*
* Destroy the wp_text_input_manager object.
*/
static inline void
zwp_text_input_manager_v3_destroy(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3)
{
wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_manager_v3,
ZWP_TEXT_INPUT_MANAGER_V3_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_manager_v3), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_zwp_text_input_manager_v3
*
* Creates a new text-input object for a given seat.
*/
static inline struct zwp_text_input_v3 *
zwp_text_input_manager_v3_get_text_input(struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3, struct wl_seat *seat)
{
struct wl_proxy *id;
id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_text_input_manager_v3,
ZWP_TEXT_INPUT_MANAGER_V3_GET_TEXT_INPUT, &zwp_text_input_v3_interface, wl_proxy_get_version((struct wl_proxy *) zwp_text_input_manager_v3), 0, NULL, seat);
return (struct zwp_text_input_v3 *) id;
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,75 @@
/* Generated by wayland-scanner 1.24.0 */
/*
* Copyright © 2013-2016 Collabora, Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface wp_viewport_interface;
static const struct wl_interface *viewporter_types[] = {
NULL,
NULL,
NULL,
NULL,
&wp_viewport_interface,
&wl_surface_interface,
};
static const struct wl_message wp_viewporter_requests[] = {
{ "destroy", "", viewporter_types + 0 },
{ "get_viewport", "no", viewporter_types + 4 },
};
WL_PRIVATE const struct wl_interface wp_viewporter_interface = {
"wp_viewporter", 1,
2, wp_viewporter_requests,
0, NULL,
};
static const struct wl_message wp_viewport_requests[] = {
{ "destroy", "", viewporter_types + 0 },
{ "set_source", "ffff", viewporter_types + 0 },
{ "set_destination", "ii", viewporter_types + 0 },
};
WL_PRIVATE const struct wl_interface wp_viewport_interface = {
"wp_viewport", 1,
3, wp_viewport_requests,
0, NULL,
};

View File

@ -0,0 +1,394 @@
/* Generated by wayland-scanner 1.24.0 */
#ifndef VIEWPORTER_CLIENT_PROTOCOL_H
#define VIEWPORTER_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_viewporter The viewporter protocol
* @section page_ifaces_viewporter Interfaces
* - @subpage page_iface_wp_viewporter - surface cropping and scaling
* - @subpage page_iface_wp_viewport - crop and scale interface to a wl_surface
* @section page_copyright_viewporter Copyright
* <pre>
*
* Copyright © 2013-2016 Collabora, Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* </pre>
*/
struct wl_surface;
struct wp_viewport;
struct wp_viewporter;
#ifndef WP_VIEWPORTER_INTERFACE
#define WP_VIEWPORTER_INTERFACE
/**
* @page page_iface_wp_viewporter wp_viewporter
* @section page_iface_wp_viewporter_desc Description
*
* The global interface exposing surface cropping and scaling
* capabilities is used to instantiate an interface extension for a
* wl_surface object. This extended interface will then allow
* cropping and scaling the surface contents, effectively
* disconnecting the direct relationship between the buffer and the
* surface size.
* @section page_iface_wp_viewporter_api API
* See @ref iface_wp_viewporter.
*/
/**
* @defgroup iface_wp_viewporter The wp_viewporter interface
*
* The global interface exposing surface cropping and scaling
* capabilities is used to instantiate an interface extension for a
* wl_surface object. This extended interface will then allow
* cropping and scaling the surface contents, effectively
* disconnecting the direct relationship between the buffer and the
* surface size.
*/
extern const struct wl_interface wp_viewporter_interface;
#endif
#ifndef WP_VIEWPORT_INTERFACE
#define WP_VIEWPORT_INTERFACE
/**
* @page page_iface_wp_viewport wp_viewport
* @section page_iface_wp_viewport_desc Description
*
* An additional interface to a wl_surface object, which allows the
* client to specify the cropping and scaling of the surface
* contents.
*
* This interface works with two concepts: the source rectangle (src_x,
* src_y, src_width, src_height), and the destination size (dst_width,
* dst_height). The contents of the source rectangle are scaled to the
* destination size, and content outside the source rectangle is ignored.
* This state is double-buffered, see wl_surface.commit.
*
* The two parts of crop and scale state are independent: the source
* rectangle, and the destination size. Initially both are unset, that
* is, no scaling is applied. The whole of the current wl_buffer is
* used as the source, and the surface size is as defined in
* wl_surface.attach.
*
* If the destination size is set, it causes the surface size to become
* dst_width, dst_height. The source (rectangle) is scaled to exactly
* this size. This overrides whatever the attached wl_buffer size is,
* unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface
* has no content and therefore no size. Otherwise, the size is always
* at least 1x1 in surface local coordinates.
*
* If the source rectangle is set, it defines what area of the wl_buffer is
* taken as the source. If the source rectangle is set and the destination
* size is not set, then src_width and src_height must be integers, and the
* surface size becomes the source rectangle size. This results in cropping
* without scaling. If src_width or src_height are not integers and
* destination size is not set, the bad_size protocol error is raised when
* the surface state is applied.
*
* The coordinate transformations from buffer pixel coordinates up to
* the surface-local coordinates happen in the following order:
* 1. buffer_transform (wl_surface.set_buffer_transform)
* 2. buffer_scale (wl_surface.set_buffer_scale)
* 3. crop and scale (wp_viewport.set*)
* This means, that the source rectangle coordinates of crop and scale
* are given in the coordinates after the buffer transform and scale,
* i.e. in the coordinates that would be the surface-local coordinates
* if the crop and scale was not applied.
*
* If src_x or src_y are negative, the bad_value protocol error is raised.
* Otherwise, if the source rectangle is partially or completely outside of
* the non-NULL wl_buffer, then the out_of_buffer protocol error is raised
* when the surface state is applied. A NULL wl_buffer does not raise the
* out_of_buffer error.
*
* If the wl_surface associated with the wp_viewport is destroyed,
* all wp_viewport requests except 'destroy' raise the protocol error
* no_surface.
*
* If the wp_viewport object is destroyed, the crop and scale
* state is removed from the wl_surface. The change will be applied
* on the next wl_surface.commit.
* @section page_iface_wp_viewport_api API
* See @ref iface_wp_viewport.
*/
/**
* @defgroup iface_wp_viewport The wp_viewport interface
*
* An additional interface to a wl_surface object, which allows the
* client to specify the cropping and scaling of the surface
* contents.
*
* This interface works with two concepts: the source rectangle (src_x,
* src_y, src_width, src_height), and the destination size (dst_width,
* dst_height). The contents of the source rectangle are scaled to the
* destination size, and content outside the source rectangle is ignored.
* This state is double-buffered, see wl_surface.commit.
*
* The two parts of crop and scale state are independent: the source
* rectangle, and the destination size. Initially both are unset, that
* is, no scaling is applied. The whole of the current wl_buffer is
* used as the source, and the surface size is as defined in
* wl_surface.attach.
*
* If the destination size is set, it causes the surface size to become
* dst_width, dst_height. The source (rectangle) is scaled to exactly
* this size. This overrides whatever the attached wl_buffer size is,
* unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface
* has no content and therefore no size. Otherwise, the size is always
* at least 1x1 in surface local coordinates.
*
* If the source rectangle is set, it defines what area of the wl_buffer is
* taken as the source. If the source rectangle is set and the destination
* size is not set, then src_width and src_height must be integers, and the
* surface size becomes the source rectangle size. This results in cropping
* without scaling. If src_width or src_height are not integers and
* destination size is not set, the bad_size protocol error is raised when
* the surface state is applied.
*
* The coordinate transformations from buffer pixel coordinates up to
* the surface-local coordinates happen in the following order:
* 1. buffer_transform (wl_surface.set_buffer_transform)
* 2. buffer_scale (wl_surface.set_buffer_scale)
* 3. crop and scale (wp_viewport.set*)
* This means, that the source rectangle coordinates of crop and scale
* are given in the coordinates after the buffer transform and scale,
* i.e. in the coordinates that would be the surface-local coordinates
* if the crop and scale was not applied.
*
* If src_x or src_y are negative, the bad_value protocol error is raised.
* Otherwise, if the source rectangle is partially or completely outside of
* the non-NULL wl_buffer, then the out_of_buffer protocol error is raised
* when the surface state is applied. A NULL wl_buffer does not raise the
* out_of_buffer error.
*
* If the wl_surface associated with the wp_viewport is destroyed,
* all wp_viewport requests except 'destroy' raise the protocol error
* no_surface.
*
* If the wp_viewport object is destroyed, the crop and scale
* state is removed from the wl_surface. The change will be applied
* on the next wl_surface.commit.
*/
extern const struct wl_interface wp_viewport_interface;
#endif
#ifndef WP_VIEWPORTER_ERROR_ENUM
#define WP_VIEWPORTER_ERROR_ENUM
enum wp_viewporter_error {
/**
* the surface already has a viewport object associated
*/
WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS = 0,
};
#endif /* WP_VIEWPORTER_ERROR_ENUM */
#define WP_VIEWPORTER_DESTROY 0
#define WP_VIEWPORTER_GET_VIEWPORT 1
/**
* @ingroup iface_wp_viewporter
*/
#define WP_VIEWPORTER_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_wp_viewporter
*/
#define WP_VIEWPORTER_GET_VIEWPORT_SINCE_VERSION 1
/** @ingroup iface_wp_viewporter */
static inline void
wp_viewporter_set_user_data(struct wp_viewporter *wp_viewporter, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) wp_viewporter, user_data);
}
/** @ingroup iface_wp_viewporter */
static inline void *
wp_viewporter_get_user_data(struct wp_viewporter *wp_viewporter)
{
return wl_proxy_get_user_data((struct wl_proxy *) wp_viewporter);
}
static inline uint32_t
wp_viewporter_get_version(struct wp_viewporter *wp_viewporter)
{
return wl_proxy_get_version((struct wl_proxy *) wp_viewporter);
}
/**
* @ingroup iface_wp_viewporter
*
* Informs the server that the client will not be using this
* protocol object anymore. This does not affect any other objects,
* wp_viewport objects included.
*/
static inline void
wp_viewporter_destroy(struct wp_viewporter *wp_viewporter)
{
wl_proxy_marshal_flags((struct wl_proxy *) wp_viewporter,
WP_VIEWPORTER_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_viewporter), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_wp_viewporter
*
* Instantiate an interface extension for the given wl_surface to
* crop and scale its content. If the given wl_surface already has
* a wp_viewport object associated, the viewport_exists
* protocol error is raised.
*/
static inline struct wp_viewport *
wp_viewporter_get_viewport(struct wp_viewporter *wp_viewporter, struct wl_surface *surface)
{
struct wl_proxy *id;
id = wl_proxy_marshal_flags((struct wl_proxy *) wp_viewporter,
WP_VIEWPORTER_GET_VIEWPORT, &wp_viewport_interface, wl_proxy_get_version((struct wl_proxy *) wp_viewporter), 0, NULL, surface);
return (struct wp_viewport *) id;
}
#ifndef WP_VIEWPORT_ERROR_ENUM
#define WP_VIEWPORT_ERROR_ENUM
enum wp_viewport_error {
/**
* negative or zero values in width or height
*/
WP_VIEWPORT_ERROR_BAD_VALUE = 0,
/**
* destination size is not integer
*/
WP_VIEWPORT_ERROR_BAD_SIZE = 1,
/**
* source rectangle extends outside of the content area
*/
WP_VIEWPORT_ERROR_OUT_OF_BUFFER = 2,
/**
* the wl_surface was destroyed
*/
WP_VIEWPORT_ERROR_NO_SURFACE = 3,
};
#endif /* WP_VIEWPORT_ERROR_ENUM */
#define WP_VIEWPORT_DESTROY 0
#define WP_VIEWPORT_SET_SOURCE 1
#define WP_VIEWPORT_SET_DESTINATION 2
/**
* @ingroup iface_wp_viewport
*/
#define WP_VIEWPORT_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_wp_viewport
*/
#define WP_VIEWPORT_SET_SOURCE_SINCE_VERSION 1
/**
* @ingroup iface_wp_viewport
*/
#define WP_VIEWPORT_SET_DESTINATION_SINCE_VERSION 1
/** @ingroup iface_wp_viewport */
static inline void
wp_viewport_set_user_data(struct wp_viewport *wp_viewport, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) wp_viewport, user_data);
}
/** @ingroup iface_wp_viewport */
static inline void *
wp_viewport_get_user_data(struct wp_viewport *wp_viewport)
{
return wl_proxy_get_user_data((struct wl_proxy *) wp_viewport);
}
static inline uint32_t
wp_viewport_get_version(struct wp_viewport *wp_viewport)
{
return wl_proxy_get_version((struct wl_proxy *) wp_viewport);
}
/**
* @ingroup iface_wp_viewport
*
* The associated wl_surface's crop and scale state is removed.
* The change is applied on the next wl_surface.commit.
*/
static inline void
wp_viewport_destroy(struct wp_viewport *wp_viewport)
{
wl_proxy_marshal_flags((struct wl_proxy *) wp_viewport,
WP_VIEWPORT_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_viewport), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_wp_viewport
*
* Set the source rectangle of the associated wl_surface. See
* wp_viewport for the description, and relation to the wl_buffer
* size.
*
* If all of x, y, width and height are -1.0, the source rectangle is
* unset instead. Any other set of values where width or height are zero
* or negative, or x or y are negative, raise the bad_value protocol
* error.
*
* The crop and scale state is double-buffered, see wl_surface.commit.
*/
static inline void
wp_viewport_set_source(struct wp_viewport *wp_viewport, wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height)
{
wl_proxy_marshal_flags((struct wl_proxy *) wp_viewport,
WP_VIEWPORT_SET_SOURCE, NULL, wl_proxy_get_version((struct wl_proxy *) wp_viewport), 0, x, y, width, height);
}
/**
* @ingroup iface_wp_viewport
*
* Set the destination size of the associated wl_surface. See
* wp_viewport for the description, and relation to the wl_buffer
* size.
*
* If width is -1 and height is -1, the destination size is unset
* instead. Any other pair of values for width and height that
* contains zero or negative values raises the bad_value protocol
* error.
*
* The crop and scale state is double-buffered, see wl_surface.commit.
*/
static inline void
wp_viewport_set_destination(struct wp_viewport *wp_viewport, int32_t width, int32_t height)
{
wl_proxy_marshal_flags((struct wl_proxy *) wp_viewport,
WP_VIEWPORT_SET_DESTINATION, NULL, wl_proxy_get_version((struct wl_proxy *) wp_viewport), 0, width, height);
}
#ifdef __cplusplus
}
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,34 @@
/*
* Copyright © 2012 Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef WAYLAND_VERSION_H
#define WAYLAND_VERSION_H
#define WAYLAND_VERSION_MAJOR 1
#define WAYLAND_VERSION_MINOR 24
#define WAYLAND_VERSION_MICRO 0
#define WAYLAND_VERSION "1.24.0"
#endif

View File

@ -0,0 +1,538 @@
/* Generated by wayland-scanner 1.24.0 */
/*
* Copyright © 2008-2011 Kristian Høgsberg
* Copyright © 2010-2011 Intel Corporation
* Copyright © 2012-2013 Collabora, Ltd.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_buffer_interface;
extern const struct wl_interface wl_callback_interface;
extern const struct wl_interface wl_data_device_interface;
extern const struct wl_interface wl_data_offer_interface;
extern const struct wl_interface wl_data_source_interface;
extern const struct wl_interface wl_keyboard_interface;
extern const struct wl_interface wl_output_interface;
extern const struct wl_interface wl_pointer_interface;
extern const struct wl_interface wl_region_interface;
extern const struct wl_interface wl_registry_interface;
extern const struct wl_interface wl_seat_interface;
extern const struct wl_interface wl_shell_surface_interface;
extern const struct wl_interface wl_shm_pool_interface;
extern const struct wl_interface wl_subsurface_interface;
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface wl_touch_interface;
static const struct wl_interface *wayland_types[] = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
&wl_callback_interface,
&wl_registry_interface,
&wl_surface_interface,
&wl_region_interface,
&wl_buffer_interface,
NULL,
NULL,
NULL,
NULL,
NULL,
&wl_shm_pool_interface,
NULL,
NULL,
&wl_data_source_interface,
&wl_surface_interface,
&wl_surface_interface,
NULL,
&wl_data_source_interface,
NULL,
&wl_data_offer_interface,
NULL,
&wl_surface_interface,
NULL,
NULL,
&wl_data_offer_interface,
&wl_data_offer_interface,
&wl_data_source_interface,
&wl_data_device_interface,
&wl_seat_interface,
&wl_shell_surface_interface,
&wl_surface_interface,
&wl_seat_interface,
NULL,
&wl_seat_interface,
NULL,
NULL,
&wl_surface_interface,
NULL,
NULL,
NULL,
NULL,
NULL,
&wl_output_interface,
&wl_seat_interface,
NULL,
&wl_surface_interface,
NULL,
NULL,
NULL,
&wl_output_interface,
&wl_buffer_interface,
NULL,
NULL,
&wl_callback_interface,
&wl_region_interface,
&wl_region_interface,
&wl_output_interface,
&wl_output_interface,
&wl_pointer_interface,
&wl_keyboard_interface,
&wl_touch_interface,
NULL,
&wl_surface_interface,
NULL,
NULL,
NULL,
&wl_surface_interface,
NULL,
NULL,
NULL,
&wl_surface_interface,
NULL,
&wl_surface_interface,
NULL,
NULL,
&wl_surface_interface,
NULL,
NULL,
&wl_surface_interface,
NULL,
NULL,
NULL,
&wl_subsurface_interface,
&wl_surface_interface,
&wl_surface_interface,
&wl_surface_interface,
&wl_surface_interface,
&wl_registry_interface,
};
static const struct wl_message wl_display_requests[] = {
{ "sync", "n", wayland_types + 8 },
{ "get_registry", "n", wayland_types + 9 },
};
static const struct wl_message wl_display_events[] = {
{ "error", "ous", wayland_types + 0 },
{ "delete_id", "u", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_display_interface = {
"wl_display", 1,
2, wl_display_requests,
2, wl_display_events,
};
static const struct wl_message wl_registry_requests[] = {
{ "bind", "usun", wayland_types + 0 },
};
static const struct wl_message wl_registry_events[] = {
{ "global", "usu", wayland_types + 0 },
{ "global_remove", "u", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_registry_interface = {
"wl_registry", 1,
1, wl_registry_requests,
2, wl_registry_events,
};
static const struct wl_message wl_callback_events[] = {
{ "done", "u", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_callback_interface = {
"wl_callback", 1,
0, NULL,
1, wl_callback_events,
};
static const struct wl_message wl_compositor_requests[] = {
{ "create_surface", "n", wayland_types + 10 },
{ "create_region", "n", wayland_types + 11 },
};
WL_PRIVATE const struct wl_interface wl_compositor_interface = {
"wl_compositor", 6,
2, wl_compositor_requests,
0, NULL,
};
static const struct wl_message wl_shm_pool_requests[] = {
{ "create_buffer", "niiiiu", wayland_types + 12 },
{ "destroy", "", wayland_types + 0 },
{ "resize", "i", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_shm_pool_interface = {
"wl_shm_pool", 2,
3, wl_shm_pool_requests,
0, NULL,
};
static const struct wl_message wl_shm_requests[] = {
{ "create_pool", "nhi", wayland_types + 18 },
{ "release", "2", wayland_types + 0 },
};
static const struct wl_message wl_shm_events[] = {
{ "format", "u", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_shm_interface = {
"wl_shm", 2,
2, wl_shm_requests,
1, wl_shm_events,
};
static const struct wl_message wl_buffer_requests[] = {
{ "destroy", "", wayland_types + 0 },
};
static const struct wl_message wl_buffer_events[] = {
{ "release", "", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_buffer_interface = {
"wl_buffer", 1,
1, wl_buffer_requests,
1, wl_buffer_events,
};
static const struct wl_message wl_data_offer_requests[] = {
{ "accept", "u?s", wayland_types + 0 },
{ "receive", "sh", wayland_types + 0 },
{ "destroy", "", wayland_types + 0 },
{ "finish", "3", wayland_types + 0 },
{ "set_actions", "3uu", wayland_types + 0 },
};
static const struct wl_message wl_data_offer_events[] = {
{ "offer", "s", wayland_types + 0 },
{ "source_actions", "3u", wayland_types + 0 },
{ "action", "3u", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_data_offer_interface = {
"wl_data_offer", 3,
5, wl_data_offer_requests,
3, wl_data_offer_events,
};
static const struct wl_message wl_data_source_requests[] = {
{ "offer", "s", wayland_types + 0 },
{ "destroy", "", wayland_types + 0 },
{ "set_actions", "3u", wayland_types + 0 },
};
static const struct wl_message wl_data_source_events[] = {
{ "target", "?s", wayland_types + 0 },
{ "send", "sh", wayland_types + 0 },
{ "cancelled", "", wayland_types + 0 },
{ "dnd_drop_performed", "3", wayland_types + 0 },
{ "dnd_finished", "3", wayland_types + 0 },
{ "action", "3u", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_data_source_interface = {
"wl_data_source", 3,
3, wl_data_source_requests,
6, wl_data_source_events,
};
static const struct wl_message wl_data_device_requests[] = {
{ "start_drag", "?oo?ou", wayland_types + 21 },
{ "set_selection", "?ou", wayland_types + 25 },
{ "release", "2", wayland_types + 0 },
};
static const struct wl_message wl_data_device_events[] = {
{ "data_offer", "n", wayland_types + 27 },
{ "enter", "uoff?o", wayland_types + 28 },
{ "leave", "", wayland_types + 0 },
{ "motion", "uff", wayland_types + 0 },
{ "drop", "", wayland_types + 0 },
{ "selection", "?o", wayland_types + 33 },
};
WL_PRIVATE const struct wl_interface wl_data_device_interface = {
"wl_data_device", 3,
3, wl_data_device_requests,
6, wl_data_device_events,
};
static const struct wl_message wl_data_device_manager_requests[] = {
{ "create_data_source", "n", wayland_types + 34 },
{ "get_data_device", "no", wayland_types + 35 },
};
WL_PRIVATE const struct wl_interface wl_data_device_manager_interface = {
"wl_data_device_manager", 3,
2, wl_data_device_manager_requests,
0, NULL,
};
static const struct wl_message wl_shell_requests[] = {
{ "get_shell_surface", "no", wayland_types + 37 },
};
WL_PRIVATE const struct wl_interface wl_shell_interface = {
"wl_shell", 1,
1, wl_shell_requests,
0, NULL,
};
static const struct wl_message wl_shell_surface_requests[] = {
{ "pong", "u", wayland_types + 0 },
{ "move", "ou", wayland_types + 39 },
{ "resize", "ouu", wayland_types + 41 },
{ "set_toplevel", "", wayland_types + 0 },
{ "set_transient", "oiiu", wayland_types + 44 },
{ "set_fullscreen", "uu?o", wayland_types + 48 },
{ "set_popup", "ouoiiu", wayland_types + 51 },
{ "set_maximized", "?o", wayland_types + 57 },
{ "set_title", "s", wayland_types + 0 },
{ "set_class", "s", wayland_types + 0 },
};
static const struct wl_message wl_shell_surface_events[] = {
{ "ping", "u", wayland_types + 0 },
{ "configure", "uii", wayland_types + 0 },
{ "popup_done", "", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_shell_surface_interface = {
"wl_shell_surface", 1,
10, wl_shell_surface_requests,
3, wl_shell_surface_events,
};
static const struct wl_message wl_surface_requests[] = {
{ "destroy", "", wayland_types + 0 },
{ "attach", "?oii", wayland_types + 58 },
{ "damage", "iiii", wayland_types + 0 },
{ "frame", "n", wayland_types + 61 },
{ "set_opaque_region", "?o", wayland_types + 62 },
{ "set_input_region", "?o", wayland_types + 63 },
{ "commit", "", wayland_types + 0 },
{ "set_buffer_transform", "2i", wayland_types + 0 },
{ "set_buffer_scale", "3i", wayland_types + 0 },
{ "damage_buffer", "4iiii", wayland_types + 0 },
{ "offset", "5ii", wayland_types + 0 },
};
static const struct wl_message wl_surface_events[] = {
{ "enter", "o", wayland_types + 64 },
{ "leave", "o", wayland_types + 65 },
{ "preferred_buffer_scale", "6i", wayland_types + 0 },
{ "preferred_buffer_transform", "6u", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_surface_interface = {
"wl_surface", 6,
11, wl_surface_requests,
4, wl_surface_events,
};
static const struct wl_message wl_seat_requests[] = {
{ "get_pointer", "n", wayland_types + 66 },
{ "get_keyboard", "n", wayland_types + 67 },
{ "get_touch", "n", wayland_types + 68 },
{ "release", "5", wayland_types + 0 },
};
static const struct wl_message wl_seat_events[] = {
{ "capabilities", "u", wayland_types + 0 },
{ "name", "2s", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_seat_interface = {
"wl_seat", 10,
4, wl_seat_requests,
2, wl_seat_events,
};
static const struct wl_message wl_pointer_requests[] = {
{ "set_cursor", "u?oii", wayland_types + 69 },
{ "release", "3", wayland_types + 0 },
};
static const struct wl_message wl_pointer_events[] = {
{ "enter", "uoff", wayland_types + 73 },
{ "leave", "uo", wayland_types + 77 },
{ "motion", "uff", wayland_types + 0 },
{ "button", "uuuu", wayland_types + 0 },
{ "axis", "uuf", wayland_types + 0 },
{ "frame", "5", wayland_types + 0 },
{ "axis_source", "5u", wayland_types + 0 },
{ "axis_stop", "5uu", wayland_types + 0 },
{ "axis_discrete", "5ui", wayland_types + 0 },
{ "axis_value120", "8ui", wayland_types + 0 },
{ "axis_relative_direction", "9uu", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_pointer_interface = {
"wl_pointer", 10,
2, wl_pointer_requests,
11, wl_pointer_events,
};
static const struct wl_message wl_keyboard_requests[] = {
{ "release", "3", wayland_types + 0 },
};
static const struct wl_message wl_keyboard_events[] = {
{ "keymap", "uhu", wayland_types + 0 },
{ "enter", "uoa", wayland_types + 79 },
{ "leave", "uo", wayland_types + 82 },
{ "key", "uuuu", wayland_types + 0 },
{ "modifiers", "uuuuu", wayland_types + 0 },
{ "repeat_info", "4ii", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_keyboard_interface = {
"wl_keyboard", 10,
1, wl_keyboard_requests,
6, wl_keyboard_events,
};
static const struct wl_message wl_touch_requests[] = {
{ "release", "3", wayland_types + 0 },
};
static const struct wl_message wl_touch_events[] = {
{ "down", "uuoiff", wayland_types + 84 },
{ "up", "uui", wayland_types + 0 },
{ "motion", "uiff", wayland_types + 0 },
{ "frame", "", wayland_types + 0 },
{ "cancel", "", wayland_types + 0 },
{ "shape", "6iff", wayland_types + 0 },
{ "orientation", "6if", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_touch_interface = {
"wl_touch", 10,
1, wl_touch_requests,
7, wl_touch_events,
};
static const struct wl_message wl_output_requests[] = {
{ "release", "3", wayland_types + 0 },
};
static const struct wl_message wl_output_events[] = {
{ "geometry", "iiiiissi", wayland_types + 0 },
{ "mode", "uiii", wayland_types + 0 },
{ "done", "2", wayland_types + 0 },
{ "scale", "2i", wayland_types + 0 },
{ "name", "4s", wayland_types + 0 },
{ "description", "4s", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_output_interface = {
"wl_output", 4,
1, wl_output_requests,
6, wl_output_events,
};
static const struct wl_message wl_region_requests[] = {
{ "destroy", "", wayland_types + 0 },
{ "add", "iiii", wayland_types + 0 },
{ "subtract", "iiii", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_region_interface = {
"wl_region", 1,
3, wl_region_requests,
0, NULL,
};
static const struct wl_message wl_subcompositor_requests[] = {
{ "destroy", "", wayland_types + 0 },
{ "get_subsurface", "noo", wayland_types + 90 },
};
WL_PRIVATE const struct wl_interface wl_subcompositor_interface = {
"wl_subcompositor", 1,
2, wl_subcompositor_requests,
0, NULL,
};
static const struct wl_message wl_subsurface_requests[] = {
{ "destroy", "", wayland_types + 0 },
{ "set_position", "ii", wayland_types + 0 },
{ "place_above", "o", wayland_types + 93 },
{ "place_below", "o", wayland_types + 94 },
{ "set_sync", "", wayland_types + 0 },
{ "set_desync", "", wayland_types + 0 },
};
WL_PRIVATE const struct wl_interface wl_subsurface_interface = {
"wl_subsurface", 1,
6, wl_subsurface_requests,
0, NULL,
};
static const struct wl_message wl_fixes_requests[] = {
{ "destroy", "", wayland_types + 0 },
{ "destroy_registry", "o", wayland_types + 95 },
};
WL_PRIVATE const struct wl_interface wl_fixes_interface = {
"wl_fixes", 1,
2, wl_fixes_requests,
0, NULL,
};

View File

@ -0,0 +1,85 @@
/* Generated by wayland-scanner 1.24.0 */
/*
* Copyright © 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
* Copyright © 2020 Carlos Garnacho <carlosg@gnome.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_seat_interface;
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface xdg_activation_token_v1_interface;
static const struct wl_interface *xdg_activation_v1_types[] = {
NULL,
&xdg_activation_token_v1_interface,
NULL,
&wl_surface_interface,
NULL,
&wl_seat_interface,
&wl_surface_interface,
};
static const struct wl_message xdg_activation_v1_requests[] = {
{ "destroy", "", xdg_activation_v1_types + 0 },
{ "get_activation_token", "n", xdg_activation_v1_types + 1 },
{ "activate", "so", xdg_activation_v1_types + 2 },
};
WL_PRIVATE const struct wl_interface xdg_activation_v1_interface = {
"xdg_activation_v1", 1,
3, xdg_activation_v1_requests,
0, NULL,
};
static const struct wl_message xdg_activation_token_v1_requests[] = {
{ "set_serial", "uo", xdg_activation_v1_types + 4 },
{ "set_app_id", "s", xdg_activation_v1_types + 0 },
{ "set_surface", "o", xdg_activation_v1_types + 6 },
{ "commit", "", xdg_activation_v1_types + 0 },
{ "destroy", "", xdg_activation_v1_types + 0 },
};
static const struct wl_message xdg_activation_token_v1_events[] = {
{ "done", "s", xdg_activation_v1_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_activation_token_v1_interface = {
"xdg_activation_token_v1", 1,
5, xdg_activation_token_v1_requests,
1, xdg_activation_token_v1_events,
};

View File

@ -0,0 +1,415 @@
/* Generated by wayland-scanner 1.24.0 */
#ifndef XDG_ACTIVATION_V1_CLIENT_PROTOCOL_H
#define XDG_ACTIVATION_V1_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_xdg_activation_v1 The xdg_activation_v1 protocol
* Protocol for requesting activation of surfaces
*
* @section page_desc_xdg_activation_v1 Description
*
* The way for a client to pass focus to another toplevel is as follows.
*
* The client that intends to activate another toplevel uses the
* xdg_activation_v1.get_activation_token request to get an activation token.
* This token is then forwarded to the client, which is supposed to activate
* one of its surfaces, through a separate band of communication.
*
* One established way of doing this is through the XDG_ACTIVATION_TOKEN
* environment variable of a newly launched child process. The child process
* should unset the environment variable again right after reading it out in
* order to avoid propagating it to other child processes.
*
* Another established way exists for Applications implementing the D-Bus
* interface org.freedesktop.Application, which should get their token under
* activation-token on their platform_data.
*
* In general activation tokens may be transferred across clients through
* means not described in this protocol.
*
* The client to be activated will then pass the token
* it received to the xdg_activation_v1.activate request. The compositor can
* then use this token to decide how to react to the activation request.
*
* The token the activating client gets may be ineffective either already at
* the time it receives it, for example if it was not focused, for focus
* stealing prevention. The activating client will have no way to discover
* the validity of the token, and may still forward it to the to be activated
* client.
*
* The created activation token may optionally get information attached to it
* that can be used by the compositor to identify the application that we
* intend to activate. This can for example be used to display a visual hint
* about what application is being started.
*
* Warning! The protocol described in this file is currently in the testing
* phase. Backward compatible changes may be added together with the
* corresponding interface version bump. Backward incompatible changes can
* only be done by creating a new major version of the extension.
*
* @section page_ifaces_xdg_activation_v1 Interfaces
* - @subpage page_iface_xdg_activation_v1 - interface for activating surfaces
* - @subpage page_iface_xdg_activation_token_v1 - an exported activation handle
* @section page_copyright_xdg_activation_v1 Copyright
* <pre>
*
* Copyright © 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
* Copyright © 2020 Carlos Garnacho <carlosg@gnome.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* </pre>
*/
struct wl_seat;
struct wl_surface;
struct xdg_activation_token_v1;
struct xdg_activation_v1;
#ifndef XDG_ACTIVATION_V1_INTERFACE
#define XDG_ACTIVATION_V1_INTERFACE
/**
* @page page_iface_xdg_activation_v1 xdg_activation_v1
* @section page_iface_xdg_activation_v1_desc Description
*
* A global interface used for informing the compositor about applications
* being activated or started, or for applications to request to be
* activated.
* @section page_iface_xdg_activation_v1_api API
* See @ref iface_xdg_activation_v1.
*/
/**
* @defgroup iface_xdg_activation_v1 The xdg_activation_v1 interface
*
* A global interface used for informing the compositor about applications
* being activated or started, or for applications to request to be
* activated.
*/
extern const struct wl_interface xdg_activation_v1_interface;
#endif
#ifndef XDG_ACTIVATION_TOKEN_V1_INTERFACE
#define XDG_ACTIVATION_TOKEN_V1_INTERFACE
/**
* @page page_iface_xdg_activation_token_v1 xdg_activation_token_v1
* @section page_iface_xdg_activation_token_v1_desc Description
*
* An object for setting up a token and receiving a token handle that can
* be passed as an activation token to another client.
*
* The object is created using the xdg_activation_v1.get_activation_token
* request. This object should then be populated with the app_id, surface
* and serial information and committed. The compositor shall then issue a
* done event with the token. In case the request's parameters are invalid,
* the compositor will provide an invalid token.
* @section page_iface_xdg_activation_token_v1_api API
* See @ref iface_xdg_activation_token_v1.
*/
/**
* @defgroup iface_xdg_activation_token_v1 The xdg_activation_token_v1 interface
*
* An object for setting up a token and receiving a token handle that can
* be passed as an activation token to another client.
*
* The object is created using the xdg_activation_v1.get_activation_token
* request. This object should then be populated with the app_id, surface
* and serial information and committed. The compositor shall then issue a
* done event with the token. In case the request's parameters are invalid,
* the compositor will provide an invalid token.
*/
extern const struct wl_interface xdg_activation_token_v1_interface;
#endif
#define XDG_ACTIVATION_V1_DESTROY 0
#define XDG_ACTIVATION_V1_GET_ACTIVATION_TOKEN 1
#define XDG_ACTIVATION_V1_ACTIVATE 2
/**
* @ingroup iface_xdg_activation_v1
*/
#define XDG_ACTIVATION_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_xdg_activation_v1
*/
#define XDG_ACTIVATION_V1_GET_ACTIVATION_TOKEN_SINCE_VERSION 1
/**
* @ingroup iface_xdg_activation_v1
*/
#define XDG_ACTIVATION_V1_ACTIVATE_SINCE_VERSION 1
/** @ingroup iface_xdg_activation_v1 */
static inline void
xdg_activation_v1_set_user_data(struct xdg_activation_v1 *xdg_activation_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) xdg_activation_v1, user_data);
}
/** @ingroup iface_xdg_activation_v1 */
static inline void *
xdg_activation_v1_get_user_data(struct xdg_activation_v1 *xdg_activation_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) xdg_activation_v1);
}
static inline uint32_t
xdg_activation_v1_get_version(struct xdg_activation_v1 *xdg_activation_v1)
{
return wl_proxy_get_version((struct wl_proxy *) xdg_activation_v1);
}
/**
* @ingroup iface_xdg_activation_v1
*
* Notify the compositor that the xdg_activation object will no longer be
* used.
*
* The child objects created via this interface are unaffected and should
* be destroyed separately.
*/
static inline void
xdg_activation_v1_destroy(struct xdg_activation_v1 *xdg_activation_v1)
{
wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_v1,
XDG_ACTIVATION_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_v1), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_xdg_activation_v1
*
* Creates an xdg_activation_token_v1 object that will provide
* the initiating client with a unique token for this activation. This
* token should be offered to the clients to be activated.
*/
static inline struct xdg_activation_token_v1 *
xdg_activation_v1_get_activation_token(struct xdg_activation_v1 *xdg_activation_v1)
{
struct wl_proxy *id;
id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_v1,
XDG_ACTIVATION_V1_GET_ACTIVATION_TOKEN, &xdg_activation_token_v1_interface, wl_proxy_get_version((struct wl_proxy *) xdg_activation_v1), 0, NULL);
return (struct xdg_activation_token_v1 *) id;
}
/**
* @ingroup iface_xdg_activation_v1
*
* Requests surface activation. It's up to the compositor to display
* this information as desired, for example by placing the surface above
* the rest.
*
* The compositor may know who requested this by checking the activation
* token and might decide not to follow through with the activation if it's
* considered unwanted.
*
* Compositors can ignore unknown activation tokens when an invalid
* token is passed.
*/
static inline void
xdg_activation_v1_activate(struct xdg_activation_v1 *xdg_activation_v1, const char *token, struct wl_surface *surface)
{
wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_v1,
XDG_ACTIVATION_V1_ACTIVATE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_v1), 0, token, surface);
}
#ifndef XDG_ACTIVATION_TOKEN_V1_ERROR_ENUM
#define XDG_ACTIVATION_TOKEN_V1_ERROR_ENUM
enum xdg_activation_token_v1_error {
/**
* The token has already been used previously
*/
XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED = 0,
};
#endif /* XDG_ACTIVATION_TOKEN_V1_ERROR_ENUM */
/**
* @ingroup iface_xdg_activation_token_v1
* @struct xdg_activation_token_v1_listener
*/
struct xdg_activation_token_v1_listener {
/**
* the exported activation token
*
* The 'done' event contains the unique token of this activation
* request and notifies that the provider is done.
* @param token the exported activation token
*/
void (*done)(void *data,
struct xdg_activation_token_v1 *xdg_activation_token_v1,
const char *token);
};
/**
* @ingroup iface_xdg_activation_token_v1
*/
static inline int
xdg_activation_token_v1_add_listener(struct xdg_activation_token_v1 *xdg_activation_token_v1,
const struct xdg_activation_token_v1_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) xdg_activation_token_v1,
(void (**)(void)) listener, data);
}
#define XDG_ACTIVATION_TOKEN_V1_SET_SERIAL 0
#define XDG_ACTIVATION_TOKEN_V1_SET_APP_ID 1
#define XDG_ACTIVATION_TOKEN_V1_SET_SURFACE 2
#define XDG_ACTIVATION_TOKEN_V1_COMMIT 3
#define XDG_ACTIVATION_TOKEN_V1_DESTROY 4
/**
* @ingroup iface_xdg_activation_token_v1
*/
#define XDG_ACTIVATION_TOKEN_V1_DONE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_activation_token_v1
*/
#define XDG_ACTIVATION_TOKEN_V1_SET_SERIAL_SINCE_VERSION 1
/**
* @ingroup iface_xdg_activation_token_v1
*/
#define XDG_ACTIVATION_TOKEN_V1_SET_APP_ID_SINCE_VERSION 1
/**
* @ingroup iface_xdg_activation_token_v1
*/
#define XDG_ACTIVATION_TOKEN_V1_SET_SURFACE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_activation_token_v1
*/
#define XDG_ACTIVATION_TOKEN_V1_COMMIT_SINCE_VERSION 1
/**
* @ingroup iface_xdg_activation_token_v1
*/
#define XDG_ACTIVATION_TOKEN_V1_DESTROY_SINCE_VERSION 1
/** @ingroup iface_xdg_activation_token_v1 */
static inline void
xdg_activation_token_v1_set_user_data(struct xdg_activation_token_v1 *xdg_activation_token_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) xdg_activation_token_v1, user_data);
}
/** @ingroup iface_xdg_activation_token_v1 */
static inline void *
xdg_activation_token_v1_get_user_data(struct xdg_activation_token_v1 *xdg_activation_token_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) xdg_activation_token_v1);
}
static inline uint32_t
xdg_activation_token_v1_get_version(struct xdg_activation_token_v1 *xdg_activation_token_v1)
{
return wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1);
}
/**
* @ingroup iface_xdg_activation_token_v1
*
* Provides information about the seat and serial event that requested the
* token.
*
* The serial can come from an input or focus event. For instance, if a
* click triggers the launch of a third-party client, the launcher client
* should send a set_serial request with the serial and seat from the
* wl_pointer.button event.
*
* Some compositors might refuse to activate toplevels when the token
* doesn't have a valid and recent enough event serial.
*
* Must be sent before commit. This information is optional.
*/
static inline void
xdg_activation_token_v1_set_serial(struct xdg_activation_token_v1 *xdg_activation_token_v1, uint32_t serial, struct wl_seat *seat)
{
wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_token_v1,
XDG_ACTIVATION_TOKEN_V1_SET_SERIAL, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1), 0, serial, seat);
}
/**
* @ingroup iface_xdg_activation_token_v1
*
* The requesting client can specify an app_id to associate the token
* being created with it.
*
* Must be sent before commit. This information is optional.
*/
static inline void
xdg_activation_token_v1_set_app_id(struct xdg_activation_token_v1 *xdg_activation_token_v1, const char *app_id)
{
wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_token_v1,
XDG_ACTIVATION_TOKEN_V1_SET_APP_ID, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1), 0, app_id);
}
/**
* @ingroup iface_xdg_activation_token_v1
*
* This request sets the surface requesting the activation. Note, this is
* different from the surface that will be activated.
*
* Some compositors might refuse to activate toplevels when the token
* doesn't have a requesting surface.
*
* Must be sent before commit. This information is optional.
*/
static inline void
xdg_activation_token_v1_set_surface(struct xdg_activation_token_v1 *xdg_activation_token_v1, struct wl_surface *surface)
{
wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_token_v1,
XDG_ACTIVATION_TOKEN_V1_SET_SURFACE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1), 0, surface);
}
/**
* @ingroup iface_xdg_activation_token_v1
*
* Requests an activation token based on the different parameters that
* have been offered through set_serial, set_surface and set_app_id.
*/
static inline void
xdg_activation_token_v1_commit(struct xdg_activation_token_v1 *xdg_activation_token_v1)
{
wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_token_v1,
XDG_ACTIVATION_TOKEN_V1_COMMIT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1), 0);
}
/**
* @ingroup iface_xdg_activation_token_v1
*
* Notify the compositor that the xdg_activation_token_v1 object will no
* longer be used. The received token stays valid.
*/
static inline void
xdg_activation_token_v1_destroy(struct xdg_activation_token_v1 *xdg_activation_token_v1)
{
wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_token_v1,
XDG_ACTIVATION_TOKEN_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1), WL_MARSHAL_FLAG_DESTROY);
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,76 @@
/* Generated by wayland-scanner 1.24.0 */
/*
* Copyright © 2018 Simon Ser
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface xdg_toplevel_interface;
extern const struct wl_interface zxdg_toplevel_decoration_v1_interface;
static const struct wl_interface *xdg_decoration_unstable_v1_types[] = {
NULL,
&zxdg_toplevel_decoration_v1_interface,
&xdg_toplevel_interface,
};
static const struct wl_message zxdg_decoration_manager_v1_requests[] = {
{ "destroy", "", xdg_decoration_unstable_v1_types + 0 },
{ "get_toplevel_decoration", "no", xdg_decoration_unstable_v1_types + 1 },
};
WL_PRIVATE const struct wl_interface zxdg_decoration_manager_v1_interface = {
"zxdg_decoration_manager_v1", 1,
2, zxdg_decoration_manager_v1_requests,
0, NULL,
};
static const struct wl_message zxdg_toplevel_decoration_v1_requests[] = {
{ "destroy", "", xdg_decoration_unstable_v1_types + 0 },
{ "set_mode", "u", xdg_decoration_unstable_v1_types + 0 },
{ "unset_mode", "", xdg_decoration_unstable_v1_types + 0 },
};
static const struct wl_message zxdg_toplevel_decoration_v1_events[] = {
{ "configure", "u", xdg_decoration_unstable_v1_types + 0 },
};
WL_PRIVATE const struct wl_interface zxdg_toplevel_decoration_v1_interface = {
"zxdg_toplevel_decoration_v1", 1,
3, zxdg_toplevel_decoration_v1_requests,
1, zxdg_toplevel_decoration_v1_events,
};

View File

@ -0,0 +1,384 @@
/* Generated by wayland-scanner 1.24.0 */
#ifndef XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H
#define XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_xdg_decoration_unstable_v1 The xdg_decoration_unstable_v1 protocol
* @section page_ifaces_xdg_decoration_unstable_v1 Interfaces
* - @subpage page_iface_zxdg_decoration_manager_v1 - window decoration manager
* - @subpage page_iface_zxdg_toplevel_decoration_v1 - decoration object for a toplevel surface
* @section page_copyright_xdg_decoration_unstable_v1 Copyright
* <pre>
*
* Copyright © 2018 Simon Ser
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* </pre>
*/
struct xdg_toplevel;
struct zxdg_decoration_manager_v1;
struct zxdg_toplevel_decoration_v1;
#ifndef ZXDG_DECORATION_MANAGER_V1_INTERFACE
#define ZXDG_DECORATION_MANAGER_V1_INTERFACE
/**
* @page page_iface_zxdg_decoration_manager_v1 zxdg_decoration_manager_v1
* @section page_iface_zxdg_decoration_manager_v1_desc Description
*
* This interface allows a compositor to announce support for server-side
* decorations.
*
* A window decoration is a set of window controls as deemed appropriate by
* the party managing them, such as user interface components used to move,
* resize and change a window's state.
*
* A client can use this protocol to request being decorated by a supporting
* compositor.
*
* If compositor and client do not negotiate the use of a server-side
* decoration using this protocol, clients continue to self-decorate as they
* see fit.
*
* Warning! The protocol described in this file is experimental and
* backward incompatible changes may be made. Backward compatible changes
* may be added together with the corresponding interface version bump.
* Backward incompatible changes are done by bumping the version number in
* the protocol and interface names and resetting the interface version.
* Once the protocol is to be declared stable, the 'z' prefix and the
* version number in the protocol and interface names are removed and the
* interface version number is reset.
* @section page_iface_zxdg_decoration_manager_v1_api API
* See @ref iface_zxdg_decoration_manager_v1.
*/
/**
* @defgroup iface_zxdg_decoration_manager_v1 The zxdg_decoration_manager_v1 interface
*
* This interface allows a compositor to announce support for server-side
* decorations.
*
* A window decoration is a set of window controls as deemed appropriate by
* the party managing them, such as user interface components used to move,
* resize and change a window's state.
*
* A client can use this protocol to request being decorated by a supporting
* compositor.
*
* If compositor and client do not negotiate the use of a server-side
* decoration using this protocol, clients continue to self-decorate as they
* see fit.
*
* Warning! The protocol described in this file is experimental and
* backward incompatible changes may be made. Backward compatible changes
* may be added together with the corresponding interface version bump.
* Backward incompatible changes are done by bumping the version number in
* the protocol and interface names and resetting the interface version.
* Once the protocol is to be declared stable, the 'z' prefix and the
* version number in the protocol and interface names are removed and the
* interface version number is reset.
*/
extern const struct wl_interface zxdg_decoration_manager_v1_interface;
#endif
#ifndef ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE
#define ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE
/**
* @page page_iface_zxdg_toplevel_decoration_v1 zxdg_toplevel_decoration_v1
* @section page_iface_zxdg_toplevel_decoration_v1_desc Description
*
* The decoration object allows the compositor to toggle server-side window
* decorations for a toplevel surface. The client can request to switch to
* another mode.
*
* The xdg_toplevel_decoration object must be destroyed before its
* xdg_toplevel.
* @section page_iface_zxdg_toplevel_decoration_v1_api API
* See @ref iface_zxdg_toplevel_decoration_v1.
*/
/**
* @defgroup iface_zxdg_toplevel_decoration_v1 The zxdg_toplevel_decoration_v1 interface
*
* The decoration object allows the compositor to toggle server-side window
* decorations for a toplevel surface. The client can request to switch to
* another mode.
*
* The xdg_toplevel_decoration object must be destroyed before its
* xdg_toplevel.
*/
extern const struct wl_interface zxdg_toplevel_decoration_v1_interface;
#endif
#define ZXDG_DECORATION_MANAGER_V1_DESTROY 0
#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION 1
/**
* @ingroup iface_zxdg_decoration_manager_v1
*/
#define ZXDG_DECORATION_MANAGER_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_decoration_manager_v1
*/
#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION_SINCE_VERSION 1
/** @ingroup iface_zxdg_decoration_manager_v1 */
static inline void
zxdg_decoration_manager_v1_set_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zxdg_decoration_manager_v1, user_data);
}
/** @ingroup iface_zxdg_decoration_manager_v1 */
static inline void *
zxdg_decoration_manager_v1_get_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_decoration_manager_v1);
}
static inline uint32_t
zxdg_decoration_manager_v1_get_version(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1);
}
/**
* @ingroup iface_zxdg_decoration_manager_v1
*
* Destroy the decoration manager. This doesn't destroy objects created
* with the manager.
*/
static inline void
zxdg_decoration_manager_v1_destroy(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1)
{
wl_proxy_marshal_flags((struct wl_proxy *) zxdg_decoration_manager_v1,
ZXDG_DECORATION_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_zxdg_decoration_manager_v1
*
* Create a new decoration object associated with the given toplevel.
*
* Creating an xdg_toplevel_decoration from an xdg_toplevel which has a
* buffer attached or committed is a client error, and any attempts by a
* client to attach or manipulate a buffer prior to the first
* xdg_toplevel_decoration.configure event must also be treated as
* errors.
*/
static inline struct zxdg_toplevel_decoration_v1 *
zxdg_decoration_manager_v1_get_toplevel_decoration(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, struct xdg_toplevel *toplevel)
{
struct wl_proxy *id;
id = wl_proxy_marshal_flags((struct wl_proxy *) zxdg_decoration_manager_v1,
ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION, &zxdg_toplevel_decoration_v1_interface, wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1), 0, NULL, toplevel);
return (struct zxdg_toplevel_decoration_v1 *) id;
}
#ifndef ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM
#define ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM
enum zxdg_toplevel_decoration_v1_error {
/**
* xdg_toplevel has a buffer attached before configure
*/
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_UNCONFIGURED_BUFFER = 0,
/**
* xdg_toplevel already has a decoration object
*/
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ALREADY_CONSTRUCTED = 1,
/**
* xdg_toplevel destroyed before the decoration object
*/
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ORPHANED = 2,
/**
* invalid mode
*/
ZXDG_TOPLEVEL_DECORATION_V1_ERROR_INVALID_MODE = 3,
};
#endif /* ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM */
#ifndef ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM
#define ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
* window decoration modes
*
* These values describe window decoration modes.
*/
enum zxdg_toplevel_decoration_v1_mode {
/**
* no server-side window decoration
*/
ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE = 1,
/**
* server-side window decoration
*/
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE = 2,
};
#endif /* ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM */
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
* @struct zxdg_toplevel_decoration_v1_listener
*/
struct zxdg_toplevel_decoration_v1_listener {
/**
* notify a decoration mode change
*
* The configure event configures the effective decoration mode.
* The configured state should not be applied immediately. Clients
* must send an ack_configure in response to this event. See
* xdg_surface.configure and xdg_surface.ack_configure for details.
*
* A configure event can be sent at any time. The specified mode
* must be obeyed by the client.
* @param mode the decoration mode
*/
void (*configure)(void *data,
struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
uint32_t mode);
};
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*/
static inline int
zxdg_toplevel_decoration_v1_add_listener(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
const struct zxdg_toplevel_decoration_v1_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) zxdg_toplevel_decoration_v1,
(void (**)(void)) listener, data);
}
#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY 0
#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE 1
#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE 2
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*/
#define ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*/
#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*/
#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*/
#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE_SINCE_VERSION 1
/** @ingroup iface_zxdg_toplevel_decoration_v1 */
static inline void
zxdg_toplevel_decoration_v1_set_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1, user_data);
}
/** @ingroup iface_zxdg_toplevel_decoration_v1 */
static inline void *
zxdg_toplevel_decoration_v1_get_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1);
}
static inline uint32_t
zxdg_toplevel_decoration_v1_get_version(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1);
}
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*
* Switch back to a mode without any server-side decorations at the next
* commit.
*/
static inline void
zxdg_toplevel_decoration_v1_destroy(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
{
wl_proxy_marshal_flags((struct wl_proxy *) zxdg_toplevel_decoration_v1,
ZXDG_TOPLEVEL_DECORATION_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*
* Set the toplevel surface decoration mode. This informs the compositor
* that the client prefers the provided decoration mode.
*
* After requesting a decoration mode, the compositor will respond by
* emitting an xdg_surface.configure event. The client should then update
* its content, drawing it without decorations if the received mode is
* server-side decorations. The client must also acknowledge the configure
* when committing the new content (see xdg_surface.ack_configure).
*
* The compositor can decide not to use the client's mode and enforce a
* different mode instead.
*
* Clients whose decoration mode depend on the xdg_toplevel state may send
* a set_mode request in response to an xdg_surface.configure event and wait
* for the next xdg_surface.configure event to prevent unwanted state.
* Such clients are responsible for preventing configure loops and must
* make sure not to send multiple successive set_mode requests with the
* same decoration mode.
*
* If an invalid mode is supplied by the client, the invalid_mode protocol
* error is raised by the compositor.
*/
static inline void
zxdg_toplevel_decoration_v1_set_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, uint32_t mode)
{
wl_proxy_marshal_flags((struct wl_proxy *) zxdg_toplevel_decoration_v1,
ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1), 0, mode);
}
/**
* @ingroup iface_zxdg_toplevel_decoration_v1
*
* Unset the toplevel surface decoration mode. This informs the compositor
* that the client doesn't prefer a particular decoration mode.
*
* This request has the same semantics as set_mode.
*/
static inline void
zxdg_toplevel_decoration_v1_unset_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1)
{
wl_proxy_marshal_flags((struct wl_proxy *) zxdg_toplevel_decoration_v1,
ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1), 0);
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,104 @@
/* Generated by wayland-scanner 1.24.0 */
/*
* Copyright © 2015-2016 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface zxdg_exported_v2_interface;
extern const struct wl_interface zxdg_imported_v2_interface;
static const struct wl_interface *xdg_foreign_unstable_v2_types[] = {
NULL,
&zxdg_exported_v2_interface,
&wl_surface_interface,
&zxdg_imported_v2_interface,
NULL,
&wl_surface_interface,
};
static const struct wl_message zxdg_exporter_v2_requests[] = {
{ "destroy", "", xdg_foreign_unstable_v2_types + 0 },
{ "export_toplevel", "no", xdg_foreign_unstable_v2_types + 1 },
};
WL_PRIVATE const struct wl_interface zxdg_exporter_v2_interface = {
"zxdg_exporter_v2", 1,
2, zxdg_exporter_v2_requests,
0, NULL,
};
static const struct wl_message zxdg_importer_v2_requests[] = {
{ "destroy", "", xdg_foreign_unstable_v2_types + 0 },
{ "import_toplevel", "ns", xdg_foreign_unstable_v2_types + 3 },
};
WL_PRIVATE const struct wl_interface zxdg_importer_v2_interface = {
"zxdg_importer_v2", 1,
2, zxdg_importer_v2_requests,
0, NULL,
};
static const struct wl_message zxdg_exported_v2_requests[] = {
{ "destroy", "", xdg_foreign_unstable_v2_types + 0 },
};
static const struct wl_message zxdg_exported_v2_events[] = {
{ "handle", "s", xdg_foreign_unstable_v2_types + 0 },
};
WL_PRIVATE const struct wl_interface zxdg_exported_v2_interface = {
"zxdg_exported_v2", 1,
1, zxdg_exported_v2_requests,
1, zxdg_exported_v2_events,
};
static const struct wl_message zxdg_imported_v2_requests[] = {
{ "destroy", "", xdg_foreign_unstable_v2_types + 0 },
{ "set_parent_of", "o", xdg_foreign_unstable_v2_types + 5 },
};
static const struct wl_message zxdg_imported_v2_events[] = {
{ "destroyed", "", xdg_foreign_unstable_v2_types + 0 },
};
WL_PRIVATE const struct wl_interface zxdg_imported_v2_interface = {
"zxdg_imported_v2", 1,
2, zxdg_imported_v2_requests,
1, zxdg_imported_v2_events,
};

View File

@ -0,0 +1,518 @@
/* Generated by wayland-scanner 1.24.0 */
#ifndef XDG_FOREIGN_UNSTABLE_V2_CLIENT_PROTOCOL_H
#define XDG_FOREIGN_UNSTABLE_V2_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_xdg_foreign_unstable_v2 The xdg_foreign_unstable_v2 protocol
* Protocol for exporting xdg surface handles
*
* @section page_desc_xdg_foreign_unstable_v2 Description
*
* This protocol specifies a way for making it possible to reference a surface
* of a different client. With such a reference, a client can, by using the
* interfaces provided by this protocol, manipulate the relationship between
* its own surfaces and the surface of some other client. For example, stack
* some of its own surface above the other clients surface.
*
* In order for a client A to get a reference of a surface of client B, client
* B must first export its surface using xdg_exporter.export_toplevel. Upon
* doing this, client B will receive a handle (a unique string) that it may
* share with client A in some way (for example D-Bus). After client A has
* received the handle from client B, it may use xdg_importer.import_toplevel
* to create a reference to the surface client B just exported. See the
* corresponding requests for details.
*
* A possible use case for this is out-of-process dialogs. For example when a
* sandboxed client without file system access needs the user to select a file
* on the file system, given sandbox environment support, it can export its
* surface, passing the exported surface handle to an unsandboxed process that
* can show a file browser dialog and stack it above the sandboxed client's
* surface.
*
* Warning! The protocol described in this file is experimental and backward
* incompatible changes may be made. Backward compatible changes may be added
* together with the corresponding interface version bump. Backward
* incompatible changes are done by bumping the version number in the protocol
* and interface names and resetting the interface version. Once the protocol
* is to be declared stable, the 'z' prefix and the version number in the
* protocol and interface names are removed and the interface version number is
* reset.
*
* @section page_ifaces_xdg_foreign_unstable_v2 Interfaces
* - @subpage page_iface_zxdg_exporter_v2 - interface for exporting surfaces
* - @subpage page_iface_zxdg_importer_v2 - interface for importing surfaces
* - @subpage page_iface_zxdg_exported_v2 - an exported surface handle
* - @subpage page_iface_zxdg_imported_v2 - an imported surface handle
* @section page_copyright_xdg_foreign_unstable_v2 Copyright
* <pre>
*
* Copyright © 2015-2016 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* </pre>
*/
struct wl_surface;
struct zxdg_exported_v2;
struct zxdg_exporter_v2;
struct zxdg_imported_v2;
struct zxdg_importer_v2;
#ifndef ZXDG_EXPORTER_V2_INTERFACE
#define ZXDG_EXPORTER_V2_INTERFACE
/**
* @page page_iface_zxdg_exporter_v2 zxdg_exporter_v2
* @section page_iface_zxdg_exporter_v2_desc Description
*
* A global interface used for exporting surfaces that can later be imported
* using xdg_importer.
* @section page_iface_zxdg_exporter_v2_api API
* See @ref iface_zxdg_exporter_v2.
*/
/**
* @defgroup iface_zxdg_exporter_v2 The zxdg_exporter_v2 interface
*
* A global interface used for exporting surfaces that can later be imported
* using xdg_importer.
*/
extern const struct wl_interface zxdg_exporter_v2_interface;
#endif
#ifndef ZXDG_IMPORTER_V2_INTERFACE
#define ZXDG_IMPORTER_V2_INTERFACE
/**
* @page page_iface_zxdg_importer_v2 zxdg_importer_v2
* @section page_iface_zxdg_importer_v2_desc Description
*
* A global interface used for importing surfaces exported by xdg_exporter.
* With this interface, a client can create a reference to a surface of
* another client.
* @section page_iface_zxdg_importer_v2_api API
* See @ref iface_zxdg_importer_v2.
*/
/**
* @defgroup iface_zxdg_importer_v2 The zxdg_importer_v2 interface
*
* A global interface used for importing surfaces exported by xdg_exporter.
* With this interface, a client can create a reference to a surface of
* another client.
*/
extern const struct wl_interface zxdg_importer_v2_interface;
#endif
#ifndef ZXDG_EXPORTED_V2_INTERFACE
#define ZXDG_EXPORTED_V2_INTERFACE
/**
* @page page_iface_zxdg_exported_v2 zxdg_exported_v2
* @section page_iface_zxdg_exported_v2_desc Description
*
* An xdg_exported object represents an exported reference to a surface. The
* exported surface may be referenced as long as the xdg_exported object not
* destroyed. Destroying the xdg_exported invalidates any relationship the
* importer may have established using xdg_imported.
* @section page_iface_zxdg_exported_v2_api API
* See @ref iface_zxdg_exported_v2.
*/
/**
* @defgroup iface_zxdg_exported_v2 The zxdg_exported_v2 interface
*
* An xdg_exported object represents an exported reference to a surface. The
* exported surface may be referenced as long as the xdg_exported object not
* destroyed. Destroying the xdg_exported invalidates any relationship the
* importer may have established using xdg_imported.
*/
extern const struct wl_interface zxdg_exported_v2_interface;
#endif
#ifndef ZXDG_IMPORTED_V2_INTERFACE
#define ZXDG_IMPORTED_V2_INTERFACE
/**
* @page page_iface_zxdg_imported_v2 zxdg_imported_v2
* @section page_iface_zxdg_imported_v2_desc Description
*
* An xdg_imported object represents an imported reference to surface exported
* by some client. A client can use this interface to manipulate
* relationships between its own surfaces and the imported surface.
* @section page_iface_zxdg_imported_v2_api API
* See @ref iface_zxdg_imported_v2.
*/
/**
* @defgroup iface_zxdg_imported_v2 The zxdg_imported_v2 interface
*
* An xdg_imported object represents an imported reference to surface exported
* by some client. A client can use this interface to manipulate
* relationships between its own surfaces and the imported surface.
*/
extern const struct wl_interface zxdg_imported_v2_interface;
#endif
#ifndef ZXDG_EXPORTER_V2_ERROR_ENUM
#define ZXDG_EXPORTER_V2_ERROR_ENUM
/**
* @ingroup iface_zxdg_exporter_v2
* error values
*
* These errors can be emitted in response to invalid xdg_exporter
* requests.
*/
enum zxdg_exporter_v2_error {
/**
* surface is not an xdg_toplevel
*/
ZXDG_EXPORTER_V2_ERROR_INVALID_SURFACE = 0,
};
#endif /* ZXDG_EXPORTER_V2_ERROR_ENUM */
#define ZXDG_EXPORTER_V2_DESTROY 0
#define ZXDG_EXPORTER_V2_EXPORT_TOPLEVEL 1
/**
* @ingroup iface_zxdg_exporter_v2
*/
#define ZXDG_EXPORTER_V2_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_exporter_v2
*/
#define ZXDG_EXPORTER_V2_EXPORT_TOPLEVEL_SINCE_VERSION 1
/** @ingroup iface_zxdg_exporter_v2 */
static inline void
zxdg_exporter_v2_set_user_data(struct zxdg_exporter_v2 *zxdg_exporter_v2, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zxdg_exporter_v2, user_data);
}
/** @ingroup iface_zxdg_exporter_v2 */
static inline void *
zxdg_exporter_v2_get_user_data(struct zxdg_exporter_v2 *zxdg_exporter_v2)
{
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_exporter_v2);
}
static inline uint32_t
zxdg_exporter_v2_get_version(struct zxdg_exporter_v2 *zxdg_exporter_v2)
{
return wl_proxy_get_version((struct wl_proxy *) zxdg_exporter_v2);
}
/**
* @ingroup iface_zxdg_exporter_v2
*
* Notify the compositor that the xdg_exporter object will no longer be
* used.
*/
static inline void
zxdg_exporter_v2_destroy(struct zxdg_exporter_v2 *zxdg_exporter_v2)
{
wl_proxy_marshal_flags((struct wl_proxy *) zxdg_exporter_v2,
ZXDG_EXPORTER_V2_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_exporter_v2), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_zxdg_exporter_v2
*
* The export_toplevel request exports the passed surface so that it can later be
* imported via xdg_importer. When called, a new xdg_exported object will
* be created and xdg_exported.handle will be sent immediately. See the
* corresponding interface and event for details.
*
* A surface may be exported multiple times, and each exported handle may
* be used to create an xdg_imported multiple times. Only xdg_toplevel
* equivalent surfaces may be exported, otherwise an invalid_surface
* protocol error is sent.
*/
static inline struct zxdg_exported_v2 *
zxdg_exporter_v2_export_toplevel(struct zxdg_exporter_v2 *zxdg_exporter_v2, struct wl_surface *surface)
{
struct wl_proxy *id;
id = wl_proxy_marshal_flags((struct wl_proxy *) zxdg_exporter_v2,
ZXDG_EXPORTER_V2_EXPORT_TOPLEVEL, &zxdg_exported_v2_interface, wl_proxy_get_version((struct wl_proxy *) zxdg_exporter_v2), 0, NULL, surface);
return (struct zxdg_exported_v2 *) id;
}
#define ZXDG_IMPORTER_V2_DESTROY 0
#define ZXDG_IMPORTER_V2_IMPORT_TOPLEVEL 1
/**
* @ingroup iface_zxdg_importer_v2
*/
#define ZXDG_IMPORTER_V2_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_importer_v2
*/
#define ZXDG_IMPORTER_V2_IMPORT_TOPLEVEL_SINCE_VERSION 1
/** @ingroup iface_zxdg_importer_v2 */
static inline void
zxdg_importer_v2_set_user_data(struct zxdg_importer_v2 *zxdg_importer_v2, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zxdg_importer_v2, user_data);
}
/** @ingroup iface_zxdg_importer_v2 */
static inline void *
zxdg_importer_v2_get_user_data(struct zxdg_importer_v2 *zxdg_importer_v2)
{
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_importer_v2);
}
static inline uint32_t
zxdg_importer_v2_get_version(struct zxdg_importer_v2 *zxdg_importer_v2)
{
return wl_proxy_get_version((struct wl_proxy *) zxdg_importer_v2);
}
/**
* @ingroup iface_zxdg_importer_v2
*
* Notify the compositor that the xdg_importer object will no longer be
* used.
*/
static inline void
zxdg_importer_v2_destroy(struct zxdg_importer_v2 *zxdg_importer_v2)
{
wl_proxy_marshal_flags((struct wl_proxy *) zxdg_importer_v2,
ZXDG_IMPORTER_V2_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_importer_v2), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_zxdg_importer_v2
*
* The import_toplevel request imports a surface from any client given a handle
* retrieved by exporting said surface using xdg_exporter.export_toplevel.
* When called, a new xdg_imported object will be created. This new object
* represents the imported surface, and the importing client can
* manipulate its relationship using it. See xdg_imported for details.
*/
static inline struct zxdg_imported_v2 *
zxdg_importer_v2_import_toplevel(struct zxdg_importer_v2 *zxdg_importer_v2, const char *handle)
{
struct wl_proxy *id;
id = wl_proxy_marshal_flags((struct wl_proxy *) zxdg_importer_v2,
ZXDG_IMPORTER_V2_IMPORT_TOPLEVEL, &zxdg_imported_v2_interface, wl_proxy_get_version((struct wl_proxy *) zxdg_importer_v2), 0, NULL, handle);
return (struct zxdg_imported_v2 *) id;
}
/**
* @ingroup iface_zxdg_exported_v2
* @struct zxdg_exported_v2_listener
*/
struct zxdg_exported_v2_listener {
/**
* the exported surface handle
*
* The handle event contains the unique handle of this exported
* surface reference. It may be shared with any client, which then
* can use it to import the surface by calling
* xdg_importer.import_toplevel. A handle may be used to import the
* surface multiple times.
* @param handle the exported surface handle
*/
void (*handle)(void *data,
struct zxdg_exported_v2 *zxdg_exported_v2,
const char *handle);
};
/**
* @ingroup iface_zxdg_exported_v2
*/
static inline int
zxdg_exported_v2_add_listener(struct zxdg_exported_v2 *zxdg_exported_v2,
const struct zxdg_exported_v2_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) zxdg_exported_v2,
(void (**)(void)) listener, data);
}
#define ZXDG_EXPORTED_V2_DESTROY 0
/**
* @ingroup iface_zxdg_exported_v2
*/
#define ZXDG_EXPORTED_V2_HANDLE_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_exported_v2
*/
#define ZXDG_EXPORTED_V2_DESTROY_SINCE_VERSION 1
/** @ingroup iface_zxdg_exported_v2 */
static inline void
zxdg_exported_v2_set_user_data(struct zxdg_exported_v2 *zxdg_exported_v2, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zxdg_exported_v2, user_data);
}
/** @ingroup iface_zxdg_exported_v2 */
static inline void *
zxdg_exported_v2_get_user_data(struct zxdg_exported_v2 *zxdg_exported_v2)
{
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_exported_v2);
}
static inline uint32_t
zxdg_exported_v2_get_version(struct zxdg_exported_v2 *zxdg_exported_v2)
{
return wl_proxy_get_version((struct wl_proxy *) zxdg_exported_v2);
}
/**
* @ingroup iface_zxdg_exported_v2
*
* Revoke the previously exported surface. This invalidates any
* relationship the importer may have set up using the xdg_imported created
* given the handle sent via xdg_exported.handle.
*/
static inline void
zxdg_exported_v2_destroy(struct zxdg_exported_v2 *zxdg_exported_v2)
{
wl_proxy_marshal_flags((struct wl_proxy *) zxdg_exported_v2,
ZXDG_EXPORTED_V2_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_exported_v2), WL_MARSHAL_FLAG_DESTROY);
}
#ifndef ZXDG_IMPORTED_V2_ERROR_ENUM
#define ZXDG_IMPORTED_V2_ERROR_ENUM
/**
* @ingroup iface_zxdg_imported_v2
* error values
*
* These errors can be emitted in response to invalid xdg_imported
* requests.
*/
enum zxdg_imported_v2_error {
/**
* surface is not an xdg_toplevel
*/
ZXDG_IMPORTED_V2_ERROR_INVALID_SURFACE = 0,
};
#endif /* ZXDG_IMPORTED_V2_ERROR_ENUM */
/**
* @ingroup iface_zxdg_imported_v2
* @struct zxdg_imported_v2_listener
*/
struct zxdg_imported_v2_listener {
/**
* the imported surface handle has been destroyed
*
* The imported surface handle has been destroyed and any
* relationship set up has been invalidated. This may happen for
* various reasons, for example if the exported surface or the
* exported surface handle has been destroyed, if the handle used
* for importing was invalid.
*/
void (*destroyed)(void *data,
struct zxdg_imported_v2 *zxdg_imported_v2);
};
/**
* @ingroup iface_zxdg_imported_v2
*/
static inline int
zxdg_imported_v2_add_listener(struct zxdg_imported_v2 *zxdg_imported_v2,
const struct zxdg_imported_v2_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) zxdg_imported_v2,
(void (**)(void)) listener, data);
}
#define ZXDG_IMPORTED_V2_DESTROY 0
#define ZXDG_IMPORTED_V2_SET_PARENT_OF 1
/**
* @ingroup iface_zxdg_imported_v2
*/
#define ZXDG_IMPORTED_V2_DESTROYED_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_imported_v2
*/
#define ZXDG_IMPORTED_V2_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zxdg_imported_v2
*/
#define ZXDG_IMPORTED_V2_SET_PARENT_OF_SINCE_VERSION 1
/** @ingroup iface_zxdg_imported_v2 */
static inline void
zxdg_imported_v2_set_user_data(struct zxdg_imported_v2 *zxdg_imported_v2, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zxdg_imported_v2, user_data);
}
/** @ingroup iface_zxdg_imported_v2 */
static inline void *
zxdg_imported_v2_get_user_data(struct zxdg_imported_v2 *zxdg_imported_v2)
{
return wl_proxy_get_user_data((struct wl_proxy *) zxdg_imported_v2);
}
static inline uint32_t
zxdg_imported_v2_get_version(struct zxdg_imported_v2 *zxdg_imported_v2)
{
return wl_proxy_get_version((struct wl_proxy *) zxdg_imported_v2);
}
/**
* @ingroup iface_zxdg_imported_v2
*
* Notify the compositor that it will no longer use the xdg_imported
* object. Any relationship that may have been set up will at this point
* be invalidated.
*/
static inline void
zxdg_imported_v2_destroy(struct zxdg_imported_v2 *zxdg_imported_v2)
{
wl_proxy_marshal_flags((struct wl_proxy *) zxdg_imported_v2,
ZXDG_IMPORTED_V2_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_imported_v2), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_zxdg_imported_v2
*
* Set the imported surface as the parent of some surface of the client.
* The passed surface must be an xdg_toplevel equivalent, otherwise an
* invalid_surface protocol error is sent. Calling this function sets up
* a surface to surface relation with the same stacking and positioning
* semantics as xdg_toplevel.set_parent.
*/
static inline void
zxdg_imported_v2_set_parent_of(struct zxdg_imported_v2 *zxdg_imported_v2, struct wl_surface *surface)
{
wl_proxy_marshal_flags((struct wl_proxy *) zxdg_imported_v2,
ZXDG_IMPORTED_V2_SET_PARENT_OF, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_imported_v2), 0, surface);
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,184 @@
/* Generated by wayland-scanner 1.24.0 */
/*
* Copyright © 2008-2013 Kristian Høgsberg
* Copyright © 2013 Rafael Antognolli
* Copyright © 2013 Jasper St. Pierre
* Copyright © 2010-2013 Intel Corporation
* Copyright © 2015-2017 Samsung Electronics Co., Ltd
* Copyright © 2015-2017 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_output_interface;
extern const struct wl_interface wl_seat_interface;
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface xdg_popup_interface;
extern const struct wl_interface xdg_positioner_interface;
extern const struct wl_interface xdg_surface_interface;
extern const struct wl_interface xdg_toplevel_interface;
static const struct wl_interface *xdg_shell_types[] = {
NULL,
NULL,
NULL,
NULL,
&xdg_positioner_interface,
&xdg_surface_interface,
&wl_surface_interface,
&xdg_toplevel_interface,
&xdg_popup_interface,
&xdg_surface_interface,
&xdg_positioner_interface,
&xdg_toplevel_interface,
&wl_seat_interface,
NULL,
NULL,
NULL,
&wl_seat_interface,
NULL,
&wl_seat_interface,
NULL,
NULL,
&wl_output_interface,
&wl_seat_interface,
NULL,
&xdg_positioner_interface,
NULL,
};
static const struct wl_message xdg_wm_base_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "create_positioner", "n", xdg_shell_types + 4 },
{ "get_xdg_surface", "no", xdg_shell_types + 5 },
{ "pong", "u", xdg_shell_types + 0 },
};
static const struct wl_message xdg_wm_base_events[] = {
{ "ping", "u", xdg_shell_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_wm_base_interface = {
"xdg_wm_base", 7,
4, xdg_wm_base_requests,
1, xdg_wm_base_events,
};
static const struct wl_message xdg_positioner_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "set_size", "ii", xdg_shell_types + 0 },
{ "set_anchor_rect", "iiii", xdg_shell_types + 0 },
{ "set_anchor", "u", xdg_shell_types + 0 },
{ "set_gravity", "u", xdg_shell_types + 0 },
{ "set_constraint_adjustment", "u", xdg_shell_types + 0 },
{ "set_offset", "ii", xdg_shell_types + 0 },
{ "set_reactive", "3", xdg_shell_types + 0 },
{ "set_parent_size", "3ii", xdg_shell_types + 0 },
{ "set_parent_configure", "3u", xdg_shell_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_positioner_interface = {
"xdg_positioner", 7,
10, xdg_positioner_requests,
0, NULL,
};
static const struct wl_message xdg_surface_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "get_toplevel", "n", xdg_shell_types + 7 },
{ "get_popup", "n?oo", xdg_shell_types + 8 },
{ "set_window_geometry", "iiii", xdg_shell_types + 0 },
{ "ack_configure", "u", xdg_shell_types + 0 },
};
static const struct wl_message xdg_surface_events[] = {
{ "configure", "u", xdg_shell_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_surface_interface = {
"xdg_surface", 7,
5, xdg_surface_requests,
1, xdg_surface_events,
};
static const struct wl_message xdg_toplevel_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "set_parent", "?o", xdg_shell_types + 11 },
{ "set_title", "s", xdg_shell_types + 0 },
{ "set_app_id", "s", xdg_shell_types + 0 },
{ "show_window_menu", "ouii", xdg_shell_types + 12 },
{ "move", "ou", xdg_shell_types + 16 },
{ "resize", "ouu", xdg_shell_types + 18 },
{ "set_max_size", "ii", xdg_shell_types + 0 },
{ "set_min_size", "ii", xdg_shell_types + 0 },
{ "set_maximized", "", xdg_shell_types + 0 },
{ "unset_maximized", "", xdg_shell_types + 0 },
{ "set_fullscreen", "?o", xdg_shell_types + 21 },
{ "unset_fullscreen", "", xdg_shell_types + 0 },
{ "set_minimized", "", xdg_shell_types + 0 },
};
static const struct wl_message xdg_toplevel_events[] = {
{ "configure", "iia", xdg_shell_types + 0 },
{ "close", "", xdg_shell_types + 0 },
{ "configure_bounds", "4ii", xdg_shell_types + 0 },
{ "wm_capabilities", "5a", xdg_shell_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_toplevel_interface = {
"xdg_toplevel", 7,
14, xdg_toplevel_requests,
4, xdg_toplevel_events,
};
static const struct wl_message xdg_popup_requests[] = {
{ "destroy", "", xdg_shell_types + 0 },
{ "grab", "ou", xdg_shell_types + 22 },
{ "reposition", "3ou", xdg_shell_types + 24 },
};
static const struct wl_message xdg_popup_events[] = {
{ "configure", "iiii", xdg_shell_types + 0 },
{ "popup_done", "", xdg_shell_types + 0 },
{ "repositioned", "3u", xdg_shell_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_popup_interface = {
"xdg_popup", 7,
3, xdg_popup_requests,
3, xdg_popup_events,
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,83 @@
/* Generated by wayland-scanner 1.24.0 */
/*
* Copyright © 2023-2024 Matthias Klumpp
* Copyright © 2024 David Edmundson
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_buffer_interface;
extern const struct wl_interface xdg_toplevel_interface;
extern const struct wl_interface xdg_toplevel_icon_v1_interface;
static const struct wl_interface *xdg_toplevel_icon_v1_types[] = {
NULL,
&xdg_toplevel_icon_v1_interface,
&xdg_toplevel_interface,
&xdg_toplevel_icon_v1_interface,
&wl_buffer_interface,
NULL,
};
static const struct wl_message xdg_toplevel_icon_manager_v1_requests[] = {
{ "destroy", "", xdg_toplevel_icon_v1_types + 0 },
{ "create_icon", "n", xdg_toplevel_icon_v1_types + 1 },
{ "set_icon", "o?o", xdg_toplevel_icon_v1_types + 2 },
};
static const struct wl_message xdg_toplevel_icon_manager_v1_events[] = {
{ "icon_size", "i", xdg_toplevel_icon_v1_types + 0 },
{ "done", "", xdg_toplevel_icon_v1_types + 0 },
};
WL_PRIVATE const struct wl_interface xdg_toplevel_icon_manager_v1_interface = {
"xdg_toplevel_icon_manager_v1", 1,
3, xdg_toplevel_icon_manager_v1_requests,
2, xdg_toplevel_icon_manager_v1_events,
};
static const struct wl_message xdg_toplevel_icon_v1_requests[] = {
{ "destroy", "", xdg_toplevel_icon_v1_types + 0 },
{ "set_name", "s", xdg_toplevel_icon_v1_types + 0 },
{ "add_buffer", "oi", xdg_toplevel_icon_v1_types + 4 },
};
WL_PRIVATE const struct wl_interface xdg_toplevel_icon_v1_interface = {
"xdg_toplevel_icon_v1", 1,
3, xdg_toplevel_icon_v1_requests,
0, NULL,
};

View File

@ -0,0 +1,410 @@
/* Generated by wayland-scanner 1.24.0 */
#ifndef XDG_TOPLEVEL_ICON_V1_CLIENT_PROTOCOL_H
#define XDG_TOPLEVEL_ICON_V1_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_xdg_toplevel_icon_v1 The xdg_toplevel_icon_v1 protocol
* protocol to assign icons to toplevels
*
* @section page_desc_xdg_toplevel_icon_v1 Description
*
* This protocol allows clients to set icons for their toplevel surfaces
* either via the XDG icon stock (using an icon name), or from pixel data.
*
* A toplevel icon represents the individual toplevel (unlike the application
* or launcher icon, which represents the application as a whole), and may be
* shown in window switchers, window overviews and taskbars that list
* individual windows.
*
* This document adheres to RFC 2119 when using words like "must",
* "should", "may", etc.
*
* Warning! The protocol described in this file is currently in the testing
* phase. Backward compatible changes may be added together with the
* corresponding interface version bump. Backward incompatible changes can
* only be done by creating a new major version of the extension.
*
* @section page_ifaces_xdg_toplevel_icon_v1 Interfaces
* - @subpage page_iface_xdg_toplevel_icon_manager_v1 - interface to manage toplevel icons
* - @subpage page_iface_xdg_toplevel_icon_v1 - a toplevel window icon
* @section page_copyright_xdg_toplevel_icon_v1 Copyright
* <pre>
*
* Copyright © 2023-2024 Matthias Klumpp
* Copyright © 2024 David Edmundson
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* </pre>
*/
struct wl_buffer;
struct xdg_toplevel;
struct xdg_toplevel_icon_manager_v1;
struct xdg_toplevel_icon_v1;
#ifndef XDG_TOPLEVEL_ICON_MANAGER_V1_INTERFACE
#define XDG_TOPLEVEL_ICON_MANAGER_V1_INTERFACE
/**
* @page page_iface_xdg_toplevel_icon_manager_v1 xdg_toplevel_icon_manager_v1
* @section page_iface_xdg_toplevel_icon_manager_v1_desc Description
*
* This interface allows clients to create toplevel window icons and set
* them on toplevel windows to be displayed to the user.
* @section page_iface_xdg_toplevel_icon_manager_v1_api API
* See @ref iface_xdg_toplevel_icon_manager_v1.
*/
/**
* @defgroup iface_xdg_toplevel_icon_manager_v1 The xdg_toplevel_icon_manager_v1 interface
*
* This interface allows clients to create toplevel window icons and set
* them on toplevel windows to be displayed to the user.
*/
extern const struct wl_interface xdg_toplevel_icon_manager_v1_interface;
#endif
#ifndef XDG_TOPLEVEL_ICON_V1_INTERFACE
#define XDG_TOPLEVEL_ICON_V1_INTERFACE
/**
* @page page_iface_xdg_toplevel_icon_v1 xdg_toplevel_icon_v1
* @section page_iface_xdg_toplevel_icon_v1_desc Description
*
* This interface defines a toplevel icon.
* An icon can have a name, and multiple buffers.
* In order to be applied, the icon must have either a name, or at least
* one buffer assigned. Applying an empty icon (with no buffer or name) to
* a toplevel should reset its icon to the default icon.
*
* It is up to compositor policy whether to prefer using a buffer or loading
* an icon via its name. See 'set_name' and 'add_buffer' for details.
* @section page_iface_xdg_toplevel_icon_v1_api API
* See @ref iface_xdg_toplevel_icon_v1.
*/
/**
* @defgroup iface_xdg_toplevel_icon_v1 The xdg_toplevel_icon_v1 interface
*
* This interface defines a toplevel icon.
* An icon can have a name, and multiple buffers.
* In order to be applied, the icon must have either a name, or at least
* one buffer assigned. Applying an empty icon (with no buffer or name) to
* a toplevel should reset its icon to the default icon.
*
* It is up to compositor policy whether to prefer using a buffer or loading
* an icon via its name. See 'set_name' and 'add_buffer' for details.
*/
extern const struct wl_interface xdg_toplevel_icon_v1_interface;
#endif
/**
* @ingroup iface_xdg_toplevel_icon_manager_v1
* @struct xdg_toplevel_icon_manager_v1_listener
*/
struct xdg_toplevel_icon_manager_v1_listener {
/**
* describes a supported & preferred icon size
*
* This event indicates an icon size the compositor prefers to be
* available if the client has scalable icons and can render to any
* size.
*
* When the 'xdg_toplevel_icon_manager_v1' object is created, the
* compositor may send one or more 'icon_size' events to describe
* the list of preferred icon sizes. If the compositor has no size
* preference, it may not send any 'icon_size' event, and it is up
* to the client to decide a suitable icon size.
*
* A sequence of 'icon_size' events must be finished with a 'done'
* event. If the compositor has no size preferences, it must still
* send the 'done' event, without any preceding 'icon_size' events.
* @param size the edge size of the square icon in surface-local coordinates, e.g. 64
*/
void (*icon_size)(void *data,
struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1,
int32_t size);
/**
* all information has been sent
*
* This event is sent after all 'icon_size' events have been
* sent.
*/
void (*done)(void *data,
struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1);
};
/**
* @ingroup iface_xdg_toplevel_icon_manager_v1
*/
static inline int
xdg_toplevel_icon_manager_v1_add_listener(struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1,
const struct xdg_toplevel_icon_manager_v1_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) xdg_toplevel_icon_manager_v1,
(void (**)(void)) listener, data);
}
#define XDG_TOPLEVEL_ICON_MANAGER_V1_DESTROY 0
#define XDG_TOPLEVEL_ICON_MANAGER_V1_CREATE_ICON 1
#define XDG_TOPLEVEL_ICON_MANAGER_V1_SET_ICON 2
/**
* @ingroup iface_xdg_toplevel_icon_manager_v1
*/
#define XDG_TOPLEVEL_ICON_MANAGER_V1_ICON_SIZE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel_icon_manager_v1
*/
#define XDG_TOPLEVEL_ICON_MANAGER_V1_DONE_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel_icon_manager_v1
*/
#define XDG_TOPLEVEL_ICON_MANAGER_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel_icon_manager_v1
*/
#define XDG_TOPLEVEL_ICON_MANAGER_V1_CREATE_ICON_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel_icon_manager_v1
*/
#define XDG_TOPLEVEL_ICON_MANAGER_V1_SET_ICON_SINCE_VERSION 1
/** @ingroup iface_xdg_toplevel_icon_manager_v1 */
static inline void
xdg_toplevel_icon_manager_v1_set_user_data(struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) xdg_toplevel_icon_manager_v1, user_data);
}
/** @ingroup iface_xdg_toplevel_icon_manager_v1 */
static inline void *
xdg_toplevel_icon_manager_v1_get_user_data(struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) xdg_toplevel_icon_manager_v1);
}
static inline uint32_t
xdg_toplevel_icon_manager_v1_get_version(struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1)
{
return wl_proxy_get_version((struct wl_proxy *) xdg_toplevel_icon_manager_v1);
}
/**
* @ingroup iface_xdg_toplevel_icon_manager_v1
*
* Destroy the toplevel icon manager.
* This does not destroy objects created with the manager.
*/
static inline void
xdg_toplevel_icon_manager_v1_destroy(struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1)
{
wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel_icon_manager_v1,
XDG_TOPLEVEL_ICON_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel_icon_manager_v1), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_xdg_toplevel_icon_manager_v1
*
* Creates a new icon object. This icon can then be attached to a
* xdg_toplevel via the 'set_icon' request.
*/
static inline struct xdg_toplevel_icon_v1 *
xdg_toplevel_icon_manager_v1_create_icon(struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1)
{
struct wl_proxy *id;
id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel_icon_manager_v1,
XDG_TOPLEVEL_ICON_MANAGER_V1_CREATE_ICON, &xdg_toplevel_icon_v1_interface, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel_icon_manager_v1), 0, NULL);
return (struct xdg_toplevel_icon_v1 *) id;
}
/**
* @ingroup iface_xdg_toplevel_icon_manager_v1
*
* This request assigns the icon 'icon' to 'toplevel', or clears the
* toplevel icon if 'icon' was null.
* This state is double-buffered and is applied on the next
* wl_surface.commit of the toplevel.
*
* After making this call, the xdg_toplevel_icon_v1 provided as 'icon'
* can be destroyed by the client without 'toplevel' losing its icon.
* The xdg_toplevel_icon_v1 is immutable from this point, and any
* future attempts to change it must raise the
* 'xdg_toplevel_icon_v1.immutable' protocol error.
*
* The compositor must set the toplevel icon from either the pixel data
* the icon provides, or by loading a stock icon using the icon name.
* See the description of 'xdg_toplevel_icon_v1' for details.
*
* If 'icon' is set to null, the icon of the respective toplevel is reset
* to its default icon (usually the icon of the application, derived from
* its desktop-entry file, or a placeholder icon).
* If this request is passed an icon with no pixel buffers or icon name
* assigned, the icon must be reset just like if 'icon' was null.
*/
static inline void
xdg_toplevel_icon_manager_v1_set_icon(struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1, struct xdg_toplevel *toplevel, struct xdg_toplevel_icon_v1 *icon)
{
wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel_icon_manager_v1,
XDG_TOPLEVEL_ICON_MANAGER_V1_SET_ICON, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel_icon_manager_v1), 0, toplevel, icon);
}
#ifndef XDG_TOPLEVEL_ICON_V1_ERROR_ENUM
#define XDG_TOPLEVEL_ICON_V1_ERROR_ENUM
enum xdg_toplevel_icon_v1_error {
/**
* the provided buffer does not satisfy requirements
*/
XDG_TOPLEVEL_ICON_V1_ERROR_INVALID_BUFFER = 1,
/**
* the icon has already been assigned to a toplevel and must not be changed
*/
XDG_TOPLEVEL_ICON_V1_ERROR_IMMUTABLE = 2,
/**
* the provided buffer has been destroyed before the toplevel icon
*/
XDG_TOPLEVEL_ICON_V1_ERROR_NO_BUFFER = 3,
};
#endif /* XDG_TOPLEVEL_ICON_V1_ERROR_ENUM */
#define XDG_TOPLEVEL_ICON_V1_DESTROY 0
#define XDG_TOPLEVEL_ICON_V1_SET_NAME 1
#define XDG_TOPLEVEL_ICON_V1_ADD_BUFFER 2
/**
* @ingroup iface_xdg_toplevel_icon_v1
*/
#define XDG_TOPLEVEL_ICON_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel_icon_v1
*/
#define XDG_TOPLEVEL_ICON_V1_SET_NAME_SINCE_VERSION 1
/**
* @ingroup iface_xdg_toplevel_icon_v1
*/
#define XDG_TOPLEVEL_ICON_V1_ADD_BUFFER_SINCE_VERSION 1
/** @ingroup iface_xdg_toplevel_icon_v1 */
static inline void
xdg_toplevel_icon_v1_set_user_data(struct xdg_toplevel_icon_v1 *xdg_toplevel_icon_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) xdg_toplevel_icon_v1, user_data);
}
/** @ingroup iface_xdg_toplevel_icon_v1 */
static inline void *
xdg_toplevel_icon_v1_get_user_data(struct xdg_toplevel_icon_v1 *xdg_toplevel_icon_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) xdg_toplevel_icon_v1);
}
static inline uint32_t
xdg_toplevel_icon_v1_get_version(struct xdg_toplevel_icon_v1 *xdg_toplevel_icon_v1)
{
return wl_proxy_get_version((struct wl_proxy *) xdg_toplevel_icon_v1);
}
/**
* @ingroup iface_xdg_toplevel_icon_v1
*
* Destroys the 'xdg_toplevel_icon_v1' object.
* The icon must still remain set on every toplevel it was assigned to,
* until the toplevel icon is reset explicitly.
*/
static inline void
xdg_toplevel_icon_v1_destroy(struct xdg_toplevel_icon_v1 *xdg_toplevel_icon_v1)
{
wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel_icon_v1,
XDG_TOPLEVEL_ICON_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel_icon_v1), WL_MARSHAL_FLAG_DESTROY);
}
/**
* @ingroup iface_xdg_toplevel_icon_v1
*
* This request assigns an icon name to this icon.
* Any previously set name is overridden.
*
* The compositor must resolve 'icon_name' according to the lookup rules
* described in the XDG icon theme specification[1] using the
* environment's current icon theme.
*
* If the compositor does not support icon names or cannot resolve
* 'icon_name' according to the XDG icon theme specification it must
* fall back to using pixel buffer data instead.
*
* If this request is made after the icon has been assigned to a toplevel
* via 'set_icon', a 'immutable' error must be raised.
*
* [1]: https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
*/
static inline void
xdg_toplevel_icon_v1_set_name(struct xdg_toplevel_icon_v1 *xdg_toplevel_icon_v1, const char *icon_name)
{
wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel_icon_v1,
XDG_TOPLEVEL_ICON_V1_SET_NAME, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel_icon_v1), 0, icon_name);
}
/**
* @ingroup iface_xdg_toplevel_icon_v1
*
* This request adds pixel data supplied as wl_buffer to the icon.
*
* The client should add pixel data for all icon sizes and scales that
* it can provide, or which are explicitly requested by the compositor
* via 'icon_size' events on xdg_toplevel_icon_manager_v1.
*
* The wl_buffer supplying pixel data as 'buffer' must be backed by wl_shm
* and must be a square (width and height being equal).
* If any of these buffer requirements are not fulfilled, a 'invalid_buffer'
* error must be raised.
*
* If this icon instance already has a buffer of the same size and scale
* from a previous 'add_buffer' request, data from the last request
* overrides the preexisting pixel data.
*
* The wl_buffer must be kept alive for as long as the xdg_toplevel_icon
* it is associated with is not destroyed, otherwise a 'no_buffer' error
* is raised. The buffer contents must not be modified after it was
* assigned to the icon. As a result, the region of the wl_shm_pool's
* backing storage used for the wl_buffer must not be modified after this
* request is sent. The wl_buffer.release event is unused.
*
* If this request is made after the icon has been assigned to a toplevel
* via 'set_icon', a 'immutable' error must be raised.
*/
static inline void
xdg_toplevel_icon_v1_add_buffer(struct xdg_toplevel_icon_v1 *xdg_toplevel_icon_v1, struct wl_buffer *buffer, int32_t scale)
{
wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel_icon_v1,
XDG_TOPLEVEL_ICON_V1_ADD_BUFFER, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel_icon_v1), 0, buffer, scale);
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="appmenu">
<copyright><![CDATA[
SPDX-FileCopyrightText: 2017 David Edmundson
SPDX-License-Identifier: MIT
]]></copyright>
<interface name="org_kde_kwin_appmenu_manager" version="2">
<description summary="appmenu dbus address interface">
This interface allows a client to link a window (or wl_surface) to an com.canonical.dbusmenu
interface registered on DBus.
</description>
<request name="create">
<arg name="id" type="new_id" interface="org_kde_kwin_appmenu"/>
<arg name="surface" type="object" interface="wl_surface"/>
</request>
<!-- version 2 additions-->
<request name="release" type="destructor" since="2">
<description summary="destroy the org_kde_kwin_appmenu_manager object" />
</request>
</interface>
<interface name="org_kde_kwin_appmenu" version="2">
<description summary="appmenu dbus address interface">
The DBus service name and object path where the appmenu interface is present
The object should be registered on the session bus before sending this request.
If not applicable, clients should remove this object.
</description>
<request name="set_address">
<description summary="initialise or update the location of the AppMenu interface">
Set or update the service name and object path.
Strings should be formatted in Latin-1 matching the relevant DBus specifications.
</description>
<arg name="service_name" type="string" />
<arg name="object_path" type="string" />
</request>
<request name="release" type="destructor">
<description summary="release the appmenu object"/>
</request>
</interface>
</protocol>

View File

@ -0,0 +1,162 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="cursor_shape_v1">
<copyright>
Copyright 2018 The Chromium Authors
Copyright 2023 Simon Ser
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<interface name="wp_cursor_shape_manager_v1" version="2">
<description summary="cursor shape manager">
This global offers an alternative, optional way to set cursor images. This
new way uses enumerated cursors instead of a wl_surface like
wl_pointer.set_cursor does.
Warning! The protocol described in this file is currently in the testing
phase. Backward compatible changes may be added together with the
corresponding interface version bump. Backward incompatible changes can
only be done by creating a new major version of the extension.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the manager">
Destroy the cursor shape manager.
</description>
</request>
<request name="get_pointer">
<description summary="manage the cursor shape of a pointer device">
Obtain a wp_cursor_shape_device_v1 for a wl_pointer object.
When the pointer capability is removed from the wl_seat, the
wp_cursor_shape_device_v1 object becomes inert.
</description>
<arg name="cursor_shape_device" type="new_id" interface="wp_cursor_shape_device_v1"/>
<arg name="pointer" type="object" interface="wl_pointer"/>
</request>
<request name="get_tablet_tool_v2">
<description summary="manage the cursor shape of a tablet tool device">
Obtain a wp_cursor_shape_device_v1 for a zwp_tablet_tool_v2 object.
When the zwp_tablet_tool_v2 is removed, the wp_cursor_shape_device_v1
object becomes inert.
</description>
<arg name="cursor_shape_device" type="new_id" interface="wp_cursor_shape_device_v1"/>
<arg name="tablet_tool" type="object" interface="zwp_tablet_tool_v2"/>
</request>
</interface>
<interface name="wp_cursor_shape_device_v1" version="2">
<description summary="cursor shape for a device">
This interface allows clients to set the cursor shape.
</description>
<enum name="shape">
<description summary="cursor shapes">
This enum describes cursor shapes.
The names are taken from the CSS W3C specification:
https://w3c.github.io/csswg-drafts/css-ui/#cursor
with a few additions.
Note that there are some groups of cursor shapes that are related:
The first group is drag-and-drop cursors which are used to indicate
the selected action during dnd operations. The second group is resize
cursors which are used to indicate resizing and moving possibilities
on window borders. It is recommended that the shapes in these groups
should use visually compatible images and metaphors.
</description>
<entry name="default" value="1" summary="default cursor"/>
<entry name="context_menu" value="2" summary="a context menu is available for the object under the cursor"/>
<entry name="help" value="3" summary="help is available for the object under the cursor"/>
<entry name="pointer" value="4" summary="pointer that indicates a link or another interactive element"/>
<entry name="progress" value="5" summary="progress indicator"/>
<entry name="wait" value="6" summary="program is busy, user should wait"/>
<entry name="cell" value="7" summary="a cell or set of cells may be selected"/>
<entry name="crosshair" value="8" summary="simple crosshair"/>
<entry name="text" value="9" summary="text may be selected"/>
<entry name="vertical_text" value="10" summary="vertical text may be selected"/>
<entry name="alias" value="11" summary="drag-and-drop: alias of/shortcut to something is to be created"/>
<entry name="copy" value="12" summary="drag-and-drop: something is to be copied"/>
<entry name="move" value="13" summary="drag-and-drop: something is to be moved"/>
<entry name="no_drop" value="14" summary="drag-and-drop: the dragged item cannot be dropped at the current cursor location"/>
<entry name="not_allowed" value="15" summary="drag-and-drop: the requested action will not be carried out"/>
<entry name="grab" value="16" summary="drag-and-drop: something can be grabbed"/>
<entry name="grabbing" value="17" summary="drag-and-drop: something is being grabbed"/>
<entry name="e_resize" value="18" summary="resizing: the east border is to be moved"/>
<entry name="n_resize" value="19" summary="resizing: the north border is to be moved"/>
<entry name="ne_resize" value="20" summary="resizing: the north-east corner is to be moved"/>
<entry name="nw_resize" value="21" summary="resizing: the north-west corner is to be moved"/>
<entry name="s_resize" value="22" summary="resizing: the south border is to be moved"/>
<entry name="se_resize" value="23" summary="resizing: the south-east corner is to be moved"/>
<entry name="sw_resize" value="24" summary="resizing: the south-west corner is to be moved"/>
<entry name="w_resize" value="25" summary="resizing: the west border is to be moved"/>
<entry name="ew_resize" value="26" summary="resizing: the east and west borders are to be moved"/>
<entry name="ns_resize" value="27" summary="resizing: the north and south borders are to be moved"/>
<entry name="nesw_resize" value="28" summary="resizing: the north-east and south-west corners are to be moved"/>
<entry name="nwse_resize" value="29" summary="resizing: the north-west and south-east corners are to be moved"/>
<entry name="col_resize" value="30" summary="resizing: that the item/column can be resized horizontally"/>
<entry name="row_resize" value="31" summary="resizing: that the item/row can be resized vertically"/>
<entry name="all_scroll" value="32" summary="something can be scrolled in any direction"/>
<entry name="zoom_in" value="33" summary="something can be zoomed in"/>
<entry name="zoom_out" value="34" summary="something can be zoomed out"/>
<entry name="dnd_ask" value="35" summary="drag-and-drop: the user will select which action will be carried out (non-css value)" since="2"/>
<entry name="all_resize" value="36" summary="resizing: something can be moved or resized in any direction (non-css value)" since="2"/>
</enum>
<enum name="error">
<entry name="invalid_shape" value="1"
summary="the specified shape value is invalid"/>
</enum>
<request name="destroy" type="destructor">
<description summary="destroy the cursor shape device">
Destroy the cursor shape device.
The device cursor shape remains unchanged.
</description>
</request>
<request name="set_shape">
<description summary="set device cursor to the shape">
Sets the device cursor to the specified shape. The compositor will
change the cursor image based on the specified shape.
The cursor actually changes only if the input device focus is one of
the requesting client's surfaces. If any, the previous cursor image
(surface or shape) is replaced.
The "shape" argument must be a valid enum entry, otherwise the
invalid_shape protocol error is raised.
This is similar to the wl_pointer.set_cursor and
zwp_tablet_tool_v2.set_cursor requests, but this request accepts a
shape instead of contents in the form of a surface. Clients can mix
set_cursor and set_shape requests.
The serial parameter must match the latest wl_pointer.enter or
zwp_tablet_tool_v2.proximity_in serial number sent to the client.
Otherwise the request will be ignored.
</description>
<arg name="serial" type="uint" summary="serial number of the enter event"/>
<arg name="shape" type="uint" enum="shape"/>
</request>
</interface>
</protocol>

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="fractional_scale_v1">
<copyright>
Copyright © 2022 Kenny Levinsen
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<description summary="Protocol for requesting fractional surface scales">
This protocol allows a compositor to suggest for surfaces to render at
fractional scales.
A client can submit scaled content by utilizing wp_viewport. This is done by
creating a wp_viewport object for the surface and setting the destination
rectangle to the surface size before the scale factor is applied.
The buffer size is calculated by multiplying the surface size by the
intended scale.
The wl_surface buffer scale should remain set to 1.
If a surface has a surface-local size of 100 px by 50 px and wishes to
submit buffers with a scale of 1.5, then a buffer of 150px by 75 px should
be used and the wp_viewport destination rectangle should be 100 px by 50 px.
For toplevel surfaces, the size is rounded halfway away from zero. The
rounding algorithm for subsurface position and size is not defined.
</description>
<interface name="wp_fractional_scale_manager_v1" version="1">
<description summary="fractional surface scale information">
A global interface for requesting surfaces to use fractional scales.
</description>
<request name="destroy" type="destructor">
<description summary="unbind the fractional surface scale interface">
Informs the server that the client will not be using this protocol
object anymore. This does not affect any other objects,
wp_fractional_scale_v1 objects included.
</description>
</request>
<enum name="error">
<entry name="fractional_scale_exists" value="0"
summary="the surface already has a fractional_scale object associated"/>
</enum>
<request name="get_fractional_scale">
<description summary="extend surface interface for scale information">
Create an add-on object for the the wl_surface to let the compositor
request fractional scales. If the given wl_surface already has a
wp_fractional_scale_v1 object associated, the fractional_scale_exists
protocol error is raised.
</description>
<arg name="id" type="new_id" interface="wp_fractional_scale_v1"
summary="the new surface scale info interface id"/>
<arg name="surface" type="object" interface="wl_surface"
summary="the surface"/>
</request>
</interface>
<interface name="wp_fractional_scale_v1" version="1">
<description summary="fractional scale interface to a wl_surface">
An additional interface to a wl_surface object which allows the compositor
to inform the client of the preferred scale.
</description>
<request name="destroy" type="destructor">
<description summary="remove surface scale information for surface">
Destroy the fractional scale object. When this object is destroyed,
preferred_scale events will no longer be sent.
</description>
</request>
<event name="preferred_scale">
<description summary="notify of new preferred scale">
Notification of a new preferred scale for this surface that the
compositor suggests that the client should use.
The sent scale is the numerator of a fraction with a denominator of 120.
</description>
<arg name="scale" type="uint" summary="the new preferred scale"/>
</event>
</interface>
</protocol>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,456 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="text_input_unstable_v3">
<copyright>
Copyright © 2012, 2013 Intel Corporation
Copyright © 2015, 2016 Jan Arne Petersen
Copyright © 2017, 2018 Red Hat, Inc.
Copyright © 2018 Purism SPC
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<description summary="Protocol for composing text">
This protocol allows compositors to act as input methods and to send text
to applications. A text input object is used to manage state of what are
typically text entry fields in the application.
This document adheres to the RFC 2119 when using words like "must",
"should", "may", etc.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
</description>
<interface name="zwp_text_input_v3" version="1">
<description summary="text input">
The zwp_text_input_v3 interface represents text input and input methods
associated with a seat. It provides enter/leave events to follow the
text input focus for a seat.
Requests are used to enable/disable the text-input object and set
state information like surrounding and selected text or the content type.
The information about the entered text is sent to the text-input object
via the preedit_string and commit_string events.
Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices
must not point to middle bytes inside a code point: they must either
point to the first byte of a code point or to the end of the buffer.
Lengths must be measured between two valid indices.
Focus moving throughout surfaces will result in the emission of
zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused
surface must commit zwp_text_input_v3.enable and
zwp_text_input_v3.disable requests as the keyboard focus moves across
editable and non-editable elements of the UI. Those two requests are not
expected to be paired with each other, the compositor must be able to
handle consecutive series of the same request.
State is sent by the state requests (set_surrounding_text,
set_content_type and set_cursor_rectangle) and a commit request. After an
enter event or disable request all state information is invalidated and
needs to be resent by the client.
</description>
<request name="destroy" type="destructor">
<description summary="Destroy the wp_text_input">
Destroy the wp_text_input object. Also disables all surfaces enabled
through this wp_text_input object.
</description>
</request>
<request name="enable">
<description summary="Request text input to be enabled">
Requests text input on the surface previously obtained from the enter
event.
This request must be issued every time the focused text input changes
to a new one, including within the current surface. Use
zwp_text_input_v3.disable when there is no longer any input focus on
the current surface.
Clients must not enable more than one text input on the single seat
and should disable the current text input before enabling the new one.
Requests to enable a text input when another text input is enabled
on the same seat must be ignored by compositor.
This request resets all state associated with previous enable, disable,
set_surrounding_text, set_text_change_cause, set_content_type, and
set_cursor_rectangle requests, as well as the state associated with
preedit_string, commit_string, and delete_surrounding_text events.
The set_surrounding_text, set_content_type and set_cursor_rectangle
requests must follow if the text input supports the necessary
functionality.
State set with this request is double-buffered. It will get applied on
the next zwp_text_input_v3.commit request, and stay valid until the
next committed enable or disable request.
The changes must be applied by the compositor after issuing a
zwp_text_input_v3.commit request.
</description>
</request>
<request name="disable">
<description summary="Disable text input on a surface">
Explicitly disable text input on the current surface (typically when
there is no focus on any text entry inside the surface).
State set with this request is double-buffered. It will get applied on
the next zwp_text_input_v3.commit request.
</description>
</request>
<request name="set_surrounding_text">
<description summary="sets the surrounding text">
Sets the surrounding plain text around the input, excluding the preedit
text.
The client should notify the compositor of any changes in any of the
values carried with this request, including changes caused by handling
incoming text-input events as well as changes caused by other
mechanisms like keyboard typing.
If the client is unaware of the text around the cursor, it should not
issue this request, to signify lack of support to the compositor.
Text is UTF-8 encoded, and should include the cursor position, the
complete selection and additional characters before and after them.
There is a maximum length of wayland messages, so text can not be
longer than 4000 bytes.
Cursor is the byte offset of the cursor within text buffer.
Anchor is the byte offset of the selection anchor within text buffer.
If there is no selected text, anchor is the same as cursor.
If any preedit text is present, it is replaced with a cursor for the
purpose of this event.
Values set with this request are double-buffered. They will get applied
on the next zwp_text_input_v3.commit request, and stay valid until the
next committed enable or disable request.
The initial state for affected fields is empty, meaning that the text
input does not support sending surrounding text. If the empty values
get applied, subsequent attempts to change them may have no effect.
</description>
<arg name="text" type="string"/>
<arg name="cursor" type="int"/>
<arg name="anchor" type="int"/>
</request>
<enum name="change_cause">
<description summary="text change reason">
Reason for the change of surrounding text or cursor posision.
</description>
<entry name="input_method" value="0" summary="input method caused the change"/>
<entry name="other" value="1" summary="something else than the input method caused the change"/>
</enum>
<request name="set_text_change_cause">
<description summary="indicates the cause of surrounding text change">
Tells the compositor why the text surrounding the cursor changed.
Whenever the client detects an external change in text, cursor, or
anchor posision, it must issue this request to the compositor. This
request is intended to give the input method a chance to update the
preedit text in an appropriate way, e.g. by removing it when the user
starts typing with a keyboard.
cause describes the source of the change.
The value set with this request is double-buffered. It must be applied
and reset to initial at the next zwp_text_input_v3.commit request.
The initial value of cause is input_method.
</description>
<arg name="cause" type="uint" enum="change_cause"/>
</request>
<enum name="content_hint" bitfield="true">
<description summary="content hint">
Content hint is a bitmask to allow to modify the behavior of the text
input.
</description>
<entry name="none" value="0x0" summary="no special behavior"/>
<entry name="completion" value="0x1" summary="suggest word completions"/>
<entry name="spellcheck" value="0x2" summary="suggest word corrections"/>
<entry name="auto_capitalization" value="0x4" summary="switch to uppercase letters at the start of a sentence"/>
<entry name="lowercase" value="0x8" summary="prefer lowercase letters"/>
<entry name="uppercase" value="0x10" summary="prefer uppercase letters"/>
<entry name="titlecase" value="0x20" summary="prefer casing for titles and headings (can be language dependent)"/>
<entry name="hidden_text" value="0x40" summary="characters should be hidden"/>
<entry name="sensitive_data" value="0x80" summary="typed text should not be stored"/>
<entry name="latin" value="0x100" summary="just Latin characters should be entered"/>
<entry name="multiline" value="0x200" summary="the text input is multiline"/>
</enum>
<enum name="content_purpose">
<description summary="content purpose">
The content purpose allows to specify the primary purpose of a text
input.
This allows an input method to show special purpose input panels with
extra characters or to disallow some characters.
</description>
<entry name="normal" value="0" summary="default input, allowing all characters"/>
<entry name="alpha" value="1" summary="allow only alphabetic characters"/>
<entry name="digits" value="2" summary="allow only digits"/>
<entry name="number" value="3" summary="input a number (including decimal separator and sign)"/>
<entry name="phone" value="4" summary="input a phone number"/>
<entry name="url" value="5" summary="input an URL"/>
<entry name="email" value="6" summary="input an email address"/>
<entry name="name" value="7" summary="input a name of a person"/>
<entry name="password" value="8" summary="input a password (combine with sensitive_data hint)"/>
<entry name="pin" value="9" summary="input is a numeric password (combine with sensitive_data hint)"/>
<entry name="date" value="10" summary="input a date"/>
<entry name="time" value="11" summary="input a time"/>
<entry name="datetime" value="12" summary="input a date and time"/>
<entry name="terminal" value="13" summary="input for a terminal"/>
</enum>
<request name="set_content_type">
<description summary="set content purpose and hint">
Sets the content purpose and content hint. While the purpose is the
basic purpose of an input field, the hint flags allow to modify some of
the behavior.
Values set with this request are double-buffered. They will get applied
on the next zwp_text_input_v3.commit request.
Subsequent attempts to update them may have no effect. The values
remain valid until the next committed enable or disable request.
The initial value for hint is none, and the initial value for purpose
is normal.
</description>
<arg name="hint" type="uint" enum="content_hint"/>
<arg name="purpose" type="uint" enum="content_purpose"/>
</request>
<request name="set_cursor_rectangle">
<description summary="set cursor position">
Marks an area around the cursor as a x, y, width, height rectangle in
surface local coordinates.
Allows the compositor to put a window with word suggestions near the
cursor, without obstructing the text being input.
If the client is unaware of the position of edited text, it should not
issue this request, to signify lack of support to the compositor.
Values set with this request are double-buffered. They will get applied
on the next zwp_text_input_v3.commit request, and stay valid until the
next committed enable or disable request.
The initial values describing a cursor rectangle are empty. That means
the text input does not support describing the cursor area. If the
empty values get applied, subsequent attempts to change them may have
no effect.
</description>
<arg name="x" type="int"/>
<arg name="y" type="int"/>
<arg name="width" type="int"/>
<arg name="height" type="int"/>
</request>
<request name="commit">
<description summary="commit state">
Atomically applies state changes recently sent to the compositor.
The commit request establishes and updates the state of the client, and
must be issued after any changes to apply them.
Text input state (enabled status, content purpose, content hint,
surrounding text and change cause, cursor rectangle) is conceptually
double-buffered within the context of a text input, i.e. between a
committed enable request and the following committed enable or disable
request.
Protocol requests modify the pending state, as opposed to the current
state in use by the input method. A commit request atomically applies
all pending state, replacing the current state. After commit, the new
pending state is as documented for each related request.
Requests are applied in the order of arrival.
Neither current nor pending state are modified unless noted otherwise.
The compositor must count the number of commit requests coming from
each zwp_text_input_v3 object and use the count as the serial in done
events.
</description>
</request>
<event name="enter">
<description summary="enter event">
Notification that this seat's text-input focus is on a certain surface.
If client has created multiple text input objects, compositor must send
this event to all of them.
When the seat has the keyboard capability the text-input focus follows
the keyboard focus. This event sets the current surface for the
text-input object.
</description>
<arg name="surface" type="object" interface="wl_surface"/>
</event>
<event name="leave">
<description summary="leave event">
Notification that this seat's text-input focus is no longer on a
certain surface. The client should reset any preedit string previously
set.
The leave notification clears the current surface. It is sent before
the enter notification for the new focus. After leave event, compositor
must ignore requests from any text input instances until next enter
event.
When the seat has the keyboard capability the text-input focus follows
the keyboard focus.
</description>
<arg name="surface" type="object" interface="wl_surface"/>
</event>
<event name="preedit_string">
<description summary="pre-edit">
Notify when a new composing text (pre-edit) should be set at the
current cursor position. Any previously set composing text must be
removed. Any previously existing selected text must be removed.
The argument text contains the pre-edit string buffer.
The parameters cursor_begin and cursor_end are counted in bytes
relative to the beginning of the submitted text buffer. Cursor should
be hidden when both are equal to -1.
They could be represented by the client as a line if both values are
the same, or as a text highlight otherwise.
Values set with this event are double-buffered. They must be applied
and reset to initial on the next zwp_text_input_v3.done event.
The initial value of text is an empty string, and cursor_begin,
cursor_end and cursor_hidden are all 0.
</description>
<arg name="text" type="string" allow-null="true"/>
<arg name="cursor_begin" type="int"/>
<arg name="cursor_end" type="int"/>
</event>
<event name="commit_string">
<description summary="text commit">
Notify when text should be inserted into the editor widget. The text to
commit could be either just a single character after a key press or the
result of some composing (pre-edit).
Values set with this event are double-buffered. They must be applied
and reset to initial on the next zwp_text_input_v3.done event.
The initial value of text is an empty string.
</description>
<arg name="text" type="string" allow-null="true"/>
</event>
<event name="delete_surrounding_text">
<description summary="delete surrounding text">
Notify when the text around the current cursor position should be
deleted.
Before_length and after_length are the number of bytes before and after
the current cursor index (excluding the selection) to delete.
If a preedit text is present, in effect before_length is counted from
the beginning of it, and after_length from its end (see done event
sequence).
Values set with this event are double-buffered. They must be applied
and reset to initial on the next zwp_text_input_v3.done event.
The initial values of both before_length and after_length are 0.
</description>
<arg name="before_length" type="uint" summary="length of text before current cursor position"/>
<arg name="after_length" type="uint" summary="length of text after current cursor position"/>
</event>
<event name="done">
<description summary="apply changes">
Instruct the application to apply changes to state requested by the
preedit_string, commit_string and delete_surrounding_text events. The
state relating to these events is double-buffered, and each one
modifies the pending state. This event replaces the current state with
the pending state.
The application must proceed by evaluating the changes in the following
order:
1. Replace existing preedit string with the cursor.
2. Delete requested surrounding text.
3. Insert commit string with the cursor at its end.
4. Calculate surrounding text to send.
5. Insert new preedit text in cursor position.
6. Place cursor inside preedit text.
The serial number reflects the last state of the zwp_text_input_v3
object known to the compositor. The value of the serial argument must
be equal to the number of commit requests already issued on that object.
When the client receives a done event with a serial different than the
number of past commit requests, it must proceed with evaluating and
applying the changes as normal, except it should not change the current
state of the zwp_text_input_v3 object. All pending state requests
(set_surrounding_text, set_content_type and set_cursor_rectangle) on
the zwp_text_input_v3 object should be sent and committed after
receiving a zwp_text_input_v3.done event with a matching serial.
</description>
<arg name="serial" type="uint"/>
</event>
</interface>
<interface name="zwp_text_input_manager_v3" version="1">
<description summary="text input manager">
A factory for text-input objects. This object is a global singleton.
</description>
<request name="destroy" type="destructor">
<description summary="Destroy the wp_text_input_manager">
Destroy the wp_text_input_manager object.
</description>
</request>
<request name="get_text_input">
<description summary="create a new text input object">
Creates a new text-input object for a given seat.
</description>
<arg name="id" type="new_id" interface="zwp_text_input_v3"/>
<arg name="seat" type="object" interface="wl_seat"/>
</request>
</interface>
</protocol>

View File

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="viewporter">
<copyright>
Copyright © 2013-2016 Collabora, Ltd.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<interface name="wp_viewporter" version="1">
<description summary="surface cropping and scaling">
The global interface exposing surface cropping and scaling
capabilities is used to instantiate an interface extension for a
wl_surface object. This extended interface will then allow
cropping and scaling the surface contents, effectively
disconnecting the direct relationship between the buffer and the
surface size.
</description>
<request name="destroy" type="destructor">
<description summary="unbind from the cropping and scaling interface">
Informs the server that the client will not be using this
protocol object anymore. This does not affect any other objects,
wp_viewport objects included.
</description>
</request>
<enum name="error">
<entry name="viewport_exists" value="0"
summary="the surface already has a viewport object associated"/>
</enum>
<request name="get_viewport">
<description summary="extend surface interface for crop and scale">
Instantiate an interface extension for the given wl_surface to
crop and scale its content. If the given wl_surface already has
a wp_viewport object associated, the viewport_exists
protocol error is raised.
</description>
<arg name="id" type="new_id" interface="wp_viewport"
summary="the new viewport interface id"/>
<arg name="surface" type="object" interface="wl_surface"
summary="the surface"/>
</request>
</interface>
<interface name="wp_viewport" version="1">
<description summary="crop and scale interface to a wl_surface">
An additional interface to a wl_surface object, which allows the
client to specify the cropping and scaling of the surface
contents.
This interface works with two concepts: the source rectangle (src_x,
src_y, src_width, src_height), and the destination size (dst_width,
dst_height). The contents of the source rectangle are scaled to the
destination size, and content outside the source rectangle is ignored.
This state is double-buffered, see wl_surface.commit.
The two parts of crop and scale state are independent: the source
rectangle, and the destination size. Initially both are unset, that
is, no scaling is applied. The whole of the current wl_buffer is
used as the source, and the surface size is as defined in
wl_surface.attach.
If the destination size is set, it causes the surface size to become
dst_width, dst_height. The source (rectangle) is scaled to exactly
this size. This overrides whatever the attached wl_buffer size is,
unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface
has no content and therefore no size. Otherwise, the size is always
at least 1x1 in surface local coordinates.
If the source rectangle is set, it defines what area of the wl_buffer is
taken as the source. If the source rectangle is set and the destination
size is not set, then src_width and src_height must be integers, and the
surface size becomes the source rectangle size. This results in cropping
without scaling. If src_width or src_height are not integers and
destination size is not set, the bad_size protocol error is raised when
the surface state is applied.
The coordinate transformations from buffer pixel coordinates up to
the surface-local coordinates happen in the following order:
1. buffer_transform (wl_surface.set_buffer_transform)
2. buffer_scale (wl_surface.set_buffer_scale)
3. crop and scale (wp_viewport.set*)
This means, that the source rectangle coordinates of crop and scale
are given in the coordinates after the buffer transform and scale,
i.e. in the coordinates that would be the surface-local coordinates
if the crop and scale was not applied.
If src_x or src_y are negative, the bad_value protocol error is raised.
Otherwise, if the source rectangle is partially or completely outside of
the non-NULL wl_buffer, then the out_of_buffer protocol error is raised
when the surface state is applied. A NULL wl_buffer does not raise the
out_of_buffer error.
If the wl_surface associated with the wp_viewport is destroyed,
all wp_viewport requests except 'destroy' raise the protocol error
no_surface.
If the wp_viewport object is destroyed, the crop and scale
state is removed from the wl_surface. The change will be applied
on the next wl_surface.commit.
</description>
<request name="destroy" type="destructor">
<description summary="remove scaling and cropping from the surface">
The associated wl_surface's crop and scale state is removed.
The change is applied on the next wl_surface.commit.
</description>
</request>
<enum name="error">
<entry name="bad_value" value="0"
summary="negative or zero values in width or height"/>
<entry name="bad_size" value="1"
summary="destination size is not integer"/>
<entry name="out_of_buffer" value="2"
summary="source rectangle extends outside of the content area"/>
<entry name="no_surface" value="3"
summary="the wl_surface was destroyed"/>
</enum>
<request name="set_source">
<description summary="set the source rectangle for cropping">
Set the source rectangle of the associated wl_surface. See
wp_viewport for the description, and relation to the wl_buffer
size.
If all of x, y, width and height are -1.0, the source rectangle is
unset instead. Any other set of values where width or height are zero
or negative, or x or y are negative, raise the bad_value protocol
error.
The crop and scale state is double-buffered, see wl_surface.commit.
</description>
<arg name="x" type="fixed" summary="source rectangle x"/>
<arg name="y" type="fixed" summary="source rectangle y"/>
<arg name="width" type="fixed" summary="source rectangle width"/>
<arg name="height" type="fixed" summary="source rectangle height"/>
</request>
<request name="set_destination">
<description summary="set the surface size for scaling">
Set the destination size of the associated wl_surface. See
wp_viewport for the description, and relation to the wl_buffer
size.
If width is -1 and height is -1, the destination size is unset
instead. Any other pair of values for width and height that
contains zero or negative values raises the bad_value protocol
error.
The crop and scale state is double-buffered, see wl_surface.commit.
</description>
<arg name="width" type="int" summary="surface width"/>
<arg name="height" type="int" summary="surface height"/>
</request>
</interface>
</protocol>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,200 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="xdg_activation_v1">
<copyright>
Copyright © 2020 Aleix Pol Gonzalez &lt;aleixpol@kde.org&gt;
Copyright © 2020 Carlos Garnacho &lt;carlosg@gnome.org&gt;
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<description summary="Protocol for requesting activation of surfaces">
The way for a client to pass focus to another toplevel is as follows.
The client that intends to activate another toplevel uses the
xdg_activation_v1.get_activation_token request to get an activation token.
This token is then forwarded to the client, which is supposed to activate
one of its surfaces, through a separate band of communication.
One established way of doing this is through the XDG_ACTIVATION_TOKEN
environment variable of a newly launched child process. The child process
should unset the environment variable again right after reading it out in
order to avoid propagating it to other child processes.
Another established way exists for Applications implementing the D-Bus
interface org.freedesktop.Application, which should get their token under
activation-token on their platform_data.
In general activation tokens may be transferred across clients through
means not described in this protocol.
The client to be activated will then pass the token
it received to the xdg_activation_v1.activate request. The compositor can
then use this token to decide how to react to the activation request.
The token the activating client gets may be ineffective either already at
the time it receives it, for example if it was not focused, for focus
stealing prevention. The activating client will have no way to discover
the validity of the token, and may still forward it to the to be activated
client.
The created activation token may optionally get information attached to it
that can be used by the compositor to identify the application that we
intend to activate. This can for example be used to display a visual hint
about what application is being started.
Warning! The protocol described in this file is currently in the testing
phase. Backward compatible changes may be added together with the
corresponding interface version bump. Backward incompatible changes can
only be done by creating a new major version of the extension.
</description>
<interface name="xdg_activation_v1" version="1">
<description summary="interface for activating surfaces">
A global interface used for informing the compositor about applications
being activated or started, or for applications to request to be
activated.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the xdg_activation object">
Notify the compositor that the xdg_activation object will no longer be
used.
The child objects created via this interface are unaffected and should
be destroyed separately.
</description>
</request>
<request name="get_activation_token">
<description summary="requests a token">
Creates an xdg_activation_token_v1 object that will provide
the initiating client with a unique token for this activation. This
token should be offered to the clients to be activated.
</description>
<arg name="id" type="new_id" interface="xdg_activation_token_v1"/>
</request>
<request name="activate">
<description summary="notify new interaction being available">
Requests surface activation. It's up to the compositor to display
this information as desired, for example by placing the surface above
the rest.
The compositor may know who requested this by checking the activation
token and might decide not to follow through with the activation if it's
considered unwanted.
Compositors can ignore unknown activation tokens when an invalid
token is passed.
</description>
<arg name="token" type="string" summary="the activation token of the initiating client"/>
<arg name="surface" type="object" interface="wl_surface"
summary="the wl_surface to activate"/>
</request>
</interface>
<interface name="xdg_activation_token_v1" version="1">
<description summary="an exported activation handle">
An object for setting up a token and receiving a token handle that can
be passed as an activation token to another client.
The object is created using the xdg_activation_v1.get_activation_token
request. This object should then be populated with the app_id, surface
and serial information and committed. The compositor shall then issue a
done event with the token. In case the request's parameters are invalid,
the compositor will provide an invalid token.
</description>
<enum name="error">
<entry name="already_used" value="0"
summary="The token has already been used previously"/>
</enum>
<request name="set_serial">
<description summary="specifies the seat and serial of the activating event">
Provides information about the seat and serial event that requested the
token.
The serial can come from an input or focus event. For instance, if a
click triggers the launch of a third-party client, the launcher client
should send a set_serial request with the serial and seat from the
wl_pointer.button event.
Some compositors might refuse to activate toplevels when the token
doesn't have a valid and recent enough event serial.
Must be sent before commit. This information is optional.
</description>
<arg name="serial" type="uint"
summary="the serial of the event that triggered the activation"/>
<arg name="seat" type="object" interface="wl_seat"
summary="the wl_seat of the event"/>
</request>
<request name="set_app_id">
<description summary="specifies the application being activated">
The requesting client can specify an app_id to associate the token
being created with it.
Must be sent before commit. This information is optional.
</description>
<arg name="app_id" type="string"
summary="the application id of the client being activated."/>
</request>
<request name="set_surface">
<description summary="specifies the surface requesting activation">
This request sets the surface requesting the activation. Note, this is
different from the surface that will be activated.
Some compositors might refuse to activate toplevels when the token
doesn't have a requesting surface.
Must be sent before commit. This information is optional.
</description>
<arg name="surface" type="object" interface="wl_surface"
summary="the requesting surface"/>
</request>
<request name="commit">
<description summary="issues the token request">
Requests an activation token based on the different parameters that
have been offered through set_serial, set_surface and set_app_id.
</description>
</request>
<event name="done">
<description summary="the exported activation token">
The 'done' event contains the unique token of this activation request
and notifies that the provider is done.
</description>
<arg name="token" type="string" summary="the exported activation token"/>
</event>
<request name="destroy" type="destructor">
<description summary="destroy the xdg_activation_token_v1 object">
Notify the compositor that the xdg_activation_token_v1 object will no
longer be used. The received token stays valid.
</description>
</request>
</interface>
</protocol>

View File

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="xdg_decoration_unstable_v1">
<copyright>
Copyright © 2018 Simon Ser
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<interface name="zxdg_decoration_manager_v1" version="1">
<description summary="window decoration manager">
This interface allows a compositor to announce support for server-side
decorations.
A window decoration is a set of window controls as deemed appropriate by
the party managing them, such as user interface components used to move,
resize and change a window's state.
A client can use this protocol to request being decorated by a supporting
compositor.
If compositor and client do not negotiate the use of a server-side
decoration using this protocol, clients continue to self-decorate as they
see fit.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the decoration manager object">
Destroy the decoration manager. This doesn't destroy objects created
with the manager.
</description>
</request>
<request name="get_toplevel_decoration">
<description summary="create a new toplevel decoration object">
Create a new decoration object associated with the given toplevel.
Creating an xdg_toplevel_decoration from an xdg_toplevel which has a
buffer attached or committed is a client error, and any attempts by a
client to attach or manipulate a buffer prior to the first
xdg_toplevel_decoration.configure event must also be treated as
errors.
</description>
<arg name="id" type="new_id" interface="zxdg_toplevel_decoration_v1"/>
<arg name="toplevel" type="object" interface="xdg_toplevel"/>
</request>
</interface>
<interface name="zxdg_toplevel_decoration_v1" version="1">
<description summary="decoration object for a toplevel surface">
The decoration object allows the compositor to toggle server-side window
decorations for a toplevel surface. The client can request to switch to
another mode.
The xdg_toplevel_decoration object must be destroyed before its
xdg_toplevel.
</description>
<enum name="error">
<entry name="unconfigured_buffer" value="0"
summary="xdg_toplevel has a buffer attached before configure"/>
<entry name="already_constructed" value="1"
summary="xdg_toplevel already has a decoration object"/>
<entry name="orphaned" value="2"
summary="xdg_toplevel destroyed before the decoration object"/>
<entry name="invalid_mode" value="3" summary="invalid mode"/>
</enum>
<request name="destroy" type="destructor">
<description summary="destroy the decoration object">
Switch back to a mode without any server-side decorations at the next
commit.
</description>
</request>
<enum name="mode">
<description summary="window decoration modes">
These values describe window decoration modes.
</description>
<entry name="client_side" value="1"
summary="no server-side window decoration"/>
<entry name="server_side" value="2"
summary="server-side window decoration"/>
</enum>
<request name="set_mode">
<description summary="set the decoration mode">
Set the toplevel surface decoration mode. This informs the compositor
that the client prefers the provided decoration mode.
After requesting a decoration mode, the compositor will respond by
emitting an xdg_surface.configure event. The client should then update
its content, drawing it without decorations if the received mode is
server-side decorations. The client must also acknowledge the configure
when committing the new content (see xdg_surface.ack_configure).
The compositor can decide not to use the client's mode and enforce a
different mode instead.
Clients whose decoration mode depend on the xdg_toplevel state may send
a set_mode request in response to an xdg_surface.configure event and wait
for the next xdg_surface.configure event to prevent unwanted state.
Such clients are responsible for preventing configure loops and must
make sure not to send multiple successive set_mode requests with the
same decoration mode.
If an invalid mode is supplied by the client, the invalid_mode protocol
error is raised by the compositor.
</description>
<arg name="mode" type="uint" enum="mode" summary="the decoration mode"/>
</request>
<request name="unset_mode">
<description summary="unset the decoration mode">
Unset the toplevel surface decoration mode. This informs the compositor
that the client doesn't prefer a particular decoration mode.
This request has the same semantics as set_mode.
</description>
</request>
<event name="configure">
<description summary="notify a decoration mode change">
The configure event configures the effective decoration mode. The
configured state should not be applied immediately. Clients must send an
ack_configure in response to this event. See xdg_surface.configure and
xdg_surface.ack_configure for details.
A configure event can be sent at any time. The specified mode must be
obeyed by the client.
</description>
<arg name="mode" type="uint" enum="mode" summary="the decoration mode"/>
</event>
</interface>
</protocol>

View File

@ -0,0 +1,200 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="xdg_foreign_unstable_v2">
<copyright>
Copyright © 2015-2016 Red Hat Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<description summary="Protocol for exporting xdg surface handles">
This protocol specifies a way for making it possible to reference a surface
of a different client. With such a reference, a client can, by using the
interfaces provided by this protocol, manipulate the relationship between
its own surfaces and the surface of some other client. For example, stack
some of its own surface above the other clients surface.
In order for a client A to get a reference of a surface of client B, client
B must first export its surface using xdg_exporter.export_toplevel. Upon
doing this, client B will receive a handle (a unique string) that it may
share with client A in some way (for example D-Bus). After client A has
received the handle from client B, it may use xdg_importer.import_toplevel
to create a reference to the surface client B just exported. See the
corresponding requests for details.
A possible use case for this is out-of-process dialogs. For example when a
sandboxed client without file system access needs the user to select a file
on the file system, given sandbox environment support, it can export its
surface, passing the exported surface handle to an unsandboxed process that
can show a file browser dialog and stack it above the sandboxed client's
surface.
Warning! The protocol described in this file is experimental and backward
incompatible changes may be made. Backward compatible changes may be added
together with the corresponding interface version bump. Backward
incompatible changes are done by bumping the version number in the protocol
and interface names and resetting the interface version. Once the protocol
is to be declared stable, the 'z' prefix and the version number in the
protocol and interface names are removed and the interface version number is
reset.
</description>
<interface name="zxdg_exporter_v2" version="1">
<description summary="interface for exporting surfaces">
A global interface used for exporting surfaces that can later be imported
using xdg_importer.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the xdg_exporter object">
Notify the compositor that the xdg_exporter object will no longer be
used.
</description>
</request>
<enum name="error">
<description summary="error values">
These errors can be emitted in response to invalid xdg_exporter
requests.
</description>
<entry name="invalid_surface" value="0" summary="surface is not an xdg_toplevel"/>
</enum>
<request name="export_toplevel">
<description summary="export a toplevel surface">
The export_toplevel request exports the passed surface so that it can later be
imported via xdg_importer. When called, a new xdg_exported object will
be created and xdg_exported.handle will be sent immediately. See the
corresponding interface and event for details.
A surface may be exported multiple times, and each exported handle may
be used to create an xdg_imported multiple times. Only xdg_toplevel
equivalent surfaces may be exported, otherwise an invalid_surface
protocol error is sent.
</description>
<arg name="id" type="new_id" interface="zxdg_exported_v2"
summary="the new xdg_exported object"/>
<arg name="surface" type="object" interface="wl_surface"
summary="the surface to export"/>
</request>
</interface>
<interface name="zxdg_importer_v2" version="1">
<description summary="interface for importing surfaces">
A global interface used for importing surfaces exported by xdg_exporter.
With this interface, a client can create a reference to a surface of
another client.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the xdg_importer object">
Notify the compositor that the xdg_importer object will no longer be
used.
</description>
</request>
<request name="import_toplevel">
<description summary="import a toplevel surface">
The import_toplevel request imports a surface from any client given a handle
retrieved by exporting said surface using xdg_exporter.export_toplevel.
When called, a new xdg_imported object will be created. This new object
represents the imported surface, and the importing client can
manipulate its relationship using it. See xdg_imported for details.
</description>
<arg name="id" type="new_id" interface="zxdg_imported_v2"
summary="the new xdg_imported object"/>
<arg name="handle" type="string"
summary="the exported surface handle"/>
</request>
</interface>
<interface name="zxdg_exported_v2" version="1">
<description summary="an exported surface handle">
An xdg_exported object represents an exported reference to a surface. The
exported surface may be referenced as long as the xdg_exported object not
destroyed. Destroying the xdg_exported invalidates any relationship the
importer may have established using xdg_imported.
</description>
<request name="destroy" type="destructor">
<description summary="unexport the exported surface">
Revoke the previously exported surface. This invalidates any
relationship the importer may have set up using the xdg_imported created
given the handle sent via xdg_exported.handle.
</description>
</request>
<event name="handle">
<description summary="the exported surface handle">
The handle event contains the unique handle of this exported surface
reference. It may be shared with any client, which then can use it to
import the surface by calling xdg_importer.import_toplevel. A handle
may be used to import the surface multiple times.
</description>
<arg name="handle" type="string" summary="the exported surface handle"/>
</event>
</interface>
<interface name="zxdg_imported_v2" version="1">
<description summary="an imported surface handle">
An xdg_imported object represents an imported reference to surface exported
by some client. A client can use this interface to manipulate
relationships between its own surfaces and the imported surface.
</description>
<enum name="error">
<description summary="error values">
These errors can be emitted in response to invalid xdg_imported
requests.
</description>
<entry name="invalid_surface" value="0" summary="surface is not an xdg_toplevel"/>
</enum>
<request name="destroy" type="destructor">
<description summary="destroy the xdg_imported object">
Notify the compositor that it will no longer use the xdg_imported
object. Any relationship that may have been set up will at this point
be invalidated.
</description>
</request>
<request name="set_parent_of">
<description summary="set as the parent of some surface">
Set the imported surface as the parent of some surface of the client.
The passed surface must be an xdg_toplevel equivalent, otherwise an
invalid_surface protocol error is sent. Calling this function sets up
a surface to surface relation with the same stacking and positioning
semantics as xdg_toplevel.set_parent.
</description>
<arg name="surface" type="object" interface="wl_surface"
summary="the child surface"/>
</request>
<event name="destroyed">
<description summary="the imported surface handle has been destroyed">
The imported surface handle has been destroyed and any relationship set
up has been invalidated. This may happen for various reasons, for
example if the exported surface or the exported surface handle has been
destroyed, if the handle used for importing was invalid.
</description>
</event>
</interface>
</protocol>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,205 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="xdg_toplevel_icon_v1">
<copyright>
Copyright © 2023-2024 Matthias Klumpp
Copyright © 2024 David Edmundson
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<description summary="protocol to assign icons to toplevels">
This protocol allows clients to set icons for their toplevel surfaces
either via the XDG icon stock (using an icon name), or from pixel data.
A toplevel icon represents the individual toplevel (unlike the application
or launcher icon, which represents the application as a whole), and may be
shown in window switchers, window overviews and taskbars that list
individual windows.
This document adheres to RFC 2119 when using words like "must",
"should", "may", etc.
Warning! The protocol described in this file is currently in the testing
phase. Backward compatible changes may be added together with the
corresponding interface version bump. Backward incompatible changes can
only be done by creating a new major version of the extension.
</description>
<interface name="xdg_toplevel_icon_manager_v1" version="1">
<description summary="interface to manage toplevel icons">
This interface allows clients to create toplevel window icons and set
them on toplevel windows to be displayed to the user.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the toplevel icon manager">
Destroy the toplevel icon manager.
This does not destroy objects created with the manager.
</description>
</request>
<request name="create_icon">
<description summary="create a new icon instance">
Creates a new icon object. This icon can then be attached to a
xdg_toplevel via the 'set_icon' request.
</description>
<arg name="id" type="new_id" interface="xdg_toplevel_icon_v1"/>
</request>
<request name="set_icon">
<description summary="set an icon on a toplevel window">
This request assigns the icon 'icon' to 'toplevel', or clears the
toplevel icon if 'icon' was null.
This state is double-buffered and is applied on the next
wl_surface.commit of the toplevel.
After making this call, the xdg_toplevel_icon_v1 provided as 'icon'
can be destroyed by the client without 'toplevel' losing its icon.
The xdg_toplevel_icon_v1 is immutable from this point, and any
future attempts to change it must raise the
'xdg_toplevel_icon_v1.immutable' protocol error.
The compositor must set the toplevel icon from either the pixel data
the icon provides, or by loading a stock icon using the icon name.
See the description of 'xdg_toplevel_icon_v1' for details.
If 'icon' is set to null, the icon of the respective toplevel is reset
to its default icon (usually the icon of the application, derived from
its desktop-entry file, or a placeholder icon).
If this request is passed an icon with no pixel buffers or icon name
assigned, the icon must be reset just like if 'icon' was null.
</description>
<arg name="toplevel" type="object" interface="xdg_toplevel" summary="the toplevel to act on"/>
<arg name="icon" type="object" interface="xdg_toplevel_icon_v1" allow-null="true"/>
</request>
<event name="icon_size">
<description summary="describes a supported &amp; preferred icon size">
This event indicates an icon size the compositor prefers to be
available if the client has scalable icons and can render to any size.
When the 'xdg_toplevel_icon_manager_v1' object is created, the
compositor may send one or more 'icon_size' events to describe the list
of preferred icon sizes. If the compositor has no size preference, it
may not send any 'icon_size' event, and it is up to the client to
decide a suitable icon size.
A sequence of 'icon_size' events must be finished with a 'done' event.
If the compositor has no size preferences, it must still send the
'done' event, without any preceding 'icon_size' events.
</description>
<arg name="size" type="int"
summary="the edge size of the square icon in surface-local coordinates, e.g. 64"/>
</event>
<event name="done">
<description summary="all information has been sent">
This event is sent after all 'icon_size' events have been sent.
</description>
</event>
</interface>
<interface name="xdg_toplevel_icon_v1" version="1">
<description summary="a toplevel window icon">
This interface defines a toplevel icon.
An icon can have a name, and multiple buffers.
In order to be applied, the icon must have either a name, or at least
one buffer assigned. Applying an empty icon (with no buffer or name) to
a toplevel should reset its icon to the default icon.
It is up to compositor policy whether to prefer using a buffer or loading
an icon via its name. See 'set_name' and 'add_buffer' for details.
</description>
<enum name="error">
<entry name="invalid_buffer"
summary="the provided buffer does not satisfy requirements"
value="1"/>
<entry name="immutable"
summary="the icon has already been assigned to a toplevel and must not be changed"
value="2"/>
<entry name="no_buffer"
summary="the provided buffer has been destroyed before the toplevel icon"
value="3"/>
</enum>
<request name="destroy" type="destructor">
<description summary="destroy the icon object">
Destroys the 'xdg_toplevel_icon_v1' object.
The icon must still remain set on every toplevel it was assigned to,
until the toplevel icon is reset explicitly.
</description>
</request>
<request name="set_name">
<description summary="set an icon name">
This request assigns an icon name to this icon.
Any previously set name is overridden.
The compositor must resolve 'icon_name' according to the lookup rules
described in the XDG icon theme specification[1] using the
environment's current icon theme.
If the compositor does not support icon names or cannot resolve
'icon_name' according to the XDG icon theme specification it must
fall back to using pixel buffer data instead.
If this request is made after the icon has been assigned to a toplevel
via 'set_icon', a 'immutable' error must be raised.
[1]: https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
</description>
<arg name="icon_name" type="string"/>
</request>
<request name="add_buffer">
<description summary="add icon data from a pixel buffer">
This request adds pixel data supplied as wl_buffer to the icon.
The client should add pixel data for all icon sizes and scales that
it can provide, or which are explicitly requested by the compositor
via 'icon_size' events on xdg_toplevel_icon_manager_v1.
The wl_buffer supplying pixel data as 'buffer' must be backed by wl_shm
and must be a square (width and height being equal).
If any of these buffer requirements are not fulfilled, a 'invalid_buffer'
error must be raised.
If this icon instance already has a buffer of the same size and scale
from a previous 'add_buffer' request, data from the last request
overrides the preexisting pixel data.
The wl_buffer must be kept alive for as long as the xdg_toplevel_icon
it is associated with is not destroyed, otherwise a 'no_buffer' error
is raised. The buffer contents must not be modified after it was
assigned to the icon. As a result, the region of the wl_shm_pool's
backing storage used for the wl_buffer must not be modified after this
request is sent. The wl_buffer.release event is unused.
If this request is made after the icon has been assigned to a toplevel
via 'set_icon', a 'immutable' error must be raised.
</description>
<arg name="buffer" type="object" interface="wl_buffer"/>
<arg name="scale" type="int"
summary="the scaling factor of the icon, e.g. 1"/>
</request>
</interface>
</protocol>

33
src/linux/redirects.h Normal file
View File

@ -0,0 +1,33 @@
/* Keep in sync with functions.inc. */
#define wl_display_connect z_wl_display_connect
#define wl_display_disconnect z_wl_display_disconnect
#define wl_display_get_fd z_wl_display_get_fd
#define wl_display_dispatch_pending z_wl_display_dispatch_pending
#define wl_display_prepare_read z_wl_display_prepare_read
#define wl_display_read_events z_wl_display_read_events
#define wl_display_cancel_read z_wl_display_cancel_read
#define wl_display_flush z_wl_display_flush
#define wl_proxy_marshal_flags z_wl_proxy_marshal_flags
#define wl_proxy_add_listener z_wl_proxy_add_listener
#define wl_proxy_destroy z_wl_proxy_destroy
#define wl_proxy_get_version z_wl_proxy_get_version
#define wl_proxy_set_user_data z_wl_proxy_set_user_data
#define wl_proxy_get_user_data z_wl_proxy_get_user_data
#define wl_egl_window_create z_wl_egl_window_create
#define wl_egl_window_destroy z_wl_egl_window_destroy
#define wl_egl_window_resize z_wl_egl_window_resize
#define eglGetPlatformDisplay z_eglGetPlatformDisplay
#define eglInitialize z_eglInitialize
#define eglTerminate z_eglTerminate
#define eglBindAPI z_eglBindAPI
#define eglChooseConfig z_eglChooseConfig
#define eglCreateContext z_eglCreateContext
#define eglDestroyContext z_eglDestroyContext
#define eglCreateWindowSurface z_eglCreateWindowSurface
#define eglDestroySurface z_eglDestroySurface
#define eglMakeCurrent z_eglMakeCurrent
#define eglGetCurrentContext z_eglGetCurrentContext
#define eglSwapBuffers z_eglSwapBuffers
#define eglSwapInterval z_eglSwapInterval
#define eglGetProcAddress z_eglGetProcAddress
#define eglReleaseThread z_eglReleaseThread

View File

@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Drop only a test client's proxied Wayland connection after startup."""
import array
import os
from pathlib import Path
import select
import socket
import subprocess
import sys
import threading
binary = str(Path(sys.argv[1]).resolve())
display = os.environ.get('WAYLAND_DISPLAY', 'wayland-0')
if not os.path.isabs(display):
display = os.path.join(os.environ['XDG_RUNTIME_DIR'], display)
upstream = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
upstream.connect(display)
client, proxy = socket.socketpair()
def relay(source, destination):
try:
while True:
data, ancillary, flags, _ = source.recvmsg(65536, socket.CMSG_SPACE(256 * 4))
if not data:
break
assert not flags & socket.MSG_CTRUNC
descriptors = []
for level, kind, value in ancillary:
if level == socket.SOL_SOCKET and kind == socket.SCM_RIGHTS:
fds = array.array('i')
fds.frombytes(value)
descriptors.extend(fds)
try:
sent = destination.sendmsg([data], ancillary)
if sent < len(data):
destination.sendall(data[sent:])
finally:
for fd in descriptors:
os.close(fd)
except OSError:
pass
threads = [threading.Thread(target=relay, args=pair, daemon=True)
for pair in [(proxy, upstream), (upstream, proxy)]]
for thread in threads:
thread.start()
process = subprocess.Popen([binary, '--disconnect'],
env=dict(os.environ, WAYLAND_SOCKET=str(client.fileno())),
pass_fds=(client.fileno(),), stdout=subprocess.PIPE, text=True)
client.close()
try:
assert select.select([process.stdout], [], [], 15)[0], 'Startup timed out'
assert process.stdout.readline().strip() == 'connected'
for sock in (proxy, upstream):
sock.shutdown(socket.SHUT_RDWR)
assert process.wait(timeout=10) == 0
print('Runtime display disconnect: PASS')
finally:
if process.poll() is None:
process.kill()
process.wait()
for sock in (proxy, upstream):
sock.close()
for thread in threads:
thread.join(timeout=1)

View File

@ -0,0 +1,32 @@
#!/usr/bin/env python3
"""Forward isolated XKB keys to verify edit defaults without an application menu."""
import sys
from wayland_fixture import Compositor, run
class EditingCompositor(Compositor):
# evdev code, XKB depressed-mask (Shift=1, Control=4, Mod1=8).
gestures = [(14, 4), (111, 4), (103, 4), (108, 5), (104, 1), (109, 0),
(15, 1), (28, 1), (111, 1), (110, 4), (110, 1), (46, 4),
(44, 5), (14, 12), (15, 4)]
def timers(self):
# The fixture tests forwarded keys, not compositor-owned IME input.
pass
def request(self, obj, iface, name, args):
if iface == 'xdg_toplevel' and name == 'set_title' and args[0].startswith('edit:'):
index = int(args[0].split(':')[1])
code, mask = self.gestures[index]
keyboard = self.ids['wl_keyboard']
if index == 0:
self.event(keyboard, 'enter', 200, next(iter(self.surfaces)), b'')
self.event(keyboard, 'modifiers', 201, mask, 0, 0, 0)
self.event(keyboard, 'key', 202, index * 2, code, 1)
self.event(keyboard, 'key', 203, index * 2 + 1, code, 0)
self.event(keyboard, 'modifiers', 204, 0, 0, 0, 0)
super().request(obj, iface, name, args)
if __name__ == '__main__':
run(sys.argv[1], 'editing', compositor_type=EditingCompositor)

View File

@ -0,0 +1,193 @@
#!/usr/bin/env python3
"""Plasma surface association and D-BusMenu on a private bus/compositor.
The consumer is replaced mid-test; menu exports must not depend on its lifetime.
No input or menu actions are sent to the user's desktop.
"""
import asyncio
import os
from pathlib import Path
import queue
import subprocess
import sys
import tempfile
from dbus_next import Message, MessageType, Variant
from dbus_next.aio import MessageBus
from wayland_fixture import Compositor, run
IFACE = 'com.canonical.dbusmenu'
async def main(binary):
config = tempfile.NamedTemporaryFile(mode='w', suffix='.conf')
config.write('<busconfig><type>session</type><listen>unix:tmpdir=/tmp</listen><auth>EXTERNAL</auth><policy context="default"><allow send_destination="*"/><allow receive_sender="*"/><allow own="*"/></policy></busconfig>')
config.flush()
daemon = subprocess.Popen(['dbus-daemon', '--config-file=' + config.name, '--nofork', '--print-address=1'], stdout=subprocess.PIPE, text=True)
bus = None
runner = None
loop = asyncio.get_running_loop()
notifications = asyncio.Queue()
commands = queue.Queue()
addresses = {}
surfaces = {}
signals = []
class MenuCompositor(Compositor):
globals = Compositor.globals + [('org_kde_kwin_appmenu_manager', 2)]
def request(self, obj, iface, name, args):
super().request(obj, iface, name, args)
if iface == 'org_kde_kwin_appmenu_manager' and name == 'create':
surfaces[args[0]] = args[1]
elif iface == 'org_kde_kwin_appmenu' and name == 'set_address':
loop.call_soon_threadsafe(notifications.put_nowait, ('address', surfaces[obj], tuple(args)))
elif iface == 'xdg_toplevel' and name == 'set_title' and args[0].endswith('-ready'):
loop.call_soon_threadsafe(notifications.put_nowait, ('phase', args[0]))
def timers(self):
# Only inject explicitly requested keys into this client's private socket.
while not commands.empty():
surface, code, press, enter, future = commands.get_nowait()
keyboard = self.ids['wl_keyboard']
if enter:
self.event(keyboard, 'enter', 200, surface, b'')
self.event(keyboard, 'modifiers', 201, 4, 0, 0, 0)
self.event(keyboard, 'key', 202, 1, code, int(press))
loop.call_soon_threadsafe(future.set_result, None)
async def phase(name):
while True:
item = await asyncio.wait_for(notifications.get(), 5)
if item[0] == 'address': addresses[item[1]] = item[2]
else:
assert item == ('phase', name), item
return
async def key(surface, code, press, enter=False):
future = loop.create_future()
commands.put((surface, code, press, enter, future))
await future
await asyncio.sleep(.06)
async def call(address, member, signature='', body=None, interface=IFACE, error=None):
reply = await asyncio.wait_for(bus.call(Message(destination=address[0], path=address[1], interface=interface, member=member, signature=signature, body=body or [])), 3)
if error:
assert reply.message_type == MessageType.ERROR and reply.error_name.endswith(error), reply
else:
assert reply.message_type == MessageType.METHOD_RETURN, (reply.error_name, reply.body)
return reply.body
async def layout(address, depth=-1, props=None):
return (await call(address, 'GetLayout', 'iias', [0, depth, props or []]))[1]
async def click(address, item, kind='clicked'):
await call(address, 'Event', 'isvu', [item, kind, Variant('i', 0), 0])
await asyncio.sleep(.03)
def flatten(node):
return [node] + [item for child in node[2] for item in flatten(child.value)]
async def connect(address):
consumer = await MessageBus(bus_address=address).connect()
consumer.add_message_handler(lambda m: signals.append(m) if m.message_type == MessageType.SIGNAL and m.interface == IFACE else None)
reply = await consumer.call(Message(destination='org.freedesktop.DBus', path='/org/freedesktop/DBus', interface='org.freedesktop.DBus', member='AddMatch', signature='s', body=["type='signal',interface='com.canonical.dbusmenu'"]))
assert reply.message_type == MessageType.METHOD_RETURN
return consumer
try:
address = daemon.stdout.readline().strip()
bus = await connect(address)
no_bus_env = dict(os.environ)
no_bus_env.pop('DBUS_SESSION_BUS_ADDRESS', None)
await asyncio.to_thread(run, binary, 'menu-noprotocol', no_bus_env)
await asyncio.to_thread(run, binary, 'menu-nobus', no_bus_env, MenuCompositor)
runner = asyncio.create_task(asyncio.to_thread(run, binary, 'menu', dict(os.environ, DBUS_SESSION_BUS_ADDRESS=address), MenuCompositor))
await phase('menus-ready')
assert len(addresses) == 2
a, b = list(addresses)
first, override = addresses[a], addresses[b]
tree = await layout(first)
nodes = flatten(tree)
by_label = {v[1]['label'].value: v[0] for v in nodes}
assert by_label.keys() >= {'File', 'Open__file', 'Checked', 'Disabled', 'Copy', 'Quit', 'Next'}
open_id, toggle, disabled, copy, quit_id, step = [by_label[k] for k in ['Open__file', 'Checked', 'Disabled', 'Copy', 'Quit', 'Next']]
assert (await call(first, 'GetProperty', 'is', [open_id, 'shortcut']))[0].value == [['Control', 'O']]
assert (await call(first, 'GetProperty', 'is', [toggle, 'toggle-state']))[0].value == 1
assert len((await layout(first, 0))[2]) == 0
shallow = await layout(first, 1, ['label'])
assert all(set(v.value[1]) == {'label'} and not v.value[2] for v in shallow[2])
groups = (await call(first, 'GetGroupProperties', 'aias', [[], ['label']]))[0]
assert len(groups) == len(nodes)
assert all(set(p) == {'label'} for _, p in groups)
assert (await call(first, 'GetGroupProperties', 'aias', [[open_id, -1], ['enabled']]))[0] == [[open_id, {'enabled': Variant('b', True)}]]
props = (await call(first, 'GetAll', 's', [IFACE], 'org.freedesktop.DBus.Properties'))[0]
assert props['Version'].value == 3 and props['IconThemePath'].value == []
xml = (await call(first, 'Introspect', interface='org.freedesktop.DBus.Introspectable'))[0]
assert 'EventGroup' in xml
await call(first, 'Ping', interface='org.freedesktop.DBus.Peer')
await call(first, 'GetLayout', 'i', [0], error='InvalidArgs')
await call(first, 'GetLayout', 'iias', [0, -2, []], error='InvalidArgs')
await call(first, 'GetLayout', 'iias', [-1, -1, []], error='InvalidMenuItem')
await call(first, 'GetProperty', 'is', [0, 'invented'], error='UnknownProperty')
await call(first, 'MissingMethod', error='UnknownMethod')
assert await call(first, 'AboutToShow', 'i', [0]) == [False]
assert await call(first, 'AboutToShowGroup', 'ai', [[0, -1]]) == [[], [-1]]
await call(first, 'AboutToShowGroup', 'ai', [[-1]], error='InvalidMenuItem')
assert await call(first, 'EventGroup', 'a(isvu)', [[[0, 'opened', Variant('s', ''), 0], [-1, 'clicked', Variant('i', 0), 0]]]) == [[-1]]
await call(first, 'EventGroup', 'a(isvu)', [[[-1, 'clicked', Variant('i', 0), 0]]], error='InvalidMenuItem')
for i in [0, by_label['File'], disabled] + [v[0] for v in nodes if v[1]['type'].value == 'separator']:
await click(first, i)
await click(first, open_id, 'hovered')
await click(first, open_id)
await click(first, copy)
await click(first, quit_id)
await click(override, 1)
await click(first, step)
await phase('flags-ready')
assert any(m.member == 'ItemsPropertiesUpdated' for m in signals)
assert not (await call(first, 'GetProperty', 'is', [open_id, 'enabled']))[0].value
assert (await call(first, 'GetProperty', 'is', [toggle, 'toggle-type']))[0].value == ''
await click(first, open_id) # Disabled ancestor blocks activation.
await key(a, 24, True, True)
await key(a, 24, False) # Disabled shortcut must reach raw key handling.
await click(first, step)
await phase('restored-ready')
inherited = addresses[b]
assert inherited != override
await call(override, 'GetLayout', 'iias', [0, -1, []], error='UnknownObject')
# A newly started consumer can obtain complete state without registration.
bus.disconnect()
bus = await connect(address)
assert (await layout(inherited))[1]['children-display'].value == 'submenu'
await key(a, 24, True, True)
await asyncio.sleep(.1) # Repeated accelerator downs remain consumed.
await key(a, 24, False)
await key(a, 46, True)
await key(a, 46, False)
await key(b, 24, True, True)
await key(b, 24, False)
await click(first, step)
await phase('detached-ready')
await call(first, 'GetLayout', 'iias', [0, -1, []], error='UnknownObject')
await call(inherited, 'GetLayout', 'iias', [0, -1, []], error='UnknownObject')
bus.disconnect()
bus = None
daemon.terminate()
daemon.wait(timeout=5)
await asyncio.sleep(.1)
# Bus loss removes the capability/association, but local shortcuts survive.
await key(b, 49, True, True) # Ctrl+N invokes the retained window override.
await asyncio.wait_for(runner, 5)
print('D-BusMenu layout/errors/state/consumer restart and KDE association: PASS')
finally:
if bus: bus.disconnect()
daemon.terminate()
daemon.wait(timeout=5)
config.close()
if runner:
await runner
if __name__ == '__main__':
asyncio.run(main(str(Path(sys.argv[1]).resolve())))

View File

@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""Open a supplied menu-enabled app on Plasma, query its export, then choose Quit.
The app must export a literal 'Quit' or 'Quit …' item. This checks real KWin association and
session-bus transport; it does not assert panel or titlebar menu rendering.
"""
import asyncio
import os
import re
import sys
from dbus_next import Message, MessageType, Variant
from dbus_next.aio import MessageBus
async def main(binary):
process = await asyncio.create_subprocess_exec(binary, env=dict(os.environ, WAYLAND_DEBUG='client'), stderr=asyncio.subprocess.PIPE)
bus = None
try:
async def address():
while line := await process.stderr.readline():
match = re.search(rb'set_address\("([^"]+)", "([^"]+)"\)', line)
if match: return [part.decode() for part in match.groups()]
raise AssertionError('Application exited without a KDE menu association')
service, path = await asyncio.wait_for(address(), 10)
# Keep draining Wayland diagnostics so a full pipe cannot stall the app.
drain = asyncio.create_task(process.stderr.read())
bus = await MessageBus().connect()
reply = await asyncio.wait_for(bus.call(Message(destination=service, path=path, interface='com.canonical.dbusmenu', member='GetLayout', signature='iias', body=[0, -1, []])), 5)
assert reply.message_type == MessageType.METHOD_RETURN, reply.body
def flatten(node):
return [node] + [item for child in node[2] for item in flatten(child.value)]
nodes = flatten(reply.body[1])
labels = [node[1]['label'].value for node in nodes]
quit_id = next(node[0] for node in nodes if node[1]['label'].value == 'Quit' or node[1]['label'].value.startswith('Quit '))
print('Live KWin association and menu layout:', labels)
reply = await bus.call(Message(destination=service, path=path, interface='com.canonical.dbusmenu', member='Event', signature='isvu', body=[quit_id, 'clicked', Variant('i', 0), 0]))
assert reply.message_type == MessageType.METHOD_RETURN
assert await asyncio.wait_for(process.wait(), 5) == 0
await drain
print('Live Plasma menu export / Quit activation: PASS')
finally:
if bus: bus.disconnect()
if process.returncode is None:
process.kill()
await process.wait()
if __name__ == '__main__':
asyncio.run(main(sys.argv[1]))

159
src/linux/tests/menus.c Normal file
View File

@ -0,0 +1,159 @@
/* Included by the service harness: all desktop traffic uses private fixtures. */
#define OPEN_ID UINT64_C(0x1234567800000065)
static void menu_phase(uhdk_app *app, uhdk_window *w, const char *phase) {
uint64_t id;
OK(uhdk_window_set_title_async(w, text(phase), &id));
OK(wait_result(app, id).status);
}
static void menus(uhdk_app *app) {
wait_capability(app, UHDK_CAP_APP_MENU);
assert(!uhdk_app_has_capability(app, UHDK_CAP_CONTEXT_MENU));
uhdk_menu_item children[] = {
{.command_id = OPEN_ID,
.label = {"Open_file", 9},
.shortcut_key = 'o',
.shortcut_modifiers = UHDK_MOD_CONTROL},
{.command_id = 102, .label = {"Checked", 7}, .flags = UHDK_MENU_CHECKED},
{.command_id = 103, .label = {"Disabled", 8}, .flags = UHDK_MENU_DISABLED},
{.role = UHDK_MENU_ROLE_COPY,
.label = {"Copy", 4},
.shortcut_key = 'c',
.shortcut_modifiers = UHDK_MOD_CONTROL},
{.role = UHDK_MENU_ROLE_QUIT, .label = {"Quit", 4}},
{.flags = UHDK_MENU_SEPARATOR},
};
uhdk_menu *child, *root;
OK(uhdk_menu_create(app, children, sizeof(children) / sizeof(*children), &child));
uhdk_menu_item roots[] = {
{.command_id = 10, .label = {"File", 4}, .submenu = child},
{.command_id = 900, .label = {"Next", 4}},
};
OK(uhdk_menu_create(app, roots, 2, &root));
uhdk_menu_release(child);
uint64_t id;
OK(uhdk_app_set_menu_async(app, root, &id));
OK(wait_result(app, id).status);
uhdk_window *a = window(app), *b = window(app);
uhdk_menu_item overrides[] = {
{.command_id = 200, .label = {"Window action", 13}},
{.command_id = 900,
.label = {"Next", 4},
.shortcut_key = 'n',
.shortcut_modifiers = UHDK_MOD_CONTROL},
};
uhdk_menu *override;
OK(uhdk_menu_create(app, overrides, 2, &override));
OK(uhdk_window_set_menu_async(b, override, &id));
OK(wait_result(app, id).status);
uhdk_menu_release(override); // The attachment must keep its model alive.
OK(uhdk_window_set_text_input_async(a, 1, &id));
OK(wait_result(app, id).status);
OK(uhdk_menu_popup_async(root, a, (uhdk_point){0, 0}, &id));
assert(wait_result(app, id).status == UHDK_STATUS_UNSUPPORTED);
menu_phase(app, a, "menus-ready");
unsigned stage = 0, opens_a = 0, opens_b = 0, copies = 0, quits = 0, overrides_seen = 0,
raw_o = 0;
uint64_t end = deadline(app);
while (stage < 4 && uhdk_app_get_time_ns(app) < end) {
uhdk_event_list events;
OK(uhdk_app_poll_events(app, 50000000, &events));
int next = 0;
for (uint64_t i = 0; i < events.len; ++i) {
const uhdk_event *e = &events.ptr[i];
if (e->type == UHDK_EVENT_MENU_COMMAND) {
uint64_t command = ((const uhdk_menu_event *)e->data)->command_id;
if (command == OPEN_ID) {
assert(stage == 0 || stage == 2);
if (e->window == a)
++opens_a;
else {
assert(e->window == b);
++opens_b;
}
} else if (command == 200) {
assert(e->window == b);
++overrides_seen;
} else {
assert(command == 900);
next = 1;
}
} else if (e->type == UHDK_EVENT_EDIT_COMMAND) {
const uhdk_edit_event *edit = e->data;
assert(edit->command == UHDK_EDIT_COPY && e->window == a && e->input_id != 0);
++copies;
} else if (e->type == UHDK_EVENT_QUIT_REQUESTED)
++quits;
else if (e->type == UHDK_EVENT_TEXT_COMMIT)
assert(!"accelerator leaked text");
else if (e->type == UHDK_EVENT_KEY) {
const uhdk_key_event *key = e->data;
if (key->logical_key == 'o' || key->logical_key == 'O') {
assert(stage == 1);
++raw_o;
}
assert(key->logical_key != 'c' && key->logical_key != 'C');
}
}
if (!next)
continue;
++stage;
if (stage == 1) {
assert(
opens_a == 1 && opens_b == 0 && copies == 1 && quits == 1 && overrides_seen == 1
);
OK(uhdk_menu_set_item_flags_async(root, 10, UHDK_MENU_DISABLED, &id));
OK(wait_result(app, id).status);
OK(uhdk_menu_set_item_flags_async(root, 102, 0, &id));
OK(wait_result(app, id).status);
menu_phase(app, a, "flags-ready");
} else if (stage == 2) {
assert(raw_o >= 2);
OK(uhdk_menu_set_item_flags_async(root, 10, 0, &id));
OK(wait_result(app, id).status);
OK(uhdk_window_set_menu_async(b, NULL, &id));
OK(wait_result(app, id).status);
menu_phase(app, a, "restored-ready");
} else if (stage == 3) {
assert(opens_a == 2 && opens_b == 1 && copies == 2);
// Override survives clearing the app default, then the first window dies.
OK(uhdk_menu_create(app, overrides, 2, &override));
OK(uhdk_window_set_menu_async(b, override, &id));
OK(wait_result(app, id).status);
uhdk_menu_release(override);
OK(uhdk_app_set_menu_async(app, NULL, &id));
OK(wait_result(app, id).status);
uhdk_menu_release(root);
uhdk_window_destroy(a);
menu_phase(app, b, "detached-ready");
}
}
assert(stage == 4);
assert(!uhdk_app_has_capability(app, UHDK_CAP_APP_MENU));
uhdk_window_destroy(b);
puts("Plasma menu routing/state/lifetime/shortcut fixture: PASS");
}
#undef OPEN_ID
static void menus_unavailable(uhdk_app *app, int no_bus) {
uhdk_window *w = window(app);
uhdk_menu_item item = {.command_id = 1, .label = {"Unavailable", 11}};
uhdk_menu *menu;
OK(uhdk_menu_create(app, &item, 1, &menu));
uint64_t id;
OK(uhdk_app_set_menu_async(app, menu, &id));
assert(
wait_result(app, id).status == (no_bus ? UHDK_STATUS_UNAVAILABLE : UHDK_STATUS_UNSUPPORTED)
);
OK(uhdk_window_set_menu_async(w, menu, &id));
assert(
wait_result(app, id).status == (no_bus ? UHDK_STATUS_UNAVAILABLE : UHDK_STATUS_UNSUPPORTED)
);
assert(!uhdk_app_has_capability(app, UHDK_CAP_APP_MENU));
OK(uhdk_menu_set_item_flags_async(menu, 1, UHDK_MENU_CHECKED, &id));
OK(wait_result(app, id).status);
OK(uhdk_app_set_menu_async(app, NULL, &id));
OK(wait_result(app, id).status);
uhdk_menu_release(menu);
uhdk_window_destroy(w);
puts("Unavailable menu protocol/session bus: PASS");
}

245
src/linux/tests/native.c Normal file
View File

@ -0,0 +1,245 @@
#define UHDK_ENABLE_OPENGL
#define UHDK_ENABLE_NATIVE
#include "uhdk.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define OK(expression) \
do { \
uhdk_status status = (expression); \
if (status != UHDK_STATUS_OK) { \
fprintf(stderr, "%s:%d: %s returned %u\n", __FILE__, __LINE__, #expression, status); \
abort(); \
} \
} while (0)
static uhdk_text text(const char *s) {
return (uhdk_text){s, strlen(s)};
}
static int entered;
static uhdk_request_result wait_request(uhdk_app *app, uint64_t id) {
uint64_t deadline = uhdk_app_get_time_ns(app) + 10000000000ull;
while (uhdk_app_get_time_ns(app) < deadline) {
uhdk_event_list events;
OK(uhdk_app_poll_events(app, 100000000ull, &events));
for (uint64_t i = 0; i < events.len; ++i) {
const uhdk_event *event = &events.ptr[i];
if (event->type == UHDK_EVENT_REQUEST_DONE) {
const uhdk_request_result *result = event->data;
assert(result->id == id);
return *result;
}
}
}
fputs("Timed out awaiting request\n", stderr);
abort();
}
static uhdk_window_metrics metrics(uhdk_window *w) {
uhdk_window_metrics m = {0};
m.struct_size = sizeof(m);
OK(uhdk_window_get_metrics(w, &m));
return m;
}
static void wait_metrics(uhdk_app *app, uhdk_window *w, int visible, int width) {
uint64_t deadline = uhdk_app_get_time_ns(app) + 10000000000ull;
while (uhdk_app_get_time_ns(app) < deadline) {
uhdk_window_metrics m = metrics(w);
if (m.visible == visible && m.logical_size.width == width)
return;
uhdk_event_list events;
OK(uhdk_app_poll_events(app, 100000000ull, &events));
}
fputs("Timed out awaiting metrics\n", stderr);
abort();
}
static uhdk_window_desc descriptor(void) {
uhdk_window_desc desc = {0};
desc.struct_size = sizeof(desc);
desc.title = text("UHDK Linux lifecycle test");
desc.flags = UHDK_WINDOW_OPENGL | UHDK_WINDOW_DECORATED | UHDK_WINDOW_RESIZABLE;
desc.content_size = (uhdk_size){320, 240};
return desc;
}
static uhdk_window *create_window(uhdk_app *app) {
uhdk_window_desc desc = descriptor();
uint64_t id;
OK(uhdk_window_create_async(app, &desc, &id));
uhdk_request_result result = wait_request(app, id);
OK(result.status);
return ((const uhdk_window_result *)result.data)->window;
}
static void test_main(uhdk_app *app, void *user) {
++entered;
assert(uhdk_app_get_backend(app) == UHDK_BACKEND_WAYLAND);
assert(!uhdk_app_has_capability(app, UHDK_CAP_FILE_DIALOGS));
uhdk_event_list ready;
OK(uhdk_app_poll_events(app, 0, &ready));
assert(ready.len > 0 && ready.ptr[0].type == UHDK_EVENT_READY);
// Cancellation must complete exactly once even before the first configure.
uint64_t id;
uhdk_window_desc desc = descriptor();
OK(uhdk_window_create_async(app, &desc, &id));
OK(uhdk_request_cancel(app, id));
uhdk_request_result cancelled = wait_request(app, id);
assert(cancelled.status == UHDK_STATUS_CANCELLED || cancelled.status == UHDK_STATUS_OK);
if (cancelled.status == UHDK_STATUS_OK)
uhdk_window_destroy(((const uhdk_window_result *)cancelled.data)->window);
// Destroying a parent must also complete a child still awaiting configure.
uhdk_window *parent = create_window(app);
desc.parent = parent;
OK(uhdk_window_create_async(app, &desc, &id));
uhdk_window_destroy(parent);
uhdk_request_result child = wait_request(app, id);
assert(child.status == UHDK_STATUS_CANCELLED || child.status == UHDK_STATUS_OK);
desc.parent = NULL;
uhdk_window *w = create_window(app);
assert(metrics(w).visible == 0);
uhdk_native_window native = {0};
native.struct_size = sizeof(native);
OK(uhdk_window_get_native(w, &native));
assert(native.backend == UHDK_BACKEND_WAYLAND && native.primary && native.secondary);
const uint8_t path[] = "/tmp/uhdk file-%\xff";
uhdk_file *file;
OK(uhdk_file_create_from_native_path(app, (uhdk_bytes){path, sizeof(path) - 1}, &file));
uint8_t uri[256];
uint64_t len;
OK(uhdk_file_copy_representation(file, UHDK_FILE_URI, uri, sizeof(uri), &len));
assert(len == strlen("file:///tmp/uhdk%20file-%25%FF"));
assert(memcmp(uri, "file:///tmp/uhdk%20file-%25%FF", len) == 0);
uhdk_file_release(file);
if (!user) {
assert(uhdk_app_has_capability(app, UHDK_CAP_OPENGL));
OK(uhdk_gl_context_create_async(w, &id));
uhdk_request_result gl_result = wait_request(app, id);
OK(gl_result.status);
uhdk_gl_context *gl = ((const uhdk_gl_context_result *)gl_result.data)->gl_context;
OK(uhdk_gl_context_begin_frame(gl));
assert(uhdk_gl_context_begin_frame(gl) == UHDK_STATUS_BUSY);
uint32_t major, minor;
OK(uhdk_gl_context_get_version(gl, &major, &minor));
assert(major > 3 || (major == 3 && minor >= 3));
fprintf(stderr, "Linux context: OpenGL %u.%u\n", major, minor);
OK(uhdk_gl_clear_current(app));
assert(uhdk_gl_context_get_version(gl, &major, &minor) == UHDK_STATUS_BUSY);
OK(uhdk_gl_context_make_current(gl));
uhdk_gl_proc address;
OK(uhdk_gl_context_get_proc(gl, text("glClearColor"), &address));
PFNGLCLEARCOLORPROC clear_color = (PFNGLCLEARCOLORPROC)address;
OK(uhdk_gl_context_get_proc(gl, text("glClear"), &address));
PFNGLCLEARPROC clear = (PFNGLCLEARPROC)address;
OK(uhdk_gl_context_get_proc(gl, text("glReadPixels"), &address));
PFNGLREADPIXELSPROC read_pixels = (PFNGLREADPIXELSPROC)address;
OK(uhdk_gl_context_set_swap_interval(gl, 1));
assert(uhdk_gl_context_swap_buffers(gl) == UHDK_STATUS_UNAVAILABLE);
OK(uhdk_gl_context_end_frame(gl));
for (int pass = 0; pass < 3; ++pass) {
int width = 320 + pass * 20;
OK(uhdk_window_set_size_async(w, (uhdk_size){width, 240}, &id));
OK(wait_request(app, id).status);
OK(uhdk_window_set_visible_async(w, 1, &id));
OK(wait_request(app, id).status);
wait_metrics(app, w, 1, width);
for (int frame = 0; frame < 4; ++frame) {
OK(uhdk_gl_context_begin_frame(gl));
clear_color(0.25f, 0.5f, 0.75f, 1.0f);
clear(GL_COLOR_BUFFER_BIT);
unsigned char pixel[4] = {0};
read_pixels(10, 10, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
assert(
pixel[0] >= 63 && pixel[0] <= 65 && pixel[1] >= 127 && pixel[1] <= 129 &&
pixel[2] >= 190 && pixel[2] <= 192
);
OK(uhdk_gl_context_swap_buffers(gl));
OK(uhdk_gl_context_end_frame(gl));
}
OK(uhdk_window_set_visible_async(w, 0, &id));
OK(wait_request(app, id).status);
wait_metrics(app, w, 0, width);
}
uhdk_gl_context_destroy(gl);
// Destroying a context releases its reservation on the window.
OK(uhdk_gl_context_create_async(w, &id));
gl_result = wait_request(app, id);
OK(gl_result.status);
uhdk_gl_context_destroy(((const uhdk_gl_context_result *)gl_result.data)->gl_context);
}
uhdk_window_destroy(w);
// Returning with a queued creation must cancel/drain it before native teardown.
OK(uhdk_window_create_async(app, &desc, &id));
}
static void disconnect_main(uhdk_app *app, void *user) {
(void)user;
++entered;
puts("connected");
fflush(stdout);
uint64_t deadline = uhdk_app_get_time_ns(app) + 5000000000ull;
int quit_seen = 0;
while (uhdk_app_get_time_ns(app) < deadline) {
uhdk_event_list events;
OK(uhdk_app_poll_events(app, 100000000ull, &events));
for (uint64_t i = 0; i < events.len; ++i) {
if (events.ptr[i].type == UHDK_EVENT_QUIT_REQUESTED)
quit_seen = 1;
if (events.ptr[i].type == UHDK_EVENT_STOPPED) {
assert(quit_seen);
return;
}
}
}
abort();
}
static void should_not_run(uhdk_app *app, void *user) {
(void)app;
(void)user;
abort();
}
int main(int argc, char **argv) {
int failure = argc > 1 && strcmp(argv[1], "--startup-failure") == 0;
int windows_only = argc > 1 && strcmp(argv[1], "--windows-only") == 0;
int disconnect = argc > 1 && strcmp(argv[1], "--disconnect") == 0;
int passes = disconnect ? 1 : 2;
for (int pass = 0; pass < passes; ++pass) {
uhdk_app_desc desc = {0};
desc.struct_size = sizeof(desc);
desc.abi_version = UHDK_ABI_VERSION;
desc.display_name = text("UHDK Linux test");
char app_id[] = "org.uhdk.LifecycleTest";
desc.application_id = text(app_id);
uhdk_app *app = NULL;
desc.preferred_backend = UHDK_BACKEND_X11;
assert(uhdk_app_create(&desc, &app) == UHDK_STATUS_UNSUPPORTED);
desc.preferred_backend = UHDK_BACKEND_AUTO;
OK(uhdk_app_create(&desc, &app));
memset(app_id, 'x', sizeof(app_id) - 1); // Descriptor bytes are copied.
assert(uhdk_app_get_backend(app) == UHDK_BACKEND_AUTO);
assert(!uhdk_app_has_capability(app, UHDK_CAP_OPENGL));
if (failure) {
assert(uhdk_app_run(app, should_not_run, NULL) != UHDK_STATUS_OK);
assert(uhdk_app_get_backend(app) == UHDK_BACKEND_AUTO);
} else if (disconnect) {
OK(uhdk_app_run(app, disconnect_main, NULL));
} else
OK(uhdk_app_run(app, test_main, windows_only ? &windows_only : NULL));
uhdk_app_destroy(app);
}
assert(entered == (failure ? 0 : passes));
fputs("Linux lifecycle: PASS\n", stderr);
return 0;
}

View File

@ -0,0 +1,187 @@
#!/usr/bin/env python3
"""Strict private Wayland popup peer; captures actual pixels to /tmp/uhdk-popup.ppm.
No session bus, EGL rendering, or desktop input injection is used.
"""
import mmap
import os
from pathlib import Path
import struct
import sys
import time
from wayland_fixture import Compositor, run
class Popups(Compositor):
globals = Compositor.globals + [('wl_shm', 1)]
def __init__(self, sock, inject=False):
super().__init__(sock, False)
self.stage = -1
self.tasks = []
self.positions = {}
self.popup_surfaces = {}
self.xdg_surfaces = {}
self.stack = []
self.pools = {}
self.buffers = {}
self.attached = {}
self.trigger_serial = 500
def schedule(self, delay, action):
self.tasks.append((time.monotonic() + delay, action))
def timers(self):
ready = [x for x in self.tasks if x[0] <= time.monotonic()]
self.tasks = [x for x in self.tasks if x[0] > time.monotonic()]
for _, action in ready: action()
def keyboard(self, code):
keyboard = self.ids['wl_keyboard']
self.event(keyboard, 'key', 551, 1, code, 1)
self.event(keyboard, 'key', 552, 2, code, 0)
def pointer(self, surface, x, y, click=False):
pointer = self.ids['wl_pointer']
self.event(pointer, 'enter', 550, surface, x * 256, y * 256)
if click:
self.event(pointer, 'button', 551, 1, 0x110, 1)
self.event(pointer, 'button', 552, 2, 0x110, 0)
def trigger(self):
surface = next(iter(self.surfaces))
self.event(self.ids['wl_keyboard'], 'enter', 499, surface, b'')
self.event(self.ids['wl_keyboard'], 'modifiers', 499, 0, 0, 0, 0)
self.event(self.ids['wl_pointer'], 'enter', 499, surface, 12 * 256, 16 * 256)
self.trigger_serial += 10
self.event(self.ids['wl_pointer'], 'button', self.trigger_serial, 1, 0x111, 1)
self.event(self.ids['wl_pointer'], 'button', self.trigger_serial + 1, 2, 0x111, 0)
def actions(self, surface, p):
self.event(self.ids['wl_keyboard'], 'enter', 549, surface, b'')
self.event(self.ids['wl_keyboard'], 'modifiers', 549, 0, 0, 0, 0)
if p['child']:
if self.stage == 10: self.schedule(.05, lambda: self.pointer(surface, 35, 15, True))
else: self.schedule(.05, lambda: self.keyboard(28))
elif self.stage == 0:
# Split popup geometry from the surface configure. Hover dirties the
# menu during the gap; the client must wait for the configure serial.
width, height = p['size']
self.event(p['popup'], 'configure', 10, 20, width + 4, height)
p['pending_configure'] = True
def acknowledge():
p['pending_configure'] = False
self.serial += 1
self.event(p['xdg'], 'configure', self.serial)
self.schedule(.06, acknowledge)
self.pointer(surface, 35, 15)
self.schedule(.12, lambda: self.pointer(surface, 35, 15, True))
elif self.stage == 1:
self.keyboard(108)
self.keyboard(108) # Skip disabled/separator; select Checked.
self.schedule(.05, lambda: self.event(self.ids['wl_keyboard'], 'key', 551, 1, 28, 1))
elif self.stage == 2:
self.keyboard(107) # End = Copy.
self.keyboard(103) # Up = More.
self.schedule(.05, lambda: self.keyboard(106))
elif self.stage == 3:
self.keyboard(1)
elif self.stage == 4:
self.event(p['popup'], 'popup_done')
elif self.stage == 6:
self.schedule(.2, lambda: self.pointer(surface, 35, 15, True))
self.schedule(.3, lambda: self.keyboard(1))
elif self.stage in (7, 8):
self.keyboard(107) # Copy, or final item in the tall scrolling menu.
self.schedule(.05, lambda: self.keyboard(28))
elif self.stage == 9:
self.pointer(next(iter(self.surfaces)), 15, 15, True)
elif self.stage == 10:
self.pointer(surface, 35, 102) # Hover opens More after a short delay.
# Stages 5/11 are cancelled by the application after 100ms.
def request(self, obj, iface, name, args):
if iface == 'wl_surface' and name == 'commit' and obj in self.popup_surfaces:
p = self.popup_surfaces[obj]
assert not p.get('pending_configure', False), 'Commit before xdg_surface.configure'
if not p['configured']:
p['configured'] = True
width, height = p['size']
if self.stage == 8: height = 120
if self.stage == 1: self.event(obj, 'preferred_buffer_scale', 2)
self.event(p['popup'], 'configure', 10, 20, width, height)
self.serial += 1
self.event(p['xdg'], 'configure', self.serial)
elif obj in self.attached:
buf = self.attached[obj]
meta = self.buffers[buf]
pixels = bytes(meta['mapping'][meta['offset']:meta['offset'] + meta['stride'] * meta['height']])
words = struct.unpack('=' + 'I' * (len(pixels) // 4), pixels)
colors = set(words)
assert len(colors) > 15, 'Expected antialiased bitmap text in wl_shm pixels'
assert all((color >> 24) == 255 for color in colors)
if self.stage == 0 and not p['child']:
rgb = bytes(channel for color in words for channel in [(color >> 16) & 255, (color >> 8) & 255, color & 255])
Path('/tmp/uhdk-popup.ppm').write_bytes(f"P6\n{meta['width']} {meta['height']}\n255\n".encode() + rgb)
if self.stage == 1: assert meta['width'] == p['size'][0] * 2
def release():
if buf in self.objects: self.event(buf, 'release')
self.schedule(.03, release)
if not p['mapped']:
p['mapped'] = True
self.schedule(.03, lambda: self.actions(obj, p))
return
super().request(obj, iface, name, args)
if iface == 'xdg_toplevel' and name == 'set_title' and args[0].startswith('popup-stage-'):
assert not self.stack
self.stage = int(args[0].split('-')[-1])
self.schedule(.03, self.trigger)
elif iface == 'xdg_wm_base' and name == 'get_xdg_surface':
self.xdg_surfaces[args[0]] = args[1]
elif iface == 'xdg_wm_base' and name == 'create_positioner':
self.positions[args[0]] = {}
elif iface == 'xdg_positioner':
if name == 'set_size': self.positions[obj]['size'] = tuple(args)
elif name == 'set_anchor_rect': self.positions[obj]['anchor'] = tuple(args)
elif iface == 'xdg_surface' and name == 'get_popup':
popup, parent, position = args
surface = self.xdg_surfaces[obj]
self.popup_surfaces[surface] = dict(popup=popup, xdg=obj, configured=False, mapped=False, child=bool(self.stack), **self.positions[position])
if self.stack: assert parent == self.stack[-1][1]
self.stack.append((popup, obj, surface))
elif iface == 'xdg_popup' and name == 'grab':
assert args[0] == self.ids['wl_seat'] and args[1] != 0
assert self.stack[-1][0] == obj
if len(self.stack) == 1: assert args[1] == self.trigger_serial, 'Must use press serial'
elif iface == 'xdg_popup' and name == 'destroy':
assert self.stack[-1][0] == obj, 'Destroy nested popups from the top down'
_, _, surface = self.stack.pop()
del self.popup_surfaces[surface]
if self.stage == 1:
keyboard = self.ids['wl_keyboard']
parent = next(iter(self.surfaces))
self.event(keyboard, 'leave', 552, surface)
self.event(keyboard, 'enter', 553, parent, struct.pack('=I', 28))
self.event(keyboard, 'modifiers', 553, 0, 0, 0, 0)
self.event(keyboard, 'key', 554, 2, 28, 0) # Consumed key's release stays consumed.
elif iface == 'wl_shm' and name == 'create_pool':
pool, fd, size = args
self.pools[pool] = mmap.mmap(fd, size, access=mmap.ACCESS_READ)
os.close(fd)
elif iface == 'wl_shm_pool' and name == 'create_buffer':
buf, offset, width, height, stride, fmt = args
assert fmt == 0 and stride == width * 4
self.buffers[buf] = dict(mapping=self.pools[obj], offset=offset, width=width, height=height, stride=stride)
elif iface == 'wl_surface' and name == 'attach':
self.attached[obj] = args[0]
elif iface == 'wl_buffer' and name == 'destroy':
self.buffers.pop(obj, None)
elif iface == 'wl_surface' and name == 'destroy':
self.surfaces.pop(obj, None)
if __name__ == '__main__':
environment = dict(os.environ)
environment.pop('DBUS_SESSION_BUS_ADDRESS', None)
run(sys.argv[1], 'popup', environment, Popups)
print('Wayland popup grab/stack/configure/scale and software pixels: PASS')

113
src/linux/tests/popups.c Normal file
View File

@ -0,0 +1,113 @@
/* Included by services.c; driven by popup_fixture.py on a private compositor. */
#define POPUP_OPEN UINT64_C(0x1234567800000001)
#define POPUP_CHILD UINT64_C(0xfedcba9800000002)
static void popups(uhdk_app *app) {
uhdk_window *w = window(app);
wait_capability(app, UHDK_CAP_CONTEXT_MENU);
assert(!uhdk_app_has_capability(app, UHDK_CAP_APP_MENU));
uint64_t id;
OK(uhdk_window_set_visible_async(w, 1, &id));
OK(wait_result(app, id).status);
OK(uhdk_window_set_text_input_async(w, 1, &id));
OK(wait_result(app, id).status);
uhdk_menu_item child_items[] = {{.command_id = POPUP_CHILD, .label = {"Child café", 11}}};
uhdk_menu *child, *root;
OK(uhdk_menu_create(app, child_items, 1, &child));
uhdk_menu_item items[] = {
{.command_id = POPUP_OPEN,
.label = {"Open…", 7},
.shortcut_key = 'o',
.shortcut_modifiers = UHDK_MOD_CONTROL},
{.command_id = 102, .label = {"Disabled", 8}, .flags = UHDK_MENU_DISABLED},
{.flags = UHDK_MENU_SEPARATOR},
{.command_id = 103, .label = {"Checked", 7}, .flags = UHDK_MENU_CHECKED},
{.command_id = 104, .label = {"More", 4}, .submenu = child},
{.label = {"Copy", 4}, .role = UHDK_MENU_ROLE_COPY},
};
OK(uhdk_menu_create(app, items, 6, &root));
uhdk_menu_release(child);
OK(uhdk_menu_popup_async(root, w, (uhdk_point){4, 4}, &id));
assert(wait_result(app, id).status == UHDK_STATUS_UNAVAILABLE); // No input action yet.
for (unsigned stage = 0; stage < 12; ++stage) {
uhdk_menu *menu = root;
if (stage == 8) {
uhdk_menu_item many[32] = {0};
for (unsigned j = 0; j < 32; ++j) {
many[j].command_id = 1000 + j;
many[j].label = text("Scrollable item");
}
OK(uhdk_menu_create(app, many, 32, &menu));
}
char title[64];
snprintf(title, sizeof(title), "popup-stage-%u", stage);
OK(uhdk_window_set_title_async(w, text(title), &id));
// Do not discard the input event that may arrive with title completion.
uint64_t popup = 0, busy = 0, started = 0;
unsigned commands = 0, complete = 0, busy_done = 0, changed = 0;
uint64_t end = deadline(app);
while (!complete && uhdk_app_get_time_ns(app) < end) {
uhdk_event_list events;
OK(uhdk_app_poll_events(app, 20000000, &events));
for (uint64_t j = 0; j < events.len; ++j) {
const uhdk_event *e = &events.ptr[j];
if (e->type == UHDK_EVENT_POINTER_BUTTON) {
const uhdk_pointer_event *p = e->data;
assert(p->button == UHDK_BUTTON_SECONDARY);
if (p->pressed) {
assert(popup == 0);
OK(uhdk_menu_popup_async(menu, w, p->position, &popup));
started = uhdk_app_get_time_ns(app);
if (stage == 0)
OK(uhdk_menu_popup_async(menu, w, p->position, &busy));
}
} else if (e->type == UHDK_EVENT_MENU_COMMAND) {
uint64_t command = ((const uhdk_menu_event *)e->data)->command_id;
uint64_t expected = stage == 0 ? POPUP_OPEN
: stage == 1 ? 103
: (stage == 2 || stage == 10) ? POPUP_CHILD
: 1031;
assert(stage == 0 || stage == 1 || stage == 2 || stage == 8 || stage == 10);
assert(command == expected && e->window == w);
++commands;
} else if (e->type == UHDK_EVENT_EDIT_COMMAND) {
assert(stage == 7 && e->window == w && e->input_id != 0);
assert(((const uhdk_edit_event *)e->data)->command == UHDK_EDIT_COPY);
++commands;
} else if (e->type == UHDK_EVENT_REQUEST_DONE) {
const uhdk_request_result *r = e->data;
if (r->id == popup && popup) {
int selected = stage <= 2 || stage == 7 || stage == 8 || stage == 10;
assert(r->status == (selected ? UHDK_STATUS_OK : UHDK_STATUS_CANCELLED));
assert(commands == (unsigned)selected); // Ordering and exactly one action.
++complete;
} else if (r->id == busy && busy) {
assert(r->status == UHDK_STATUS_BUSY);
++busy_done;
} else
OK(r->status);
} else if (e->type == UHDK_EVENT_KEY || e->type == UHDK_EVENT_TEXT_COMMIT)
assert(!"popup input leaked to application");
}
if (popup && !changed && uhdk_app_get_time_ns(app) - started > 100000000ull) {
if (stage == 5)
OK(uhdk_request_cancel(app, popup));
if (stage == 6) {
OK(uhdk_menu_set_item_flags_async(root, POPUP_OPEN, UHDK_MENU_DISABLED, &id));
OK(uhdk_menu_set_item_flags_async(root, 103, 0, &id));
}
if (stage == 11)
uhdk_window_destroy(w);
changed = 1;
}
}
assert(complete == 1);
if (stage == 0)
assert(busy_done == 1);
if (stage == 8)
uhdk_menu_release(menu);
}
uhdk_menu_release(root);
puts("software popup selection/roles/submenus/cancellation/input/scroll/lifetime: PASS");
}
#undef POPUP_OPEN
#undef POPUP_CHILD

View File

@ -0,0 +1,129 @@
#!/usr/bin/env python3
"""Exercise portal races on a private session bus and private Wayland socket.
Needs dbus-daemon and Python dbus-next (provided by the optional shell.nix).
Never calls the user's portal, opens a real dialog, or launches a URL handler.
"""
import asyncio
import os
from pathlib import Path
import subprocess
import sys
from dbus_next import Message, MessageType, Variant
from dbus_next.aio import MessageBus
from wayland_fixture import run
DESKTOP = 'org.freedesktop.portal.Desktop'
PATH = '/org/freedesktop/portal/desktop'
REQUEST = 'org.freedesktop.portal.Request'
async def main(binary):
daemon = subprocess.Popen(['dbus-daemon', '--session', '--nofork', '--print-address=1'], stdout=subprocess.PIPE, text=True)
bus = None
tasks = set()
closed = []
seen = set()
try:
address = daemon.stdout.readline().strip()
assert address
bus = await MessageBus(bus_address=address).connect()
await bus.request_name(DESKTOP)
def response(handle, code=0, results=None):
bus.send(Message.new_signal(handle, REQUEST, 'Response', 'ua{sv}', [code, results or {}]))
async def delayed_reply(message, handle, delay, is_url=False):
await asyncio.sleep(delay)
bus.send(Message.new_method_return(message, 'o', [handle]))
if is_url:
for key, value in [('color-scheme', 2), ('contrast', 0), ('reduced-motion', 0)]:
bus.send(Message.new_signal(PATH, 'org.freedesktop.portal.Settings', 'SettingChanged', 'ssv', ['org.freedesktop.appearance', key, Variant('u', value)]))
response(handle)
async def restart():
await bus.release_name(DESKTOP)
await asyncio.sleep(.1)
await bus.request_name(DESKTOP)
def schedule(coro):
task = asyncio.create_task(coro)
tasks.add(task)
task.add_done_callback(tasks.discard)
def handle(message):
if message.message_type != MessageType.METHOD_CALL:
return False
if message.interface == 'org.freedesktop.DBus.Properties' and message.member == 'Get':
assert message.body == ['org.freedesktop.portal.FileChooser', 'version']
bus.send(Message.new_method_return(message, 'v', [Variant('u', 4)]))
elif message.interface == 'org.freedesktop.portal.Settings' and message.member == 'ReadAll':
assert message.body == [['org.freedesktop.appearance']]
bus.send(Message.new_method_return(message, 'a{sa{sv}}', [{'org.freedesktop.appearance': {
'color-scheme': Variant('u', 1), 'contrast': Variant('u', 1), 'reduced-motion': Variant('u', 1)}}]))
elif message.interface in ('org.freedesktop.portal.FileChooser', 'org.freedesktop.portal.OpenURI'):
parent, title, options = message.body
token = options['handle_token'].value
handle_path = PATH + '/request/' + message.sender[1:].replace('.', '_') + '/' + token
if message.member == 'OpenURI':
assert title == 'https://example.org/test'
assert options['activation_token'].value == 'fixture-token'
schedule(delayed_reply(message, handle_path, .35, True))
seen.add('url')
return True
seen.add(title)
assert message.signature == 'ssa{sv}'
if message.member == 'OpenFile':
assert options['multiple'].value and not options['directory'].value
assert options['filters'].value == [['Text', [[0, '*.txt'], [0, '*.md']]]]
if title == 'success':
assert parent == 'wayland:fixture-parent'
assert options['modal'].value
# A fast response can beat the method return. The frontend
# must already be subscribed using its chosen handle token.
response(handle_path, results={'uris': Variant('as', ['file:///tmp/a%20b%25%FF.txt', 'file:///tmp/second'])})
bus.send(Message.new_method_return(message, 'o', [handle_path]))
elif title == 'save':
assert message.member == 'SaveFile'
assert options['current_name'].value == 'book.txt'
assert options['accept_label'].value == 'Store'
assert bytes(options['current_folder'].value) == b'/tmp\0'
bus.send(Message.new_method_return(message, 'o', [handle_path]))
response(handle_path, results={'uris': Variant('as', ['file:///tmp/save.txt'])})
elif title == 'cancel':
# Return a different handle after local cancellation; both
# the predicted and returned paths need Close calls.
schedule(delayed_reply(message, handle_path + '_late', .2))
elif title == 'parent':
bus.send(Message.new_method_return(message, 'o', [handle_path]))
elif title == 'restart':
schedule(restart())
elif title == 'dismiss':
bus.send(Message.new_method_return(message, 'o', [handle_path]))
response(handle_path, 1)
else:
raise AssertionError(title)
elif message.interface == REQUEST and message.member == 'Close':
closed.append(message.path)
bus.send(Message.new_method_return(message))
else:
return False
return True
bus.add_message_handler(handle)
await asyncio.to_thread(run, binary, 'portal', dict(os.environ, DBUS_SESSION_BUS_ADDRESS=address))
await asyncio.sleep(.1)
assert {'success', 'save', 'cancel', 'url', 'restart', 'dismiss'} <= seen, seen
assert any(p.endswith('_late') for p in closed), closed
assert len(closed) >= 2, closed
print('private portal protocol/cancellation/restart fixture: PASS')
finally:
for task in tasks: task.cancel()
if bus: bus.disconnect()
daemon.terminate()
daemon.wait(timeout=5)
if __name__ == '__main__':
asyncio.run(main(str(Path(sys.argv[1]).resolve())))

532
src/linux/tests/services.c Normal file
View File

@ -0,0 +1,532 @@
#define _GNU_SOURCE
#include "uhdk.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define OK(expr) \
do { \
uhdk_status status_ = (expr); \
if (status_ != UHDK_STATUS_OK) { \
fprintf(stderr, "%d: %s: status %u\n", __LINE__, #expr, status_); \
abort(); \
} \
} while (0)
static uhdk_text text(const char *s) {
return (uhdk_text){s, strlen(s)};
}
static uint64_t deadline(uhdk_app *app) {
return uhdk_app_get_time_ns(app) + 8000000000ull;
}
static uhdk_request_result wait_result(uhdk_app *app, uint64_t id) {
uint64_t end = deadline(app);
while (uhdk_app_get_time_ns(app) < end) {
uhdk_event_list events;
OK(uhdk_app_poll_events(app, 50000000, &events));
for (uint64_t i = 0; i < events.len; ++i)
if (events.ptr[i].type == UHDK_EVENT_REQUEST_DONE) {
const uhdk_request_result *r = events.ptr[i].data;
if (r->id == id)
return *r;
}
}
fprintf(stderr, "request %lu timed out\n", (unsigned long)id);
abort();
}
static uhdk_window *window(uhdk_app *app) {
uhdk_window_desc desc = {0};
desc.struct_size = sizeof(desc);
desc.title = text("UHDK service tests");
desc.content_size = (uhdk_size){320, 240};
desc.flags = UHDK_WINDOW_DECORATED | UHDK_WINDOW_RESIZABLE;
uint64_t id;
OK(uhdk_window_create_async(app, &desc, &id));
uhdk_request_result r = wait_result(app, id);
OK(r.status);
return ((const uhdk_window_result *)r.data)->window;
}
static void wait_capability(uhdk_app *app, uint32_t cap) {
uint64_t end = deadline(app);
while (!uhdk_app_has_capability(app, cap) && uhdk_app_get_time_ns(app) < end) {
uhdk_event_list events;
OK(uhdk_app_poll_events(app, 50000000, &events));
}
assert(uhdk_app_has_capability(app, cap));
}
static void write_file(const char *path, const char *bytes) {
FILE *f = fopen(path, "wb");
assert(f);
assert(fwrite(bytes, 1, strlen(bytes), f) == strlen(bytes));
assert(fclose(f) == 0);
}
static void wait_change(uhdk_app *app, uhdk_watch *watch, uint32_t flag) {
uint64_t end = deadline(app);
while (uhdk_app_get_time_ns(app) < end) {
uhdk_event_list events;
OK(uhdk_app_poll_events(app, 50000000, &events));
for (uint64_t i = 0; i < events.len; ++i)
if (events.ptr[i].type == UHDK_EVENT_WATCH) {
const uhdk_watch_event *e = events.ptr[i].data;
if (e->watch == watch && (e->flags & flag)) {
OK(e->status);
return;
}
}
}
fprintf(stderr, "watch flag %x timed out\n", flag);
abort();
}
static void watches(uhdk_app *app) {
char directory[] = "/tmp/uhdk-watch-XXXXXX";
assert(mkdtemp(directory));
char path[256], replacement[256];
snprintf(path, sizeof(path), "%s/document", directory);
snprintf(replacement, sizeof(replacement), "%s/replacement", directory);
write_file(path, "old");
uhdk_file *file;
OK(uhdk_file_create_from_native_path(
app, (uhdk_bytes){(const uint8_t *)path, strlen(path)}, &file
));
uint64_t id;
OK(uhdk_watch_create_async(file, &id));
uhdk_request_result result = wait_result(app, id);
OK(result.status);
uhdk_watch *watch = ((const uhdk_watch_result *)result.data)->watch;
write_file(replacement, "new");
assert(rename(replacement, path) == 0);
wait_change(app, watch, UHDK_CHANGE_CREATED);
write_file(path, "after replacement");
wait_change(app, watch, UHDK_CHANGE_CONTENT);
assert(unlink(path) == 0);
wait_change(app, watch, UHDK_CHANGE_REMOVED);
write_file(path, "recreated");
wait_change(app, watch, UHDK_CHANGE_CREATED);
assert(unlink(path) == 0);
assert(rmdir(directory) == 0);
wait_change(app, watch, UHDK_CHANGE_ROOT_INVALIDATED);
uhdk_watch_destroy(watch);
uhdk_file_release(file);
puts("watch replacement/removal/recreation/invalidation: PASS");
}
static void real_desktop(uhdk_app *app) {
wait_capability(app, UHDK_CAP_FILE_DIALOGS);
uhdk_preferences prefs = {0};
prefs.struct_size = sizeof(prefs);
OK(uhdk_app_get_preferences(app, &prefs));
printf(
"Portal available; appearance=%u, reduced_motion=%d, high_contrast=%d\n",
prefs.appearance,
prefs.reduced_motion,
prefs.high_contrast
);
watches(app);
uhdk_window *w = window(app);
uint8_t rgba[] = {255, 0, 0, 128, 0, 255, 0, 255};
uhdk_image image = {0};
image.struct_size = sizeof(image);
image.width = 2;
image.height = 1;
image.stride_bytes = 8;
image.pixels = (uhdk_bytes){rgba, sizeof(rgba)};
uint64_t id;
OK(uhdk_app_set_icon_async(app, &image, &id));
OK(wait_result(app, id).status);
// A hidden, unfocused window must not commandeer a later user's keystroke.
OK(uhdk_clipboard_write_text_async(app, text("must not replace clipboard"), &id));
assert(wait_result(app, id).status == UHDK_STATUS_UNAVAILABLE);
uhdk_window_destroy(w);
}
static void portals(uhdk_app *app) {
wait_capability(app, UHDK_CAP_FILE_DIALOGS);
uhdk_window *w = window(app);
uhdk_file_dialog_desc desc = {0};
desc.struct_size = sizeof(desc);
desc.parent = w;
desc.title = text("success");
desc.kind = UHDK_DIALOG_OPEN_FILES;
uhdk_text extensions[] = {text("txt"), text("md")};
uhdk_file_filter filter = {text("Text"), {extensions, 2}};
desc.filters = &filter;
desc.filters_len = 1;
uint64_t id;
OK(uhdk_file_dialog_show_async(app, &desc, &id));
uhdk_request_result r = wait_result(app, id);
OK(r.status);
const uhdk_file_list *files = r.data;
assert(files->len == 2);
uint8_t bytes[256];
uint64_t len;
OK(uhdk_file_copy_representation(
files->ptr[0], UHDK_FILE_NATIVE_PATH, bytes, sizeof(bytes), &len
));
assert(len == 14 && memcmp(bytes, "/tmp/a b%\xff.txt", 14) == 0);
uhdk_preferences prefs = {0};
prefs.struct_size = sizeof(prefs);
OK(uhdk_app_get_preferences(app, &prefs));
assert(prefs.appearance == UHDK_APPEARANCE_DARK && prefs.reduced_motion && prefs.high_contrast);
uhdk_file *directory;
OK(
uhdk_file_create_from_native_path(app, (uhdk_bytes){(const uint8_t *)"/tmp", 4}, &directory)
);
desc.title = text("save");
desc.kind = UHDK_DIALOG_SAVE_FILE;
desc.suggested_name = text("book.txt");
desc.accept_label = text("Store");
desc.initial_directory = directory;
OK(uhdk_file_dialog_show_async(app, &desc, &id));
r = wait_result(app, id);
OK(r.status);
assert(((const uhdk_file_list *)r.data)->len == 1);
uhdk_file_release(directory);
desc.kind = UHDK_DIALOG_OPEN_FILES;
desc.initial_directory = NULL;
desc.suggested_name = text("");
desc.accept_label = text("");
desc.title = text("cancel");
OK(uhdk_file_dialog_show_async(app, &desc, &id));
// Allow method submission, then cancel while the mock withholds its reply.
uhdk_event_list events;
OK(uhdk_app_poll_events(app, 100000000, &events));
OK(uhdk_request_cancel(app, id));
assert(wait_result(app, id).status == UHDK_STATUS_CANCELLED);
OK(uhdk_shell_open_url_async(app, text("https://example.org/test"), &id));
OK(wait_result(app, id).status);
OK(uhdk_app_get_preferences(app, &prefs));
assert(
prefs.appearance == UHDK_APPEARANCE_LIGHT && !prefs.reduced_motion && !prefs.high_contrast
);
desc.title = text("parent");
OK(uhdk_file_dialog_show_async(app, &desc, &id));
uhdk_window_destroy(w);
assert(wait_result(app, id).status == UHDK_STATUS_CANCELLED);
desc.parent = NULL;
desc.title = text("restart");
OK(uhdk_file_dialog_show_async(app, &desc, &id));
assert(wait_result(app, id).status == UHDK_STATUS_UNAVAILABLE);
wait_capability(app, UHDK_CAP_FILE_DIALOGS);
desc.title = text("dismiss");
OK(uhdk_file_dialog_show_async(app, &desc, &id));
assert(wait_result(app, id).status == UHDK_STATUS_CANCELLED);
puts("portal results/cancellation/parent/restart: PASS");
}
static void window_observations(uhdk_app *app, int version) {
uhdk_window *w = window(app);
uhdk_window_metrics metrics = {0};
metrics.struct_size = sizeof(metrics);
OK(uhdk_window_get_metrics(w, &metrics));
uint32_t known =
UHDK_WINDOW_OBSERVE_RESIZING | (version >= 6 ? UHDK_WINDOW_OBSERVE_SUSPENDED : 0);
assert(metrics.observations == known);
assert(!metrics.minimized && !metrics.occluded && !metrics.suspended && !metrics.resizing);
uint64_t request;
OK(uhdk_window_set_state_async(w, UHDK_WINDOW_MINIMIZED, &request));
assert(wait_result(app, request).status == UHDK_STATUS_UNSUPPORTED);
OK(uhdk_window_set_state_async(w, UHDK_WINDOW_FULLSCREEN, &request));
assert(wait_result(app, request).status == UHDK_STATUS_UNSUPPORTED);
for (int phase = 0; phase < 2; ++phase) {
OK(uhdk_window_set_title_async(
w, text(phase ? "fixture:resume" : "fixture:suspend"), &request
));
int matched = 0, completed = 0;
uint64_t end = deadline(app);
while (uhdk_app_get_time_ns(app) < end && !(matched && completed)) {
uhdk_event_list events;
OK(uhdk_app_poll_events(app, 50000000, &events));
for (uint64_t i = 0; i < events.len; ++i) {
if (events.ptr[i].type == UHDK_EVENT_WINDOW_METRICS) {
const uhdk_window_metrics *m = events.ptr[i].data;
assert(m->observations == known);
assert(!m->minimized && !m->occluded);
if (m->state == (phase ? UHDK_WINDOW_NORMAL : UHDK_WINDOW_FULLSCREEN) &&
!!m->resizing == !phase && !!m->focused == !phase &&
!!m->suspended == (!phase && version >= 6))
matched = 1;
} else if (events.ptr[i].type == UHDK_EVENT_REQUEST_DONE) {
const uhdk_request_result *r = events.ptr[i].data;
if (r->id == request) {
OK(r->status);
completed = 1;
}
}
}
}
assert(matched && completed);
}
OK(uhdk_window_set_state_async(w, UHDK_WINDOW_MINIMIZED, &request));
OK(wait_result(app, request).status);
OK(uhdk_window_get_metrics(w, &metrics));
assert(!(metrics.observations & UHDK_WINDOW_OBSERVE_MINIMIZED));
assert(metrics.state == UHDK_WINDOW_NORMAL); // Submission is not observation.
uhdk_window_destroy(w);
puts("Wayland versioned window observations/capabilities: PASS");
}
static void editing(uhdk_app *app) {
uhdk_window *w = window(app);
uint64_t request;
OK(uhdk_window_set_text_input_async(w, 1, &request));
OK(wait_result(app, request).status);
const struct {
uint32_t command;
int extend;
} cases[] = {
{UHDK_EDIT_DELETE_WORD_BACKWARD, 0},
{UHDK_EDIT_DELETE_WORD_FORWARD, 0},
{UHDK_EDIT_PARAGRAPH_START, 0},
{UHDK_EDIT_PARAGRAPH_END, 1},
{UHDK_EDIT_PAGE_UP, 1},
{UHDK_EDIT_PAGE_DOWN, 0},
{UHDK_EDIT_BACKTAB, 0},
{UHDK_EDIT_LINE_BREAK, 0},
{UHDK_EDIT_CUT, 0},
{UHDK_EDIT_COPY, 0},
{UHDK_EDIT_PASTE, 0},
{UHDK_EDIT_COPY, 0},
{UHDK_EDIT_REDO, 0},
{0, 0},
{0, 0}
};
uint64_t acknowledged = 0;
for (unsigned i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) {
uhdk_text_state state = {0};
state.struct_size = sizeof(state);
state.surrounding_text = text("one two");
state.selection_start = 7;
state.revision = i + 1;
state.acknowledged_input_id = acknowledged;
OK(uhdk_window_set_text_state(w, &state));
char title[32];
snprintf(title, sizeof(title), "edit:%u", i);
OK(uhdk_window_set_title_async(w, text(title), &request));
int released = 0, completed = 0;
unsigned commands = 0;
uint64_t end = deadline(app);
while (uhdk_app_get_time_ns(app) < end && !(released && completed)) {
uhdk_event_list events;
OK(uhdk_app_poll_events(app, 50000000, &events));
for (uint64_t j = 0; j < events.len; ++j) {
const uhdk_event *event = &events.ptr[j];
assert(event->type != UHDK_EVENT_TEXT_COMMIT);
if (event->type == UHDK_EVENT_KEY) {
const uhdk_key_event *key = event->data;
if (!key->pressed)
released = 1;
} else if (event->type == UHDK_EVENT_EDIT_COMMAND) {
const uhdk_edit_event *edit = event->data;
assert(edit->command == cases[i].command);
assert(!!edit->extend_selection == cases[i].extend);
acknowledged = event->input_id;
++commands;
} else if (event->type == UHDK_EVENT_REQUEST_DONE) {
const uhdk_request_result *r = event->data;
if (r->id == request) {
OK(r->status);
completed = 1;
}
}
}
}
assert(released && completed && commands == (cases[i].command ? 1u : 0u));
}
uhdk_window_destroy(w);
puts("Wayland editing defaults/selection/AltGr/no duplicate text: PASS");
}
static void input(uhdk_app *app) {
uhdk_window *w = window(app);
uint64_t id;
OK(uhdk_window_set_text_input_async(w, 1, &id));
OK(wait_result(app, id).status);
OK(uhdk_window_set_drop_enabled_async(w, 1, &id));
OK(wait_result(app, id).status);
uhdk_text_state state = {0};
state.struct_size = sizeof(state);
state.surrounding_text = text("abc");
state.revision = 77;
state.selection_start = 1;
state.selection_len = 1;
OK(uhdk_window_set_text_state(w, &state));
unsigned key = 0, repeat = 0, commit = 0, preedit = 0, pointer = 0, scroll = 0, dropped = 0,
copy = 0, compose = 0, captured = 0, released = 0;
uint64_t acknowledged = 0, capture_request = 0;
int published = 0;
uint64_t end = deadline(app);
while (uhdk_app_get_time_ns(app) < end &&
!(key && repeat && commit >= 2 && preedit && pointer && scroll && dropped && copy &&
compose && captured && released)) {
uhdk_event_list events;
OK(uhdk_app_poll_events(app, 50000000, &events));
for (uint64_t i = 0; i < events.len; ++i) {
const uhdk_event *e = &events.ptr[i];
if (e->input_id > acknowledged)
acknowledged = e->input_id;
switch (e->type) {
case UHDK_EVENT_KEY: {
const uhdk_key_event *k = e->data;
if (k->physical_key == 4 && k->logical_key == 'a' && k->pressed)
key++;
if (k->physical_key == 224)
assert(!!(k->modifiers & UHDK_MOD_CONTROL) == !!k->pressed);
if (k->logical_key == 'A' && k->pressed)
assert(!(k->modifiers & UHDK_MOD_SHIFT));
if (k->repeat)
repeat++;
break;
}
case UHDK_EVENT_EDIT_COMMAND:
if (((const uhdk_edit_event *)e->data)->command == UHDK_EDIT_COPY)
copy++;
break;
case UHDK_EVENT_TEXT_COMMIT: {
const uhdk_text_event *t = e->data;
if (t->text.len == 1 && t->text.ptr[0] == 'a')
commit++;
if (t->text.len == 2 && memcmp(t->text.ptr, "é", 2) == 0)
compose++;
if (t->has_replacement) {
assert(
t->revision == 77 && t->replacement_start == 0 && t->replacement_len == 3
);
commit++;
}
break;
}
case UHDK_EVENT_TEXT_PREEDIT: {
const uhdk_text_event *t = e->data;
assert(t->text.len == 2 && memcmp(t->text.ptr, "é", 2) == 0);
preedit++;
break;
}
case UHDK_EVENT_POINTER_BUTTON:
if (((const uhdk_pointer_event *)e->data)->pressed)
OK(uhdk_window_set_pointer_mode_async(
w, UHDK_POINTER_CAPTURE, &capture_request
));
break;
case UHDK_EVENT_POINTER_MODE_CHANGED:
if (((const uhdk_pointer_mode_event *)e->data)->mode == UHDK_POINTER_CAPTURE)
captured++;
else
released++;
break;
case UHDK_EVENT_REQUEST_DONE:
if (((const uhdk_request_result *)e->data)->id == capture_request)
OK(((const uhdk_request_result *)e->data)->status);
break;
case UHDK_EVENT_POINTER_MOVE: {
const uhdk_pointer_event *p = e->data;
if (p->position.x == 12 && p->position.y == 18)
pointer++;
break;
}
case UHDK_EVENT_SCROLL: {
const uhdk_scroll_event *s = e->data;
assert(s->unit == UHDK_SCROLL_LINES && s->delta.y == 0.5f);
scroll++;
break;
}
case UHDK_EVENT_DROP_FILES: {
const uhdk_drop_files_event *d = e->data;
assert(d->files.len == 1 && d->position.x == 12);
dropped++;
break;
}
default:
break;
}
}
if (repeat && !published) {
state.surrounding_text = text("abcd");
state.revision = 78;
state.acknowledged_input_id = acknowledged;
OK(uhdk_window_set_text_state(w, &state));
published = 1;
}
}
fprintf(
stderr,
"input: key=%u repeat=%u commit=%u preedit=%u pointer=%u scroll=%u drop=%u copy=%u\n",
key,
repeat,
commit,
preedit,
pointer,
scroll,
dropped,
copy
);
assert(
key && repeat && commit >= 2 && preedit && pointer && scroll && dropped && copy && compose
);
OK(uhdk_clipboard_read_text_async(app, 3, &id));
assert(wait_result(app, id).status == UHDK_STATUS_BUFFER_TOO_SMALL);
OK(uhdk_clipboard_read_text_async(app, 100, &id));
uhdk_request_result r = wait_result(app, id);
OK(r.status);
const uhdk_text *t = r.data;
assert(t->len == 11 && memcmp(t->ptr, "hello café", 11) == 0);
OK(uhdk_clipboard_read_text_async(app, 100, &id));
uhdk_event_list pause;
OK(uhdk_app_poll_events(app, 100000000, &pause));
OK(uhdk_request_cancel(app, id));
assert(wait_result(app, id).status == UHDK_STATUS_CANCELLED);
OK(uhdk_clipboard_read_text_async(app, 100, &id));
assert(wait_result(app, id).status == UHDK_STATUS_INVALID_ARGUMENT);
OK(uhdk_clipboard_read_text_async(app, 0, &id));
r = wait_result(app, id);
OK(r.status);
assert(((const uhdk_text *)r.data)->len == 0);
OK(uhdk_clipboard_write_text_async(app, text("local copy"), &id));
OK(wait_result(app, id).status);
OK(uhdk_window_request_focus_async(w, &id));
OK(wait_result(app, id).status);
uhdk_window_destroy(w);
puts("input/IME/clipboard/drop/activation: PASS");
}
#include "menus.c"
#include "popups.c"
static void run(uhdk_app *app, void *mode) {
if (strcmp(mode, "popup") == 0) {
popups(app);
return;
}
if (strcmp(mode, "menu-noprotocol") == 0 || strcmp(mode, "menu-nobus") == 0) {
menus_unavailable(app, strcmp(mode, "menu-nobus") == 0);
return;
}
if (strcmp(mode, "menu") == 0) {
menus(app);
return;
}
if (strncmp(mode, "window-state-v", 14) == 0) {
window_observations(app, atoi(mode + 14));
return;
}
if (strcmp(mode, "editing") == 0) {
editing(app);
return;
}
if (strcmp(mode, "input") == 0)
input(app);
else if (strcmp(mode, "portal") == 0)
portals(app);
else
real_desktop(app);
}
int main(int argc, char **argv) {
assert(argc == 2);
uhdk_app_desc desc = {0};
desc.struct_size = sizeof(desc);
desc.abi_version = UHDK_ABI_VERSION;
desc.display_name = text("UHDK service tests");
desc.application_id = text("org.uhdk.ServiceTests");
uhdk_app *app;
OK(uhdk_app_create(&desc, &app));
OK(uhdk_app_run(app, run, argv[1]));
uhdk_app_destroy(app);
return 0;
}

View File

@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""Exercise failed Wayland startup without opening windows or changing a desktop."""
import os
from pathlib import Path
import socket
import subprocess
import sys
import tempfile
import time
binary = str(Path(sys.argv[1]).resolve())
with tempfile.TemporaryDirectory(prefix='uhdk-startup-') as directory:
env = dict(os.environ, WAYLAND_DISPLAY=directory + '/missing', XDG_RUNTIME_DIR=directory)
env.pop('WAYLAND_SOCKET', None)
subprocess.run([binary, '--startup-failure'], env=env, check=True, timeout=15)
for scenario in ('disconnected', 'unresponsive'):
client, server = socket.socketpair()
env['WAYLAND_SOCKET'] = str(client.fileno())
if scenario == 'disconnected':
server.close()
start = time.monotonic()
subprocess.run([binary, '--startup-failure'], env=env,
pass_fds=(client.fileno(),), check=True, timeout=15)
elapsed = time.monotonic() - start
client.close()
server.close()
if scenario == 'unresponsive':
assert 4 <= elapsed < 12, elapsed
print(f'{scenario}: PASS ({elapsed:.2f}s)')

View File

@ -0,0 +1,299 @@
#!/usr/bin/env python3
"""Minimal isolated Wayland peer for protocol tests, driven by the pinned XML.
This never attaches to the real compositor or injects input into other clients.
It deliberately sends delayed IME batches, selection pipes and copy-only drops.
"""
import array
import ctypes
import ctypes.util
import fcntl
import os
from pathlib import Path
import select
import socket
import struct
import subprocess
import sys
import tempfile
import time
import xml.etree.ElementTree as ET
XML = Path(__file__).resolve().parents[1] / 'protocols/xml'
INTERFACES = {}
for filename in XML.glob('*.xml'):
for interface in ET.parse(filename).getroot().findall('interface'):
INTERFACES[interface.get('name')] = interface
def keymap():
lib = ctypes.CDLL(ctypes.util.find_library('xkbcommon') or 'libxkbcommon.so.0')
lib.xkb_context_new.restype = ctypes.c_void_p
lib.xkb_keymap_new_from_names.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int]
lib.xkb_keymap_new_from_names.restype = ctypes.c_void_p
lib.xkb_keymap_get_as_string.argtypes = [ctypes.c_void_p, ctypes.c_int]
lib.xkb_keymap_get_as_string.restype = ctypes.c_void_p
lib.xkb_context_unref.argtypes = [ctypes.c_void_p]
lib.xkb_keymap_unref.argtypes = [ctypes.c_void_p]
names = (ctypes.c_char_p * 5)(None, None, b'us', b'intl', None)
context = lib.xkb_context_new(0)
mapping = lib.xkb_keymap_new_from_names(context, names, 0)
assert mapping
pointer = lib.xkb_keymap_get_as_string(mapping, 1)
result = ctypes.string_at(pointer) + b'\0'
libc = ctypes.CDLL(None)
libc.free.argtypes = [ctypes.c_void_p]
libc.free(pointer)
lib.xkb_keymap_unref(mapping)
lib.xkb_context_unref(context)
return result
class Compositor:
globals = [('wl_compositor', 6), ('xdg_wm_base', 6),
('zxdg_decoration_manager_v1', 1), ('wl_seat', 8),
('wp_cursor_shape_manager_v1', 1), ('zwp_text_input_manager_v3', 1),
('wl_data_device_manager', 3), ('zxdg_exporter_v2', 1),
('xdg_activation_v1', 1)]
def __init__(self, sock, inject=False):
self.sock = sock
self.inject = inject
self.buffer = b''
self.fds = []
self.objects = {1: 'wl_display'}
self.ids = {}
self.server_id = 0xfeffffff
self.surfaces = {}
self.serial = 100
self.text_serial = 0
self.surrounding = ''
self.injected = False
self.later = None
self.tokens = False
self.finished_drop = False
self.outgoing = None
self.receives = 0
self.held = []
def event(self, obj, name, *values):
interface = INTERFACES[self.objects[obj]]
events = interface.findall('event')
description = next(e for e in events if e.get('name') == name)
body, fds = b'', []
for arg, value in zip(description.findall('arg'), values, strict=True):
kind = arg.get('type')
if kind in ('uint', 'object', 'new_id'):
body += struct.pack('=I', value or 0)
elif kind in ('int', 'fixed'):
body += struct.pack('=i', value)
elif kind in ('string', 'array'):
data = (value.encode() + b'\0') if kind == 'string' and value is not None else (value or b'')
body += struct.pack('=I', len(data)) + data + b'\0' * (-len(data) % 4)
elif kind == 'fd':
fds.append(value)
else:
raise AssertionError(kind)
wire = struct.pack('=II', obj, ((len(body) + 8) << 16) | events.index(description)) + body
self.sock.sendmsg([wire], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, array.array('i', fds))] if fds else [])
def new_offer(self, mime):
self.server_id += 1
obj = self.server_id
self.objects[obj] = 'wl_data_offer'
self.event(self.ids['wl_data_device'], 'data_offer', obj)
self.event(obj, 'offer', mime)
return obj
def dispatch(self):
try:
data, ancillary, _, _ = self.sock.recvmsg(65536, socket.CMSG_SPACE(64 * 4))
except ConnectionResetError:
return False
if not data:
return False
self.buffer += data
for level, kind, payload in ancillary:
if level == socket.SOL_SOCKET and kind == socket.SCM_RIGHTS:
values = array.array('i'); values.frombytes(payload[:len(payload) // 4 * 4]); self.fds.extend(values)
while len(self.buffer) >= 8:
obj, header = struct.unpack_from('=II', self.buffer)
length, opcode = header >> 16, header & 65535
if len(self.buffer) < length:
break
payload, self.buffer = self.buffer[8:length], self.buffer[length:]
iface = self.objects[obj]
request = INTERFACES[iface].findall('request')[opcode]
args, offset = [], 0
for arg in request.findall('arg'):
kind = arg.get('type')
if kind == 'new_id' and arg.get('interface') is None:
size = struct.unpack_from('=I', payload, offset)[0]; offset += 4
dynamic = payload[offset:offset + size - 1].decode(); offset += (size + 3) & ~3
version, value = struct.unpack_from('=II', payload, offset); offset += 8
self.objects[value] = dynamic; self.ids[dynamic] = value
args.extend([dynamic, version, value])
continue
if kind == 'fd':
value = self.fds.pop(0)
elif kind in ('string', 'array'):
size = struct.unpack_from('=I', payload, offset)[0]; offset += 4
value = payload[offset:offset + size]; offset += (size + 3) & ~3
if kind == 'string': value = value[:-1].decode() if size else None
else:
value = struct.unpack_from('=i' if kind in ('int', 'fixed') else '=I', payload, offset)[0]; offset += 4
if kind == 'new_id':
self.objects[value] = arg.get('interface'); self.ids[arg.get('interface')] = value
args.append(value)
self.request(obj, iface, request.get('name'), args)
if request.get('type') == 'destructor':
if obj < 0xff000000:
self.event(1, 'delete_id', obj)
del self.objects[obj]
return True
def request(self, obj, iface, name, args):
if iface == 'wl_display' and name == 'get_registry':
for index, (interface, version) in enumerate(self.globals, 1): self.event(args[0], 'global', index, interface, version)
elif iface == 'wl_display' and name == 'sync':
self.event(args[0], 'done', self.serial)
self.event(1, 'delete_id', args[0]); del self.objects[args[0]]
elif iface == 'wl_registry' and name == 'bind' and args[1] == 'wl_seat':
self.event(args[-1], 'capabilities', 3); self.event(args[-1], 'name', 'fixture')
elif iface == 'wl_seat' and name == 'get_keyboard':
contents = keymap(); fd = os.memfd_create('uhdk-test-keymap'); os.write(fd, contents)
self.event(args[0], 'keymap', 1, fd, len(contents)); os.close(fd)
self.event(args[0], 'repeat_info', 40, 20)
elif iface == 'xdg_wm_base' and name == 'get_xdg_surface':
self.surfaces[args[1]] = args[0]
elif iface == 'wl_surface' and name == 'commit' and obj in self.surfaces:
self.serial += 1; self.event(self.surfaces[obj], 'configure', self.serial)
elif iface == 'zxdg_decoration_manager_v1' and name == 'get_toplevel_decoration':
self.event(args[0], 'configure', 2)
elif iface == 'zwp_text_input_v3' and name == 'set_surrounding_text':
self.surrounding = args[0]
elif iface == 'zwp_text_input_v3' and name == 'commit':
self.text_serial += 1
if self.inject and not self.injected and self.surrounding == 'abc': self.start_input(obj)
elif iface == 'wl_data_offer' and name == 'receive':
mime, fd = args
assert not fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_NONBLOCK
if mime == 'text/uri-list':
payload = b'file:///tmp/drop%20file\r\n'
else:
self.receives += 1
if self.receives == 3:
self.held.append(fd)
return
payload = b'\xff' if self.receives == 4 else b'' if self.receives == 5 else 'hello café'.encode()
os.write(fd, payload)
os.close(fd)
elif iface == 'wl_data_offer' and name == 'finish':
self.finished_drop = True
elif iface == 'wl_data_device' and name == 'set_selection':
source = args[0]
closed_read, closed_write = os.pipe()
os.close(closed_read)
self.event(source, 'send', 'text/plain;charset=utf-8', closed_write)
os.close(closed_write)
read, write = os.pipe()
self.event(source, 'send', 'text/plain;charset=utf-8', write)
os.close(write); self.outgoing = read
elif iface == 'xdg_activation_token_v1' and name == 'commit':
self.event(obj, 'done', 'fixture-token')
elif iface == 'xdg_activation_v1' and name == 'activate':
assert args[0] == 'fixture-token'; self.tokens = True
elif iface == 'zxdg_exporter_v2' and name == 'export_toplevel':
self.event(args[0], 'handle', 'fixture-parent')
def start_input(self, text_input):
self.injected = True
surface = next(iter(self.surfaces))
keyboard, pointer = self.ids['wl_keyboard'], self.ids['wl_pointer']
self.event(keyboard, 'enter', 200, surface, b'')
self.event(keyboard, 'modifiers', 200, 0, 0, 0, 0)
self.event(keyboard, 'key', 201, 1, 30, 1)
self.event(keyboard, 'key', 202, 2, 30, 0)
self.event(keyboard, 'key', 203, 3, 48, 1)
self.event(pointer, 'enter', 204, surface, 10 * 256, 10 * 256)
self.event(pointer, 'motion', 4, 12 * 256, 18 * 256)
self.event(pointer, 'button', 204, 4, 0x110, 1)
self.event(pointer, 'axis_source', 0)
self.event(pointer, 'axis', 5, 0, 5 * 256)
self.event(pointer, 'axis_value120', 0, 60)
self.event(pointer, 'frame')
selection = self.new_offer('text/plain;charset=utf-8')
self.event(self.ids['wl_data_device'], 'selection', selection)
drop = self.new_offer('text/uri-list')
self.event(drop, 'source_actions', 1)
self.event(self.ids['wl_data_device'], 'enter', 205, surface, 12 * 256, 18 * 256, drop)
self.event(drop, 'action', 1)
self.event(self.ids['wl_data_device'], 'drop')
self.later = (time.monotonic() + .15, text_input, self.text_serial)
def timers(self):
# Text input enter precedes the application's first surrounding snapshot.
text = self.ids.get('zwp_text_input_v3')
if text and self.surfaces and not getattr(self, 'text_entered', False):
self.text_entered = True
surface = next(iter(self.surfaces))
self.event(text, 'enter', surface)
if not self.inject:
keyboard = self.ids['wl_keyboard']
self.event(keyboard, 'enter', 190, surface, b'')
self.event(keyboard, 'key', 191, 1, 30, 1)
self.event(keyboard, 'key', 192, 2, 30, 0)
if self.later and time.monotonic() >= self.later[0]:
_, text, serial = self.later; self.later = None
assert self.text_serial > serial, (self.text_serial, serial)
self.event(self.ids['wl_pointer'], 'button', 206, 6, 0x110, 0)
keyboard = self.ids['wl_keyboard']
self.event(keyboard, 'key', 206, 6, 48, 0)
self.event(keyboard, 'key', 207, 7, 29, 1)
self.event(keyboard, 'modifiers', 207, 4, 0, 0, 0)
self.event(keyboard, 'key', 208, 7, 46, 1)
self.event(keyboard, 'key', 209, 8, 46, 0)
self.event(keyboard, 'key', 210, 9, 29, 0)
self.event(keyboard, 'modifiers', 210, 0, 0, 0, 0)
self.event(keyboard, 'modifiers', 210, 0, 1, 0, 0)
self.event(keyboard, 'key', 210, 9, 30, 1)
self.event(keyboard, 'modifiers', 210, 0, 0, 0, 0)
self.event(keyboard, 'key', 210, 9, 30, 0)
self.event(keyboard, 'key', 211, 9, 40, 1)
self.event(keyboard, 'key', 212, 10, 40, 0)
self.event(keyboard, 'key', 213, 11, 18, 1)
self.event(keyboard, 'key', 214, 12, 18, 0)
self.event(text, 'preedit_string', 'é', 0, 2)
self.event(text, 'done', serial)
self.event(text, 'preedit_string', '', 0, 0)
self.event(text, 'delete_surrounding_text', 1, 1)
self.event(text, 'commit_string', 'λ')
self.event(text, 'done', serial)
def run(binary, mode, env=None, compositor_type=Compositor):
client, server = socket.socketpair()
environment = dict(os.environ if env is None else env, WAYLAND_SOCKET=str(client.fileno()))
if mode == 'input': environment.pop('DBUS_SESSION_BUS_ADDRESS', None)
process = subprocess.Popen([str(Path(binary).resolve()), mode], env=environment, pass_fds=(client.fileno(),))
client.close()
compositor = compositor_type(server, mode == 'input')
end = time.monotonic() + 20
try:
while process.poll() is None and time.monotonic() < end:
if select.select([server], [], [], .01)[0] and not compositor.dispatch(): break
compositor.timers()
assert process.wait(timeout=2) == 0, process.returncode
if mode == 'input':
assert compositor.finished_drop and compositor.tokens
assert compositor.outgoing is not None
assert os.read(compositor.outgoing, 1024) == b'local copy'
finally:
if process.poll() is None: process.kill(); process.wait()
server.close()
for fd in compositor.fds + compositor.held: os.close(fd)
if compositor.outgoing is not None: os.close(compositor.outgoing)
if __name__ == '__main__':
run(sys.argv[1], 'input')

Some files were not shown because too many files have changed in this diff Show More