Compare commits
12 Commits
97c4b41e12
...
c0d923cbed
| Author | SHA1 | Date |
|---|---|---|
|
|
c0d923cbed | |
|
|
85acfb5b67 | |
|
|
0d2c8eedf9 | |
|
|
b0da270a67 | |
|
|
fdbc99a274 | |
|
|
c1c2de8c4b | |
|
|
5623376284 | |
|
|
fa3959e221 | |
|
|
7a7a3493d6 | |
|
|
685985e9cd | |
|
|
afed52d567 | |
|
|
8a1f8fbd28 |
|
|
@ -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
103
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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, .{});
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
577
include/uhdk.h
577
include/uhdk.h
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
@ -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 */
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
];
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
|
@ -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;
|
||||
|
|
@ -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)
|
||||
|
|
@ -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");
|
||||
|
|
@ -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, ®istry_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);
|
||||
}
|
||||
|
|
@ -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";
|
||||
}) { }
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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 */
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
|
@ -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>
|
||||
|
|
@ -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
|
|
@ -0,0 +1,200 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="xdg_activation_v1">
|
||||
|
||||
<copyright>
|
||||
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.
|
||||
</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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
|
@ -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 & 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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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())))
|
||||
|
|
@ -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]))
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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')
|
||||
|
|
@ -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
|
||||
|
|
@ -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())))
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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)')
|
||||
|
|
@ -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
Loading…
Reference in New Issue