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