init
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
/zig-out/
|
||||
/.zig-cache/
|
||||
@@ -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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
];
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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
@@ -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");
|
||||
Reference in New Issue
Block a user