commit 40abee74a2c06d2e37cfb8f0a3b2ba7d96bcae35 Author: Liam Malone Date: Tue Mar 24 13:29:46 2026 +0000 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..77d4f6e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/zig-out/ +/.zig-cache/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4701583 --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) Liam Malone +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4c7f9c5 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# Simple Wayland Client + +There are TWO Wayland client programs in this repo. The single-file version is +a direct implementation of my related +[blog post](https://ptrtoliam.dev/blog/wlclient-nolibwayland), and lives in +`src/simple-client.zig`. This version can be run with either: + +```shell +$ zig build simple-client +# OR: +$ zig run src/simple-client.zig +``` + +The other implementation utilizes a stripped-down version of +my personal Zig base layer and my own wayland code generation tool, and presents +a more object-oriented interface which is more in-line with what one might +expect, given the object-oriented design of the Wayland protocol. This second +implementation runs from `src/client.zig`. This version can be run with: + +```shell +$ zig build client +``` diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..95c71df --- /dev/null +++ b/build.zig @@ -0,0 +1,87 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const wayland_protocol_specifications = [_]std.Build.LazyPath{ + b.path("src/wayland-protocols/wayland.xml"), + b.path("src/wayland-protocols/xdg-shell.xml"), + b.path("src/wayland-protocols/xdg-decoration-unstable-v1.xml"), + }; + + const wayland_protocols = b.dependency("wayland_protocol_codegen", .{ + .protocols = &wayland_protocol_specifications, + }).module("wayland-protocols"); + + const os_mod = b.addModule("os", .{ + .root_source_file = b.path("src/os/os.zig"), + .target = target, + }); + + const base_mod = b.addModule("base", .{ + .root_source_file = b.path("src/base/base.zig"), + .target = target, + .imports = &.{ + .{ .name = "os", .module = os_mod }, + }, + }); + const root = b.createModule(.{ + .root_source_file = b.path("src/client.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "base", .module = base_mod }, + .{ .name = "os", .module = os_mod }, + .{ .name = "wayland-protocols", .module = wayland_protocols }, + }, + }); + + const simple_root = b.createModule(.{ + .root_source_file = b.path("src/simple-client.zig"), + .target = target, + .optimize = optimize, + }); + + const exe = b.addExecutable(.{ + .name = "wl-client", + .root_module = root, + }); + b.installArtifact(exe); + + const simple_exe = b.addExecutable(.{ + .name = "wl-simple-client", + .root_module = simple_root, + }); + b.installArtifact(simple_exe); + + const run_step = b.step("client", "Run src/client.zig"); + const run_cmd = b.addRunArtifact(exe); + run_step.dependOn(&run_cmd.step); + run_cmd.step.dependOn(b.getInstallStep()); + + if (b.args) |args| { + run_cmd.addArgs(args); + } + const simple_run_step = b.step("simple-client", "Run src/simple.zig"); + const simple_run_cmd = b.addRunArtifact(simple_exe); + simple_run_step.dependOn(&simple_run_cmd.step); + simple_run_cmd.step.dependOn(b.getInstallStep()); + + if (b.args) |args| { + simple_run_cmd.addArgs(args); + } + + const base_tests = b.addTest(.{ + .root_module = base_mod, + }); + const run_base_tests = b.addRunArtifact(base_tests); + const exe_tests = b.addTest(.{ + .root_module = exe.root_module, + }); + const run_exe_tests = b.addRunArtifact(exe_tests); + + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&run_base_tests.step); + test_step.dependOn(&run_exe_tests.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..6ae5231 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,19 @@ +.{ + .name = .wl_simple_client, + .version = "0.0.0", + .minimum_zig_version = "0.16.0-dev.2471+e9eadee00", + .fingerprint = 0xe6fb6be6c7f406f6, + .dependencies = .{ + .wayland_protocol_codegen = .{ + .url = "git+https://github.com/ptrToLiam/wayland-protocol-codegen#7fbd0518fe5f2826f80bb1205066fdbdf61688c9", + .hash = "wayland_protocol_codegen-0.0.0-Qh8GvO8bAQB6Vb783fZe2lR_AE-gzrwrSEhN_PpDyZDL", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "LICENSE", + "README.md", + "src", + }, +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..0b5915c --- /dev/null +++ b/flake.lock @@ -0,0 +1,96 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1773821835, + "narHash": "sha256-TJ3lSQtW0E2JrznGVm8hOQGVpXjJyXY2guAxku2O9A4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b40629efe5d6ec48dd1efba650c797ddbd39ace0", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1771043024, + "narHash": "sha256-O1XDr7EWbRp+kHrNNgLWgIrB0/US5wvw9K6RERWAj6I=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3aadb7ca9eac2891d52a9dec199d9580a6e2bf44", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "zig": "zig" + } + }, + "systems": { + "flake": false, + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "zig": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": "nixpkgs_2", + "systems": "systems" + }, + "locked": { + "lastModified": 1774140580, + "narHash": "sha256-4V/DZzhyomuNjIIr0OFLUUf0TdgYWo7H6TZeF5op4Nc=", + "owner": "mitchellh", + "repo": "zig-overlay", + "rev": "487e301b84fdf86d5561f4aa24e50c5308004b67", + "type": "github" + }, + "original": { + "owner": "mitchellh", + "repo": "zig-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..2e9b2d5 --- /dev/null +++ b/flake.nix @@ -0,0 +1,24 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + zig.url = "github:mitchellh/zig-overlay"; + }; + + outputs = { self, nixpkgs, zig }: + let + supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; + forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f { + pkgs = import nixpkgs { inherit system; }; + }); + in + { + devShells = forEachSupportedSystem({ pkgs }: { + default = pkgs.mkShell { + packages = with pkgs; [ + man-pages + zig.packages.${system}."master-2026-02-03" + ]; + }; + }); + }; +} diff --git a/src/base/Arena.zig b/src/base/Arena.zig new file mode 100644 index 0000000..09b897b --- /dev/null +++ b/src/base/Arena.zig @@ -0,0 +1,366 @@ +prev: ?*Arena, +cur: *Arena, +flags: Flags, +cmt_size: usize, +res_size: usize, +base_pos: usize, +_pos: usize, +cmt: usize, +res: usize, + +pub const HeaderSize = @sizeOf(Arena); + +pub fn init(params: InitParams) *Arena { + var reserve_size: usize = params.reserve_size; + var commit_size: usize = params.commit_size; + var flags: Flags = params.flags; + if (params.flags.large_pages) { + reserve_size = align_pow2(reserve_size, math.Units.MB(2)); + commit_size = align_pow2(commit_size, math.Units.MB(2)); + } else { + reserve_size = align_pow2(reserve_size, std.heap.page_size_min); + commit_size = align_pow2(commit_size, std.heap.page_size_min); + } + + const base = if (params.backing_buffer) |buf| + buf + else if (params.flags.large_pages) base: { + const ptr = os.mem_reserve_large(reserve_size) orelse ptr: { + // Fallback to standard page sizes if large pages not working + flags.large_pages = false; + reserve_size = align_pow2(reserve_size, std.heap.page_size_min); + commit_size = align_pow2(commit_size, std.heap.page_size_min); + + log.warn("Arena :: Mem_Reserve :: Large pages not supported, falling back to standard page sizes", .{}); + break :ptr os.mem_reserve(reserve_size); + }; + + if (flags.large_pages) { + if (!os.mem_commit_large(ptr[0..commit_size])) + std.debug.print("Failed Large ({d} Bytes) Page Commit\n", .{commit_size}); + } else { + if (!os.mem_commit(ptr[0..commit_size])) + std.debug.print("Failed {d}KB Page(s) Commit\n", .{std.heap.page_size_min}); + } + + break :base ptr; + } else base: { + const ptr = os.mem_reserve(reserve_size); + if (!os.mem_commit(ptr[0..commit_size])) + std.debug.print("Failed {d}KB Page(s) Commit\n", .{std.heap.page_size_min}); + + break :base ptr; + }; + + const arena = transmute(*Arena, base); + + arena.* = .{ + .flags = flags, + .prev = null, + .cur = arena, + .cmt_size = commit_size, + .res_size = reserve_size, + .base_pos = 0, + ._pos = Arena.HeaderSize, + .cmt = commit_size, + .res = reserve_size, + }; + + return arena; +} + +pub fn push_no_zero(arena: *Arena, comptime T: type, count: usize) []T { + const data: []u8 = arena._push_impl((@sizeOf(T) * count), @max(8, @alignOf(T))); + const res = transmute([*]T, data); + + return (res[0..count]); +} + +pub inline fn push(arena: *Arena, comptime T: type, count: usize) []T { + const bytes = arena.push_no_zero(T, count); + const raw_bytes = transmute([]u8, bytes); + @memset(raw_bytes[0 .. count * @sizeOf(T)], 0); + + return bytes; +} + +pub fn create(arena: *Arena, comptime T: type) *T { + const data = arena.push(T, 1); + + return transmute(*T, data); +} + +pub fn release(arena: *Arena) void { + var next: ?*Arena = arena.cur; + var prev: ?*Arena = null; + while (next) |n| : (next = prev) { + prev = n.prev; + const ptr = transmute( + [*]align(std.heap.page_size_min) u8, + n, + ); + os.mem_release(ptr[0..n.res]); + } +} + +pub fn pos(arena: *Arena) usize { + const cur = arena.cur; + const _pos = cur.base_pos + cur._pos; + + return _pos; +} + +pub fn pop_to(arena: *Arena, _pos: usize) void { + const big_pos = if (Arena.HeaderSize < _pos) _pos else Arena.HeaderSize; + var cur: *Arena = arena.cur; + var prev_opt: ?*Arena = null; + + while (cur.base_pos >= big_pos) { + prev_opt = cur.prev; + const ptr = transmute( + [*]align(std.heap.page_size_min) const u8, + cur, + ); + os.mem_release(ptr[0..cur.res]); + + if (prev_opt) |prev| { + cur = prev; + } + } + + arena.cur = cur; + const new_pos = big_pos - cur.base_pos; + cur._pos = new_pos; +} + +pub fn temp(arena: *Arena) Temp { + return .{ + .arena = arena, + .pos = arena.pos(), + }; +} + +pub fn end(tmp: *Temp) void { + tmp.arena.pop_to(tmp.pos); +} + +pub fn clear(arena: *Arena) void { + arena.pop_to(0); +} + +pub fn _push_impl(arena: *Arena, size: usize, @"align": usize) []u8 { + var cur: *Arena = arena.cur; + var pos_pre = cast(usize, align_pow2(cur._pos, @"align")); + var pos_pst: usize = pos_pre + size; + + // chain if needed + if (cur.res < pos_pst and !(arena.flags.no_chain)) { + var new_block: *Arena = new_block: { + var res_size: usize = cur.res_size; + var cmt_size: usize = cur.cmt_size; + + if (size + Arena.HeaderSize > res_size) { + res_size = align_pow2(size + Arena.HeaderSize, @"align"); + cmt_size = align_pow2(size + Arena.HeaderSize, @"align"); + } + + break :new_block .init(.{ + .flags = cur.flags, + .reserve_size = res_size, + .commit_size = cmt_size, + .backing_buffer = null, + }); + }; + + new_block.base_pos = cur.base_pos + cur.res; + new_block.prev = arena.cur; + arena.cur = new_block; + + cur = new_block; + pos_pre = align_pow2(cur._pos, @"align"); + pos_pst = pos_pre + size; + } + + // commit new pages if needed + if (cur.cmt < pos_pst) { + var cmt_pst_aligned: usize = pos_pst + cur.cmt_size - 1; + cmt_pst_aligned -= cmt_pst_aligned % cur.cmt_size; + + const cmt_pst_clamped: usize = @min(cmt_pst_aligned, cur.res); + const cmt_size = cmt_pst_clamped - cur.cmt; + + const ptr = transmute( + [*]align(std.heap.page_size_min) u8, + cur, + ); + + const cmt_range = ptr[cur.cmt .. cur.cmt + cmt_size]; + if (cur.flags.large_pages) { + if (!os.mem_commit_large(@alignCast(cmt_range))) + std.debug.print("Failed to commit large page of mem: [{d}..{d}]\n", .{ + cur.cmt, + cur.cmt + cmt_size, + }); + } else { + if (!os.mem_commit(@alignCast(cmt_range))) + std.debug.print("Failed to commit page of mem: [{d}..{d}]\n", .{ + cur.cmt, + cur.cmt + cmt_size, + }); + } + + cur.cmt = cmt_pst_aligned; + } + + const result: []u8 = if (cur.cmt >= pos_pst) result: { + cur._pos = pos_pst; + const ptr = transmute( + [*]u8, + cur, + ); + break :result ptr[pos_pre .. pos_pre + pos_pst]; + } else unreachable; + + return result; +} + +pub const Temp = packed struct { + arena: *Arena, + pos: usize, + + pub fn end(tmp: *const Temp) void { + tmp.arena.pop_to(tmp.pos); + } +}; + +pub const Flags = packed struct { + no_chain: bool, + large_pages: bool, + + pub const default: Flags = .{ + .no_chain = false, + .large_pages = false, + }; + + pub const largepage: Flags = .{ + .no_chain = false, + .large_pages = true, + }; + + pub const nochain: Flags = .{ + .no_chain = true, + .large_pages = false, + }; + + pub const large_nochain: Flags = .{ + .no_chain = true, + .large_pages = true, + }; +}; + +pub const InitParams = struct { + flags: Flags, + reserve_size: usize, + commit_size: usize, + backing_buffer: ?[]align(std.heap.page_size_min) u8, + + pub const default: InitParams = .{ + .flags = .default, + .reserve_size = std.heap.page_size_min, + .commit_size = std.heap.page_size_min, + .backing_buffer = null, + }; + + pub const large_pages: InitParams = .{ + .flags = .largepage, + .reserve_size = math.Units.MB(2), + .commit_size = math.Units.MB(2), + .backing_buffer = null, + }; +}; + +//----------------------------------------------------------------------------- +// Zig Allocator Interface Implementation +//----------------------------------------------------------------------------- +pub fn allocator(arena: *Arena) std.mem.Allocator { + return .{ + .ptr = @ptrCast(arena), + .vtable = &.{ + .alloc = alloc, + .resize = resize, + .remap = remap, + .free = free, + }, + }; +} + +fn alloc(ctx: *anyopaque, n: usize, @"align": mem.Alignment, ret_addr: usize) ?[*]u8 { + const arena = transmute(*Arena, ctx); + _ = ret_addr; + const ptr_align = @as(usize, 1) << @as(std.math.Log2Int(usize), @intCast(@"align".toByteUnits())); + return @ptrCast(arena._push_impl(n, ptr_align)); +} + +fn resize(ctx: *anyopaque, buf: []u8, log2_buf_align: mem.Alignment, new_len: usize, ret_addr: usize) bool { + const arena = transmute(*Arena, ctx); + const current = arena.cur; + _ = log2_buf_align; + _ = ret_addr; + + if (cast(usize, buf.ptr) != (cast(usize, current) + current.pos()) - buf.len) { + return new_len <= buf.len; + } + + if (buf.len >= new_len) { + current._pos = @max(Arena.HeaderSize, current.pos() - (buf.len - new_len)); + return true; + } else if (current.res - current.pos() >= new_len - buf.len) { + current._pos += (new_len - buf.len); + return true; + } else { + return false; + } +} + +fn remap( + context: *anyopaque, + memory: []u8, + alignment: mem.Alignment, + new_len: usize, + return_address: usize, +) ?[*]u8 { + return if (resize(context, memory, alignment, new_len, return_address)) memory.ptr else null; +} + +fn free(ctx: *anyopaque, buf: []u8, pow2_buf_align: mem.Alignment, ret_addr: usize) void { + _ = ctx; + _ = pow2_buf_align; + _ = ret_addr; + _ = buf; +} + +//----------------------------------------------------------------------------- + +const Arena = @This(); + +const align_pow2 = math.align_pow2; + +const log = std.log.scoped(.Arena); +const TargetOs = builtin.target.os; + +const mem = std.mem; +const posix = std.posix; + +const cast = casts.cast; +const transmute = casts.transmute; + +// File Imports +const math = @import("math.zig"); +const casts = @import("casts.zig"); + +// Internal Module Imports +const os = @import("os"); + +// 3rd-Party Module Imports +const std = @import("std"); +const builtin = @import("builtin"); diff --git a/src/base/Thread.zig b/src/base/Thread.zig new file mode 100644 index 0000000..0587aad --- /dev/null +++ b/src/base/Thread.zig @@ -0,0 +1,71 @@ +pub threadlocal var tctx: *Context = undefined; +pub threadlocal var is_async: bool = false; + +pub inline fn ctx_init() void { + tctx = .init(); +} + +pub inline fn ctx_release() void { + tctx.release(); +} + +pub const sleep = os.sleep; +pub const yield = std.Thread.yield; + +pub const Context = struct { + arenas: [2]*Arena, + + name: [32]u8 = @splat(0), + name_len: u64 = 0, + + pub fn init() *Context { + const arena: *Arena = .init(.default); + const ctx: *Context = arena.create(Context); + ctx.* = .{ + .arenas = .{ + arena, + .init(.default), + }, + .name = undefined, + .name_len = undefined, + }; + + return ctx; + } + + pub fn release(ctx: *Context) void { + ctx.arenas[1].release(); + ctx.arenas[0].release(); + } + + pub fn get_scratch(comptime N: comptime_int, conflicts: [N]*Arena) ?Arena.Temp { + var result: ?Arena.Temp = null; + outer: for (tctx.arenas) |arena| { + result = arena.temp(); + for (conflicts) |conflict| { + if (arena == conflict) { + result = null; + continue :outer; + } + } + if (result != null) + break; + } + return result; + } +}; + +const Thread = @This(); +const ThreadHandle = std.Thread; + +const log = std.log.scoped(.Thread); + +// File Imports +const Arena = @import("Arena.zig"); + +// Internal Module Imports +const os = @import("os"); + +// 3rd-Party Module Imports +const builtin = @import("builtin"); +const std = @import("std"); diff --git a/src/base/base.zig b/src/base/base.zig new file mode 100644 index 0000000..461d715 --- /dev/null +++ b/src/base/base.zig @@ -0,0 +1,169 @@ +//----------------------------------------------------------------------------- +// Module Re-Exports +//----------------------------------------------------------------------------- + +pub const Arena = @import("Arena.zig"); +pub const Thread = @import("Thread.zig"); + +pub const math = @import("math.zig"); +pub const casts = @import("casts.zig"); + +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Module Toplevel Types +//----------------------------------------------------------------------------- + +/// Requires backing buffer to be of a power of 2 length. +pub const RingBuffer = struct { + buf: []u8, + read: u32 = 0, + write: u32 = 0, + + /// Assumes provided buffer to be of pow2 length + pub fn init_backing(bytes: []u8) RingBuffer { + AssertMsg( + (bytes.len < MAX_SIZE) and + math.is_pow2(bytes.len), + "Buffer size must be power of 2, and fit within 2^31", + ); + return .{ .buf = bytes }; + } + + pub fn size(rb: *RingBuffer) u32 { + return rb.write -% rb.read; + } + + pub fn empty(rb: *RingBuffer) bool { + return rb.write == rb.read; + } + + pub fn mask(rb: *RingBuffer, idx: u32) u32 { + return idx & u32_(rb.buf.len - 1); + } + + pub fn putBytes(rb: *RingBuffer, bytes: []const u8) void { + const write_idx = rb.mask(rb.write); + defer rb.write +%= u32_(bytes.len); + + const contiguous_bytes = rb.buf[write_idx..]; + const copy0_len = @min(contiguous_bytes.len, bytes.len); + const copy1_len = bytes.len - copy0_len; + + @memcpy(contiguous_bytes[0..copy0_len], bytes[0..copy0_len]); + @memcpy(rb.buf[0..copy1_len], bytes[copy0_len..]); + } + + pub fn getNBytesFrom(rb: *RingBuffer, pos: u32, count: usize, out: []u8) void { + const start = rb.mask(pos); + + const contiguous_bytes = rb.buf[start..]; + const copy0_len = @min(count, contiguous_bytes.len); + const copy1_len = count - copy0_len; + + @memcpy(out[0..copy0_len], contiguous_bytes[0..copy0_len]); + @memcpy(out[copy0_len..], rb.buf[0..copy1_len]); + } + + const MAX_SIZE = math.maxInt(u31); +}; + +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Module Toplevel Functions +//----------------------------------------------------------------------------- + +pub inline fn StaticAssert(cond: bool, msg: []const u8) void { + comptime { + if (!cond) + @compileError(msg); + } +} + +pub inline fn Assert(cond: bool) void { + if (!cond) + @trap(); +} + +pub inline fn AssertMsg(cond: bool, msg: []const u8) void { + if (!cond) + @panic(msg); +} + +pub inline fn DebugAssert(cond: bool, msg: []const u8) void { + switch (builtin.mode) { + .Debug, .ReleaseSafe => { + AssertMsg(cond, msg); + }, + else => {}, + } +} + +//----------------------------------------------------------------------------- +// Value-preserving cast quick helpers +//----------------------------------------------------------------------------- + +pub inline fn u8_(v: anytype) u8 { + return cast(u8, v); +} + +pub inline fn i8_(v: anytype) i8 { + return cast(i8, v); +} + +pub inline fn u16_(v: anytype) u16 { + return cast(u16, v); +} + +pub inline fn i16_(v: anytype) i16 { + return cast(i16, v); +} + +pub inline fn u32_(v: anytype) u32 { + return cast(u32, v); +} + +pub inline fn i32_(v: anytype) i32 { + return cast(i32, v); +} + +pub inline fn u64_(v: anytype) u64 { + return cast(u64, v); +} + +pub inline fn i64_(v: anytype) i64 { + return cast(i64, v); +} + +pub inline fn u128_(v: anytype) u128 { + return cast(u128, v); +} + +pub inline fn i128_(v: anytype) i128 { + return cast(i128, v); +} + +pub inline fn usize_(v: anytype) usize { + return cast(usize, v); +} + +pub inline fn isize_(v: anytype) isize { + return cast(isize, v); +} + +pub inline fn f32_(v: anytype) f32 { + return cast(f32, v); +} + +pub inline fn f64_(v: anytype) f64 { + return cast(f64, v); +} + +//----------------------------------------------------------------------------- + +const cast = casts.cast; +const transmute = casts.transmute; + +const os = @import("os"); +const builtin = @import("builtin"); \ No newline at end of file diff --git a/src/base/casts.zig b/src/base/casts.zig new file mode 100644 index 0000000..55051a7 --- /dev/null +++ b/src/base/casts.zig @@ -0,0 +1,165 @@ +/// Reinterpret the bytes of `src` as type `T`. +/// This WILL cast const -> non-const if `T` is non_const. +pub fn transmute(comptime T: type, src: anytype) T { + const SourceType = @TypeOf(src); + + const TargetTypeInfo = @typeInfo(T); + const TargetTypeName = @typeName(T); + const SourceTypeInfo = @typeInfo(SourceType); + const SourceTypeName = @typeName(SourceType); + + const result: T = if (T == SourceType) + src + else res: { + switch (TargetTypeInfo) { + .array => { + if (SourceTypeInfo != .array) + @compileError("Cannot transmute non-array type to array type"); + + break :res @bitCast(src); + }, + .pointer => { + if (SourceTypeInfo == .pointer) { + break :res if (TargetTypeInfo.pointer.is_const) + @ptrCast(@alignCast(src)) + else + @constCast(@ptrCast(@alignCast(src))); + } else if (SourceTypeInfo == .array) { + break :res if (TargetTypeInfo.pointer.is_const) + @ptrCast(@alignCast(&src)) + else + @constCast(@ptrCast(@alignCast(&src))); + + } else if (SourceTypeInfo == .int) { + if (@sizeOf(SourceType) != @sizeOf(T)) { + @compileError( + "Cannot transmute type " ++ + SourceTypeName ++ + "to type " ++ + TargetTypeName ++ + ". Type sizes do not match!" + ); + } + break :res @ptrFromInt(src); + } else { + @compileError( + "Cannot transmute non-pointer, non-int type to pointer type." + ); + } + }, + .optional => { + @compileError("Transmute to optional types not yet implemented"); + }, + .@"struct" => |struct_t| { + if (struct_t.layout == .@"packed") { + if (SourceTypeInfo == .@"struct" and + SourceTypeInfo.@"struct".layout == .@"packed") + { + @compileError( + "Cannot transmute from non-packed struct type " ++ + SourceTypeName ++ + "." + ); + } else if (@sizeOf(SourceType) != @sizeOf(T)) { + @compileError( + "Cannot transmute type " ++ + SourceTypeName ++ + "to type " ++ + TargetTypeName ++ + ". Type sizes do not match!" + ); + } + break :res @bitCast(src); + } + @compileError( + "Cannot transmute to non-packed struct type " ++ + TargetTypeName ++ + "." + ); + }, + .int, .float => { + if (@sizeOf(SourceType) != @sizeOf(T)) + @compileError( + "Cannot transmute type " ++ + SourceTypeName ++ + "to type " ++ + TargetTypeName ++ + ". Type sizes do not match!" + ); + break :res @bitCast(src); + }, + else => @compileError( + "Unsupported transmute target type: " ++ + TargetTypeName + ), + } + }; + return result; +} + +/// Cast the value of `src` to the same value of type `T` +/// Will invoke `transmute` if +/// - `T` is a pointer type. +/// - `T` is an integer type and `src` is a packed struct or vice versa. +/// - `T` is a packed struct and `src` is an enum type or vice versa. +pub fn cast(comptime T: type, src: anytype) T { + const SourceType = @TypeOf(src); + + const TargetTypeInfo = @typeInfo(T); + const TargetTypeName = @typeName(T); + const SourceTypeInfo = @typeInfo(SourceType); + const SourceTypeName = @typeName(SourceType); + + const result: T = if (T == SourceType) + src + else res: { + switch (TargetTypeInfo) { + .int => { + if (SourceTypeInfo == .int or SourceTypeInfo == .comptime_int) + break :res @intCast(src); + if (SourceTypeInfo == .float or SourceTypeInfo == .comptime_float) + break :res @intFromFloat(src); + if (SourceTypeInfo == .@"struct") break :res transmute(T, src); + if (SourceTypeInfo == .@"enum") break :res @intFromEnum(src); + if (SourceTypeInfo == .pointer) break :res @intFromPtr(src); + }, + .float => { + if (SourceTypeInfo == .int or SourceTypeInfo == .comptime_int) + break :res @floatFromInt(src); + if (SourceTypeInfo == .float or SourceTypeInfo == .comptime_float) + break :res @floatCast(src); + }, + .@"enum" => { + if (SourceTypeInfo == .int) break :res @enumFromInt(src); + if (SourceTypeInfo == .@"enum") break :res @enumFromInt(@intFromEnum(src)); + if (SourceTypeInfo == .@"struct") + if (SourceTypeInfo.@"struct".backing_integer) |int_t| + break :res @enumFromInt(transmute(int_t, src)); + }, + .pointer => { + break :res transmute(T, src); + }, + .@"struct" => |struct_t| { + if (struct_t.layout == .@"packed") { + if (SourceTypeInfo == .int) break :res transmute(T, src); + if (SourceTypeInfo == .@"enum") break :res transmute(T, @intFromEnum(src)); + if (SourceTypeInfo == .@"struct") break :res transmute(T, src); + } + }, + .bool => { + if (SourceTypeInfo == .int) break :res src != 0; + }, + else => @compileError( + "Unsupported cast target type: " ++ + TargetTypeName ++ "." + ), + } + @compileError( + "No cast available from source type " ++ + SourceTypeName ++ " to target type " ++ + TargetTypeName ++ "." + ); + }; + + return result; +} diff --git a/src/base/math.zig b/src/base/math.zig new file mode 100644 index 0000000..2cf4c5f --- /dev/null +++ b/src/base/math.zig @@ -0,0 +1,123 @@ +pub const Units = struct { + pub inline fn KB(val: anytype) @TypeOf(val) { + comptime { + const t_info = @typeInfo(@TypeOf(val)); + if (!(t_info == .int or t_info == .comptime_int)) { + @compileError("Unit conversion values must be of unsigned integer type"); + } + if (!(t_info == .comptime_int and val >= 0) and t_info.int.signedness == .signed) { + @compileError("Unit conversion values must be unsigned integers"); + } + + if ((t_info == .int and t_info.int.bits < 16)) { + @compileError("Integer too small. Must have at minimum 16 bits"); + } + } + + return val << 10; + } + + pub inline fn MB(val: anytype) @TypeOf(val) { + comptime { + const t_info = @typeInfo(@TypeOf(val)); + if (!(t_info == .int or t_info == .comptime_int)) { + @compileError("Unit conversion values must be of unsigned integer type"); + } + if (!(t_info == .comptime_int and val >= 0) and t_info.int.signedness == .signed) { + @compileError("Unit conversion values must be unsigned integers"); + } + + if ((t_info == .int and t_info.int.bits < 32)) { + @compileError("Integer too small. Must have at minimum 32 bits"); + } + } + + return val << 20; + } + + pub inline fn GB(val: anytype) @TypeOf(val) { + comptime { + const t_info = @typeInfo(@TypeOf(val)); + if (!(t_info == .int or t_info == .comptime_int)) { + @compileError("Unit conversion values must be of unsigned integer type"); + } + if (!(t_info == .comptime_int and val >= 0) and t_info.int.signedness == .signed) { + @compileError("Unit conversion values must be unsigned integers"); + } + + if ((t_info == .int and t_info.int.bits < 64)) { + @compileError("Integer too small. Must have at minimum 64 bits"); + } + } + + return val << 30; + } + + pub inline fn TB(val: anytype) @TypeOf(val) { + comptime { + const t_info = @typeInfo(@TypeOf(val)); + if (!(t_info == .int or t_info == .comptime_int)) { + @compileError("Unit conversion values must be of unsigned integer type"); + } + if (!(t_info == .comptime_int and val >= 0) and t_info.int.signedness == .signed) { + @compileError("Unit conversion values must be unsigned integers"); + } + + if ((t_info == .int and t_info.int.bits < 64)) { + @compileError("Integer too small. Must have at minimum 64 bits"); + } + } + + return val << 40; + } +}; + +pub inline fn div_roundup(n: anytype, size: usize) u32 { + // TODO: See if there's a better way to do this... am tired + return base.u32_(size * ((base.u64_(n) + (size-1)) / size)); +} + +pub fn align_pow2(x: usize, b: usize) usize { + return @as(usize, (@as(usize, (x + b - 1)) & (~@as(usize, (b - 1))))); +} + +pub fn is_pow2(x: anytype) bool { + base.Assert(x > 0); + return (x & (x - 1)) == 0; +} + +// --- STD CONST ALIASES --- +const math = std.math; +pub const tau = math.tau; +pub const pi = math.pi; + +// --- STD FN ALIASES --- +pub const cos = math.cos; +pub const sin = math.sin; +pub const tan = math.tan; +pub const inf = math.inf; +pub const pow = math.pow; +pub const sqrt = math.sqrt; +pub const maxInt = math.maxInt; +pub const maxFloat = math.floatMax; +pub const degToRad = math.degreesToRadians; +pub const radToDeg = math.radiansToDegrees; + +const log = std.log.scoped(.Math); + +const cpu_arch = builtin.cpu.arch; +const has_avx = if (cpu_arch == .x86_64) + std.Target.x86.featureSetHas(builtin.cpu.features, .avx) +else false; +const has_avx512f = if (cpu_arch == .x86_64) + std.Target.x86.featureSetHas(builtin.cpu.features, .avx512f) +else false; +const has_fma = if (cpu_arch == .x86_64) + std.Target.x86.featureSetHas(builtin.cpu.features, .fma) +else false; + +const base = @import("base.zig"); + +// 3rd-Party Imports +const std = @import("std"); +const builtin = @import("builtin"); diff --git a/src/client.zig b/src/client.zig new file mode 100644 index 0000000..f0323b6 --- /dev/null +++ b/src/client.zig @@ -0,0 +1,75 @@ +pub fn main(init: std.process.Init.Minimal) !void { + base.Thread.ctx_init(); + defer base.Thread.ctx_release(); + + var arena: *Arena = .init(.default); + defer arena.release(); + var conn: wayland.Connection = .open(arena, init.environ); + defer conn.close(); + var proxy = conn.proxy(); + + const wl_surface = conn.client_state.compositor.create_surface(&proxy); + const xdg_surface = conn.client_state.xdg_wm_base.get_xdg_surface(&proxy, wl_surface); + const xdg_toplevel = xdg_surface.get_toplevel(&proxy); + const xdg_decoration = conn.client_state.xdg_decoration_manager.get_toplevel_decoration(&proxy, xdg_toplevel); + xdg_toplevel.set_title(&proxy, "Simple Wayland"); + wl_surface.commit(&proxy); + xdg_decoration.set_mode(&proxy, .server_side); + + try conn.flush(); + + var shm_pool: wayland.ShmPool = .create(&conn, 960, 540); + const wl_buffer = shm_pool.create_buffer( + 960, 540, .xrgb8888, + ); + + { + var tmp = arena.temp(); + defer tmp.end(); + var surface_acked = false; + while (!surface_acked) { + if (conn.get_event(tmp.arena)) |ev| { + std.log.debug("ev :: {}", .{ev}); + switch (ev) { + .xdg_surface_configure => |configure| { + xdg_surface.ack_configure(&proxy, configure.serial); + surface_acked = true; + }, + else => {}, + } + } else { + conn.load_events(); + } + } + } + + var tmp = arena.temp(); + var want_exit = false; + while (!want_exit) { + tmp = arena.temp(); + defer tmp.end(); + conn.load_events(); + while (conn.get_event(tmp.arena)) |ev| { + switch (ev) { + .xdg_toplevel_close => { want_exit = true; }, + .xdg_wm_base_ping => |ping| { + conn.client_state.xdg_wm_base.pong(&proxy, ping.serial); + }, + else => { std.log.debug("{}", .{ev}); }, + } + } + wl_surface.damage_buffer(&proxy, 0, 0, 960, 540); + wl_surface.attach(&proxy, wl_buffer, 0, 0); + wl_surface.commit(&proxy); + try conn.flush(); + Thread.sleep(time.ns_per_ms * 16); + } +} + +const Arena = base.Arena; +const Thread = base.Thread; +const base = @import("base"); + +const wayland = @import("wayland.zig"); +const time = std.time; +const std = @import("std"); diff --git a/src/os/linux.zig b/src/os/linux.zig new file mode 100644 index 0000000..4526cc6 --- /dev/null +++ b/src/os/linux.zig @@ -0,0 +1,285 @@ +//------------------------------------------------------------------------------ +// Memory Management API Surface +//------------------------------------------------------------------------------ +pub inline fn mem_reserve(size: usize) []align(page_size_min) u8 { + const rc = mmap( + null, + size, + .{}, + .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, + -1, + 0, + ); + + // intentional crash on failed alloc + if (errno(rc) != .SUCCESS) unreachable; + + const ptr: [*]align(page_size_min) u8 = @ptrFromInt(rc); + return ptr[0..size]; +} + +pub inline fn mem_commit(bytes: []align(page_size_min) u8) bool { + const rc = mprotect(bytes.ptr, bytes.len, .{ .READ = true, .WRITE = true }); + + if (errno(rc) != .SUCCESS) return false; + + return true; +} +pub inline fn mem_decommit(bytes: []align(page_size_min) const u8) void { + _ = madvise(bytes.ptr, bytes.len, MADV.DONTNEED); + _ = mprotect(bytes.ptr, bytes.len, PROT.NONE); +} + +pub inline fn mem_release(bytes: []align(page_size_min) const u8) void { + _ = munmap(bytes.ptr, bytes.len); +} + +pub inline fn mem_reserve_large(size: usize) ?[]align(page_size_min) u8 { + const rc = mmap( + null, + size, + .{}, + .{ + .TYPE = .PRIVATE, + .ANONYMOUS = true, + .HUGETLB = true, + }, + -1, + 0, + ); + + if (errno(rc) != .SUCCESS) return null; + + const ptr: [*]align(page_size_min) u8 = @ptrFromInt(rc); + return ptr[0..size]; +} + +pub inline fn mem_commit_large(bytes: []align(page_size_min) u8) bool { + const rc = mprotect(bytes.ptr, bytes.len, .{ .READ = true, .WRITE = true }); + + if (errno(rc) != .SUCCESS) return false; + + return true; +} +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// Sleep/Time API Surface +//------------------------------------------------------------------------------ +pub fn sleep(ns: u64) void { + const ns_per_s = 1000000000; + + const seconds = ns / ns_per_s; + const nanoseconds = ns % ns_per_s; + + var req: timespec = .{ + .sec = @intCast(seconds), + .nsec = @intCast(nanoseconds), + }; + var rem: timespec = .{ .sec = 0, .nsec = 0 }; + var res: usize = @bitCast(@as(isize, -1)); + + while (res != 0) { + res = nanosleep(&req, &rem); + + if (errno(res) != .INTR) break; + + req = rem; + } +} +//------------------------------------------------------------------------------ + +/// Create container type for control messages +pub fn cmsg(comptime T: type) type { + const msg_len = cmsghdr.msg_len(@sizeOf(T)); + const padded_bit_count = cmsghdr.padding_bits(msg_len, @bitSizeOf(T)); + + return packed struct { + /// Control message header + header: cmsghdr, + /// Data we actually want + data: T, + + /// padding to reach data alignment + __padding: @Int(.unsigned, padded_bit_count) = 0, + + pub fn init(level: i32, @"type": i32, data: T) cmsg_t { + return .{ + .header = .{ + .len = msg_len, + .level = level, + .type = @"type", + }, + .data = data, + }; + } + + pub const Size = @sizeOf(cmsg_t); + + const cmsg_t = @This(); + }; +} + +const CmsgIterator = struct { + buf: []const u8, + idx: usize, + + const Iterator = @This(); + pub fn next(iter: *Iterator) ?*const cmsghdr { + if (iter.buf[iter.idx..].len < @sizeOf(cmsghdr)) return null; + + const hdr: *const cmsghdr = @ptrCast(@alignCast(iter.buf[iter.idx..].ptr)); + if (hdr.len < @sizeOf(cmsghdr)) return null; + + const aligned_len = cmsghdr.__msg_len(hdr); + iter.idx += aligned_len; + if (iter.idx > iter.buf.len) iter.idx = iter.buf.len; + + return hdr; + } + pub fn first(iter: *Iterator) ?cmsghdr { + const result: ?cmsghdr = if (iter.buf[iter.idx..].len > @sizeOf(cmsghdr)) + std.mem.bytesToValue(cmsghdr, iter.buf[iter.idx..][0..@sizeOf(cmsghdr)]) + else + null; + + return result; + } + + pub fn reset(iter: *Iterator) void { + iter.idx = 0; + } +}; + +pub const cmsghdr = packed struct { + /// Data byte count, including header + len: usize, + /// Originating protocol + level: i32, + /// Protocol-specific type + type: i32, + + // TODO: Revise. This is prolly a rather unsafe API + pub fn iter(buf: []const u8) CmsgIterator { + return .{ + .buf = buf, + .idx = 0, + }; + } + + // TODO: Revise. This is prolly a rather unsafe API + pub fn data(ptr: *const cmsghdr, comptime T: type) *const T { + const buf: [*]const u8 = @ptrCast(@alignCast(ptr)); + + return @ptrCast(@alignCast(buf[Size..][0..@sizeOf(T)].ptr)); + } + + /// Calculate length of control message given data of length `len` + /// + /// Port of musl libc's CMSG_LEN macro + /// + /// Macro Definition: + /// #define CMSG_LEN(len) (CMSG_ALIGN (sizeof (struct cmsghdr)) + (len)) + pub inline fn msg_len(len: usize) usize { + return msg_align(cmsghdr.Size) + len; + } + + pub inline fn __msg_len(msg: *const cmsghdr) usize { + return ((msg.len + @sizeOf(c_ulong) - 1) & ~@as(usize, (@sizeOf(c_ulong) - 1))); + } + + /// Get the number of bits needed to pad out the message + pub inline fn padding_bits(len: usize, data_t_size: usize) usize { + return (8 * len) - (@bitSizeOf(cmsghdr) + data_t_size); + } + + /// Calculate alignment of control message of length `len` to cmsghdr size + /// + /// Port of musl libc's CMSG_ALIGN macro + /// + /// Macro Definition: + /// #define CMSG_ALIGN(len) (((len) + sizeof (size_t) - 1) & (size_t) ~(sizeof (size_t) - 1)) + inline fn msg_align(len: usize) usize { + return (((len) + @sizeOf(size_t) - 1) & ~@as(usize, (@sizeOf(size_t) - 1))); + } + + const size_t = usize; + const Size = @sizeOf(@This()); +}; + +// Types +pub const dev_t = enum(u64) { + _, + + pub fn toInt(dev: dev_t) u64 { + return @intFromEnum(dev); + } + pub fn fromInt(int: u64) dev_t { + return @enumFromInt(int); + } + + pub fn major(dev: dev_t) u64 { + return ((dev.toInt() >> 8) & 0xfff); + } + pub fn minor(dev: dev_t) u64 { + return ((dev.toInt() & 0xff) | ((dev.toInt() >> 12) & 0xffffff00)); + } +}; + +// Syscall aliases +pub const recvmsg = linux.recvmsg; +pub const sendmsg = linux.sendmsg; +pub const prctl = linux.prctl; +pub const mmap = linux.mmap; +pub const munmap = linux.munmap; +pub const madvise = linux.madvise; +pub const mprotect = linux.mprotect; +pub const socket = linux.socket; +pub const nanosleep = linux.nanosleep; +pub const pread = linux.pread; +pub const read = linux.read; +pub const close = linux.close; +pub const msync = linux.msync; +pub const lseek = linux.lseek; +pub const open = linux.open; +pub const unlink = linux.unlink; +pub const ftruncate = linux.ftruncate; +pub const connect = linux.connect; +pub const statx = linux.statx; +pub const ioctl = linux.ioctl; +pub const memfd_create = linux.memfd_create; +pub const sockaddr = linux.sockaddr; +pub const errno = std.posix.errno; + +// Type aliases +pub const STATX = linux.STATX; +pub const Statx = linux.Statx; +pub const timespec = linux.timespec; +pub const iovec = std.posix.iovec; +pub const msghdr = std.posix.msghdr; +pub const msghdr_const = std.posix.msghdr_const; + +// Constant/Namespace aliases +pub const E = linux.E; +pub const PR = linux.PR; +pub const MS = linux.MS; +pub const AT = linux.AT; +pub const AF = linux.AF; +pub const MSG = linux.MSG; +pub const MAP = linux.MAP; +pub const SCM = linux.SCM; +pub const SOL = linux.SOL; +pub const SOCK = linux.SOCK; +pub const PROT = linux.PROT; +pub const MADV = linux.MADV; +pub const SEEK = linux.SEEK; + +const linux = std.os.linux; + +const page_size_min = std.heap.page_size_min; + +const log = std.log.scoped(.Linux); + +// 3rd-Party Module Imports +const builtin = @import("builtin"); +const std = @import("std"); diff --git a/src/os/os.zig b/src/os/os.zig new file mode 100644 index 0000000..3eec5b4 --- /dev/null +++ b/src/os/os.zig @@ -0,0 +1,21 @@ +pub const mem_reserve = linux.mem_reserve; +pub const mem_commit = linux.mem_commit; +pub const mem_decommit = linux.mem_decommit; +pub const mem_release = linux.mem_release; +pub const mem_reserve_large = linux.mem_reserve_large; +pub const mem_commit_large = linux.mem_commit_large; + +pub const sleep = linux.sleep; + +pub const page_size_min = std.heap.page_size_min; +pub const page_size_max = std.heap.page_size_max; + +pub const Environ = std.process.Environ; +pub const Target = builtin.target.os; + +// File Imports +pub const linux = @import("linux.zig"); + +// 3rd-Party Modules +const std = @import("std"); +const builtin = @import("builtin"); diff --git a/src/simple-client.zig b/src/simple-client.zig new file mode 100644 index 0000000..e03d158 --- /dev/null +++ b/src/simple-client.zig @@ -0,0 +1,805 @@ +pub fn main(init: std.process.Init.Minimal) !void { + // Allocators to use as needed + var arena: ArenaAllocator = .init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); // persistent allocations + + var temp_arena: ArenaAllocator = .init(std.heap.page_allocator); + defer temp_arena.deinit(); + const temp_allocator = temp_arena.allocator(); // short-lived allocations + + var stio: std.Io.Threaded = .init_single_threaded; + defer stio.deinit(); + const io = stio.io(); + + var connection: Connection = undefined; + connection.in = .init_backing(try allocator.alloc(u8, 4096)); + connection.out = .init_backing(try allocator.alloc(u8, 4096)); + connection.fd_in = .init_backing(try allocator.alloc(u8, 2048)); + connection.fd_out = .init_backing(try allocator.alloc(u8, 2048)); + + const env = init.environ; + + // Establish Connection + connection.sock_fd = wl_sock: { + defer _ = temp_arena.reset(.retain_capacity); + + const xdg_rt_dir = env.getPosix("XDG_RUNTIME_DIR").?; + const wl_display_sockname = env.getPosix("WAYLAND_DISPLAY") + orelse "wayland-0"; + const sockpath = try std.mem.join( + temp_allocator, "/", &.{xdg_rt_dir, wl_display_sockname}); + + const fd = i32_(linux.socket( + linux.AF.UNIX, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, 0)); + + const sock_addr = sock_addr: { + var addr: linux.sockaddr.un = .{ + .family = linux.AF.UNIX, + .path = @splat(0), + }; + if (sockpath.len >= addr.path.len) return error.SocketPathTooLong; + @memcpy(addr.path[0..sockpath.len], sockpath); + break :sock_addr addr; + }; + + const res = linux.connect( + fd, &sock_addr, u32_(@sizeOf(@TypeOf(sock_addr)))); + + if (@as(isize, @bitCast(res)) < 0) return error.FailedToConnectSocket; + + break :wl_sock fd; + }; + defer _ = linux.close(connection.sock_fd); + + var client_state: ClientState = undefined; + client_state.wl_display_id = 1; client_state.wl_registry_id = 2; + client_state.current_id = client_state.wl_registry_id+1; + + // Bind desired interfaces + { + defer _ = temp_arena.reset(.retain_capacity); + const get_registry_msg_size = 12; + const display_get_registry_msg = [_]u32{ + client_state.wl_display_id, + (u32_(get_registry_msg_size) << 16) | op_wl_display_get_registry, + client_state.wl_registry_id, + }; + + connection.out.putBytes(@ptrCast(&display_get_registry_msg)); + write_outgoing(connection.sock_fd, &connection.out, &connection.fd_out); + + try read_incoming(connection.sock_fd, &connection.in, &connection.fd_in); + + const BoundGlobals = packed struct (u8) { + wl_seat: bool = false, + wl_compositor: bool = false, + wl_shm: bool = false, + xdg_wm_base: bool = false, + xdg_decoration_manager: bool = false, + __pad: u3 = 0, + + pub fn match(a: @This(), b: @This()) bool { + return @as(u8, @bitCast(a)) == @as(u8, @bitCast(b)); + } + }; + + const all_bound: BoundGlobals = .{ + .wl_seat = true, + .wl_compositor = true, + .wl_shm = true, + .xdg_wm_base = true, + .xdg_decoration_manager = true, + }; + var globals_bound: BoundGlobals = .{}; + + while (!connection.in.empty()) { + const size = connection.in.size(); + if (size < @sizeOf(WireHeader)) { + try read_incoming(connection.sock_fd, &connection.in, &connection.fd_in); + continue; + } + const header_start = connection.in.mask(connection.in.read); + var header: WireHeader = undefined; + connection.in.getNBytesFrom(header_start, 8, std.mem.asBytes(&header)); + + if (size < header.len) { + try read_incoming(connection.sock_fd, &connection.in, &connection.fd_in); + continue; + } + + defer connection.in.read +%= header.len; + const data_start: u32 = header_start + @sizeOf(WireHeader); + const data_len = header.len - @sizeOf(WireHeader); + const data = try temp_allocator.alloc(u8, data_len); + connection.in.getNBytesFrom(data_start, data_len, data); + + if (header.id == 0) { + std.log.warn("Invalid header ID! (ID=0)", .{}); + } else if (header.id == client_state.wl_display_id) { + std.log.warn( + "Unexpected wl_display event during binding!", .{}); + continue; + } else if (header.id == client_state.wl_registry_id) { + if (header.op != ev_wl_registry_global) { + std.log.warn( + "Unexpected wl_registry global remove event during binding!", .{}); + continue; + } + + const global_name = std.mem.bytesToValue(u32, data[0..4]); + const global_interface_len = std.mem.bytesToValue(u32, data[4..8]); + const global_interface = data[8..][0..global_interface_len-1:0]; + const global_interface_aligned_len = + div_roundup(global_interface_len, 4); + const global_version = std.mem.bytesToValue( + u32, data[8+global_interface_aligned_len..][0..4]); + std.debug.print( + "wl_registry#{}.global: name={}, interface=\"{s}\", version={}\n", + .{client_state.wl_registry_id, global_name, + global_interface, global_version}); + + const interface_id = client_state.current_id; // only used on desired interfaces + + if (false) { + } else if (std.mem.eql(u8, "wl_seat", global_interface)) { + client_state.wl_seat_id = interface_id; + globals_bound.wl_seat = true; + defer client_state.current_id += 1; + + bind_interface(&connection, client_state.wl_registry_id, global_name, + global_interface, global_version, interface_id); + } else if (std.mem.eql(u8, "wl_compositor", global_interface)) { + client_state.wl_compositor_id = interface_id; + globals_bound.wl_compositor = true; + defer client_state.current_id += 1; + + bind_interface(&connection, client_state.wl_registry_id, global_name, + global_interface, global_version, interface_id); + } else if (std.mem.eql(u8, "wl_shm", global_interface)) { + client_state.wl_shm_id = interface_id; + globals_bound.wl_shm = true; + defer client_state.current_id += 1; + + bind_interface(&connection, client_state.wl_registry_id, global_name, + global_interface, global_version, interface_id); + } else if (std.mem.eql(u8, "xdg_wm_base", global_interface)) { + client_state.xdg_wm_base_id = interface_id; + globals_bound.xdg_wm_base = true; + defer client_state.current_id += 1; + + bind_interface(&connection, client_state.wl_registry_id, global_name, + global_interface, global_version, interface_id); + } else if (std.mem.eql(u8, "zxdg_decoration_manager_v1", global_interface)) { + client_state.xdg_decoration_manager_id = interface_id; + globals_bound.xdg_decoration_manager = true; + defer client_state.current_id += 1; + + bind_interface(&connection, client_state.wl_registry_id, global_name, + global_interface, global_version, interface_id); + } + } + } + if (globals_bound.match(all_bound)) + std.log.info("All desired globals bound!", .{}); + write_outgoing(connection.sock_fd, &connection.out, &connection.fd_out); + } + + const wl_surface_id = client_state.current_id; + client_state.current_id += 1; + const xdg_surface_id = client_state.current_id; + client_state.current_id += 1; + const xdg_toplevel_id = client_state.current_id; + client_state.current_id += 1; + const xdg_decoration_id = client_state.current_id; + client_state.current_id += 1; + + // Tell compositor to create our surface and associated objects + { + // - write wl_compositor.create_surface + const create_surface_msg = [_]u32{ + client_state.wl_compositor_id, + (u32_(12) << 16) | op_wl_compositor_create_surface, + wl_surface_id, + }; + // - write xdg_wm_base.get_xdg_surface + const get_xdg_surface_msg = [_]u32{ + client_state.xdg_wm_base_id, + (u32_(16) << 16) | op_xdg_wm_base_get_xdg_surface, + xdg_surface_id, wl_surface_id, + }; + // - write xdg_surface.get_toplevel + const get_toplevel_msg = [_]u32{ + xdg_surface_id, + (u32_(12) << 16) | op_xdg_surface_get_toplevel, + xdg_toplevel_id, + }; + // - write xdg_decoration_manager.get_toplevel_decoration + const get_toplevel_decoration_msg = [_]u32{ + client_state.xdg_decoration_manager_id, + (u32_(16) << 16) | op_xdg_decoration_manager_get_toplevel_decoration, + xdg_decoration_id, xdg_toplevel_id, + }; + // - write wl_surface.commit + const wl_surface_commit_msg = [_]u32{ + wl_surface_id, + (u32_(8) << 16) | op_wl_surface_commit, + }; + + connection.out.putBytes(@ptrCast(&create_surface_msg)); + connection.out.putBytes(@ptrCast(&get_xdg_surface_msg)); + connection.out.putBytes(@ptrCast(&get_toplevel_msg)); + connection.out.putBytes(@ptrCast(&get_toplevel_decoration_msg)); + connection.out.putBytes(@ptrCast(&wl_surface_commit_msg)); + } + + const window_width = 960; + const window_height = 540; + const image_format = wl_shm_format_xrgb8888; + const stride = window_width * 4; + const image_size = window_height * stride; + const shm_fd = i32_(linux.memfd_create("wl-shm", 0)); + _ = linux.ftruncate(shm_fd, image_size); + + const image_bytes: []u32 = img: { + const rc = linux.mmap( + null, @intCast(image_size), + .{ .READ = true, .WRITE = true }, + .{ .TYPE = .SHARED }, + shm_fd, 0); + + const irc = @as(isize, @bitCast(rc)); + if (irc < 0) { + return error.FailedToMapShmFile; + } + const bytes: [*]u8 = @ptrFromInt(rc); + break :img @ptrCast(@alignCast(bytes[0..image_size])); + }; + @memset(image_bytes, (u32_(255) << 16) | 0); + + const wl_shm_pool_id = client_state.current_id; + client_state.current_id += 1; + const wl_buffer_id = client_state.current_id; + client_state.current_id += 1; + + // Tell compositor to create our wl_shm_pool object and, from it, the wl_buffer + { + // - write wl_shm.create_pool + const wl_shm_create_pool_msg = [_]u32{ + client_state.wl_shm_id, + (u32_(16) << 16) | op_wl_shm_create_pool, + wl_shm_pool_id, u32_(image_size), + }; + // - write wl_shm_pool.create_buffer + const wl_shm_pool_create_buffer_msg = [_]u32{ + wl_shm_pool_id, + (u32_(32) << 16) | op_wl_shm_pool_create_buffer, + wl_buffer_id, 0, window_width, window_height, + u32_(stride), image_format, + }; + + connection.out.putBytes(@ptrCast(&wl_shm_create_pool_msg)); + connection.out.putBytes(@ptrCast(&wl_shm_pool_create_buffer_msg)); + connection.fd_out.putBytes(std.mem.asBytes(&shm_fd)); + + write_outgoing(connection.sock_fd, &connection.out, &connection.fd_out); + } + + // Wait for xdg_surface.configure, and ACK on receipt + while (true) { + if (!connection.in.empty()) { + const size = connection.in.size(); + if (size < @sizeOf(WireHeader)) { + try read_incoming( + connection.sock_fd, &connection.in, &connection.fd_in); + continue; + } + const header_start = connection.in.mask(connection.in.read); + var header: WireHeader = undefined; + connection.in.getNBytesFrom(header_start, 8, std.mem.asBytes(&header)); + + if (size < header.len) + { + try read_incoming( + connection.sock_fd, &connection.in, &connection.fd_in); + continue; + } + + defer connection.in.read +%= header.len; + const data_start: u32 = header_start + @sizeOf(WireHeader); + const data_len = header.len - @sizeOf(WireHeader); + const data = try temp_allocator.alloc(u8, data_len); + connection.in.getNBytesFrom(data_start, data_len, data); + + if (false) { + } else if (header.id == client_state.wl_display_id) { + const object_id = std.mem.bytesToValue( + u32, connection.in.buf[data_start..][0..4]); + const err_code = std.mem.bytesToValue( + u32, connection.in.buf[data_start+4..][0..4]); + const strlen = std.mem.bytesToValue( + u32, connection.in.buf[data_start+8..][0..4]); + const str = connection.in.buf[data_start+12..][0..strlen-1:0]; + std.log.warn( + "wl_display#1.error: object#{}, code={}, message=\"{s}\"", + .{object_id, err_code, str}); + } else if (header.id == xdg_surface_id and + header.op == ev_xdg_surface_configure) + { + const config_serial = std.mem.bytesToValue( + u32, connection.in.buf[data_start..][0..4]); + std.log.info("Config serial :: {}", .{config_serial}); + break; + } else { + std.log.debug("Unused header :: {}", .{header}); + } + } else { + try read_incoming(connection.sock_fd, &connection.in, &connection.fd_in); + } + } + + var want_exit = false; + while (!want_exit){ + defer _ = temp_arena.reset(.retain_capacity); + + while (!connection.in.empty()) { + const size = connection.in.size(); + if (size < @sizeOf(WireHeader)) { break; } + const header_start = connection.in.mask(connection.in.read); + var header: WireHeader = undefined; + connection.in.getNBytesFrom(header_start, 8, std.mem.asBytes(&header)); + + if (size < header.len) { break; } + + defer connection.in.read +%= header.len; + + const data_start: u32 = header_start + @sizeOf(WireHeader); + const data_len = header.len - @sizeOf(WireHeader); + const data = try temp_allocator.alloc(u8, data_len); + connection.in.getNBytesFrom(data_start, data_len, data); + + if (false) { + } else if (header.id == xdg_toplevel_id) { + if (header.op == ev_xdg_toplevel_close) { want_exit = true; break; } + } else if (header.id == client_state.xdg_wm_base_id) { + if (header.op == ev_xdg_wm_base_ping) { + const serial = std.mem.bytesToValue(u32, data); + const pong_msg = [_]u32{ + client_state.xdg_wm_base_id, + (u32_(12) << 16) | op_xdg_wm_base_pong, + serial, + }; + + connection.out.putBytes(@ptrCast(&pong_msg)); + } + } else { + // std.log.debug("Unused header :: {}", .{header}); + } + } + + // attach buffer to surface + { + const surface_damage_msg = [_]u32{ + wl_surface_id, + (u32_(24) << 16) | op_wl_surface_damage_buffer, + 0, 0, window_width, window_height, + }; + const surface_attach_msg = [_]u32{ + wl_surface_id, + (u32_(20) << 16) | op_wl_surface_attach, + wl_buffer_id, 0, 0, + }; + const surface_commit_msg = [_]u32{ + wl_surface_id, + (u32_(8) << 16) | op_wl_surface_commit, + }; + + connection.out.putBytes(@ptrCast(&surface_damage_msg)); + connection.out.putBytes(@ptrCast(&surface_attach_msg)); + connection.out.putBytes(@ptrCast(&surface_commit_msg)); + } + + + write_outgoing(connection.sock_fd, &connection.out, &connection.fd_out); + try read_incoming( + connection.sock_fd, &connection.in, &connection.fd_in); + + // sleep for 120fps, avoid burning too much CPU + const timeout: std.Io.Timeout = .{ + .duration = .{ + .raw = .{ .nanoseconds = std.time.ns_per_ms * 8 }, + .clock = .awake, + }, + }; + try timeout.sleep(io); + try std.Thread.yield(); + } +} + +inline fn bind_interface( + connection: *Connection, + wl_registry_id: u32, + name: u32, + interface: [:0]const u8, + version: u32, + interface_id: u32, +) void { + const string_len = u32_(interface.len+1); + const aligned_string_len = div_roundup(string_len, 4); + const max_str_padding: [4]u8 = @splat(0); + + const msg_data_len = + @sizeOf(WireHeader) + 4 // header + name + + 4 + aligned_string_len // string encoding + 4 + + 4 + 4; // version + id + + const bindmsg_header_bytes = [_]u32{ + wl_registry_id, + (u32_(msg_data_len) << 16) | op_wl_registry_bind, + }; + + connection.out.putBytes(@ptrCast(&bindmsg_header_bytes)); + + connection.out.putBytes(std.mem.asBytes(&name)); + connection.out.putBytes(std.mem.asBytes(&string_len)); + connection.out.putBytes(interface.ptr[0..string_len]); + const padding_needed = + aligned_string_len - string_len; + connection.out.putBytes(max_str_padding[0..padding_needed]); + connection.out.putBytes(std.mem.asBytes(&version)); + connection.out.putBytes(std.mem.asBytes(&interface_id)); + std.log.debug("bound interface {s} of name {} with id={}", + .{ interface, name, interface_id }); +} + +inline fn write_outgoing( + sock_fd: i32, + noalias rb: *RingBuffer, + noalias fd_rb: *RingBuffer, +) void { + var iov: [2]iovec = undefined; + const iov_len = if (rb.read != rb.write) + rb.prep_iovecs_out(&iov) + else 0; + + var cmsg_buf: [CMSG_BUF_MAX]u8 = undefined; + var controllen: usize = 0; + + while (!fd_rb.empty()) { + const fd_out_read = fd_rb.mask(fd_rb.read); + + const fd_out = std.mem.bytesToValue(i32, fd_rb.buf[fd_out_read..][0..4]); + + const control_msg: fd_cmsg_t = .init( + linux.SOL.SOCKET, linux.SCM.RIGHTS, fd_out); + + @memcpy(cmsg_buf[controllen..][0..@sizeOf(@TypeOf(control_msg))], std.mem.asBytes(&control_msg)); + fd_rb.read +%= 4; + controllen += @sizeOf(@TypeOf(control_msg)); + } + + const msg: linux.msghdr_const = .{ + .name = null, + .namelen = 0, + .iov = @ptrCast(&iov), + .iovlen = iov_len, + .control = &cmsg_buf, + .controllen = controllen, + .flags = 0, + }; + + _ = linux.sendmsg(sock_fd, &msg, 0); +} + +inline fn read_incoming( + sock_fd: i32, + noalias rb: *RingBuffer, + noalias fd_rb: *RingBuffer, +) !void { + _ = fd_rb; + var iov: [2]iovec = undefined; + const iov_len = rb.prep_iovecs_in(&iov); + + var cmsg_buf: [CMSG_BUF_MAX]u8 = undefined; + var msg: linux.msghdr = .{ + .name = null, + .namelen = 0, + .iov = &iov, + .iovlen = iov_len, + .control = &cmsg_buf, + .controllen = cmsg_buf.len, + .flags = 0, + }; + + var rc: usize = linux.recvmsg(sock_fd, &msg, linux.MSG.DONTWAIT); + var err: linux.E = linux.errno(rc); + while (err == .INTR or err == .AGAIN) { + if (err == .AGAIN) try std.Thread.yield(); + rc = linux.recvmsg(sock_fd, &msg, linux.MSG.DONTWAIT); + err = linux.errno(rc); + } + + if (@as(isize, @bitCast(rc)) < 0) return error.SocketReadFailed; + + const bytes_read = u32_(rc); + defer rb.write +%= bytes_read; +} + +inline fn div_roundup(n: u32, size: usize) u32 { + return u32_(size * ((u64_(n) + (size-1)) / size)); +} + +// Event opcodes we care about +const ev_wl_registry_global: u16 = 0; +const ev_wl_shm_pool_format: u16 = 0; +const ev_wl_buffer_release: u16 = 0; +const ev_xdg_wm_base_ping: u16 = 0; +const ev_xdg_toplevel_configure: u16 = 0; +const ev_xdg_toplevel_close: u16 = 1; +const ev_xdg_surface_configure: u16 = 0; +const ev_wl_display_error: u16 = 0; + +// Request opcodes we care about +const op_wl_display_get_registry: u16 = 1; +const op_wl_registry_bind: u16 = 0; +const op_wl_compositor_create_surface: u16 = 0; +const op_wl_xdg_wm_base_pong: u16 = 3; +const op_xdg_surface_ack_configure: u16 = 4; +const op_wl_shm_create_pool: u16 = 0; +const op_xdg_wm_base_get_xdg_surface: u16 = 2; +const op_xdg_wm_base_pong: u16 = 3; +const op_wl_shm_pool_create_buffer: u16 = 0; +const op_wl_surface_attach: u16 = 1; +const op_wl_surface_damage_buffer: u16 = 9; +const op_wl_surface_commit: u16 = 6; +const op_xdg_surface_get_toplevel: u16 = 1; +const op_xdg_decoration_manager_get_toplevel_decoration: u16 = 1; +const op_xdg_decoration_set_mode: u16 = 1; + +// Enum values we care about +const wl_shm_format_argb8888: u32 = 0; +const wl_shm_format_xrgb8888: u32 = 1; +const xdg_decoration_mode_server_size: u32 = 2; + +const CMSG_BUF_MAX = @sizeOf(fd_cmsg_t) * 32; +const fd_cmsg_t = cmsg(i32); + +const Connection = struct { + sock_fd: i32, + in: RingBuffer, + out: RingBuffer, + fd_in: RingBuffer, + fd_out: RingBuffer, +}; + +const ClientState = struct { + connection: *Connection, + + wl_display_id: u32, + wl_registry_id: u32, + + wl_seat_id: u32, + wl_compositor_id: u32, + wl_shm_id: u32, + xdg_wm_base_id: u32, + xdg_decoration_manager_id: u32, + + current_id: u32, +}; + +const WireHeader = packed struct (u64) { + id: u32, + op: u16, + len: u16, +}; + +pub fn cmsg(comptime T: type) type { + const msg_len = cmsghdr.msg_len(@sizeOf(T)); + const padded_bit_count = cmsghdr.padding_bits(msg_len, @bitSizeOf(T)); + + return packed struct { + /// Control message header + header: cmsghdr, + /// Data we actually want + data: T, + + /// padding to reach data alignment + __padding: @Int(.unsigned, padded_bit_count) = 0, + + pub fn init(level: i32, @"type": i32, data: T) cmsg_t { + return .{ + .header = .{ + .len = msg_len, + .level = level, + .type = @"type", + }, + .data = data, + }; + } + + pub const Size = @sizeOf(cmsg_t); + + const cmsg_t = @This(); + }; +} + +pub const cmsghdr = packed struct { + /// Data byte count, including header + len: usize, + /// Originating protocol + level: i32, + /// Protocol-specific type + type: i32, + + /// Calculate length of control message given data of length `len` + /// + /// Port of musl libc's CMSG_LEN macro + /// + /// Macro Definition: + /// #define CMSG_LEN(len) (CMSG_ALIGN (sizeof (struct cmsghdr)) + (len)) + pub inline fn msg_len(len: usize) usize { + return msg_align(cmsghdr.Size) + len; + } + + pub inline fn __msg_len(msg: *const cmsghdr) usize { + return ((msg.len + @sizeOf(c_ulong) - 1) & ~@as(usize, (@sizeOf(c_ulong) - 1))); + } + + /// Get the number of bits needed to pad out the message + pub inline fn padding_bits(len: usize, data_t_size: usize) usize { + return (8 * len) - (@bitSizeOf(cmsghdr) + data_t_size); + } + + /// Calculate alignment of control message of length `len` to cmsghdr size + /// + /// Port of musl libc's CMSG_ALIGN macro + /// + /// Macro Definition: + /// #define CMSG_ALIGN(len) (((len) + sizeof (size_t) - 1) & (size_t) ~(sizeof (size_t) - 1)) + inline fn msg_align(len: usize) usize { + return (((len) + @sizeOf(size_t) - 1) & ~@as(usize, (@sizeOf(size_t) - 1))); + } + + const size_t = usize; + const Size = @sizeOf(@This()); +}; + +/// Requires backing buffer to be of a power of 2 length. +pub const RingBuffer = struct { + buf: []u8, + read: u32 = 0, + write: u32 = 0, + + /// Assumes provided buffer to be of pow2 length + pub fn init_backing(bytes: []u8) RingBuffer { + std.debug.assert((bytes.len < MAX_SIZE) and is_pow2(bytes.len)); + return .{ .buf = bytes }; + } + + pub fn size(rb: *RingBuffer) u32 { + return u32_((rb.write -% rb.read) & (rb.buf.len-1)); + } + + pub fn empty(rb: *RingBuffer) bool { + return rb.write == rb.read; + } + + pub fn mask(rb: *const RingBuffer, idx: u32) u32 { + return idx & u32_(rb.buf.len - 1); + } + + pub fn putBytes(rb: *RingBuffer, bytes: []const u8) void { + const write_idx = rb.mask(rb.write); + defer rb.write +%= u32_(bytes.len); + + const contiguous_bytes = rb.buf[write_idx..]; + const copy0_len = @min(contiguous_bytes.len, bytes.len); + const copy1_len = bytes.len - copy0_len; + + @memcpy(contiguous_bytes[0..copy0_len], bytes[0..copy0_len]); + @memcpy(rb.buf[0..copy1_len], bytes[copy0_len..]); + } + + pub fn getNBytesFrom(rb: *RingBuffer, pos: u32, count: usize, out: []u8) void { + const start = rb.mask(pos); + + const contiguous_bytes = rb.buf[start..]; + const copy0_len = @min(count, contiguous_bytes.len); + const copy1_len = count - copy0_len; + + @memcpy(out[0..copy0_len], contiguous_bytes[0..copy0_len]); + @memcpy(out[copy0_len..], rb.buf[0..copy1_len]); + } + + pub fn prep_iovecs_out( + noalias rb: *RingBuffer, + iov: *[2]iovec, + ) usize { + const out_read = rb.mask(rb.read); + const out_write = rb.mask(rb.write); + var iov_len: usize = 1; + + if (out_read < out_write) { + const iov_buf = rb.buf[out_read..out_write]; + iov[0].base = iov_buf.ptr; + iov[0].len = iov_buf.len; + rb.read +%= u32_(iov_buf.len); + } else if (out_write == 0) { + const iov_buf = rb.buf[out_read..]; + iov[0].base = iov_buf.ptr; + iov[0].len = iov_buf.len; + rb.read +%= u32_(iov_buf.len); + } else { + const iov_buf_0 = rb.buf[out_read..]; + iov[0].base = iov_buf_0.ptr; + iov[0].len = iov_buf_0.len; + + const iov_buf_1 = rb.buf[0..out_write]; + iov[1].base = iov_buf_1.ptr; + iov[1].len = iov_buf_1.len; + iov_len = 2; + + rb.read +%= u32_(iov_buf_0.len + iov_buf_1.len); + } + + return iov_len; + } + + pub fn prep_iovecs_in( + noalias rb: *RingBuffer, + iov: *[2]iovec, + ) usize { + const out_read = rb.mask(rb.read); + const out_write = rb.mask(rb.write); + var iov_len: usize = 1; + + if (out_read > out_write) { + const iov_buf = rb.buf[out_write..out_read]; + iov[0].base = iov_buf.ptr; + iov[0].len = iov_buf.len; + // rb.read +%= u32_(iov_buf.len); + } else if (out_read == 0) { + const iov_buf = rb.buf[out_write..]; + iov[0].base = iov_buf.ptr; + iov[0].len = iov_buf.len; + // rb.read +%= u32_(iov_buf.len); + } else { + const iov_buf_0 = rb.buf[out_write..]; + iov[0].base = iov_buf_0.ptr; + iov[0].len = iov_buf_0.len; + + const iov_buf_1 = rb.buf[0..out_read]; + iov[1].base = iov_buf_1.ptr; + iov[1].len = iov_buf_1.len; + iov_len = 2; + + // rb.read +%= u32_(iov_buf_0.len + iov_buf_1.len); + } + + return iov_len; + } + + const MAX_SIZE = std.math.maxInt(u31); +}; + +inline fn u16_(x: anytype) u16 { + return @intCast(x); +} +inline fn u32_(x: anytype) u32 { + return @intCast(x); +} +inline fn i32_(x: anytype) i32 { + return @intCast(x); +} +inline fn u64_(x: anytype) u64 { + return @intCast(x); +} +inline fn is_pow2(x: anytype) bool { + std.debug.assert(x > 0); + return (x & (x-1)) == 0; +} + +const ArenaAllocator = std.heap.ArenaAllocator; +const linux = std.os.linux; +const iovec = std.posix.iovec; +const std = @import("std"); diff --git a/src/wayland-protocols/wayland.xml b/src/wayland-protocols/wayland.xml new file mode 100644 index 0000000..5a6a189 --- /dev/null +++ b/src/wayland-protocols/wayland.xml @@ -0,0 +1,3319 @@ + + + + + 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. + + + + + The core global object. This is a special singleton object. It + is used for internal Wayland protocol features. + + + + + The sync request asks the server to emit the 'done' event + on the returned wl_callback object. Since requests are + handled in-order and events are delivered in-order, this can + be used as a barrier to ensure all previous requests and the + resulting events have been handled. + + The object returned by this request will be destroyed by the + compositor after the callback is fired and as such the client must not + attempt to use it after that point. + + The callback_data passed in the callback is undefined and should be ignored. + + + + + + + This request creates a registry object that allows the client + to list and bind the global objects available from the + compositor. + + It should be noted that the server side resources consumed in + response to a get_registry request can only be released when the + client disconnects, not when the client side proxy is destroyed. + Therefore, clients should invoke get_registry as infrequently as + possible to avoid wasting memory. + + + + + + + The error event is sent out when a fatal (non-recoverable) + error has occurred. The object_id argument is the object + where the error occurred, most often in response to a request + to that object. The code identifies the error and is defined + by the object interface. As such, each interface defines its + own set of error codes. The message is a brief description + of the error, for (debugging) convenience. + + + + + + + + + These errors are global and can be emitted in response to any + server request. + + + + + + + + + + This event is used internally by the object ID management + logic. When a client deletes an object that it had created, + the server will send this event to acknowledge that it has + seen the delete request. When the client receives this event, + it will know that it can safely reuse the object ID. + + + + + + + + The singleton global registry object. The server has a number of + global objects that are available to all clients. These objects + typically represent an actual object in the server (for example, + an input device) or they are singleton objects that provide + extension functionality. + + When a client creates a registry object, the registry object + will emit a global event for each global currently in the + registry. Globals come and go as a result of device or + monitor hotplugs, reconfiguration or other events, and the + registry will send out global and global_remove events to + keep the client up to date with the changes. To mark the end + of the initial burst of events, the client can use the + wl_display.sync request immediately after calling + wl_display.get_registry. + + A client can bind to a global object by using the bind + request. This creates a client-side handle that lets the object + emit events to the client and lets the client invoke requests on + the object. + + + + + Binds a new, client-created object to the server using the + specified name as the identifier. + + + + + + + + Notify the client of global objects. + + The event notifies the client that a global object with + the given name is now available, and it implements the + given version of the given interface. + + + + + + + + + Notify the client of removed global objects. + + This event notifies the client that the global identified + by name is no longer available. If the client bound to + the global using the bind request, the client should now + destroy that object. + + The object remains valid and requests to the object will be + ignored until the client destroys it, to avoid races between + the global going away and a client sending a request to it. + + + + + + + + Clients can handle the 'done' event to get notified when + the related request is done. + + Note, because wl_callback objects are created from multiple independent + factory interfaces, the wl_callback interface is frozen at version 1. + + + + + Notify the client when the related request is done. + + + + + + + + A compositor. This object is a singleton global. The + compositor is in charge of combining the contents of multiple + surfaces into one displayable output. + + + + + Ask the compositor to create a new surface. + + + + + + + Ask the compositor to create a new region. + + + + + + + + The wl_shm_pool object encapsulates a piece of memory shared + between the compositor and client. Through the wl_shm_pool + object, the client can allocate shared memory wl_buffer objects. + All objects created through the same pool share the same + underlying mapped memory. Reusing the mapped memory avoids the + setup/teardown overhead and is useful when interactively resizing + a surface or for many small buffers. + + + + + Create a wl_buffer object from the pool. + + The buffer is created offset bytes into the pool and has + width and height as specified. The stride argument specifies + the number of bytes from the beginning of one row to the beginning + of the next. The format is the pixel format of the buffer and + must be one of those advertised through the wl_shm.format event. + + A buffer will keep a reference to the pool it was created from + so it is valid to destroy the pool immediately after creating + a buffer from it. + + + + + + + + + + + + Destroy the shared memory pool. + + The mmapped memory will be released when all + buffers that have been created from this pool + are gone. + + + + + + This request will cause the server to remap the backing memory + for the pool from the file descriptor passed when the pool was + created, but using the new size. This request can only be + used to make the pool bigger. + + This request only changes the amount of bytes that are mmapped + by the server and does not touch the file corresponding to the + file descriptor passed at creation time. It is the client's + responsibility to ensure that the file is at least as big as + the new pool size. + + + + + + + + A singleton global object that provides support for shared + memory. + + Clients can create wl_shm_pool objects using the create_pool + request. + + On binding the wl_shm object one or more format events + are emitted to inform clients about the valid pixel formats + that can be used for buffers. + + + + + These errors can be emitted in response to wl_shm requests. + + + + + + + + + This describes the memory layout of an individual pixel. + + All renderers should support argb8888 and xrgb8888 but any other + formats are optional and may not be supported by the particular + renderer in use. + + The drm format codes match the macros defined in drm_fourcc.h, except + argb8888 and xrgb8888. The formats actually supported by the compositor + will be reported by the format event. + + For all wl_shm formats and unless specified in another protocol + extension, pre-multiplied alpha is used for pixel values. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create a new wl_shm_pool object. + + The pool can be used to create shared memory based buffer + objects. The server will mmap size bytes of the passed file + descriptor, to use as backing memory for the pool. + + + + + + + + + Informs the client about a valid pixel format that + can be used for buffers. Known formats include + argb8888 and xrgb8888. + + + + + + + + + Using this request a client can tell the server that it is not going to + use the shm object anymore. + + Objects created via this interface remain unaffected. + + + + + + + A buffer provides the content for a wl_surface. Buffers are + created through factory interfaces such as wl_shm, wp_linux_buffer_params + (from the linux-dmabuf protocol extension) or similar. It has a width and + a height and can be attached to a wl_surface, but the mechanism by which a + client provides and updates the contents is defined by the buffer factory + interface. + + Color channels are assumed to be electrical rather than optical (in other + words, encoded with a transfer function) unless otherwise specified. If + the buffer uses a format that has an alpha channel, the alpha channel is + assumed to be premultiplied into the electrical color channel values + (after transfer function encoding) unless otherwise specified. + + Note, because wl_buffer objects are created from multiple independent + factory interfaces, the wl_buffer interface is frozen at version 1. + + + + + Destroy a buffer. If and how you need to release the backing + storage is defined by the buffer factory interface. + + For possible side-effects to a surface, see wl_surface.attach. + + + + + + Sent when this wl_buffer is no longer used by the compositor. + + For more information on when release events may or may not be sent, + and what consequences it has, please see the description of + wl_surface.attach. + + If a client receives a release event before the frame callback + requested in the same wl_surface.commit that attaches this + wl_buffer to a surface, then the client is immediately free to + reuse the buffer and its backing storage, and does not need a + second buffer for the next surface content update. Typically + this is possible, when the compositor maintains a copy of the + wl_surface contents, e.g. as a GL texture. This is an important + optimization for GL(ES) compositors with wl_shm clients. + + + + + + + A wl_data_offer represents a piece of data offered for transfer + by another client (the source client). It is used by the + copy-and-paste and drag-and-drop mechanisms. The offer + describes the different mime types that the data can be + converted to and provides the mechanism for transferring the + data directly from the source client. + + + + + + + + + + + + Indicate that the client can accept the given mime type, or + NULL for not accepted. + + For objects of version 2 or older, this request is used by the + client to give feedback whether the client can receive the given + mime type, or NULL if none is accepted; the feedback does not + determine whether the drag-and-drop operation succeeds or not. + + For objects of version 3 or newer, this request determines the + final result of the drag-and-drop operation. If the end result + is that no mime types were accepted, the drag-and-drop operation + will be cancelled and the corresponding drag source will receive + wl_data_source.cancelled. Clients may still use this event in + conjunction with wl_data_source.action for feedback. + + + + + + + + To transfer the offered data, the client issues this request + and indicates the mime type it wants to receive. The transfer + happens through the passed file descriptor (typically created + with the pipe system call). The source client writes the data + in the mime type representation requested and then closes the + file descriptor. + + The receiving client reads from the read end of the pipe until + EOF and then closes its end, at which point the transfer is + complete. + + This request may happen multiple times for different mime types, + both before and after wl_data_device.drop. Drag-and-drop destination + clients may preemptively fetch data or examine it more closely to + determine acceptance. + + + + + + + + Destroy the data offer. + + + + + + Sent immediately after creating the wl_data_offer object. One + event per offered mime type. + + + + + + + + + Notifies the compositor that the drag destination successfully + finished the drag-and-drop operation. + + Upon receiving this request, the compositor will emit + wl_data_source.dnd_finished on the drag source client. + + It is a client error to perform other requests than + wl_data_offer.destroy after this one. It is also an error to perform + this request after a NULL mime type has been set in + wl_data_offer.accept or no action was received through + wl_data_offer.action. + + If wl_data_offer.finish request is received for a non drag and drop + operation, the invalid_finish protocol error is raised. + + + + + + Sets the actions that the destination side client supports for + this operation. This request may trigger the emission of + wl_data_source.action and wl_data_offer.action events if the compositor + needs to change the selected action. + + This request can be called multiple times throughout the + drag-and-drop operation, typically in response to wl_data_device.enter + or wl_data_device.motion events. + + This request determines the final result of the drag-and-drop + operation. If the end result is that no action is accepted, + the drag source will receive wl_data_source.cancelled. + + The dnd_actions argument must contain only values expressed in the + wl_data_device_manager.dnd_actions enum, and the preferred_action + argument must only contain one of those values set, otherwise it + will result in a protocol error. + + While managing an "ask" action, the destination drag-and-drop client + may perform further wl_data_offer.receive requests, and is expected + to perform one last wl_data_offer.set_actions request with a preferred + action other than "ask" (and optionally wl_data_offer.accept) before + requesting wl_data_offer.finish, in order to convey the action selected + by the user. If the preferred action is not in the + wl_data_offer.source_actions mask, an error will be raised. + + If the "ask" action is dismissed (e.g. user cancellation), the client + is expected to perform wl_data_offer.destroy right away. + + This request can only be made on drag-and-drop offers, a protocol error + will be raised otherwise. + + + + + + + + This event indicates the actions offered by the data source. It + will be sent immediately after creating the wl_data_offer object, + or anytime the source side changes its offered actions through + wl_data_source.set_actions. + + + + + + + This event indicates the action selected by the compositor after + matching the source/destination side actions. Only one action (or + none) will be offered here. + + This event can be emitted multiple times during the drag-and-drop + operation in response to destination side action changes through + wl_data_offer.set_actions. + + This event will no longer be emitted after wl_data_device.drop + happened on the drag-and-drop destination, the client must + honor the last action received, or the last preferred one set + through wl_data_offer.set_actions when handling an "ask" action. + + Compositors may also change the selected action on the fly, mainly + in response to keyboard modifier changes during the drag-and-drop + operation. + + The most recent action received is always the valid one. Prior to + receiving wl_data_device.drop, the chosen action may change (e.g. + due to keyboard modifiers being pressed). At the time of receiving + wl_data_device.drop the drag-and-drop destination must honor the + last action received. + + Action changes may still happen after wl_data_device.drop, + especially on "ask" actions, where the drag-and-drop destination + may choose another action afterwards. Action changes happening + at this stage are always the result of inter-client negotiation, the + compositor shall no longer be able to induce a different action. + + Upon "ask" actions, it is expected that the drag-and-drop destination + may potentially choose a different action and/or mime type, + based on wl_data_offer.source_actions and finally chosen by the + user (e.g. popping up a menu with the available options). The + final wl_data_offer.set_actions and wl_data_offer.accept requests + must happen before the call to wl_data_offer.finish. + + + + + + + + The wl_data_source object is the source side of a wl_data_offer. + It is created by the source client in a data transfer and + provides a way to describe the offered data and a way to respond + to requests to transfer the data. + + + + + + + + + + This request adds a mime type to the set of mime types + advertised to targets. Can be called several times to offer + multiple types. + + + + + + + Destroy the data source. + + + + + + Sent when a target accepts pointer_focus or motion events. If + a target does not accept any of the offered types, type is NULL. + + Used for feedback during drag-and-drop. + + + + + + + Request for data from the client. Send the data as the + specified mime type over the passed file descriptor, then + close it. + + + + + + + + This data source is no longer valid. There are several reasons why + this could happen: + + - The data source has been replaced by another data source. + - The drag-and-drop operation was performed, but the drop destination + did not accept any of the mime types offered through + wl_data_source.target. + - The drag-and-drop operation was performed, but the drop destination + did not select any of the actions present in the mask offered through + wl_data_source.action. + - The drag-and-drop operation was performed but didn't happen over a + surface. + - The compositor cancelled the drag-and-drop operation (e.g. compositor + dependent timeouts to avoid stale drag-and-drop transfers). + + The client should clean up and destroy this data source. + + For objects of version 2 or older, wl_data_source.cancelled will + only be emitted if the data source was replaced by another data + source. + + + + + + + + Sets the actions that the source side client supports for this + operation. This request may trigger wl_data_source.action and + wl_data_offer.action events if the compositor needs to change the + selected action. + + The dnd_actions argument must contain only values expressed in the + wl_data_device_manager.dnd_actions enum, otherwise it will result + in a protocol error. + + This request must be made once only, and can only be made on sources + used in drag-and-drop, so it must be performed before + wl_data_device.start_drag. Attempting to use the source other than + for drag-and-drop will raise a protocol error. + + + + + + + The user performed the drop action. This event does not indicate + acceptance, wl_data_source.cancelled may still be emitted afterwards + if the drop destination does not accept any mime type. + + However, this event might however not be received if the compositor + cancelled the drag-and-drop operation before this event could happen. + + Note that the data_source may still be used in the future and should + not be destroyed here. + + + + + + The drop destination finished interoperating with this data + source, so the client is now free to destroy this data source and + free all associated data. + + If the action used to perform the operation was "move", the + source can now delete the transferred data. + + + + + + This event indicates the action selected by the compositor after + matching the source/destination side actions. Only one action (or + none) will be offered here. + + This event can be emitted multiple times during the drag-and-drop + operation, mainly in response to destination side changes through + wl_data_offer.set_actions, and as the data device enters/leaves + surfaces. + + It is only possible to receive this event after + wl_data_source.dnd_drop_performed if the drag-and-drop operation + ended in an "ask" action, in which case the final wl_data_source.action + event will happen immediately before wl_data_source.dnd_finished. + + Compositors may also change the selected action on the fly, mainly + in response to keyboard modifier changes during the drag-and-drop + operation. + + The most recent action received is always the valid one. The chosen + action may change alongside negotiation (e.g. an "ask" action can turn + into a "move" operation), so the effects of the final action must + always be applied in wl_data_offer.dnd_finished. + + Clients can trigger cursor surface changes from this point, so + they reflect the current action. + + + + + + + + There is one wl_data_device per seat which can be obtained + from the global wl_data_device_manager singleton. + + A wl_data_device provides access to inter-client data transfer + mechanisms such as copy-and-paste and drag-and-drop. + + + + + + + + + + This request asks the compositor to start a drag-and-drop + operation on behalf of the client. + + The source argument is the data source that provides the data + for the eventual data transfer. If source is NULL, enter, leave + and motion events are sent only to the client that initiated the + drag and the client is expected to handle the data passing + internally. If source is destroyed, the drag-and-drop session will be + cancelled. + + The origin surface is the surface where the drag originates and + the client must have an active implicit grab that matches the + serial. + + The icon surface is an optional (can be NULL) surface that + provides an icon to be moved around with the cursor. Initially, + the top-left corner of the icon surface is placed at the cursor + hotspot, but subsequent wl_surface.offset requests can move the + relative position. Attach requests must be confirmed with + wl_surface.commit as usual. The icon surface is given the role of + a drag-and-drop icon. If the icon surface already has another role, + it raises a protocol error. + + The input region is ignored for wl_surfaces with the role of a + drag-and-drop icon. + + The given source may not be used in any further set_selection or + start_drag requests. Attempting to reuse a previously-used source + may send a used_source error. + + + + + + + + + + This request asks the compositor to set the selection + to the data from the source on behalf of the client. + + To unset the selection, set the source to NULL. + + The given source may not be used in any further set_selection or + start_drag requests. Attempting to reuse a previously-used source + may send a used_source error. + + + + + + + + The data_offer event introduces a new wl_data_offer object, + which will subsequently be used in either the + data_device.enter event (for drag-and-drop) or the + data_device.selection event (for selections). Immediately + following the data_device.data_offer event, the new data_offer + object will send out data_offer.offer events to describe the + mime types it offers. + + + + + + + This event is sent when an active drag-and-drop pointer enters + a surface owned by the client. The position of the pointer at + enter time is provided by the x and y arguments, in surface-local + coordinates. + + + + + + + + + + + This event is sent when the drag-and-drop pointer leaves the + surface and the session ends. The client must destroy the + wl_data_offer introduced at enter time at this point. + + + + + + This event is sent when the drag-and-drop pointer moves within + the currently focused surface. The new position of the pointer + is provided by the x and y arguments, in surface-local + coordinates. + + + + + + + + + The event is sent when a drag-and-drop operation is ended + because the implicit grab is removed. + + The drag-and-drop destination is expected to honor the last action + received through wl_data_offer.action, if the resulting action is + "copy" or "move", the destination can still perform + wl_data_offer.receive requests, and is expected to end all + transfers with a wl_data_offer.finish request. + + If the resulting action is "ask", the action will not be considered + final. The drag-and-drop destination is expected to perform one last + wl_data_offer.set_actions request, or wl_data_offer.destroy in order + to cancel the operation. + + + + + + The selection event is sent out to notify the client of a new + wl_data_offer for the selection for this device. The + data_device.data_offer and the data_offer.offer events are + sent out immediately before this event to introduce the data + offer object. The selection event is sent to a client + immediately before receiving keyboard focus and when a new + selection is set while the client has keyboard focus. The + data_offer is valid until a new data_offer or NULL is received + or until the client loses keyboard focus. Switching surface with + keyboard focus within the same client doesn't mean a new selection + will be sent. The client must destroy the previous selection + data_offer, if any, upon receiving this event. + + + + + + + + + This request destroys the data device. + + + + + + + The wl_data_device_manager is a singleton global object that + provides access to inter-client data transfer mechanisms such as + copy-and-paste and drag-and-drop. These mechanisms are tied to + a wl_seat and this interface lets a client get a wl_data_device + corresponding to a wl_seat. + + Depending on the version bound, the objects created from the bound + wl_data_device_manager object will have different requirements for + functioning properly. See wl_data_source.set_actions, + wl_data_offer.accept and wl_data_offer.finish for details. + + + + + Create a new data source. + + + + + + + Create a new data device for a given seat. + + + + + + + + + + This is a bitmask of the available/preferred actions in a + drag-and-drop operation. + + In the compositor, the selected action is a result of matching the + actions offered by the source and destination sides. "action" events + with a "none" action will be sent to both source and destination if + there is no match. All further checks will effectively happen on + (source actions ∩ destination actions). + + In addition, compositors may also pick different actions in + reaction to key modifiers being pressed. One common design that + is used in major toolkits (and the behavior recommended for + compositors) is: + + - If no modifiers are pressed, the first match (in bit order) + will be used. + - Pressing Shift selects "move", if enabled in the mask. + - Pressing Control selects "copy", if enabled in the mask. + + Behavior beyond that is considered implementation-dependent. + Compositors may for example bind other modifiers (like Alt/Meta) + or drags initiated with other buttons than BTN_LEFT to specific + actions (e.g. "ask"). + + + + + + + + + + + This interface is implemented by servers that provide + desktop-style user interfaces. + + It allows clients to associate a wl_shell_surface with + a basic surface. + + Note! This protocol is deprecated and not intended for production use. + For desktop-style user interfaces, use xdg_shell. Compositors and clients + should not implement this interface. + + + + + + + + + Create a shell surface for an existing surface. This gives + the wl_surface the role of a shell surface. If the wl_surface + already has another role, it raises a protocol error. + + Only one shell surface can be associated with a given surface. + + + + + + + + + An interface that may be implemented by a wl_surface, for + implementations that provide a desktop-style user interface. + + It provides requests to treat surfaces like toplevel, fullscreen + or popup windows, move, resize or maximize them, associate + metadata like title and class, etc. + + On the server side the object is automatically destroyed when + the related wl_surface is destroyed. On the client side, + wl_shell_surface_destroy() must be called before destroying + the wl_surface object. + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. + + + + + + + Start a pointer-driven move of the surface. + + This request must be used in response to a button press event. + The server may ignore move requests depending on the state of + the surface (e.g. fullscreen or maximized). + + + + + + + + These values are used to indicate which edge of a surface + is being dragged in a resize operation. The server may + use this information to adapt its behavior, e.g. choose + an appropriate cursor image. + + + + + + + + + + + + + + + Start a pointer-driven resizing of the surface. + + This request must be used in response to a button press event. + The server may ignore resize requests depending on the state of + the surface (e.g. fullscreen or maximized). + + + + + + + + + Map the surface as a toplevel surface. + + A toplevel surface is not fullscreen, maximized or transient. + + + + + + These flags specify details of the expected behaviour + of transient surfaces. Used in the set_transient request. + + + + + + + Map the surface relative to an existing surface. + + The x and y arguments specify the location of the upper left + corner of the surface relative to the upper left corner of the + parent surface, in surface-local coordinates. + + The flags argument controls details of the transient behaviour. + + + + + + + + + + Hints to indicate to the compositor how to deal with a conflict + between the dimensions of the surface and the dimensions of the + output. The compositor is free to ignore this parameter. + + + + + + + + + + Map the surface as a fullscreen surface. + + If an output parameter is given then the surface will be made + fullscreen on that output. If the client does not specify the + output then the compositor will apply its policy - usually + choosing the output on which the surface has the biggest surface + area. + + The client may specify a method to resolve a size conflict + between the output size and the surface size - this is provided + through the method parameter. + + The framerate parameter is used only when the method is set + to "driver", to indicate the preferred framerate. A value of 0 + indicates that the client does not care about framerate. The + framerate is specified in mHz, that is framerate of 60000 is 60Hz. + + A method of "scale" or "driver" implies a scaling operation of + the surface, either via a direct scaling operation or a change of + the output mode. This will override any kind of output scaling, so + that mapping a surface with a buffer size equal to the mode can + fill the screen independent of buffer_scale. + + A method of "fill" means we don't scale up the buffer, however + any output scale is applied. This means that you may run into + an edge case where the application maps a buffer with the same + size of the output mode but buffer_scale 1 (thus making a + surface larger than the output). In this case it is allowed to + downscale the results to fit the screen. + + The compositor must reply to this request with a configure event + with the dimensions for the output on which the surface will + be made fullscreen. + + + + + + + + + Map the surface as a popup. + + A popup surface is a transient surface with an added pointer + grab. + + An existing implicit grab will be changed to owner-events mode, + and the popup grab will continue after the implicit grab ends + (i.e. releasing the mouse button does not cause the popup to + be unmapped). + + The popup grab continues until the window is destroyed or a + mouse button is pressed in any other client's window. A click + in any of the client's surfaces is reported as normal, however, + clicks in other clients' surfaces will be discarded and trigger + the callback. + + The x and y arguments specify the location of the upper left + corner of the surface relative to the upper left corner of the + parent surface, in surface-local coordinates. + + + + + + + + + + + + Map the surface as a maximized surface. + + If an output parameter is given then the surface will be + maximized on that output. If the client does not specify the + output then the compositor will apply its policy - usually + choosing the output on which the surface has the biggest surface + area. + + The compositor will reply with a configure event telling + the expected new surface size. The operation is completed + on the next buffer attach to this surface. + + A maximized surface typically fills the entire output it is + bound to, except for desktop elements such as panels. This is + the main difference between a maximized shell surface and a + fullscreen shell surface. + + The details depend on the compositor implementation. + + + + + + + Set a short title for the surface. + + This string may be used to identify the surface in a task bar, + window list, or other user interface elements provided by the + compositor. + + The string must be encoded in UTF-8. + + + + + + + Set a class for the surface. + + The surface class identifies the general class of applications + to which the surface belongs. A common convention is to use the + file name (or the full path if it is a non-standard location) of + the application's .desktop file as the class. + + + + + + + Ping a client to check if it is receiving events and sending + requests. A client is expected to reply with a pong request. + + + + + + + The configure event asks the client to resize its surface. + + The size is a hint, in the sense that the client is free to + ignore it if it doesn't resize, pick a smaller size (to + satisfy aspect ratio or resize in steps of NxM pixels). + + The edges parameter provides a hint about how the surface + was resized. The client may use this information to decide + how to adjust its content to the new size (e.g. a scrolling + area might adjust its content position to leave the viewable + content unmoved). + + The client is free to dismiss all but the last configure + event it received. + + The width and height arguments specify the size of the window + in surface-local coordinates. + + + + + + + + + The popup_done event is sent out when a popup grab is broken, + that is, when the user clicks a surface that doesn't belong + to the client owning the popup surface. + + + + + + + A surface is a rectangular area that may be displayed on zero + or more outputs, and shown any number of times at the compositor's + discretion. They can present wl_buffers, receive user input, and + define a local coordinate system. + + The size of a surface (and relative positions on it) is described + in surface-local coordinates, which may differ from the buffer + coordinates of the pixel content, in case a buffer_transform + or a buffer_scale is used. + + A surface without a "role" is fairly useless: a compositor does + not know where, when or how to present it. The role is the + purpose of a wl_surface. Examples of roles are a cursor for a + pointer (as set by wl_pointer.set_cursor), a drag icon + (wl_data_device.start_drag), a sub-surface + (wl_subcompositor.get_subsurface), and a window as defined by a + shell protocol (e.g. wl_shell.get_shell_surface). + + A surface can have only one role at a time. Initially a + wl_surface does not have a role. Once a wl_surface is given a + role, it is set permanently for the whole lifetime of the + wl_surface object. Giving the current role again is allowed, + unless explicitly forbidden by the relevant interface + specification. + + Surface roles are given by requests in other interfaces such as + wl_pointer.set_cursor. The request should explicitly mention + that this request gives a role to a wl_surface. Often, this + request also creates a new protocol object that represents the + role and adds additional functionality to wl_surface. When a + client wants to destroy a wl_surface, they must destroy this role + object before the wl_surface, otherwise a defunct_role_object error is + sent. + + Destroying the role object does not remove the role from the + wl_surface, but it may stop the wl_surface from "playing the role". + For instance, if a wl_subsurface object is destroyed, the wl_surface + it was created for will be unmapped and forget its position and + z-order. It is allowed to create a wl_subsurface for the same + wl_surface again, but it is not allowed to use the wl_surface as + a cursor (cursor is a different role than sub-surface, and role + switching is not allowed). + + + + + These errors can be emitted in response to wl_surface requests. + + + + + + + + + + + Deletes the surface and invalidates its object ID. + + + + + + Set a buffer as the content of this surface. + + The new size of the surface is calculated based on the buffer + size transformed by the inverse buffer_transform and the + inverse buffer_scale. This means that at commit time the supplied + buffer size must be an integer multiple of the buffer_scale. If + that's not the case, an invalid_size error is sent. + + The x and y arguments specify the location of the new pending + buffer's upper left corner, relative to the current buffer's upper + left corner, in surface-local coordinates. In other words, the + x and y, combined with the new surface size define in which + directions the surface's size changes. Setting anything other than 0 + as x and y arguments is discouraged, and should instead be replaced + with using the separate wl_surface.offset request. + + When the bound wl_surface version is 5 or higher, passing any + non-zero x or y is a protocol violation, and will result in an + 'invalid_offset' error being raised. The x and y arguments are ignored + and do not change the pending state. To achieve equivalent semantics, + use wl_surface.offset. + + Surface contents are double-buffered state, see wl_surface.commit. + + The initial surface contents are void; there is no content. + wl_surface.attach assigns the given wl_buffer as the pending + wl_buffer. wl_surface.commit makes the pending wl_buffer the new + surface contents, and the size of the surface becomes the size + calculated from the wl_buffer, as described above. After commit, + there is no pending buffer until the next attach. + + Committing a pending wl_buffer allows the compositor to read the + pixels in the wl_buffer. The compositor may access the pixels at + any time after the wl_surface.commit request. When the compositor + will not access the pixels anymore, it will send the + wl_buffer.release event. Only after receiving wl_buffer.release, + the client may reuse the wl_buffer. A wl_buffer that has been + attached and then replaced by another attach instead of committed + will not receive a release event, and is not used by the + compositor. + + If a pending wl_buffer has been committed to more than one wl_surface, + the delivery of wl_buffer.release events becomes undefined. A well + behaved client should not rely on wl_buffer.release events in this + case. Alternatively, a client could create multiple wl_buffer objects + from the same backing storage or use a protocol extension providing + per-commit release notifications. + + Destroying the wl_buffer after wl_buffer.release does not change + the surface contents. Destroying the wl_buffer before wl_buffer.release + is allowed as long as the underlying buffer storage isn't re-used (this + can happen e.g. on client process termination). However, if the client + destroys the wl_buffer before receiving the wl_buffer.release event and + mutates the underlying buffer storage, the surface contents become + undefined immediately. + + If wl_surface.attach is sent with a NULL wl_buffer, the + following wl_surface.commit will remove the surface content. + + If a pending wl_buffer has been destroyed, the result is not specified. + Many compositors are known to remove the surface content on the following + wl_surface.commit, but this behaviour is not universal. Clients seeking to + maximise compatibility should not destroy pending buffers and should + ensure that they explicitly remove content from surfaces, even after + destroying buffers. + + + + + + + + + This request is used to describe the regions where the pending + buffer is different from the current surface contents, and where + the surface therefore needs to be repainted. The compositor + ignores the parts of the damage that fall outside of the surface. + + Damage is double-buffered state, see wl_surface.commit. + + The damage rectangle is specified in surface-local coordinates, + where x and y specify the upper left corner of the damage rectangle. + + The initial value for pending damage is empty: no damage. + wl_surface.damage adds pending damage: the new pending damage + is the union of old pending damage and the given rectangle. + + wl_surface.commit assigns pending damage as the current damage, + and clears pending damage. The server will clear the current + damage as it repaints the surface. + + Note! New clients should not use this request. Instead damage can be + posted with wl_surface.damage_buffer which uses buffer coordinates + instead of surface coordinates. + + + + + + + + + + Request a notification when it is a good time to start drawing a new + frame, by creating a frame callback. This is useful for throttling + redrawing operations, and driving animations. + + When a client is animating on a wl_surface, it can use the 'frame' + request to get notified when it is a good time to draw and commit the + next frame of animation. If the client commits an update earlier than + that, it is likely that some updates will not make it to the display, + and the client is wasting resources by drawing too often. + + The frame request will take effect on the next wl_surface.commit. + The notification will only be posted for one frame unless + requested again. For a wl_surface, the notifications are posted in + the order the frame requests were committed. + + The server must send the notifications so that a client + will not send excessive updates, while still allowing + the highest possible update rate for clients that wait for the reply + before drawing again. The server should give some time for the client + to draw and commit after sending the frame callback events to let it + hit the next output refresh. + + A server should avoid signaling the frame callbacks if the + surface is not visible in any way, e.g. the surface is off-screen, + or completely obscured by other opaque surfaces. + + The object returned by this request will be destroyed by the + compositor after the callback is fired and as such the client must not + attempt to use it after that point. + + The callback_data passed in the callback is the current time, in + milliseconds, with an undefined base. + + + + + + + This request sets the region of the surface that contains + opaque content. + + The opaque region is an optimization hint for the compositor + that lets it optimize the redrawing of content behind opaque + regions. Setting an opaque region is not required for correct + behaviour, but marking transparent content as opaque will result + in repaint artifacts. + + The opaque region is specified in surface-local coordinates. + + The compositor ignores the parts of the opaque region that fall + outside of the surface. + + Opaque region is double-buffered state, see wl_surface.commit. + + wl_surface.set_opaque_region changes the pending opaque region. + wl_surface.commit copies the pending region to the current region. + Otherwise, the pending and current regions are never changed. + + The initial value for an opaque region is empty. Setting the pending + opaque region has copy semantics, and the wl_region object can be + destroyed immediately. A NULL wl_region causes the pending opaque + region to be set to empty. + + + + + + + This request sets the region of the surface that can receive + pointer and touch events. + + Input events happening outside of this region will try the next + surface in the server surface stack. The compositor ignores the + parts of the input region that fall outside of the surface. + + The input region is specified in surface-local coordinates. + + Input region is double-buffered state, see wl_surface.commit. + + wl_surface.set_input_region changes the pending input region. + wl_surface.commit copies the pending region to the current region. + Otherwise the pending and current regions are never changed, + except cursor and icon surfaces are special cases, see + wl_pointer.set_cursor and wl_data_device.start_drag. + + The initial value for an input region is infinite. That means the + whole surface will accept input. Setting the pending input region + has copy semantics, and the wl_region object can be destroyed + immediately. A NULL wl_region causes the input region to be set + to infinite. + + + + + + + Surface state (input, opaque, and damage regions, attached buffers, + etc.) is double-buffered. Protocol requests modify the pending state, + as opposed to the active state in use by the compositor. + + A commit request atomically creates a content update from the pending + state, even if the pending state has not been touched. The content + update is placed in a queue until it becomes active. After commit, the + new pending state is as documented for each related request. + + When the content update is applied, the wl_buffer is applied before all + other state. This means that all coordinates in double-buffered state + are relative to the newly attached wl_buffers, except for + wl_surface.attach itself. If there is no newly attached wl_buffer, the + coordinates are relative to the previous content update. + + All requests that need a commit to become effective are documented + to affect double-buffered state. + + Other interfaces may add further double-buffered surface state. + + + + + + This is emitted whenever a surface's creation, movement, or resizing + results in some part of it being within the scanout region of an + output. + + Note that a surface may be overlapping with zero or more outputs. + + + + + + + This is emitted whenever a surface's creation, movement, or resizing + results in it no longer having any part of it within the scanout region + of an output. + + Clients should not use the number of outputs the surface is on for frame + throttling purposes. The surface might be hidden even if no leave event + has been sent, and the compositor might expect new surface content + updates even if no enter event has been sent. The frame event should be + used instead. + + + + + + + + + This request sets the transformation that the client has already applied + to the content of the buffer. The accepted values for the transform + parameter are the values for wl_output.transform. + + The compositor applies the inverse of this transformation whenever it + uses the buffer contents. + + Buffer transform is double-buffered state, see wl_surface.commit. + + A newly created surface has its buffer transformation set to normal. + + wl_surface.set_buffer_transform changes the pending buffer + transformation. wl_surface.commit copies the pending buffer + transformation to the current one. Otherwise, the pending and current + values are never changed. + + The purpose of this request is to allow clients to render content + according to the output transform, thus permitting the compositor to + use certain optimizations even if the display is rotated. Using + hardware overlays and scanning out a client buffer for fullscreen + surfaces are examples of such optimizations. Those optimizations are + highly dependent on the compositor implementation, so the use of this + request should be considered on a case-by-case basis. + + Note that if the transform value includes 90 or 270 degree rotation, + the width of the buffer will become the surface height and the height + of the buffer will become the surface width. + + If transform is not one of the values from the + wl_output.transform enum the invalid_transform protocol error + is raised. + + + + + + + + + This request sets an optional scaling factor on how the compositor + interprets the contents of the buffer attached to the window. + + Buffer scale is double-buffered state, see wl_surface.commit. + + A newly created surface has its buffer scale set to 1. + + wl_surface.set_buffer_scale changes the pending buffer scale. + wl_surface.commit copies the pending buffer scale to the current one. + Otherwise, the pending and current values are never changed. + + The purpose of this request is to allow clients to supply higher + resolution buffer data for use on high resolution outputs. It is + intended that you pick the same buffer scale as the scale of the + output that the surface is displayed on. This means the compositor + can avoid scaling when rendering the surface on that output. + + Note that if the scale is larger than 1, then you have to attach + a buffer that is larger (by a factor of scale in each dimension) + than the desired surface size. + + If scale is not greater than 0 the invalid_scale protocol error is + raised. + + + + + + + + This request is used to describe the regions where the pending + buffer is different from the current surface contents, and where + the surface therefore needs to be repainted. The compositor + ignores the parts of the damage that fall outside of the surface. + + Damage is double-buffered state, see wl_surface.commit. + + The damage rectangle is specified in buffer coordinates, + where x and y specify the upper left corner of the damage rectangle. + + The initial value for pending damage is empty: no damage. + wl_surface.damage_buffer adds pending damage: the new pending + damage is the union of old pending damage and the given rectangle. + + wl_surface.commit assigns pending damage as the current damage, + and clears pending damage. The server will clear the current + damage as it repaints the surface. + + This request differs from wl_surface.damage in only one way - it + takes damage in buffer coordinates instead of surface-local + coordinates. While this generally is more intuitive than surface + coordinates, it is especially desirable when using wp_viewport + or when a drawing library (like EGL) is unaware of buffer scale + and buffer transform. + + Note: Because buffer transformation changes and damage requests may + be interleaved in the protocol stream, it is impossible to determine + the actual mapping between surface and buffer damage until + wl_surface.commit time. Therefore, compositors wishing to take both + kinds of damage into account will have to accumulate damage from the + two requests separately and only transform from one to the other + after receiving the wl_surface.commit. + + + + + + + + + + + + The x and y arguments specify the location of the new pending + buffer's upper left corner, relative to the current buffer's upper + left corner, in surface-local coordinates. In other words, the + x and y, combined with the new surface size define in which + directions the surface's size changes. + + The exact semantics of wl_surface.offset are role-specific. Refer to + the documentation of specific roles for more information. + + Surface location offset is double-buffered state, see + wl_surface.commit. + + This request is semantically equivalent to and the replaces the x and y + arguments in the wl_surface.attach request in wl_surface versions prior + to 5. See wl_surface.attach for details. + + + + + + + + + + This event indicates the preferred buffer scale for this surface. It is + sent whenever the compositor's preference changes. + + Before receiving this event the preferred buffer scale for this surface + is 1. + + It is intended that scaling aware clients use this event to scale their + content and use wl_surface.set_buffer_scale to indicate the scale they + have rendered with. This allows clients to supply a higher detail + buffer. + + The compositor shall emit a scale value greater than 0. + + + + + + + This event indicates the preferred buffer transform for this surface. + It is sent whenever the compositor's preference changes. + + Before receiving this event the preferred buffer transform for this + surface is normal. + + Applying this transformation to the surface buffer contents and using + wl_surface.set_buffer_transform might allow the compositor to use the + surface buffer more efficiently. + + + + + + + + A seat is a group of keyboards, pointer and touch devices. This + object is published as a global during start up, or when such a + device is hot plugged. A seat typically has a pointer and + maintains a keyboard focus and a pointer focus. + + + + + This is a bitmask of capabilities this seat has; if a member is + set, then it is present on the seat. + + + + + + + + + These errors can be emitted in response to wl_seat requests. + + + + + + + This is sent on binding to the seat global or whenever a seat gains + or loses the pointer, keyboard or touch capabilities. + The argument is a capability enum containing the complete set of + capabilities this seat has. + + When the pointer capability is added, a client may create a + wl_pointer object using the wl_seat.get_pointer request. This object + will receive pointer events until the capability is removed in the + future. + + When the pointer capability is removed, a client should destroy the + wl_pointer objects associated with the seat where the capability was + removed, using the wl_pointer.release request. No further pointer + events will be received on these objects. + + In some compositors, if a seat regains the pointer capability and a + client has a previously obtained wl_pointer object of version 4 or + less, that object may start sending pointer events again. This + behavior is considered a misinterpretation of the intended behavior + and must not be relied upon by the client. wl_pointer objects of + version 5 or later must not send events if created before the most + recent event notifying the client of an added pointer capability. + + The above behavior also applies to wl_keyboard and wl_touch with the + keyboard and touch capabilities, respectively. + + + + + + + The ID provided will be initialized to the wl_pointer interface + for this seat. + + This request only takes effect if the seat has the pointer + capability, or has had the pointer capability in the past. + It is a protocol violation to issue this request on a seat that has + never had the pointer capability. The missing_capability error will + be sent in this case. + + + + + + + The ID provided will be initialized to the wl_keyboard interface + for this seat. + + This request only takes effect if the seat has the keyboard + capability, or has had the keyboard capability in the past. + It is a protocol violation to issue this request on a seat that has + never had the keyboard capability. The missing_capability error will + be sent in this case. + + + + + + + The ID provided will be initialized to the wl_touch interface + for this seat. + + This request only takes effect if the seat has the touch + capability, or has had the touch capability in the past. + It is a protocol violation to issue this request on a seat that has + never had the touch capability. The missing_capability error will + be sent in this case. + + + + + + + + + In a multi-seat configuration the seat name can be used by clients to + help identify which physical devices the seat represents. + + The seat name is a UTF-8 string with no convention defined for its + contents. Each name is unique among all wl_seat globals. The name is + only guaranteed to be unique for the current compositor instance. + + The same seat names are used for all clients. Thus, the name can be + shared across processes to refer to a specific wl_seat global. + + The name event is sent after binding to the seat global, and should be sent + before announcing capabilities. This event only sent once per seat object, + and the name does not change over the lifetime of the wl_seat global. + + Compositors may re-use the same seat name if the wl_seat global is + destroyed and re-created later. + + + + + + + + + Using this request a client can tell the server that it is not going to + use the seat object anymore. + + + + + + + + The wl_pointer interface represents one or more input devices, + such as mice, which control the pointer location and pointer_focus + of a seat. + + The wl_pointer interface generates motion, enter and leave + events for the surfaces that the pointer is located over, + and button and axis events for button presses, button releases + and scrolling. + + + + + + + + + Set the pointer surface, i.e., the surface that contains the + pointer image (cursor). This request gives the surface the role + of a cursor. If the surface already has another role, it raises + a protocol error. + + The cursor actually changes only if the pointer + focus for this device is one of the requesting client's surfaces + or the surface parameter is the current pointer surface. If + there was a previous surface set with this request it is + replaced. If surface is NULL, the pointer image is hidden. + + The parameters hotspot_x and hotspot_y define the position of + the pointer surface relative to the pointer location. Its + top-left corner is always at (x, y) - (hotspot_x, hotspot_y), + where (x, y) are the coordinates of the pointer location, in + surface-local coordinates. + + On wl_surface.offset requests to the pointer surface, hotspot_x + and hotspot_y are decremented by the x and y parameters + passed to the request. The offset must be applied by + wl_surface.commit as usual. + + The hotspot can also be updated by passing the currently set + pointer surface to this request with new values for hotspot_x + and hotspot_y. + + The input region is ignored for wl_surfaces with the role of + a cursor. When the use as a cursor ends, the wl_surface is + unmapped. + + The serial parameter must match the latest wl_pointer.enter + serial number sent to the client. Otherwise the request will be + ignored. + + + + + + + + + + Notification that this seat's pointer is focused on a certain + surface. + + When a seat's focus enters a surface, the pointer image + is undefined and a client should respond to this event by setting + an appropriate pointer image with the set_cursor request. + + + + + + + + + + Notification that this seat's pointer is no longer focused on + a certain surface. + + The leave notification is sent before the enter notification + for the new focus. + + + + + + + + Notification of pointer location change. The arguments + surface_x and surface_y are the location relative to the + focused surface. + + + + + + + + + Describes the physical state of a button that produced the button + event. + + + + + + + + Mouse button click and release notifications. + + The location of the click is given by the last motion or + enter event. + The time argument is a timestamp with millisecond + granularity, with an undefined base. + + The button is a button code as defined in the Linux kernel's + linux/input-event-codes.h header file, e.g. BTN_LEFT. + + Any 16-bit button code value is reserved for future additions to the + kernel's event code list. All other button codes above 0xFFFF are + currently undefined but may be used in future versions of this + protocol. + + + + + + + + + + Describes the axis types of scroll events. + + + + + + + + Scroll and other axis notifications. + + For scroll events (vertical and horizontal scroll axes), the + value parameter is the length of a vector along the specified + axis in a coordinate space identical to those of motion events, + representing a relative movement along the specified axis. + + For devices that support movements non-parallel to axes multiple + axis events will be emitted. + + When applicable, for example for touch pads, the server can + choose to emit scroll events where the motion vector is + equivalent to a motion event vector. + + When applicable, a client can transform its content relative to the + scroll distance. + + + + + + + + + + + Using this request a client can tell the server that it is not going to + use the pointer object anymore. + + This request destroys the pointer proxy object, so clients must not call + wl_pointer_destroy() after using this request. + + + + + + + + Indicates the end of a set of events that logically belong together. + A client is expected to accumulate the data in all events within the + frame before proceeding. + + All wl_pointer events before a wl_pointer.frame event belong + logically together. For example, in a diagonal scroll motion the + compositor will send an optional wl_pointer.axis_source event, two + wl_pointer.axis events (horizontal and vertical) and finally a + wl_pointer.frame event. The client may use this information to + calculate a diagonal vector for scrolling. + + When multiple wl_pointer.axis events occur within the same frame, + the motion vector is the combined motion of all events. + When a wl_pointer.axis and a wl_pointer.axis_stop event occur within + the same frame, this indicates that axis movement in one axis has + stopped but continues in the other axis. + When multiple wl_pointer.axis_stop events occur within the same + frame, this indicates that these axes stopped in the same instance. + + A wl_pointer.frame event is sent for every logical event group, + even if the group only contains a single wl_pointer event. + Specifically, a client may get a sequence: motion, frame, button, + frame, axis, frame, axis_stop, frame. + + The wl_pointer.enter and wl_pointer.leave events are logical events + generated by the compositor and not the hardware. These events are + also grouped by a wl_pointer.frame. When a pointer moves from one + surface to another, a compositor should group the + wl_pointer.leave event within the same wl_pointer.frame. + However, a client must not rely on wl_pointer.leave and + wl_pointer.enter being in the same wl_pointer.frame. + Compositor-specific policies may require the wl_pointer.leave and + wl_pointer.enter event being split across multiple wl_pointer.frame + groups. + + + + + + Describes the source types for axis events. This indicates to the + client how an axis event was physically generated; a client may + adjust the user interface accordingly. For example, scroll events + from a "finger" source may be in a smooth coordinate space with + kinetic scrolling whereas a "wheel" source may be in discrete steps + of a number of lines. + + The "continuous" axis source is a device generating events in a + continuous coordinate space, but using something other than a + finger. One example for this source is button-based scrolling where + the vertical motion of a device is converted to scroll events while + a button is held down. + + The "wheel tilt" axis source indicates that the actual device is a + wheel but the scroll event is not caused by a rotation but a + (usually sideways) tilt of the wheel. + + + + + + + + + + Source information for scroll and other axes. + + This event does not occur on its own. It is sent before a + wl_pointer.frame event and carries the source information for + all events within that frame. + + The source specifies how this event was generated. If the source is + wl_pointer.axis_source.finger, a wl_pointer.axis_stop event will be + sent when the user lifts the finger off the device. + + If the source is wl_pointer.axis_source.wheel, + wl_pointer.axis_source.wheel_tilt or + wl_pointer.axis_source.continuous, a wl_pointer.axis_stop event may + or may not be sent. Whether a compositor sends an axis_stop event + for these sources is hardware-specific and implementation-dependent; + clients must not rely on receiving an axis_stop event for these + scroll sources and should treat scroll sequences from these scroll + sources as unterminated by default. + + This event is optional. If the source is unknown for a particular + axis event sequence, no event is sent. + Only one wl_pointer.axis_source event is permitted per frame. + + The order of wl_pointer.axis_discrete and wl_pointer.axis_source is + not guaranteed. + + + + + + + Stop notification for scroll and other axes. + + For some wl_pointer.axis_source types, a wl_pointer.axis_stop event + is sent to notify a client that the axis sequence has terminated. + This enables the client to implement kinetic scrolling. + See the wl_pointer.axis_source documentation for information on when + this event may be generated. + + Any wl_pointer.axis events with the same axis_source after this + event should be considered as the start of a new axis motion. + + The timestamp is to be interpreted identical to the timestamp in the + wl_pointer.axis event. The timestamp value may be the same as a + preceding wl_pointer.axis event. + + + + + + + + Discrete step information for scroll and other axes. + + This event carries the axis value of the wl_pointer.axis event in + discrete steps (e.g. mouse wheel clicks). + + This event is deprecated with wl_pointer version 8 - this event is not + sent to clients supporting version 8 or later. + + This event does not occur on its own, it is coupled with a + wl_pointer.axis event that represents this axis value on a + continuous scale. The protocol guarantees that each axis_discrete + event is always followed by exactly one axis event with the same + axis number within the same wl_pointer.frame. Note that the protocol + allows for other events to occur between the axis_discrete and + its coupled axis event, including other axis_discrete or axis + events. A wl_pointer.frame must not contain more than one axis_discrete + event per axis type. + + This event is optional; continuous scrolling devices + like two-finger scrolling on touchpads do not have discrete + steps and do not generate this event. + + The discrete value carries the directional information. e.g. a value + of -2 is two steps towards the negative direction of this axis. + + The axis number is identical to the axis number in the associated + axis event. + + The order of wl_pointer.axis_discrete and wl_pointer.axis_source is + not guaranteed. + + + + + + + + Discrete high-resolution scroll information. + + This event carries high-resolution wheel scroll information, + with each multiple of 120 representing one logical scroll step + (a wheel detent). For example, an axis_value120 of 30 is one quarter of + a logical scroll step in the positive direction, a value120 of + -240 are two logical scroll steps in the negative direction within the + same hardware event. + Clients that rely on discrete scrolling should accumulate the + value120 to multiples of 120 before processing the event. + + The value120 must not be zero. + + This event replaces the wl_pointer.axis_discrete event in clients + supporting wl_pointer version 8 or later. + + Where a wl_pointer.axis_source event occurs in the same + wl_pointer.frame, the axis source applies to this event. + + The order of wl_pointer.axis_value120 and wl_pointer.axis_source is + not guaranteed. + + + + + + + + + + This specifies the direction of the physical motion that caused a + wl_pointer.axis event, relative to the wl_pointer.axis direction. + + + + + + + + Relative directional information of the entity causing the axis + motion. + + For a wl_pointer.axis event, the wl_pointer.axis_relative_direction + event specifies the movement direction of the entity causing the + wl_pointer.axis event. For example: + - if a user's fingers on a touchpad move down and this + causes a wl_pointer.axis vertical_scroll down event, the physical + direction is 'identical' + - if a user's fingers on a touchpad move down and this causes a + wl_pointer.axis vertical_scroll up scroll up event ('natural + scrolling'), the physical direction is 'inverted'. + + A client may use this information to adjust scroll motion of + components. Specifically, enabling natural scrolling causes the + content to change direction compared to traditional scrolling. + Some widgets like volume control sliders should usually match the + physical direction regardless of whether natural scrolling is + active. This event enables clients to match the scroll direction of + a widget to the physical direction. + + This event does not occur on its own, it is coupled with a + wl_pointer.axis event that represents this axis value. + The protocol guarantees that each axis_relative_direction event is + always followed by exactly one axis event with the same + axis number within the same wl_pointer.frame. Note that the protocol + allows for other events to occur between the axis_relative_direction + and its coupled axis event. + + The axis number is identical to the axis number in the associated + axis event. + + The order of wl_pointer.axis_relative_direction, + wl_pointer.axis_discrete and wl_pointer.axis_source is not + guaranteed. + + + + + + + + + The wl_keyboard interface represents one or more keyboards + associated with a seat. + + Each wl_keyboard has the following logical state: + + - an active surface (possibly null), + - the keys currently logically down, + - the active modifiers, + - the active group. + + By default, the active surface is null, the keys currently logically down + are empty, the active modifiers and the active group are 0. + + + + + This specifies the format of the keymap provided to the + client with the wl_keyboard.keymap event. + + + + + + + + This event provides a file descriptor to the client which can be + memory-mapped in read-only mode to provide a keyboard mapping + description. + + From version 7 onwards, the fd must be mapped with MAP_PRIVATE by + the recipient, as MAP_SHARED may fail. + + + + + + + + + Notification that this seat's keyboard focus is on a certain + surface. + + The compositor must send the wl_keyboard.modifiers event after this + event. + + In the wl_keyboard logical state, this event sets the active surface to + the surface argument and the keys currently logically down to the keys + in the keys argument. The compositor must not send this event if the + wl_keyboard already had an active surface immediately before this event. + + Clients should not use the list of pressed keys to emulate key-press + events. The order of keys in the list is unspecified. + + + + + + + + + Notification that this seat's keyboard focus is no longer on + a certain surface. + + The leave notification is sent before the enter notification + for the new focus. + + In the wl_keyboard logical state, this event resets all values to their + defaults. The compositor must not send this event if the active surface + of the wl_keyboard was not equal to the surface argument immediately + before this event. + + + + + + + + Describes the physical state of a key that produced the key event. + + Since version 10, the key can be in a "repeated" pseudo-state which + means the same as "pressed", but is used to signal repetition in the + key event. + + The key may only enter the repeated state after entering the pressed + state and before entering the released state. This event may be + generated multiple times while the key is down. + + + + + + + + + A key was pressed or released. + The time argument is a timestamp with millisecond + granularity, with an undefined base. + + The key is a platform-specific key code that can be interpreted + by feeding it to the keyboard mapping (see the keymap event). + + If this event produces a change in modifiers, then the resulting + wl_keyboard.modifiers event must be sent after this event. + + In the wl_keyboard logical state, this event adds the key to the keys + currently logically down (if the state argument is pressed) or removes + the key from the keys currently logically down (if the state argument is + released). The compositor must not send this event if the wl_keyboard + did not have an active surface immediately before this event. The + compositor must not send this event if state is pressed (resp. released) + and the key was already logically down (resp. was not logically down) + immediately before this event. + + Since version 10, compositors may send key events with the "repeated" + key state when a wl_keyboard.repeat_info event with a rate argument of + 0 has been received. This allows the compositor to take over the + responsibility of key repetition. + + + + + + + + + + Notifies clients that the modifier and/or group state has + changed, and it should update its local state. + + The compositor may send this event without a surface of the client + having keyboard focus, for example to tie modifier information to + pointer focus instead. If a modifier event with pressed modifiers is sent + without a prior enter event, the client can assume the modifier state is + valid until it receives the next wl_keyboard.modifiers event. In order to + reset the modifier state again, the compositor can send a + wl_keyboard.modifiers event with no pressed modifiers. + + In the wl_keyboard logical state, this event updates the modifiers and + group. + + + + + + + + + + + + + + + + + + + Informs the client about the keyboard's repeat rate and delay. + + This event is sent as soon as the wl_keyboard object has been created, + and is guaranteed to be received by the client before any key press + event. + + Negative values for either rate or delay are illegal. A rate of zero + will disable any repeating (regardless of the value of delay). + + This event can be sent later on as well with a new value if necessary, + so clients should continue listening for the event past the creation + of wl_keyboard. + + + + + + + + + The wl_touch interface represents a touchscreen + associated with a seat. + + Touch interactions can consist of one or more contacts. + For each contact, a series of events is generated, starting + with a down event, followed by zero or more motion events, + and ending with an up event. Events relating to the same + contact point can be identified by the ID of the sequence. + + + + + A new touch point has appeared on the surface. This touch point is + assigned a unique ID. Future events from this touch point reference + this ID. The ID ceases to be valid after a touch up event and may be + reused in the future. + + + + + + + + + + + + The touch point has disappeared. No further events will be sent for + this touch point and the touch point's ID is released and may be + reused in a future touch down event. + + + + + + + + + A touch point has changed coordinates. + + + + + + + + + + Indicates the end of a set of events that logically belong together. + A client is expected to accumulate the data in all events within the + frame before proceeding. + + A wl_touch.frame terminates at least one event but otherwise no + guarantee is provided about the set of events within a frame. A client + must assume that any state not updated in a frame is unchanged from the + previously known state. + + + + + + Sent if the compositor decides the touch stream is a global + gesture. No further events are sent to the clients from that + particular gesture. Touch cancellation applies to all touch points + currently active on this client's surface. The client is + responsible for finalizing the touch points, future touch points on + this surface may reuse the touch point ID. + + No frame event is required after the cancel event. + + + + + + + + + + + + + + Sent when a touchpoint has changed its shape. + + This event does not occur on its own. It is sent before a + wl_touch.frame event and carries the new shape information for + any previously reported, or new touch points of that frame. + + Other events describing the touch point such as wl_touch.down, + wl_touch.motion or wl_touch.orientation may be sent within the + same wl_touch.frame. A client should treat these events as a single + logical touch point update. The order of wl_touch.shape, + wl_touch.orientation and wl_touch.motion is not guaranteed. + A wl_touch.down event is guaranteed to occur before the first + wl_touch.shape event for this touch ID but both events may occur within + the same wl_touch.frame. + + A touchpoint shape is approximated by an ellipse through the major and + minor axis length. The major axis length describes the longer diameter + of the ellipse, while the minor axis length describes the shorter + diameter. Major and minor are orthogonal and both are specified in + surface-local coordinates. The center of the ellipse is always at the + touchpoint location as reported by wl_touch.down or wl_touch.move. + + This event is only sent by the compositor if the touch device supports + shape reports. The client has to make reasonable assumptions about the + shape if it did not receive this event. + + + + + + + + + Sent when a touchpoint has changed its orientation. + + This event does not occur on its own. It is sent before a + wl_touch.frame event and carries the new shape information for + any previously reported, or new touch points of that frame. + + Other events describing the touch point such as wl_touch.down, + wl_touch.motion or wl_touch.shape may be sent within the + same wl_touch.frame. A client should treat these events as a single + logical touch point update. The order of wl_touch.shape, + wl_touch.orientation and wl_touch.motion is not guaranteed. + A wl_touch.down event is guaranteed to occur before the first + wl_touch.orientation event for this touch ID but both events may occur + within the same wl_touch.frame. + + The orientation describes the clockwise angle of a touchpoint's major + axis to the positive surface y-axis and is normalized to the -180 to + +180 degree range. The granularity of orientation depends on the touch + device, some devices only support binary rotation values between 0 and + 90 degrees. + + This event is only sent by the compositor if the touch device supports + orientation reports. + + + + + + + + + An output describes part of the compositor geometry. The + compositor works in the 'compositor coordinate system' and an + output corresponds to a rectangular area in that space that is + actually visible. This typically corresponds to a monitor that + displays part of the compositor space. This object is published + as global during start up, or when a monitor is hotplugged. + + + + + This enumeration describes how the physical + pixels on an output are laid out. + + + + + + + + + + + + This describes transformations that clients and compositors apply to + buffer contents. + + The flipped values correspond to an initial flip around a + vertical axis followed by rotation. + + The purpose is mainly to allow clients to render accordingly and + tell the compositor, so that for fullscreen surfaces, the + compositor will still be able to scan out directly from client + surfaces. + + + + + + + + + + + + + + The geometry event describes geometric properties of the output. + The event is sent when binding to the output object and whenever + any of the properties change. + + The physical size can be set to zero if it doesn't make sense for this + output (e.g. for projectors or virtual outputs). + + The geometry event will be followed by a done event (starting from + version 2). + + Clients should use wl_surface.preferred_buffer_transform instead of the + transform advertised by this event to find the preferred buffer + transform to use for a surface. + + Note: wl_output only advertises partial information about the output + position and identification. Some compositors, for instance those not + implementing a desktop-style output layout or those exposing virtual + outputs, might fake this information. Instead of using x and y, clients + should use xdg_output.logical_position. Instead of using make and model, + clients should use name and description. + + + + + + + + + + + + + + These flags describe properties of an output mode. + They are used in the flags bitfield of the mode event. + + + + + + + + The mode event describes an available mode for the output. + + The event is sent when binding to the output object and there + will always be one mode, the current mode. The event is sent + again if an output changes mode, for the mode that is now + current. In other words, the current mode is always the last + mode that was received with the current flag set. + + Non-current modes are deprecated. A compositor can decide to only + advertise the current mode and never send other modes. Clients + should not rely on non-current modes. + + The size of a mode is given in physical hardware units of + the output device. This is not necessarily the same as + the output size in the global compositor space. For instance, + the output may be scaled, as described in wl_output.scale, + or transformed, as described in wl_output.transform. Clients + willing to retrieve the output size in the global compositor + space should use xdg_output.logical_size instead. + + The vertical refresh rate can be set to zero if it doesn't make + sense for this output (e.g. for virtual outputs). + + The mode event will be followed by a done event (starting from + version 2). + + Clients should not use the refresh rate to schedule frames. Instead, + they should use the wl_surface.frame event or the presentation-time + protocol. + + Note: this information is not always meaningful for all outputs. Some + compositors, such as those exposing virtual outputs, might fake the + refresh rate or the size. + + + + + + + + + + + + This event is sent after all other properties have been + sent after binding to the output object and after any + other property changes done after that. This allows + changes to the output properties to be seen as + atomic, even if they happen via multiple events. + + + + + + This event contains scaling geometry information + that is not in the geometry event. It may be sent after + binding the output object or if the output scale changes + later. The compositor will emit a non-zero, positive + value for scale. If it is not sent, the client should + assume a scale of 1. + + A scale larger than 1 means that the compositor will + automatically scale surface buffers by this amount + when rendering. This is used for very high resolution + displays where applications rendering at the native + resolution would be too small to be legible. + + Clients should use wl_surface.preferred_buffer_scale + instead of this event to find the preferred buffer + scale to use for a surface. + + The scale event will be followed by a done event. + + + + + + + + + Using this request a client can tell the server that it is not going to + use the output object anymore. + + + + + + + + Many compositors will assign user-friendly names to their outputs, show + them to the user, allow the user to refer to an output, etc. The client + may wish to know this name as well to offer the user similar behaviors. + + The name is a UTF-8 string with no convention defined for its contents. + Each name is unique among all wl_output globals. The name is only + guaranteed to be unique for the compositor instance. + + The same output name is used for all clients for a given wl_output + global. Thus, the name can be shared across processes to refer to a + specific wl_output global. + + The name is not guaranteed to be persistent across sessions, thus cannot + be used to reliably identify an output in e.g. configuration files. + + Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do + not assume that the name is a reflection of an underlying DRM connector, + X11 connection, etc. + + The name event is sent after binding the output object. This event is + only sent once per output object, and the name does not change over the + lifetime of the wl_output global. + + Compositors may re-use the same output name if the wl_output global is + destroyed and re-created later. Compositors should avoid re-using the + same name if possible. + + The name event will be followed by a done event. + + + + + + + Many compositors can produce human-readable descriptions of their + outputs. The client may wish to know this description as well, e.g. for + output selection purposes. + + The description is a UTF-8 string with no convention defined for its + contents. The description is not guaranteed to be unique among all + wl_output globals. Examples might include 'Foocorp 11" Display' or + 'Virtual X11 output via :1'. + + The description event is sent after binding the output object and + whenever the description changes. The description is optional, and may + not be sent at all. + + The description event will be followed by a done event. + + + + + + + + A region object describes an area. + + Region objects are used to describe the opaque and input + regions of a surface. + + + + + Destroy the region. This will invalidate the object ID. + + + + + + Add the specified rectangle to the region. + + + + + + + + + + Subtract the specified rectangle from the region. + + + + + + + + + + + The global interface exposing sub-surface compositing capabilities. + A wl_surface, that has sub-surfaces associated, is called the + parent surface. Sub-surfaces can be arbitrarily nested and create + a tree of sub-surfaces. + + The root surface in a tree of sub-surfaces is the main + surface. The main surface cannot be a sub-surface, because + sub-surfaces must always have a parent. + + A main surface with its sub-surfaces forms a (compound) window. + For window management purposes, this set of wl_surface objects is + to be considered as a single window, and it should also behave as + such. + + The aim of sub-surfaces is to offload some of the compositing work + within a window from clients to the compositor. A prime example is + a video player with decorations and video in separate wl_surface + objects. This should allow the compositor to pass YUV video buffer + processing to dedicated overlay hardware when possible. + + + + + Informs the server that the client will not be using this + protocol object anymore. This does not affect any other + objects, wl_subsurface objects included. + + + + + + + + + + + Create a sub-surface interface for the given surface, and + associate it with the given parent surface. This turns a + plain wl_surface into a sub-surface. + + The to-be sub-surface must not already have another role, and it + must not have an existing wl_subsurface object. Otherwise the + bad_surface protocol error is raised. + + Adding sub-surfaces to a parent is a double-buffered operation on the + parent (see wl_surface.commit). The effect of adding a sub-surface + becomes visible on the next time the state of the parent surface is + applied. + + The parent surface must not be one of the child surface's descendants, + and the parent must be different from the child surface, otherwise the + bad_parent protocol error is raised. + + This request modifies the behaviour of wl_surface.commit request on + the sub-surface, see the documentation on wl_subsurface interface. + + + + + + + + + + An additional interface to a wl_surface object, which has been + made a sub-surface. A sub-surface has one parent surface. A + sub-surface's size and position are not limited to that of the parent. + Particularly, a sub-surface is not automatically clipped to its + parent's area. + + A sub-surface becomes mapped, when a non-NULL wl_buffer is applied + and the parent surface is mapped. The order of which one happens + first is irrelevant. A sub-surface is hidden if the parent becomes + hidden, or if a NULL wl_buffer is applied. These rules apply + recursively through the tree of surfaces. + + The behaviour of a wl_surface.commit request on a sub-surface + depends on the sub-surface's mode. The possible modes are + synchronized and desynchronized, see methods + wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized + mode caches the wl_surface state to be applied when the parent's + state gets applied, and desynchronized mode applies the pending + wl_surface state directly. A sub-surface is initially in the + synchronized mode. + + Sub-surfaces also have another kind of state, which is managed by + wl_subsurface requests, as opposed to wl_surface requests. This + state includes the sub-surface position relative to the parent + surface (wl_subsurface.set_position), and the stacking order of + the parent and its sub-surfaces (wl_subsurface.place_above and + .place_below). This state is applied when the parent surface's + wl_surface state is applied, regardless of the sub-surface's mode. + As the exception, set_sync and set_desync are effective immediately. + + The main surface can be thought to be always in desynchronized mode, + since it does not have a parent in the sub-surfaces sense. + + Even if a sub-surface is in desynchronized mode, it will behave as + in synchronized mode, if its parent surface behaves as in + synchronized mode. This rule is applied recursively throughout the + tree of surfaces. This means, that one can set a sub-surface into + synchronized mode, and then assume that all its child and grand-child + sub-surfaces are synchronized, too, without explicitly setting them. + + Destroying a sub-surface takes effect immediately. If you need to + synchronize the removal of a sub-surface to the parent surface update, + unmap the sub-surface first by attaching a NULL wl_buffer, update parent, + and then destroy the sub-surface. + + If the parent wl_surface object is destroyed, the sub-surface is + unmapped. + + A sub-surface never has the keyboard focus of any seat. + + The wl_surface.offset request is ignored: clients must use set_position + instead to move the sub-surface. + + + + + The sub-surface interface is removed from the wl_surface object + that was turned into a sub-surface with a + wl_subcompositor.get_subsurface request. The wl_surface's association + to the parent is deleted. The wl_surface is unmapped immediately. + + + + + + + + + + This schedules a sub-surface position change. + The sub-surface will be moved so that its origin (top left + corner pixel) will be at the location x, y of the parent surface + coordinate system. The coordinates are not restricted to the parent + surface area. Negative values are allowed. + + The scheduled coordinates will take effect whenever the state of the + parent surface is applied. + + If more than one set_position request is invoked by the client before + the commit of the parent surface, the position of a new request always + replaces the scheduled position from any previous request. + + The initial position is 0, 0. + + + + + + + + This sub-surface is taken from the stack, and put back just + above the reference surface, changing the z-order of the sub-surfaces. + The reference surface must be one of the sibling surfaces, or the + parent surface. Using any other surface, including this sub-surface, + will cause a protocol error. + + The z-order is double-buffered. Requests are handled in order and + applied immediately to a pending state. The final pending state is + copied to the active state the next time the state of the parent + surface is applied. + + A new sub-surface is initially added as the top-most in the stack + of its siblings and parent. + + + + + + + The sub-surface is placed just below the reference surface. + See wl_subsurface.place_above. + + + + + + + Change the commit behaviour of the sub-surface to synchronized + mode, also described as the parent dependent mode. + + In synchronized mode, wl_surface.commit on a sub-surface will + accumulate the committed state in a cache, but the state will + not be applied and hence will not change the compositor output. + The cached state is applied to the sub-surface immediately after + the parent surface's state is applied. This ensures atomic + updates of the parent and all its synchronized sub-surfaces. + Applying the cached state will invalidate the cache, so further + parent surface commits do not (re-)apply old state. + + See wl_subsurface for the recursive effect of this mode. + + + + + + Change the commit behaviour of the sub-surface to desynchronized + mode, also described as independent or freely running mode. + + In desynchronized mode, wl_surface.commit on a sub-surface will + apply the pending state directly, without caching, as happens + normally with a wl_surface. Calling wl_surface.commit on the + parent surface has no effect on the sub-surface's wl_surface + state. This mode allows a sub-surface to be updated on its own. + + If cached state exists when wl_surface.commit is called in + desynchronized mode, the pending state is added to the cached + state, and applied as a whole. This invalidates the cache. + + Note: even if a sub-surface is set to desynchronized, a parent + sub-surface may override it to behave as synchronized. For details, + see wl_subsurface. + + If a surface's parent surface behaves as desynchronized, then + the cached state is applied on set_desync. + + + + + + + This global fixes problems with other core-protocol interfaces that + cannot be fixed in these interfaces themselves. + + + + + + + + + This request destroys a wl_registry object. + + The client should no longer use the wl_registry after making this + request. + + The compositor will emit a wl_display.delete_id event with the object ID + of the registry and will no longer emit any events on the registry. The + client should re-use the object ID once it receives the + wl_display.delete_id event. + + + + + + diff --git a/src/wayland-protocols/xdg-decoration-unstable-v1.xml b/src/wayland-protocols/xdg-decoration-unstable-v1.xml new file mode 100644 index 0000000..82ca247 --- /dev/null +++ b/src/wayland-protocols/xdg-decoration-unstable-v1.xml @@ -0,0 +1,160 @@ + + + + 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. + + + + + 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. + + + + + Destroy the decoration manager. This doesn't destroy objects created + with the manager. + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + + + + Switch back to a mode without any server-side decorations at the next + commit. + + + + + + These values describe window decoration modes. + + + + + + + + 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. + + + + + + + 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. + + + + + + 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. + + + + + diff --git a/src/wayland-protocols/xdg-shell.xml b/src/wayland-protocols/xdg-shell.xml new file mode 100644 index 0000000..39ecf8a --- /dev/null +++ b/src/wayland-protocols/xdg-shell.xml @@ -0,0 +1,1418 @@ + + + + + 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. + + + + + The xdg_wm_base interface is exposed as a global object enabling clients + to turn their wl_surfaces into windows in a desktop environment. It + defines the basic functionality needed for clients and the compositor to + create windows that can be dragged, resized, maximized, etc, as well as + creating transient windows such as popup menus. + + + + + + + + + + + + + + + Destroy this xdg_wm_base object. + + Destroying a bound xdg_wm_base object while there are surfaces + still alive created by this xdg_wm_base object instance is illegal + and will result in a defunct_surfaces error. + + + + + + Create a positioner object. A positioner object is used to position + surfaces relative to some parent surface. See the interface description + and xdg_surface.get_popup for details. + + + + + + + This creates an xdg_surface for the given surface. While xdg_surface + itself is not a role, the corresponding surface may only be assigned + a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is + illegal to create an xdg_surface for a wl_surface which already has an + assigned role and this will result in a role error. + + This creates an xdg_surface for the given surface. An xdg_surface is + used as basis to define a role to a given surface, such as xdg_toplevel + or xdg_popup. It also manages functionality shared between xdg_surface + based surface roles. + + See the documentation of xdg_surface for more details about what an + xdg_surface is and how it is used. + + + + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. See xdg_wm_base.ping + and xdg_wm_base.error.unresponsive. + + + + + + + The ping event asks the client if it's still alive. Pass the + serial specified in the event back to the compositor by sending + a "pong" request back with the specified serial. See xdg_wm_base.pong. + + Compositors can use this to determine if the client is still + alive. It's unspecified what will happen if the client doesn't + respond to the ping request, or in what timeframe. Clients should + try to respond in a reasonable amount of time. The “unresponsive” + error is provided for compositors that wish to disconnect unresponsive + clients. + + A compositor is free to ping in any way it wants, but a client must + always respond to any xdg_wm_base object it created. + + + + + + + + The xdg_positioner provides a collection of rules for the placement of a + child surface relative to a parent surface. Rules can be defined to ensure + the child surface remains within the visible area's borders, and to + specify how the child surface changes its position, such as sliding along + an axis, or flipping around a rectangle. These positioner-created rules are + constrained by the requirement that a child surface must intersect with or + be at least partially adjacent to its parent surface. + + See the various requests for details about possible rules. + + At the time of the request, the compositor makes a copy of the rules + specified by the xdg_positioner. Thus, after the request is complete the + xdg_positioner object can be destroyed or reused; further changes to the + object will have no effect on previous usages. + + For an xdg_positioner object to be considered complete, it must have a + non-zero size set by set_size, and a non-zero anchor rectangle set by + set_anchor_rect. Passing an incomplete xdg_positioner object when + positioning a surface raises an invalid_positioner error. + + + + + + + + + Notify the compositor that the xdg_positioner will no longer be used. + + + + + + Set the size of the surface that is to be positioned with the positioner + object. The size is in surface-local coordinates and corresponds to the + window geometry. See xdg_surface.set_window_geometry. + + If a zero or negative size is set the invalid_input error is raised. + + + + + + + + Specify the anchor rectangle within the parent surface that the child + surface will be placed relative to. The rectangle is relative to the + window geometry as defined by xdg_surface.set_window_geometry of the + parent surface. + + When the xdg_positioner object is used to position a child surface, the + anchor rectangle may not extend outside the window geometry of the + positioned child's parent surface. + + If a negative size is set the invalid_input error is raised. + + + + + + + + + + + + + + + + + + + + + + Defines the anchor point for the anchor rectangle. The specified anchor + is used derive an anchor point that the child surface will be + positioned relative to. If a corner anchor is set (e.g. 'top_left' or + 'bottom_right'), the anchor point will be at the specified corner; + otherwise, the derived anchor point will be centered on the specified + edge, or in the center of the anchor rectangle if no edge is specified. + + + + + + + + + + + + + + + + + + + Defines in what direction a surface should be positioned, relative to + the anchor point of the parent surface. If a corner gravity is + specified (e.g. 'bottom_right' or 'top_left'), then the child surface + will be placed towards the specified gravity; otherwise, the child + surface will be centered over the anchor point on any axis that had no + gravity specified. If the gravity is not in the ‘gravity’ enum, an + invalid_input error is raised. + + + + + + + The constraint adjustment value define ways the compositor will adjust + the position of the surface, if the unadjusted position would result + in the surface being partly constrained. + + Whether a surface is considered 'constrained' is left to the compositor + to determine. For example, the surface may be partly outside the + compositor's defined 'work area', thus necessitating the child surface's + position be adjusted until it is entirely inside the work area. + + The adjustments can be combined, according to a defined precedence: 1) + Flip, 2) Slide, 3) Resize. + + + + Don't alter the surface position even if it is constrained on some + axis, for example partially outside the edge of an output. + + + + + Slide the surface along the x axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the x axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + x axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Slide the surface along the y axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the y axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + y axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Invert the anchor and gravity on the x axis if the surface is + constrained on the x axis. For example, if the left edge of the + surface is constrained, the gravity is 'left' and the anchor is + 'left', change the gravity to 'right' and the anchor to 'right'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_x adjustment will be the one before the + adjustment. + + + + + Invert the anchor and gravity on the y axis if the surface is + constrained on the y axis. For example, if the bottom edge of the + surface is constrained, the gravity is 'bottom' and the anchor is + 'bottom', change the gravity to 'top' and the anchor to 'top'. + + The adjusted position is calculated given the original anchor + rectangle and offset, but with the new flipped anchor and gravity + values. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_y adjustment will be the one before the + adjustment. + + + + + Resize the surface horizontally so that it is completely + unconstrained. + + + + + Resize the surface vertically so that it is completely unconstrained. + + + + + + + Specify how the window should be positioned if the originally intended + position caused the surface to be constrained, meaning at least + partially outside positioning boundaries set by the compositor. The + adjustment is set by constructing a bitmask describing the adjustment to + be made when the surface is constrained on that axis. + + If no bit for one axis is set, the compositor will assume that the child + surface should not change its position on that axis when constrained. + + If more than one bit for one axis is set, the order of how adjustments + are applied is specified in the corresponding adjustment descriptions. + + The default adjustment is none. + + + + + + + Specify the surface position offset relative to the position of the + anchor on the anchor rectangle and the anchor on the surface. For + example if the anchor of the anchor rectangle is at (x, y), the surface + has the gravity bottom|right, and the offset is (ox, oy), the calculated + surface position will be (x + ox, y + oy). The offset position of the + surface is the one used for constraint testing. See + set_constraint_adjustment. + + An example use case is placing a popup menu on top of a user interface + element, while aligning the user interface element of the parent surface + with some user interface element placed somewhere in the popup surface. + + + + + + + + + + When set reactive, the surface is reconstrained if the conditions used + for constraining changed, e.g. the parent window moved. + + If the conditions changed and the popup was reconstrained, an + xdg_popup.configure event is sent with updated geometry, followed by an + xdg_surface.configure event. + + + + + + Set the parent window geometry the compositor should use when + positioning the popup. The compositor may use this information to + determine the future state the popup should be constrained using. If + this doesn't match the dimension of the parent the popup is eventually + positioned against, the behavior is undefined. + + The arguments are given in the surface-local coordinate space. + + + + + + + + Set the serial of an xdg_surface.configure event this positioner will be + used in response to. The compositor may use this information together + with set_parent_size to determine what future state the popup should be + constrained using. + + + + + + + + An interface that may be implemented by a wl_surface, for + implementations that provide a desktop-style user interface. + + It provides a base set of functionality required to construct user + interface elements requiring management by the compositor, such as + toplevel windows, menus, etc. The types of functionality are split into + xdg_surface roles. + + Creating an xdg_surface does not set the role for a wl_surface. In order + to map an xdg_surface, the client must create a role-specific object + using, e.g., get_toplevel, get_popup. The wl_surface for any given + xdg_surface can have at most one role, and may not be assigned any role + not based on xdg_surface. + + A role must be assigned before any other requests are made to the + xdg_surface object. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_surface state to take effect. + + Creating an xdg_surface from a wl_surface 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_surface.configure call must + also be treated as errors. + + After creating a role-specific object and setting it up (e.g. by sending + the title, app ID, size constraints, parent, etc), the client must + perform an initial commit without any buffer attached. The compositor + will reply with initial wl_surface state such as + wl_surface.preferred_buffer_scale followed by an xdg_surface.configure + event. The client must acknowledge it and is then allowed to attach a + buffer to map the surface. + + Mapping an xdg_surface-based role surface is defined as making it + possible for the surface to be shown by the compositor. Note that + a mapped surface is not guaranteed to be visible once it is mapped. + + For an xdg_surface to be mapped by the compositor, the following + conditions must be met: + (1) the client has assigned an xdg_surface-based role to the surface + (2) the client has set and committed the xdg_surface state and the + role-dependent state to the surface + (3) the client has committed a buffer to the surface + + A newly-unmapped surface is considered to have met condition (1) out + of the 3 required conditions for mapping a surface if its role surface + has not been destroyed, i.e. the client must perform the initial commit + again before attaching a buffer. + + + + + + + + + + + + + + Destroy the xdg_surface object. An xdg_surface must only be destroyed + after its role object has been destroyed, otherwise + a defunct_role_object error is raised. + + + + + + This creates an xdg_toplevel object for the given xdg_surface and gives + the associated wl_surface the xdg_toplevel role. + + See the documentation of xdg_toplevel for more details about what an + xdg_toplevel is and how it is used. + + + + + + + This creates an xdg_popup object for the given xdg_surface and gives + the associated wl_surface the xdg_popup role. + + If null is passed as a parent, a parent surface must be specified using + some other protocol, before committing the initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + + + The window geometry of a surface is its "visible bounds" from the + user's perspective. Client-side decorations often have invisible + portions like drop-shadows which should be ignored for the + purposes of aligning, placing and constraining windows. Note that + in some situations, compositors may clip rendering to the window + geometry, so the client should avoid putting functional elements + outside of it. + + The window geometry is double-buffered state, see wl_surface.commit. + + When maintaining a position, the compositor should treat the (x, y) + coordinate of the window geometry as the top left corner of the window. + A client changing the (x, y) window geometry coordinate should in + general not alter the position of the window. + + Once the window geometry of the surface is set, it is not possible to + unset it, and it will remain the same until set_window_geometry is + called again, even if a new subsurface or buffer is attached. + + If never set, the value is the full bounds of the surface, + including any subsurfaces. This updates dynamically on every + commit. This unset is meant for extremely simple clients. + + The arguments are given in the surface-local coordinate space of + the wl_surface associated with this xdg_surface, and may extend outside + of the wl_surface itself to mark parts of the subsurface tree as part of + the window geometry. + + When applied, the effective window geometry will be the set window + geometry clamped to the bounding rectangle of the combined + geometry of the surface of the xdg_surface and the associated + subsurfaces. + + The effective geometry will not be recalculated unless a new call to + set_window_geometry is done and the new pending surface state is + subsequently applied. + + The width and height of the effective window geometry must be + greater than zero. Setting an invalid size will raise an + invalid_size error. + + + + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + For instance, for toplevel surfaces the compositor might use this + information to move a surface to the top left only when the client has + drawn itself for the maximized or fullscreen state. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + Acking a configure event that was never sent raises an invalid_serial + error. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + Sending an ack_configure request consumes the serial number sent with + the request, as well as serial numbers sent by all configure events + sent on this xdg_surface prior to the configure event referenced by + the committed serial. + + It is an error to issue multiple ack_configure requests referencing a + serial from the same configure event, or to issue an ack_configure + request referencing a serial from a configure event issued before the + event identified by the last ack_configure request for the same + xdg_surface. Doing so will raise an invalid_serial error. + + + + + + + The configure event marks the end of a configure sequence. A configure + sequence is a set of one or more events configuring the state of the + xdg_surface, including the final xdg_surface.configure event. + + Where applicable, xdg_surface surface roles will during a configure + sequence extend this event as a latched state sent as events before the + xdg_surface.configure event. Such events should be considered to make up + a set of atomically applied configuration states, where the + xdg_surface.configure commits the accumulated state. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + If the client receives multiple configure events before it can respond + to one, it is free to discard all but the last event it received. + + + + + + + + + This interface defines an xdg_surface role which allows a surface to, + among other things, set window-like properties such as maximize, + fullscreen, and minimize, set application-specific metadata like title and + id, and well as trigger user interactive operations such as interactive + resize and move. + + A xdg_toplevel by default is responsible for providing the full intended + visual representation of the toplevel, which depending on the window + state, may mean things like a title bar, window controls and drop shadow. + + Unmapping an xdg_toplevel means that the surface cannot be shown + by the compositor until it is explicitly mapped again. + All active operations (e.g., move, resize) are canceled and all + attributes (e.g. title, state, stacking, ...) are discarded for + an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + the state it had right after xdg_surface.get_toplevel. The client + can re-map the toplevel by performing a commit without any buffer + attached, waiting for a configure event and handling it as usual (see + xdg_surface description). + + Attaching a null buffer to a toplevel unmaps the surface. + + + + + This request destroys the role surface and unmaps the surface; + see "Unmapping" behavior in interface section for details. + + + + + + + + + + + + Set the "parent" of this surface. This surface should be stacked + above the parent surface and all other ancestor surfaces. + + Parent surfaces should be set on dialogs, toolboxes, or other + "auxiliary" surfaces, so that the parent is raised when the dialog + is raised. + + Setting a null parent for a child surface unsets its parent. Setting + a null parent for a surface which currently has no parent is a no-op. + + Only mapped surfaces can have child surfaces. Setting a parent which + is not mapped is equivalent to setting a null parent. If a surface + becomes unmapped, its children's parent is set to the parent of + the now-unmapped surface. If the now-unmapped surface has no parent, + its children's parent is unset. If the now-unmapped surface becomes + mapped again, its parent-child relationship is not restored. + + The parent toplevel must not be one of the child toplevel's + descendants, and the parent must be different from the child toplevel, + otherwise the invalid_parent protocol error is raised. + + + + + + + Set a short title for the surface. + + This string may be used to identify the surface in a task bar, + window list, or other user interface elements provided by the + compositor. + + The string must be encoded in UTF-8. + + + + + + + Set an application identifier for the surface. + + The app ID identifies the general class of applications to which + the surface belongs. The compositor can use this to group multiple + surfaces together, or to determine how to launch a new application. + + For D-Bus activatable applications, the app ID is used as the D-Bus + service name. + + The compositor shell will try to group application surfaces together + by their app ID. As a best practice, it is suggested to select app + ID's that match the basename of the application's .desktop file. + For example, "org.freedesktop.FooViewer" where the .desktop file is + "org.freedesktop.FooViewer.desktop". + + Like other properties, a set_app_id request can be sent after the + xdg_toplevel has been mapped to update the property. + + See the desktop-entry specification [0] for more details on + application identifiers and how they relate to well-known D-Bus + names and .desktop files. + + [0] https://standards.freedesktop.org/desktop-entry-spec/ + + + + + + + Clients implementing client-side decorations might want to show + a context menu when right-clicking on the decorations, giving the + user a menu that they can use to maximize or minimize the window. + + This request asks the compositor to pop up such a window menu at + the given position, relative to the local surface coordinates of + the parent surface. There are no guarantees as to what menu items + the window menu contains, or even if a window menu will be drawn + at all. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. + + + + + + + + + + Start an interactive, user-driven move of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive move (touch, + pointer, etc). + + The server may ignore move requests depending on the state of + the surface (e.g. fullscreen or maximized), or if the passed serial + is no longer valid. + + If triggered, the surface will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the move. It is up to the + compositor to visually indicate that the move is taking place, such as + updating a pointer cursor, during the move. There is no guarantee + that the device focus will return when the move is completed. + + + + + + + + These values are used to indicate which edge of a surface + is being dragged in a resize operation. + + + + + + + + + + + + + + + Start a user-driven, interactive resize of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive resize (touch, + pointer, etc). + + The server may ignore resize requests depending on the state of + the surface (e.g. fullscreen or maximized). + + If triggered, the client will receive configure events with the + "resize" state enum value and the expected sizes. See the "resize" + enum value for more details about what is required. The client + must also acknowledge configure events using "ack_configure". After + the resize is completed, the client will receive another "configure" + event without the resize state. + + If triggered, the surface also will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the resize. It is up to the + compositor to visually indicate that the resize is taking place, + such as updating a pointer cursor, during the resize. There is no + guarantee that the device focus will return when the resize is + completed. + + The edges parameter specifies how the surface should be resized, and + is one of the values of the resize_edge enum. Values not matching + a variant of the enum will cause the invalid_resize_edge protocol error. + The compositor may use this information to update the surface position + for example when dragging the top left corner. The compositor may also + use this information to adapt its behavior, e.g. choose an appropriate + cursor image. + + + + + + + + + The different state values used on the surface. This is designed for + state values like maximized, fullscreen. It is paired with the + configure event to ensure that both the client and the compositor + setting the state can be synchronized. + + States set in this way are double-buffered, see wl_surface.commit. + + + + The surface is maximized. The window geometry specified in the configure + event must be obeyed by the client, or the xdg_wm_base.invalid_surface_state + error is raised. + + The client should draw without shadow or other + decoration outside of the window geometry. + + + + + The surface is fullscreen. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. For + a surface to cover the whole fullscreened area, the geometry + dimensions must be obeyed by the client. For more details, see + xdg_toplevel.set_fullscreen. + + + + + The surface is being resized. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. + Clients that have aspect ratio or cell sizing configuration can use + a smaller size, however. + + + + + Client window decorations should be painted as if the window is + active. Do not assume this means that the window actually has + keyboard or pointer focus. + + + + + The window is currently in a tiled layout and the left edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the left edge. + + + + + The window is currently in a tiled layout and the right edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the right edge. + + + + + The window is currently in a tiled layout and the top edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the top edge. + + + + + The window is currently in a tiled layout and the bottom edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the bottom edge. + + + + + The surface is currently not ordinarily being repainted; for + example because its content is occluded by another window, or its + outputs are switched off due to screen locking. + + + + + The left edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + + + + The right edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + + + + The top edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + + + + The bottom edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + + + + + + Set a maximum size for the window. + + The client can specify a maximum size so that the compositor does + not try to configure the window beyond this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered, see wl_surface.commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the maximum + size. The compositor may decide to ignore the values set by the + client and request a larger size. + + If never set, or a value of zero in the request, means that the + client has no expected maximum size in the given dimension. + As a result, a client wishing to reset the maximum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a maximum size to be smaller than the minimum size of + a surface is illegal and will result in an invalid_size error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width or height will result in a + invalid_size error. + + + + + + + + Set a minimum size for the window. + + The client can specify a minimum size so that the compositor does + not try to configure the window below this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered, see wl_surface.commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the minimum + size. The compositor may decide to ignore the values set by the + client and request a smaller size. + + If never set, or a value of zero in the request, means that the + client has no expected minimum size in the given dimension. + As a result, a client wishing to reset the minimum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a minimum size to be larger than the maximum size of + a surface is illegal and will result in an invalid_size error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + invalid_size error. + + + + + + + + Maximize the surface. + + After requesting that the surface should be maximized, the compositor + will respond by emitting a configure event. Whether this configure + actually sets the window maximized is subject to compositor policies. + The client must then update its content, drawing in the configured + state. The client must also acknowledge the configure when committing + the new content (see ack_configure). + + It is up to the compositor to decide how and where to maximize the + surface, for example which output and what region of the screen should + be used. + + If the surface was already maximized, the compositor will still emit + a configure event with the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + + + + + + Unmaximize the surface. + + After requesting that the surface should be unmaximized, the compositor + will respond by emitting a configure event. Whether this actually + un-maximizes the window is subject to compositor policies. + If available and applicable, the compositor will include the window + geometry dimensions the window had prior to being maximized in the + configure event. The client must then update its content, drawing it in + the configured state. The client must also acknowledge the configure + when committing the new content (see ack_configure). + + It is up to the compositor to position the surface after it was + unmaximized; usually the position the surface had before maximizing, if + applicable. + + If the surface was already not maximized, the compositor will still + emit a configure event without the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + + + + + + Make the surface fullscreen. + + After requesting that the surface should be fullscreened, the + compositor will respond by emitting a configure event. Whether the + client is actually put into a fullscreen state is subject to compositor + policies. The client must also acknowledge the configure when + committing the new content (see ack_configure). + + The output passed by the request indicates the client's preference as + to which display it should be set fullscreen on. If this value is NULL, + it's up to the compositor to choose which display will be used to map + this surface. + + If the surface doesn't cover the whole output, the compositor will + position the surface in the center of the output and compensate with + with border fill covering the rest of the output. The content of the + border fill is undefined, but should be assumed to be in some way that + attempts to blend into the surrounding area (e.g. solid black). + + If the fullscreened surface is not opaque, the compositor must make + sure that other screen content not part of the same surface tree (made + up of subsurfaces, popups or similarly coupled surfaces) are not + visible below the fullscreened surface. + + + + + + + Make the surface no longer fullscreen. + + After requesting that the surface should be unfullscreened, the + compositor will respond by emitting a configure event. + Whether this actually removes the fullscreen state of the client is + subject to compositor policies. + + Making a surface unfullscreen sets states for the surface based on the following: + * the state(s) it may have had before becoming fullscreen + * any state(s) decided by the compositor + * any state(s) requested by the client while the surface was fullscreen + + The compositor may include the previous window geometry dimensions in + the configure event, if applicable. + + The client must also acknowledge the configure when committing the new + content (see ack_configure). + + + + + + Request that the compositor minimize your surface. There is no + way to know if the surface is currently minimized, nor is there + any way to unset minimization on this surface. + + If you are looking to throttle redrawing when minimized, please + instead use the wl_surface.frame event for this, as this will + also work with live previews on windows in Alt-Tab, Expose or + similar compositor features. + + + + + + This configure event asks the client to resize its toplevel surface or + to change its state. The configured state should not be applied + immediately. See xdg_surface.configure for details. + + The width and height arguments specify a hint to the window + about how its surface should be resized in window geometry + coordinates. See set_window_geometry. + + If the width or height arguments are zero, it means the client + should decide its own window dimension. This may happen when the + compositor needs to configure the state of the surface but doesn't + have any information about any previous or expected dimension. + + The states listed in the event specify how the width/height + arguments should be interpreted, and possibly how it should be + drawn. + + Clients must send an ack_configure in response to this event. See + xdg_surface.configure and xdg_surface.ack_configure for details. + + + + + + + + + The close event is sent by the compositor when the user + wants the surface to be closed. This should be equivalent to + the user clicking the close button in client-side decorations, + if your application has any. + + This is only a request that the user intends to close the + window. The client may choose to ignore this request, or show + a dialog to ask the user to save their data, etc. + + + + + + + + The configure_bounds event may be sent prior to a xdg_toplevel.configure + event to communicate the bounds a window geometry size is recommended + to constrain to. + + The passed width and height are in surface coordinate space. If width + and height are 0, it means bounds is unknown and equivalent to as if no + configure_bounds event was ever sent for this surface. + + The bounds can for example correspond to the size of a monitor excluding + any panels or other shell components, so that a surface isn't created in + a way that it cannot fit. + + The bounds may change at any point, and in such a case, a new + xdg_toplevel.configure_bounds will be sent, followed by + xdg_toplevel.configure and xdg_surface.configure. + + + + + + + + + + + + + + + + + This event advertises the capabilities supported by the compositor. If + a capability isn't supported, clients should hide or disable the UI + elements that expose this functionality. For instance, if the + compositor doesn't advertise support for minimized toplevels, a button + triggering the set_minimized request should not be displayed. + + The compositor will ignore requests it doesn't support. For instance, + a compositor which doesn't advertise support for minimized will ignore + set_minimized requests. + + Compositors must send this event once before the first + xdg_surface.configure event. When the capabilities change, compositors + must send this event again and then send an xdg_surface.configure + event. + + The configured state should not be applied immediately. See + xdg_surface.configure for details. + + The capabilities are sent as an array of 32-bit unsigned integers in + native endianness. + + + + + + + + A popup surface is a short-lived, temporary surface. It can be used to + implement for example menus, popovers, tooltips and other similar user + interface concepts. + + A popup can be made to take an explicit grab. See xdg_popup.grab for + details. + + When the popup is dismissed, a popup_done event will be sent out, and at + the same time the surface will be unmapped. See the xdg_popup.popup_done + event for details. + + Explicitly destroying the xdg_popup object will also dismiss the popup and + unmap the surface. Clients that want to dismiss the popup when another + surface of their own is clicked should dismiss the popup using the destroy + request. + + A newly created xdg_popup will be stacked on top of all previously created + xdg_popup surfaces associated with the same xdg_toplevel. + + The parent of an xdg_popup must be mapped (see the xdg_surface + description) before the xdg_popup itself. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_popup state to take effect. + + + + + + + + + This destroys the popup. Explicitly destroying the xdg_popup + object will also dismiss the popup, and unmap the surface. + + If this xdg_popup is not the "topmost" popup, the + xdg_wm_base.not_the_topmost_popup protocol error will be sent. + + + + + + This request makes the created popup take an explicit grab. An explicit + grab will be dismissed when the user dismisses the popup, or when the + client destroys the xdg_popup. This can be done by the user clicking + outside the surface, using the keyboard, or even locking the screen + through closing the lid or a timeout. + + If the compositor denies the grab, the popup will be immediately + dismissed. + + This request must be used in response to some sort of user action like a + button press, key press, or touch down event. The serial number of the + event should be passed as 'serial'. + + The parent of a grabbing popup must either be an xdg_toplevel surface or + another xdg_popup with an explicit grab. If the parent is another + xdg_popup it means that the popups are nested, with this popup now being + the topmost popup. + + Nested popups must be destroyed in the reverse order they were created + in, e.g. the only popup you are allowed to destroy at all times is the + topmost one. + + When compositors choose to dismiss a popup, they may dismiss every + nested grabbing popup as well. When a compositor dismisses popups, it + will follow the same dismissing order as required from the client. + + If the topmost grabbing popup is destroyed, the grab will be returned to + the parent of the popup, if that parent previously had an explicit grab. + + If the parent is a grabbing popup which has already been dismissed, this + popup will be immediately dismissed. If the parent is a popup that did + not take an explicit grab, an error will be raised. + + During a popup grab, the client owning the grab will receive pointer + and touch events for all their surfaces as normal (similar to an + "owner-events" grab in X11 parlance), while the top most grabbing popup + will always have keyboard focus. + + + + + + + + This event asks the popup surface to configure itself given the + configuration. The configured state should not be applied immediately. + See xdg_surface.configure for details. + + The x and y arguments represent the position the popup was placed at + given the xdg_positioner rule, relative to the upper left corner of the + window geometry of the parent surface. + + For version 2 or older, the configure event for an xdg_popup is only + ever sent once for the initial configuration. Starting with version 3, + it may be sent again if the popup is setup with an xdg_positioner with + set_reactive requested, or in response to xdg_popup.reposition requests. + + + + + + + + + + The popup_done event is sent out when a popup is dismissed by the + compositor. The client should destroy the xdg_popup object at this + point. + + + + + + + + Reposition an already-mapped popup. The popup will be placed given the + details in the passed xdg_positioner object, and a + xdg_popup.repositioned followed by xdg_popup.configure and + xdg_surface.configure will be emitted in response. Any parameters set + by the previous positioner will be discarded. + + The passed token will be sent in the corresponding + xdg_popup.repositioned event. The new popup position will not take + effect until the corresponding configure event is acknowledged by the + client. See xdg_popup.repositioned for details. The token itself is + opaque, and has no other special meaning. + + If multiple reposition requests are sent, the compositor may skip all + but the last one. + + If the popup is repositioned in response to a configure event for its + parent, the client should send an xdg_positioner.set_parent_configure + and possibly an xdg_positioner.set_parent_size request to allow the + compositor to properly constrain the popup. + + If the popup is repositioned together with a parent that is being + resized, but not in response to a configure event, the client should + send an xdg_positioner.set_parent_size request. + + + + + + + + The repositioned event is sent as part of a popup configuration + sequence, together with xdg_popup.configure and lastly + xdg_surface.configure to notify the completion of a reposition request. + + The repositioned event is to notify about the completion of a + xdg_popup.reposition request. The token argument is the token passed + in the xdg_popup.reposition request. + + Immediately after this event is emitted, xdg_popup.configure and + xdg_surface.configure will be sent with the updated size and position, + as well as a new configure serial. + + The client should optionally update the content of the popup, but must + acknowledge the new popup configuration for the new position to take + effect. See xdg_surface.ack_configure for details. + + + + + + diff --git a/src/wayland.zig b/src/wayland.zig new file mode 100644 index 0000000..7647c89 --- /dev/null +++ b/src/wayland.zig @@ -0,0 +1,938 @@ +pub const Connection = struct { + fd: i32 = 0, + want_flush: u32 = 0, + + /// Inbound event queue + in: RingBuffer, + /// Outbound event queue + out: RingBuffer, + /// Inbound fd queue + fd_in: RingBuffer, + /// Outbound fd queue + fd_out: RingBuffer, + + /// Client-side state + client_state: *ClientState, + + /// Open client connection to host wayland compositor + pub fn open(arena: *Arena, env: os.Environ) Connection { + //------------------------------------------------------------------------- + // Allocate & Initialize Ring Buffers + //------------------------------------------------------------------------- + + // Allocating 3 pages -- 1 each for standard in/out, 1/2 each for fd in/out + var ringbuffers: [4]RingBuffer = undefined; + const ringbuffer_bytes = os.mem_reserve(ring_buffers_mmap_size); + + if (!os.mem_commit(ringbuffer_bytes)) + @panic("Failed to map pages for ring buffers!"); + + inline for (0..2) |i| { + const backing_bytes_rng_start = (i * ring_buffer_size); + const backing_bytes = + ringbuffer_bytes[backing_bytes_rng_start..][0..ring_buffer_size]; + + @memset(backing_bytes, 0); + ringbuffers[i] = .init_backing(backing_bytes); + + const fd_backing_bytes_rng_start = (2 * ring_buffer_size) + + (i * fd_ring_buffer_size); + const fd_backing_bytes = + ringbuffer_bytes[fd_backing_bytes_rng_start..][0..fd_ring_buffer_size]; + @memset(fd_backing_bytes, 0); + ringbuffers[i+2] = .init_backing(fd_backing_bytes); + } + + //------------------------------------------------------------------------- + + //------------------------------------------------------------------------- + // Retrieve Path & Connect to Socket + //------------------------------------------------------------------------- + + const scratch = Thread.Context.get_scratch(0, .{}).?; + const scratch_arena = scratch.arena; + defer scratch.end(); + + const xdg_runtime_dir = env.getPosix("XDG_RUNTIME_DIR").?; + const wayland_display = env.getPosix("WAYLAND_DISPLAY") + orelse "wayland-0"; + const socket_path = socket_path: { + const socket_path_len = xdg_runtime_dir.len + wayland_display.len + 1; + var joint_path_bytes = scratch_arena.push(u8, socket_path_len); + var joint_path_offset: usize = 0; + @memcpy( + joint_path_bytes[joint_path_offset..][0..xdg_runtime_dir.len], + xdg_runtime_dir, + ); + joint_path_offset += xdg_runtime_dir.len; + joint_path_bytes[joint_path_offset] = '/'; + joint_path_offset += 1; + @memcpy( + joint_path_bytes[joint_path_offset..][0..wayland_display.len], + wayland_display, + ); + break :socket_path joint_path_bytes; + }; + + const socket_fd = i32_(linux.socket( + linux.AF.UNIX, + linux.SOCK.STREAM | linux.SOCK.CLOEXEC, + 0, + )); + + const socket_addr = socket_addr: { + var addr: linux.sockaddr.un = .{ + .family = linux.AF.UNIX, + .path = @splat(0), + }; + + if (socket_path.len + 1 > addr.path.len) @panic("Socket Path Too Long"); + @memcpy(addr.path[0..socket_path.len], socket_path); + break :socket_addr addr; + }; + + const connect_rc = linux.connect( + socket_fd, + &socket_addr, + u32_(@sizeOf(@TypeOf(socket_addr))), + ); + if (transmute(isize, connect_rc) < 0) { + @panic("Failed to connec to wayland socket!"); + } + + //------------------------------------------------------------------------- + + //------------------------------------------------------------------------- + // Bind Registry & Globals, Initialize Client-Side Registry + //------------------------------------------------------------------------- + + const client_state = arena.create(ClientState); + const client_objects = arena.push(Object, 512); + const client_object_indices = arena.push(u32, 512); + const client_object_index_list: FreeIdxList = .init_backing(client_object_indices); + client_state.object_pool = .{ + .objects = client_objects, + .free_idx_list = client_object_index_list, + }; + + var connection: Connection = .{ + // Base Wayland Connection + .fd = socket_fd, + + // Base Wayland Connection I/O Management + .in = ringbuffers[0], + .out = ringbuffers[1], + .fd_in = ringbuffers[2], + .fd_out = ringbuffers[3], + + // Wayland State Management + .client_state = client_state, + }; + var conn_proxy = connection.proxy(); + + client_state.display = .fromInt(client_state.object_pool.next_object_id()); + client_state.object_pool.push_object(client_state.display.object()); + client_state.registry = client_state.display.get_registry(&conn_proxy); + + connection.flush() catch @panic("failed to write to wayland socket"); + + const GlobalsBound = packed struct (u8) { + wl_seat: bool = false, + wl_compositor: bool = false, + xdg_wm_base: bool = false, + wl_shm: bool = false, + xdg_decoration_manager: bool = false, + __reserved_bits: u3 = 0, + + pub fn match(a: @This(), b: @This()) bool { + return transmute(u8, a) == transmute(u8, b); + } + + pub const desired: @This() = .{ + .wl_seat = true, + .wl_compositor = true, + .xdg_wm_base = true, + .wl_shm = true, + .xdg_decoration_manager = true, + }; + }; + + var globals_bound: GlobalsBound = .{}; + // Loop until all desired globals are bound OR no more globals are available + while (true) { + const event = connection.peek_event(scratch_arena) orelse { + if (globals_bound.match(.desired)) + break; + + connection.flush() catch unreachable; + connection.load_events(); + + continue; + }; + switch (event) { + .wl_registry_global => |registry_global| { + defer connection.consume_event(); + + if (std.mem.eql(u8, Seat.Name, registry_global.interface)) { + connection.client_state.seat = connection.client_state.registry.bind( + &conn_proxy, + registry_global.name, + Seat, + registry_global.version, + ); + globals_bound.wl_seat = true; + } else if (std.mem.eql(u8, Compositor.Name, registry_global.interface)) { + connection.client_state.compositor = connection.client_state.registry.bind( + &conn_proxy, + registry_global.name, + Compositor, + registry_global.version, + ); + globals_bound.wl_compositor = true; + } else if (std.mem.eql(u8, Shm.Name, registry_global.interface)) { + connection.client_state.wl_shm = connection.client_state.registry.bind( + &conn_proxy, + registry_global.name, + Shm, + registry_global.version, + ); + globals_bound.wl_shm = true; + } else if (std.mem.eql(u8, XdgWmBase.Name, registry_global.interface)) { + connection.client_state.xdg_wm_base = connection.client_state.registry.bind( + &conn_proxy, + registry_global.name, + XdgWmBase, + registry_global.version, + ); + globals_bound.xdg_wm_base = true; + } else if (std.mem.eql(u8, XdgDecorationManager.Name, registry_global.interface)) { + connection.client_state.xdg_decoration_manager = connection.client_state.registry.bind( + &conn_proxy, + registry_global.name, + XdgDecorationManager, + registry_global.version, + ); + globals_bound.xdg_decoration_manager = true; + } + }, + .wl_display_error => |display_error| { + defer connection.consume_event(); + log.err( + "display error :: {{ obj_id={}, code={}, message=\"{s}\" }}", + .{ display_error.object_id, display_error.code, display_error.message }, + ); + }, + .wl_display_delete_id => |delete_id| { + defer connection.consume_event(); + log.debug("display requested delete_id :: {{ id={} }} ", .{delete_id.id}); + }, + else => { break; }, + } + } + + //------------------------------------------------------------------------- + + return connection; + } + + /// Close connection to host compositor and free ringbuffer memory + pub fn close(conn: *Connection) void { + //------------------------------------------------------------------------- + // Retrieve & Free Ring Buffer Backing Pages + //------------------------------------------------------------------------- + + const ring_buffer_bytes_len = ring_buffers_mmap_size; + const ring_buffer_bytes = transmute( + []align(os.page_size_min) u8, + conn.in.buf.ptr[0..ring_buffer_bytes_len] + ); + + os.mem_release(ring_buffer_bytes); + + //------------------------------------------------------------------------- + + // Disconnect from compositor + _ = linux.close(conn.fd); + } + + pub fn load_events(conn: *Connection) void { + const read = conn.in.mask(conn.in.read); + const write = conn.in.mask(conn.in.write); + + //------------------------------------------------------------------------- + // Prepare IOV Buffer(s) For Read + //------------------------------------------------------------------------- + + var iov: [2]linux.iovec = undefined; + var iov_len: usize = 1; + if (write < read) { + const iov_buf = conn.in.buf[write..read]; + iov[0].base = iov_buf.ptr; + iov[0].len = iov_buf.len; + } else if (read == 0) { + const iov_buf = conn.in.buf[write..]; + iov[0].base = iov_buf.ptr; + iov[0].len = iov_buf.len; + } else { + const iov_buf_0 = conn.in.buf[write..]; + iov[0].base = iov_buf_0.ptr; + iov[0].len = iov_buf_0.len; + const iov_buf_1 = conn.in.buf[0..read]; + iov[1].base = iov_buf_1.ptr; + iov[1].len = iov_buf_1.len; + iov_len = 2; + } + + //------------------------------------------------------------------------- + + //------------------------------------------------------------------------- + // Read Into Buffer(s) + //------------------------------------------------------------------------- + + var cmsg_buf: [cmsg_buf_len] u8 align(@alignOf(linux.cmsghdr)) = @splat(0); + var msg: linux.msghdr = .{ + .name = null, + .namelen = 0, + .iov = &iov, + .iovlen = iov_len, + .control = &cmsg_buf, + .controllen = cmsg_buf.len, + .flags = 0, + }; + + var rc: usize = linux.recvmsg( + conn.fd, + &msg, + linux.MSG.DONTWAIT, + ); + + while (linux.errno(rc) == .INTR) { + rc = linux.recvmsg( + conn.fd, + &msg, + linux.MSG.DONTWAIT, + ); + } + + const err = linux.errno(rc); + const bytes_read = if (transmute(isize, rc) < 0) + switch (err) { + .SUCCESS => return, + .AGAIN => return, + .INVAL => { log.err("EINVAL on socket read!", .{}); return; }, + .PIPE, .CONNRESET => @panic("Socket connection lost!"), + else => |e| {log.err("socket read failed with err :: {s}", .{@tagName(e)}); @panic("unknown err"); }, + } + else + u32_(rc); + + defer conn.in.write +%= bytes_read; + + //------------------------------------------------------------------------- + + //------------------------------------------------------------------------- + // Parse Control Messages + //------------------------------------------------------------------------- + + var cmsg_iter = linux.cmsghdr.iter(cmsg_buf[0..msg.controllen]); + while (cmsg_iter.next()) |cmsg_header| { + if (cmsg_header.level == linux.SOL.SOCKET and cmsg_header.type == linux.SCM.RIGHTS) { + const fd = cmsg_header.data(c_int).*; + conn.fd_in.putBytes(std.mem.asBytes(&fd)); + } + } + + //------------------------------------------------------------------------- + } + + pub fn peek_event(noalias conn: *Connection, noalias arena: *Arena) ?Event { + var conn_proxy = conn.proxy(); + + const event = if (!conn.in.empty()) wayland_event: { + const size = conn.in.size(); + + // not enough data for header + if (size < @sizeOf(WireEventHeader)) + break :wayland_event null; + + //----------------------------------------------------------------------- + // Parse Event Header + //----------------------------------------------------------------------- + + var header: WireEventHeader = .{ + .id = 0, + .op = 0, + .len = 0, + }; + const header_read_idx = conn.in.mask(conn.in.read); + + conn.in.getNBytesFrom(header_read_idx, @sizeOf(WireEventHeader), + std.mem.asBytes(&header)); + + //----------------------------------------------------------------------- + + //----------------------------------------------------------------------- + // Parse Event Body + //----------------------------------------------------------------------- + + // not enough data for whole event + if (size < header.len) + break :wayland_event null; + + const data_read_idx = conn.in.mask( + header_read_idx + @sizeOf(WireEventHeader)); + const data_len = header.len - @sizeOf(WireEventHeader); + + const scratch = Thread.Context.get_scratch(1, .{arena}).?; + const scratch_arena = scratch.arena; + defer scratch.end(); + + const data_bytes = scratch_arena.push(u8, data_len); + conn.in.getNBytesFrom( + data_read_idx, data_len, data_bytes); + + const relevant_object = conn.client_state.object_pool.get(header.id); + const wayland_event = relevant_object.message_decode( + &conn_proxy, + header.op, + data_bytes, + ); + + if (wayland_event == .wl_callback_done) + log.debug( + "received response on callback object of id: {}", + .{transmute(*const u32, relevant_object).*}, + ); + + break :wayland_event wayland_event; + } else null; + + return event; + } + + pub fn get_event(noalias conn: *Connection, noalias arena: *Arena) ?Event { + const event = conn.peek_event(arena) orelse return null; + conn.consume_event(); + return event; + } + + pub fn consume_event(conn: *Connection) void { + var header: WireEventHeader = .{ + .id = 0, + .op = 0, + .len = 0, + }; + + const header_read_idx = conn.in.mask(conn.in.read); + conn.in.getNBytesFrom(header_read_idx, @sizeOf(WireEventHeader), + std.mem.asBytes(&header)); + + conn.in.read +%= header.len; + } + + pub fn flush(conn: *Connection) !void { + const out_read_start = conn.out.read; + const out_read = conn.out.mask(conn.out.read); + const out_write = conn.out.mask(conn.out.write); + + //------------------------------------------------------------------------- + // Prepare outgoing iovecs + //------------------------------------------------------------------------- + + var iov: [2]linux.iovec = undefined; + var iov_len: usize = 1; + + if (conn.out.read == conn.out.write) { + iov_len = 0; + } else if (out_read < out_write) { + const iov_buf = conn.out.buf[out_read..out_write]; + iov[0].base = iov_buf.ptr; + iov[0].len = iov_buf.len; + conn.out.read +%= u32_(iov_buf.len); + } else if (out_write == 0) { + const iov_buf = conn.out.buf[out_read..]; + iov[0].base = iov_buf.ptr; + iov[0].len = iov_buf.len; + conn.out.read +%= u32_(iov_buf.len); + } else { + const iov_buf_0 = conn.out.buf[out_read..]; + iov[0].base = iov_buf_0.ptr; + iov[0].len = iov_buf_0.len; + + const iov_buf_1 = conn.out.buf[0..out_write]; + iov[1].base = iov_buf_1.ptr; + iov[1].len = iov_buf_1.len; + iov_len = 2; + + conn.out.read +%= u32_(iov_buf_0.len + iov_buf_1.len); + } + + const bytes_to_write = conn.out.read - out_read_start; + + //------------------------------------------------------------------------- + + //------------------------------------------------------------------------- + // Prepare outgoing control messages + //------------------------------------------------------------------------- + + var cmsg: [cmsg_buf_len]u8 = undefined; + var cmsg_len: usize = 0; + + const c_int_size = @sizeOf(c_int); + const fd_cmsg_t = linux.cmsg(c_int); + const ctrlmsg_size = @sizeOf(fd_cmsg_t); + + while (!conn.fd_out.empty()) { + const fd_out_read = conn.fd_out.mask(conn.fd_out.read); + + var fd_out: c_int = -1; + conn.fd_out.getNBytesFrom(fd_out_read, @sizeOf(c_int), + std.mem.asBytes(&fd_out)); + + const control_msg: fd_cmsg_t = .init( + linux.SOL.SOCKET, + linux.SCM.RIGHTS, + fd_out, + ); + + @memcpy( + cmsg[cmsg_len..][0..ctrlmsg_size], + std.mem.asBytes(&control_msg), + ); + + conn.fd_out.read +%= u32_(c_int_size); + cmsg_len += ctrlmsg_size; + } + + //------------------------------------------------------------------------- + + //------------------------------------------------------------------------- + // Construct and send message + //------------------------------------------------------------------------- + + const msg: linux.msghdr_const = .{ + .name = null, + .namelen = 0, + .iov = @ptrCast(&iov), + .iovlen = iov_len, + .control = &cmsg, + .controllen = cmsg_len, + .flags = 0, + }; + + var written: usize = 0; + var rc: isize = -1; + var errno: linux.E = .AGAIN; + read: while (rc < 0 and (errno == .AGAIN or errno == .INTR)) { + const val = linux.sendmsg( + conn.fd, + &msg, + 0, + ); + + rc = transmute(isize, val); + + if (rc < 0) { + errno = linux.errno(@bitCast(rc)); + if (errno == .INTR) written += cast(usize, -rc); + continue :read; + } + } + + + if (rc < 0) { + log.err( + "Failed to write to socket! :: {s}", + .{ @tagName(linux.errno(u32_(-rc))) }, + ); + } + + written += cast(usize, rc); + + base.DebugAssert( + written == bytes_to_write, + "bytes_written should match bytes_to_write!", + ); + + //------------------------------------------------------------------------- + } + + pub fn proxy(conn: *Connection) Proxy { + return .{ + .ctx = conn, + .vtable = .{ + .message_decode = msg_decode, + .message_encode = msg_encode, + .get_id = next_id, + .put_object = obj_push, + .destroy_object = obj_destroy, + }, + }; + } + + fn msg_decode(noalias ctx: *anyopaque, args_out: []MessageArg, noalias data: []const u8) void { + const connection = transmute(*Connection, ctx); + var offset: u32 = 0; + + for (args_out) |*arg| { + switch (arg.*) { + .fd => |*arg_fd| { + arg_fd.* = connection.next_fd(); + }, + .uint, .object, .new_id => |*uint_arg| { + uint_arg.* = std.mem.bytesToValue(u32, data[offset..][0..4]); + offset += 4; + }, + .int => |*int_arg| { + int_arg.* = std.mem.bytesToValue(i32, data[offset..][0..4]); + offset += 4; + }, + .@"enum" => |*enum_arg| { + const int_ptr = transmute(*u32, enum_arg); + int_ptr.* = std.mem.bytesToValue(u32, data[offset..][0..4]); + offset += 4; + }, + .fixed => |*fixed_arg| { + const int_val = std.mem.bytesToValue(i32, data[offset..][0..4]); + offset += 4; + fixed_arg.* = f32_(int_val) / 256; + }, + .string => |*string_arg| { + const str_len = std.mem.bytesToValue(u32, data[offset..][0..4]); + offset += 4; + string_arg.* = @ptrCast(data[offset..][0..(str_len - 1):0]); + + const rounded_len = math.div_roundup(str_len, 4); + offset += rounded_len; + }, + .array => |*array_arg| { + const arr_len = std.mem.bytesToValue(u32, data[offset..][0..4]); + offset += 4; + const rounded_len = math.div_roundup(arr_len, 4); + array_arg.* = data[offset..][0..arr_len]; + offset += rounded_len; + }, + } + } + } + + fn msg_encode(noalias ctx: *anyopaque, id: u32, op: u16, noalias args: []const ?MessageArg) void { + const connection = transmute(*Connection, ctx); + + var msg_len: u16 = @sizeOf(WireEventHeader); + for (args) |arg_opt| { + if (arg_opt) |arg| switch (arg) { + .int, .uint, .fixed, .object, .new_id, .@"enum" => msg_len += @sizeOf(u32), + .string => |string_arg| msg_len += msg_str_len(string_arg), + .array => |array_arg| msg_len += msg_arr_len(array_arg), + .fd => {}, + } else { + msg_len += @sizeOf(u32); + } + } + if (!connection.out.empty() and connection.out.size() < msg_len) { + connection.flush() catch |err| { + log.err("Connection flush failed due to err :: {s}", .{@errorName(err)}); + }; + } + + const header: WireEventHeader = .{ + .id = id, + .op = op, + .len = msg_len, + }; + connection.out.putBytes(std.mem.asBytes(&header)); + + for (args) |arg_opt| { + if (arg_opt) |arg| arg: switch (arg) { + .uint, .new_id, .object => |uint_arg| { + connection.out.putBytes(std.mem.asBytes(&uint_arg)); + }, + .int => |int_arg| { + continue :arg .{ .uint = transmute(u32, int_arg) }; + }, + .@"enum" => |*enum_arg| { + const u32_val = transmute(*const u32, enum_arg); + continue :arg .{ .uint = u32_val.* }; + }, + .fixed => |float_arg| { + const val = transmute(i32, float_arg * 256); + continue :arg .{ .uint = transmute(u32, val) }; + }, + .string => |string_arg| { + continue :arg .{ .array = string_arg[0 .. string_arg.len + 1] }; + }, + .array => |array_arg| { + const padding_bytes: [4]u8 = @splat(0); + + const write_len = u32_(msg_arr_len(array_arg)); + const len = u32_(array_arg.len); + const padding_bytes_needed = (write_len - @sizeOf(u32)) - len; + + connection.out.putBytes(std.mem.asBytes(&len)); + connection.out.putBytes(array_arg); + connection.out.putBytes(padding_bytes[0..padding_bytes_needed]); + }, + .fd => |fd_arg| { + connection.fd_out.putBytes(std.mem.asBytes(&fd_arg)); + }, + } else { + const null_value: u32 = 0; + connection.out.putBytes(std.mem.asBytes(&null_value)); + } + } + } + + fn next_id(noalias ctx: *anyopaque) u32 { + const conn = transmute(*Connection, ctx); + const idx = conn.client_state.object_pool.next_object_id(); + return idx; + } + + fn next_fd(conn: *Connection) i32 { + var fd: i32 = -1; + const read = conn.fd_in.mask(conn.fd_in.read); + @memcpy( + std.mem.asBytes(&fd), + conn.fd_in.buf[read..][0..@sizeOf(i32)], + ); + conn.fd_in.read +%= @sizeOf(i32); + + return fd; + } + + fn obj_destroy(noalias ctx: *anyopaque, object_id: u32) void { + const conn = transmute(*Connection, ctx); + conn.client_state.object_pool.release_object(object_id); + } + + fn obj_push(noalias ctx: *anyopaque, object: Object) void { + const conn = transmute(*Connection, ctx); + conn.client_state.object_pool.push_object(object); + } + + inline fn msg_str_len(str: [:0]const u8) u16 { + return msg_arr_len(str[0 .. str.len + 1]); + } + + inline fn msg_arr_len(arr: []const u8) u16 { + return u16_(math.div_roundup(@sizeOf(u32) + arr.len, @sizeOf(u32))); + } + + const cmsg_buf_len = 32 * linux.cmsghdr.msg_len(@sizeOf(c_int)); + const ring_buffers_mmap_size: usize = (2 * default_ring_buffer_size + + 2 * default_fd_ring_buffer_size); + const ring_buffer_size: usize = default_ring_buffer_size; + const fd_ring_buffer_size: usize = default_fd_ring_buffer_size; + const default_ring_buffer_size = 4096; + const default_fd_ring_buffer_size = 2048; +}; + +pub const ClientState = struct { + // Base Wayland Connection + display: Display, + registry: Registry, + + // Globals + seat: Seat, + compositor: Compositor, + wl_shm: Shm, + xdg_wm_base: XdgWmBase, + xdg_decoration_manager: XdgDecorationManager, + + // Wayland Objects + object_pool: ObjectPool, + + // Runtime Compositor Information + seat_info: SeatInfo = .{}, + + const SeatInfo = struct { + name: [512]u8 = undefined, + capabilities: Seat.Capability = .{}, + }; +}; + +pub const ObjectPool = struct { + objects: []Object, + free_idx_list: FreeIdxList, + + fn id_to_idx(id: u32) u32 { + return id-1; + } + pub fn next_object_id(op: *ObjectPool) u32 { + return op.free_idx_list.pull(); + } + + pub fn push_object(op: *ObjectPool, object: Object) void { + const idx = transmute(*const u32, &object); + + op.objects[ id_to_idx(idx.*) ] = object; + } + + pub fn release_object(op: *ObjectPool, object_id: u32) void { + op.objects[ id_to_idx(object_id) ] = undefined; + op.free_idx_list.push(object_id); + } + + pub fn get(op: *ObjectPool, object_id: u32) *Object { + return &op.objects[id_to_idx(object_id)]; + } +}; + +const FreeIdxList = struct { + indices: []u32, + index_available: u32, + + pub fn init_backing(buf: []u32) FreeIdxList { + for (buf, 0..) |*index, i| { + index.* = u32_(buf.len - i); + } + + return .{ + .indices = buf, + .index_available = u32_(buf.len), + }; + } + + pub fn pull(fil: *FreeIdxList) u32 { + fil.index_available -= 1; + return fil.indices[fil.index_available]; + } + + pub fn push(fil: *FreeIdxList, index: u32) void { + base.DebugAssert( + fil.index_available < fil.indices.len, + "Index Queue Already Full", + ); + + fil.indices[fil.index_available] = index; + fil.index_available += 1; + } +}; + +pub const ShmPool = struct { + proxy: Proxy, + wl_shm_pool: WaylandShmPool, + buffer: []u32, + fd: c_int, + + pub fn create( + conn: *Connection, + width: i32, + height:i32, + ) ShmPool { + const shm = conn.client_state.wl_shm; + + const shm_fd = i32_(linux.memfd_create("wl-shm", 0)); + var proxy = conn.proxy(); + + const img_stride = width * 4; + const img_size = height * img_stride; + _ = linux.ftruncate( + shm_fd, + img_size, + ); + + const rc = linux.mmap( + null, + base.usize_(img_size), + .{ .READ = true, .WRITE = true }, + .{ .TYPE = .SHARED }, + shm_fd, + 0 + ); + + const irc = transmute(isize, rc); + if (irc < 0) { + log.err("failed to map in shmfile memory!", .{}); + } + + const ptr: []u8 = transmute([*]u8, rc)[0..base.usize_(img_size)]; + const img_buffer = transmute([]u32, ptr); + + const shm_pool = shm.create_pool( + &proxy, + shm_fd, + img_size, + ); + + return .{ + .proxy = proxy, + .fd = shm_fd, + .buffer = img_buffer, + .wl_shm_pool = shm_pool, + }; + } + + pub fn create_buffer( + pool: *ShmPool, + width: i32, + height: i32, + format: Shm.Format, + ) WaylandBuffer { + @memset(pool.buffer, 0xefefefef); + return pool.wl_shm_pool.create_buffer( + &pool.proxy, + 0, + width, + height, + width*4, + format, + ); + } +}; + +const WireEventHeader = packed struct (u64) { + id: u32, + op: u16, + len: u16, +}; + +// Object Aliases +pub const WaylandBuffer = wl_protocols.wl_buffer; +pub const WaylandSurface = wl_protocols.wl_surface; +pub const XdgSurface = wl_protocols.xdg_surface; +pub const XdgToplevel = wl_protocols.xdg_toplevel; +pub const WaylandShmPool = wl_protocols.wl_shm_pool; +pub const LinuxDmabufFeedback = wl_protocols.zwp_linux_dmabuf_feedback_v1; + +pub const Display = wl_protocols.wl_display; +pub const Registry = wl_protocols.wl_registry; +pub const XdgDecoration = wl_protocols.zxdg_toplevel_decoration_v1; + +// Registry Global Aliases +pub const Shm = wl_protocols.wl_shm; +pub const Seat = wl_protocols.wl_seat; +pub const XdgWmBase = wl_protocols.xdg_wm_base; +pub const Compositor = wl_protocols.wl_compositor; +pub const XdgDecorationManager = wl_protocols.zxdg_decoration_manager_v1; + + +// Wayland Base Type Aliases +pub const Proxy = wl_protocols.Proxy; +pub const Object = wl_protocols.Object; +pub const Event = wl_protocols.Event; +pub const MessageArg = wl_protocols.MessageArg; + +const wl_protocols = @import("wayland-protocols"); + +const log = std.log.scoped(.wayland); + +const Arena = base.Arena; +const Thread = base.Thread; +const RingBuffer = base.RingBuffer; + +const u16_ = base.u16_; +const u32_ = base.u32_; +const i32_ = base.i32_; +const f32_ = base.f32_; + +const cast = base.casts.cast; +const transmute = base.casts.transmute; + +const math = base.math; +const linux = os.linux; + +const os = @import("os"); +const base = @import("base"); +const std = @import("std");