Compare commits
10 Commits
e66fbb225b
...
847051ebc3
Author | SHA1 | Date |
---|---|---|
Louis Pearson | 847051ebc3 | |
Louis Pearson | 10475dd036 | |
Louis Pearson | c4c0880eff | |
LeRoyce Pearson | 4a8254cca6 | |
LeRoyce Pearson | c3ca5aa772 | |
LeRoyce Pearson | 0042b56b93 | |
LeRoyce Pearson | 50a9774a07 | |
LeRoyce Pearson | 13b0ea6a6f | |
LeRoyce Pearson | 1542a95499 | |
LeRoyce Pearson | 47c57a5b87 |
|
@ -0,0 +1,5 @@
|
|||
# Zig Wayland Wire
|
||||
|
||||
This is library implements the Wayland protocol in pure zig, with a goal of presenting low level details instead of
|
||||
abstracting them away.
|
||||
|
10
build.zig
10
build.zig
|
@ -27,8 +27,16 @@ pub fn build(b: *std.Build) void {
|
|||
const test_step = b.step("test", "Run library tests");
|
||||
test_step.dependOn(&run_main_tests.step);
|
||||
|
||||
const client_connect_raw_exe = b.addExecutable(.{
|
||||
.name = "00_client_connect",
|
||||
.root_source_file = .{ .path = "examples/00_client_connect.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
b.installArtifact(client_connect_raw_exe);
|
||||
|
||||
const client_connect_exe = b.addExecutable(.{
|
||||
.name = "client_connect",
|
||||
.name = "01_client_connect",
|
||||
.root_source_file = .{ .path = "examples/01_client_connect.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
|
|
|
@ -0,0 +1,556 @@
|
|||
const std = @import("std");
|
||||
|
||||
/// The version of the wl_shm protocol we will be targeting.
|
||||
const WL_SHM_VERSION = 1;
|
||||
/// The version of the wl_compositor protocol we will be targeting.
|
||||
const WL_COMPOSITOR_VERSION = 5;
|
||||
/// The version of the xdg_wm_base protocol we will be targeting.
|
||||
const XDG_WM_BASE_VERSION = 2;
|
||||
|
||||
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:ack_configure
|
||||
const XDG_SURFACE_REQUEST_ACK_CONFIGURE = 4;
|
||||
|
||||
// https://wayland.app/protocols/wayland#wl_registry:event:global
|
||||
const WL_REGISTRY_EVENT_GLOBAL = 0;
|
||||
|
||||
pub fn main() !void {
|
||||
var general_allocator = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = general_allocator.deinit();
|
||||
const gpa = general_allocator.allocator();
|
||||
|
||||
const display_path = try getDisplayPath(gpa);
|
||||
defer gpa.free(display_path);
|
||||
|
||||
const socket = try std.net.connectUnixSocket(display_path);
|
||||
defer socket.close();
|
||||
|
||||
const display_id = 1;
|
||||
var next_id: u32 = 2;
|
||||
|
||||
// reserve an object id for the registry
|
||||
const registry_id = next_id;
|
||||
next_id += 1;
|
||||
|
||||
try socket.writeAll(std.mem.sliceAsBytes(&[_]u32{
|
||||
// ID of the object; in this case the default wl_display object at 1
|
||||
1,
|
||||
|
||||
// The size (in bytes) of the message and the opcode, which is object specific.
|
||||
// In this case we are using opcode 1, which corresponds to `wl_display::get_registry`.
|
||||
//
|
||||
// The size includes the size of the header.
|
||||
(0x000C << 16) | (0x0001),
|
||||
|
||||
// Finally, we pass in the only argument that this opcode takes: an id for the `wl_registry`
|
||||
// we are creating.
|
||||
registry_id,
|
||||
}));
|
||||
|
||||
// create a sync callback so we know when we are caught up with the server
|
||||
const registry_done_callback_id = next_id;
|
||||
next_id += 1;
|
||||
|
||||
try socket.writeAll(std.mem.sliceAsBytes(&[_]u32{
|
||||
display_id,
|
||||
|
||||
// The size (in bytes) of the message and the opcode.
|
||||
// In this case we are using opcode 0, which corresponds to `wl_display::sync`.
|
||||
//
|
||||
// The size includes the size of the header.
|
||||
(0x000C << 16) | (0x0000),
|
||||
|
||||
// Finally, we pass in the only argument that this opcode takes: an id for the `wl_registry`
|
||||
// we are creating.
|
||||
registry_done_callback_id,
|
||||
}));
|
||||
|
||||
var shm_id_opt: ?u32 = null;
|
||||
var compositor_id_opt: ?u32 = null;
|
||||
var xdg_wm_base_id_opt: ?u32 = null;
|
||||
|
||||
// How do we know that the opcode for WL_REGISTRY_REQUEST is 0? Because it is the first `request` in the protocol for `wl_registry`.
|
||||
const WL_REGISTRY_REQUEST_BIND = 0;
|
||||
|
||||
var message_buffer = std.ArrayList(u8).init(gpa);
|
||||
defer message_buffer.deinit();
|
||||
while (true) {
|
||||
const event = try Event.read(socket, &message_buffer);
|
||||
|
||||
// Parse event messages based on which object it is for
|
||||
if (event.header.object_id == registry_done_callback_id) {
|
||||
// No need to parse the message body, there is only one possible opcode
|
||||
break;
|
||||
}
|
||||
|
||||
if (event.header.object_id == registry_id and event.header.opcode == WL_REGISTRY_EVENT_GLOBAL) {
|
||||
// Parse out the fields of the global event
|
||||
const name: u32 = @bitCast(event.body[0..4].*);
|
||||
|
||||
const interface_str_len: u32 = @bitCast(event.body[4..8].*);
|
||||
// The interface_str is `interface_str_len - 1` because `interface_str_len` includes the null pointer
|
||||
const interface_str: [:0]const u8 = event.body[8..][0 .. interface_str_len - 1 :0];
|
||||
|
||||
const interface_str_len_u32_align = std.mem.alignForward(u32, interface_str_len, @alignOf(u32));
|
||||
const version: u32 = @bitCast(event.body[8 + interface_str_len_u32_align ..][0..4].*);
|
||||
|
||||
// Check to see if the interface is one of the globals we are looking for
|
||||
if (std.mem.eql(u8, interface_str, "wl_shm")) {
|
||||
if (version < WL_SHM_VERSION) {
|
||||
std.log.err("compositor supports only {s} version {}, client expected version >= {}", .{ interface_str, version, WL_SHM_VERSION });
|
||||
return error.WaylandInterfaceOutOfDate;
|
||||
}
|
||||
shm_id_opt = next_id;
|
||||
next_id += 1;
|
||||
|
||||
try writeRequest(socket, registry_id, WL_REGISTRY_REQUEST_BIND, &[_]u32{
|
||||
// The numeric name of the global we want to bind.
|
||||
name,
|
||||
|
||||
// `new_id` arguments have three parts when the sub-type is not specified by the protocol:
|
||||
// 1. A string specifying the textual name of the interface
|
||||
"wl_shm".len + 1, // length of "wl_shm" plus one for the required null byte
|
||||
@bitCast(@as([4]u8, "wl_s".*)),
|
||||
@bitCast(@as([4]u8, "hm\x00\x00".*)), // we have two 0x00 bytes to align the string with u32
|
||||
|
||||
// 2. The version you are using, affects which functions you can access
|
||||
WL_SHM_VERSION,
|
||||
|
||||
// 3. And the `new_id` part, where we tell it which client id we are giving it
|
||||
shm_id_opt.?,
|
||||
});
|
||||
} else if (std.mem.eql(u8, interface_str, "wl_compositor")) {
|
||||
if (version < WL_COMPOSITOR_VERSION) {
|
||||
std.log.err("compositor supports only {s} version {}, client expected version >= {}", .{ interface_str, version, WL_COMPOSITOR_VERSION });
|
||||
return error.WaylandInterfaceOutOfDate;
|
||||
}
|
||||
compositor_id_opt = next_id;
|
||||
next_id += 1;
|
||||
|
||||
try writeRequest(socket, registry_id, WL_REGISTRY_REQUEST_BIND, &[_]u32{
|
||||
name,
|
||||
"wl_compositor".len + 1, // add one for the required null byte
|
||||
@bitCast(@as([4]u8, "wl_c".*)),
|
||||
@bitCast(@as([4]u8, "ompo".*)),
|
||||
@bitCast(@as([4]u8, "sito".*)),
|
||||
@bitCast(@as([4]u8, "r\x00\x00\x00".*)),
|
||||
WL_COMPOSITOR_VERSION,
|
||||
compositor_id_opt.?,
|
||||
});
|
||||
} else if (std.mem.eql(u8, interface_str, "xdg_wm_base")) {
|
||||
if (version < XDG_WM_BASE_VERSION) {
|
||||
std.log.err("compositor supports only {s} version {}, client expected version >= {}", .{ interface_str, version, XDG_WM_BASE_VERSION });
|
||||
return error.WaylandInterfaceOutOfDate;
|
||||
}
|
||||
xdg_wm_base_id_opt = next_id;
|
||||
next_id += 1;
|
||||
|
||||
try writeRequest(socket, registry_id, WL_REGISTRY_REQUEST_BIND, &[_]u32{
|
||||
name,
|
||||
"xdg_wm_base".len + 1,
|
||||
@bitCast(@as([4]u8, "xdg_".*)),
|
||||
@bitCast(@as([4]u8, "wm_b".*)),
|
||||
@bitCast(@as([4]u8, "ase\x00".*)),
|
||||
XDG_WM_BASE_VERSION,
|
||||
xdg_wm_base_id_opt.?,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const shm_id = shm_id_opt orelse return error.NeccessaryWaylandExtensionMissing;
|
||||
const compositor_id = compositor_id_opt orelse return error.NeccessaryWaylandExtensionMissing;
|
||||
const xdg_wm_base_id = xdg_wm_base_id_opt orelse return error.NeccessaryWaylandExtensionMissing;
|
||||
|
||||
std.log.debug("wl_shm client id = {}; wl_compositor client id = {}; xdg_wm_base client id = {}", .{ shm_id, compositor_id, xdg_wm_base_id });
|
||||
|
||||
// Create a surface using wl_compositor::create_surface
|
||||
const surface_id = next_id;
|
||||
next_id += 1;
|
||||
// https://wayland.app/protocols/wayland#wl_compositor:request:create_surface
|
||||
const WL_COMPOSITOR_REQUEST_CREATE_SURFACE = 0;
|
||||
try writeRequest(socket, compositor_id, WL_COMPOSITOR_REQUEST_CREATE_SURFACE, &[_]u32{
|
||||
// id: new_id<wl_surface>
|
||||
surface_id,
|
||||
});
|
||||
|
||||
// Create an xdg_surface
|
||||
const xdg_surface_id = next_id;
|
||||
next_id += 1;
|
||||
// https://wayland.app/protocols/xdg-shell#xdg_wm_base:request:get_xdg_surface
|
||||
const XDG_WM_BASE_REQUEST_GET_XDG_SURFACE = 2;
|
||||
try writeRequest(socket, xdg_wm_base_id, XDG_WM_BASE_REQUEST_GET_XDG_SURFACE, &[_]u32{
|
||||
// id: new_id<xdg_surface>
|
||||
xdg_surface_id,
|
||||
// surface: object<wl_surface>
|
||||
surface_id,
|
||||
});
|
||||
|
||||
// Get the xdg_surface as an xdg_toplevel object
|
||||
const xdg_toplevel_id = next_id;
|
||||
next_id += 1;
|
||||
// https://wayland.app/protocols/xdg-shell#xdg_surface:request:get_toplevel
|
||||
const XDG_SURFACE_REQUEST_GET_TOPLEVEL = 1;
|
||||
try writeRequest(socket, xdg_surface_id, XDG_SURFACE_REQUEST_GET_TOPLEVEL, &[_]u32{
|
||||
// id: new_id<xdg_surface>
|
||||
xdg_toplevel_id,
|
||||
});
|
||||
|
||||
// Commit the surface. This tells the compositor that the current batch of
|
||||
// changes is ready, and they can now be applied.
|
||||
|
||||
// https://wayland.app/protocols/wayland#wl_surface:request:commit
|
||||
const WL_SURFACE_REQUEST_COMMIT = 6;
|
||||
try writeRequest(socket, surface_id, WL_SURFACE_REQUEST_COMMIT, &[_]u32{});
|
||||
|
||||
// Wait for the surface to be configured before moving on
|
||||
while (true) {
|
||||
const event = try Event.read(socket, &message_buffer);
|
||||
|
||||
if (event.header.object_id == xdg_surface_id) {
|
||||
switch (event.header.opcode) {
|
||||
// https://wayland.app/protocols/xdg-shell#xdg_surface:event:configure
|
||||
0 => {
|
||||
// The configure event acts as a heartbeat. Every once in a while the compositor will send us
|
||||
// a `configure` event, and if our application doesn't respond with an `ack_configure` response
|
||||
// it will assume our program has died and destroy the window.
|
||||
const serial: u32 = @bitCast(event.body[0..4].*);
|
||||
|
||||
try writeRequest(socket, xdg_surface_id, XDG_SURFACE_REQUEST_ACK_CONFIGURE, &[_]u32{
|
||||
// We respond with the number it sent us, so it knows which configure we are responding to.
|
||||
serial,
|
||||
});
|
||||
|
||||
try writeRequest(socket, surface_id, WL_SURFACE_REQUEST_COMMIT, &[_]u32{});
|
||||
|
||||
// The surface has been configured! We can move on
|
||||
break;
|
||||
},
|
||||
else => return error.InvalidOpcode,
|
||||
}
|
||||
} else {
|
||||
std.log.warn("unknown event {{ .object_id = {}, .opcode = {x}, .message = \"{}\" }}", .{ event.header.object_id, event.header.opcode, std.zig.fmtEscapes(std.mem.sliceAsBytes(event.body)) });
|
||||
}
|
||||
}
|
||||
|
||||
// allocate a shared memory file, which we will use as a framebuffer to write pixels into
|
||||
const Pixel = [4]u8;
|
||||
const framebuffer_size = [2]usize{ 128, 128 };
|
||||
const shared_memory_pool_len = framebuffer_size[0] * framebuffer_size[1] * @sizeOf(Pixel);
|
||||
|
||||
const shared_memory_pool_fd = try std.os.memfd_create("my-wayland-framebuffer", 0);
|
||||
try std.os.ftruncate(shared_memory_pool_fd, shared_memory_pool_len);
|
||||
|
||||
// Create a wl_shm_pool (wayland shared memory pool). This will be used to create framebuffers,
|
||||
// though in this article we only plan on creating one.
|
||||
const wl_shm_pool_id = try writeWlShmRequestCreatePool(
|
||||
socket,
|
||||
shm_id,
|
||||
&next_id,
|
||||
shared_memory_pool_fd,
|
||||
@intCast(shared_memory_pool_len),
|
||||
);
|
||||
|
||||
// Now we allocate a framebuffer from the shared memory pool
|
||||
const wl_buffer_id = next_id;
|
||||
next_id += 1;
|
||||
|
||||
// https://wayland.app/protocols/wayland#wl_shm_pool:request:create_buffer
|
||||
const WL_SHM_POOL_REQUEST_CREATE_BUFFER = 0;
|
||||
// https://wayland.app/protocols/wayland#wl_shm:enum:format
|
||||
const WL_SHM_POOL_ENUM_FORMAT_ARGB8888 = 0;
|
||||
try writeRequest(socket, wl_shm_pool_id, WL_SHM_POOL_REQUEST_CREATE_BUFFER, &[_]u32{
|
||||
// id: new_id<wl_buffer>,
|
||||
wl_buffer_id,
|
||||
// Byte offset of the framebuffer in the pool. In this case we allocate it at the very start of the file.
|
||||
0,
|
||||
// Width of the framebuffer.
|
||||
framebuffer_size[0],
|
||||
// Height of the framebuffer.
|
||||
framebuffer_size[1],
|
||||
// Stride of the framebuffer, or rather, how many bytes are in a single row of pixels.
|
||||
framebuffer_size[0] * @sizeOf(Pixel),
|
||||
// The format of the framebuffer. In this case we choose argb8888.
|
||||
WL_SHM_POOL_ENUM_FORMAT_ARGB8888,
|
||||
});
|
||||
|
||||
// Now we turn the shared memory pool and the framebuffer we just allocated into slices on our side for ease of use.
|
||||
const shared_memory_pool_bytes = try std.os.mmap(null, shared_memory_pool_len, std.os.PROT.READ | std.os.PROT.WRITE, std.os.MAP.SHARED, shared_memory_pool_fd, 0);
|
||||
const framebuffer = @as([*]Pixel, @ptrCast(shared_memory_pool_bytes.ptr))[0 .. shared_memory_pool_bytes.len / @sizeOf(Pixel)];
|
||||
|
||||
// put some interesting colors into the framebuffer
|
||||
for (0..framebuffer_size[1]) |y| {
|
||||
const row = framebuffer[y * framebuffer_size[0] .. (y + 1) * framebuffer_size[0]];
|
||||
for (row, 0..framebuffer_size[0]) |*pixel, x| {
|
||||
pixel.* = .{
|
||||
@truncate(x),
|
||||
@truncate(y),
|
||||
0x00,
|
||||
0xFF,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Now we attach the framebuffer to the surface at <0, 0>. The x and y MUST be <0, 0> since version 5 of WL_SURFACE,
|
||||
// which we are using.
|
||||
|
||||
// https://wayland.app/protocols/wayland#wl_surface:request:attach
|
||||
const WL_SURFACE_REQUEST_ATTACH = 1;
|
||||
try writeRequest(socket, surface_id, WL_SURFACE_REQUEST_ATTACH, &[_]u32{
|
||||
// buffer: object<wl_buffer>,
|
||||
wl_buffer_id,
|
||||
// The x offset of the buffer.
|
||||
0,
|
||||
// The y offset of the buffer.
|
||||
0,
|
||||
});
|
||||
|
||||
// We mark the surface as damaged, meaning that the compositor should update what is rendered on the window.
|
||||
// You can specify specific damage regions; but in this case we just damage the entire surface.
|
||||
|
||||
// https://wayland.app/protocols/wayland#wl_surface:request:damage
|
||||
const WL_SURFACE_REQUEST_DAMAGE = 2;
|
||||
try writeRequest(socket, surface_id, WL_SURFACE_REQUEST_DAMAGE, &[_]u32{
|
||||
// The x offset of the damage region.
|
||||
0,
|
||||
// The y offset of the damage region.
|
||||
0,
|
||||
// The width of the damage region.
|
||||
@bitCast(@as(i32, std.math.maxInt(i32))),
|
||||
// The height of the damage region.
|
||||
@bitCast(@as(i32, std.math.maxInt(i32))),
|
||||
});
|
||||
|
||||
// Commit the surface. This tells wayland that we are done making changes, and it can display all the changes that have been
|
||||
// made so far.
|
||||
// const WL_SURFACE_REQUEST_COMMIT = 6;
|
||||
try writeRequest(socket, surface_id, WL_SURFACE_REQUEST_COMMIT, &[_]u32{});
|
||||
|
||||
// Now we finally, finally, get to the main loop of the program.
|
||||
var running = true;
|
||||
while (running) {
|
||||
const event = try Event.read(socket, &message_buffer);
|
||||
|
||||
if (event.header.object_id == xdg_surface_id) {
|
||||
switch (event.header.opcode) {
|
||||
// https://wayland.app/protocols/xdg-shell#xdg_surface:event:configure
|
||||
0 => {
|
||||
// The configure event acts as a heartbeat. Every once in a while the compositor will send us
|
||||
// a `configure` event, and if our application doesn't respond with an `ack_configure` response
|
||||
// it will assume our program has died and destroy the window.
|
||||
const serial: u32 = @bitCast(event.body[0..4].*);
|
||||
|
||||
try writeRequest(socket, xdg_surface_id, XDG_SURFACE_REQUEST_ACK_CONFIGURE, &[_]u32{
|
||||
// We respond with the number it sent us, so it knows which configure we are responding to.
|
||||
serial,
|
||||
});
|
||||
try writeRequest(socket, surface_id, WL_SURFACE_REQUEST_COMMIT, &[_]u32{});
|
||||
},
|
||||
else => return error.InvalidOpcode,
|
||||
}
|
||||
} else if (event.header.object_id == xdg_toplevel_id) {
|
||||
switch (event.header.opcode) {
|
||||
// https://wayland.app/protocols/xdg-shell#xdg_toplevel:event:configure
|
||||
0 => {
|
||||
// The xdg_toplevel:configure event asks us to resize the window. For now, we will ignore it expect to
|
||||
// log it.
|
||||
const width: u32 = @bitCast(event.body[0..4].*);
|
||||
const height: u32 = @bitCast(event.body[4..8].*);
|
||||
const states_len: u32 = @bitCast(event.body[8..12].*);
|
||||
const states = @as([*]const u32, @ptrCast(@alignCast(event.body[12..].ptr)))[0..states_len];
|
||||
|
||||
std.log.debug("xdg_toplevel:configure({}, {}, {any})", .{ width, height, states });
|
||||
},
|
||||
// https://wayland.app/protocols/xdg-shell#xdg_toplevel:event:close
|
||||
1 => {
|
||||
// The compositor asked us to close the window.
|
||||
running = false;
|
||||
std.log.debug("xdg_toplevel:close()", .{});
|
||||
},
|
||||
// https://wayland.app/protocols/xdg-shell#xdg_toplevel:event:configure_bounds
|
||||
2 => std.log.debug("xdg_toplevel:configure_bounds()", .{}),
|
||||
// https://wayland.app/protocols/xdg-shell#xdg_toplevel:event:wm_capabilities
|
||||
3 => std.log.debug("xdg_toplevel:wm_capabilities()", .{}),
|
||||
else => return error.InvalidOpcode,
|
||||
}
|
||||
} else if (event.header.object_id == wl_buffer_id) {
|
||||
switch (event.header.opcode) {
|
||||
// https://wayland.app/protocols/wayland#wl_buffer:event:release
|
||||
0 => {
|
||||
// The xdg_toplevel:release event let's us know that it is safe to reuse the buffer now.
|
||||
std.log.debug("wl_buffer:release()", .{});
|
||||
},
|
||||
else => return error.InvalidOpcode,
|
||||
}
|
||||
} else if (event.header.object_id == display_id) {
|
||||
switch (event.header.opcode) {
|
||||
// https://wayland.app/protocols/wayland#wl_display:event:error
|
||||
0 => {
|
||||
const object_id: u32 = @bitCast(event.body[0..4].*);
|
||||
const error_code: u32 = @bitCast(event.body[4..8].*);
|
||||
const error_message_len: u32 = @bitCast(event.body[8..12].*);
|
||||
const error_message = event.body[12 .. error_message_len - 1 :0];
|
||||
std.log.warn("wl_display:error({}, {}, \"{}\")", .{ object_id, error_code, std.zig.fmtEscapes(error_message) });
|
||||
},
|
||||
// https://wayland.app/protocols/wayland#wl_display:event:delete_id
|
||||
1 => {
|
||||
// wl_display:delete_id tells us that we can reuse an id. In this article we log it, but
|
||||
// otherwise ignore it.
|
||||
const name: u32 = @bitCast(event.body[0..4].*);
|
||||
std.log.debug("wl_display:delete_id({})", .{name});
|
||||
},
|
||||
else => return error.InvalidOpcode,
|
||||
}
|
||||
} else {
|
||||
std.log.warn("unknown event {{ .object_id = {}, .opcode = {x}, .message = \"{}\" }}", .{ event.header.object_id, event.header.opcode, std.zig.fmtEscapes(std.mem.sliceAsBytes(event.body)) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getDisplayPath(gpa: std.mem.Allocator) ![]u8 {
|
||||
const xdg_runtime_dir_path = try std.process.getEnvVarOwned(gpa, "XDG_RUNTIME_DIR");
|
||||
defer gpa.free(xdg_runtime_dir_path);
|
||||
const display_name = std.process.getEnvVarOwned(gpa, "WAYLAND_DISPLAY") catch |err| switch (err) {
|
||||
error.EnvironmentVariableNotFound => return try std.fs.path.join(gpa, &.{ xdg_runtime_dir_path, "wayland-0" }),
|
||||
else => return err,
|
||||
};
|
||||
defer gpa.free(display_name);
|
||||
|
||||
return try std.fs.path.join(gpa, &.{ xdg_runtime_dir_path, display_name });
|
||||
}
|
||||
|
||||
/// A wayland packet header
|
||||
const Header = extern struct {
|
||||
object_id: u32 align(1),
|
||||
opcode: u16 align(1),
|
||||
size: u16 align(1),
|
||||
|
||||
pub fn read(socket: std.net.Stream) !Header {
|
||||
var header: Header = undefined;
|
||||
const header_bytes_read = try socket.readAll(std.mem.asBytes(&header));
|
||||
if (header_bytes_read < @sizeOf(Header)) {
|
||||
return error.UnexpectedEOF;
|
||||
}
|
||||
return header;
|
||||
}
|
||||
};
|
||||
|
||||
/// This is the general shape of a Wayland `Event` (a message from the compositor to the client).
|
||||
const Event = struct {
|
||||
header: Header,
|
||||
body: []const u8,
|
||||
|
||||
pub fn read(socket: std.net.Stream, body_buffer: *std.ArrayList(u8)) !Event {
|
||||
const header = try Header.read(socket);
|
||||
|
||||
// read bytes until we match the size in the header, not including the bytes in the header.
|
||||
try body_buffer.resize(header.size - @sizeOf(Header));
|
||||
const message_bytes_read = try socket.readAll(body_buffer.items);
|
||||
if (message_bytes_read < body_buffer.items.len) {
|
||||
return error.UnexpectedEOF;
|
||||
}
|
||||
|
||||
return Event{
|
||||
.header = header,
|
||||
.body = body_buffer.items,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Handles creating a header and writing the request to the socket.
|
||||
pub fn writeRequest(socket: std.net.Stream, object_id: u32, opcode: u16, message: []const u32) !void {
|
||||
const message_bytes = std.mem.sliceAsBytes(message);
|
||||
const header = Header{
|
||||
.object_id = object_id,
|
||||
.opcode = opcode,
|
||||
.size = @sizeOf(Header) + @as(u16, @intCast(message_bytes.len)),
|
||||
};
|
||||
|
||||
try socket.writeAll(std.mem.asBytes(&header));
|
||||
try socket.writeAll(message_bytes);
|
||||
}
|
||||
|
||||
/// https://wayland.app/protocols/wayland#wl_shm:request:create_pool
|
||||
const WL_SHM_REQUEST_CREATE_POOL = 0;
|
||||
|
||||
/// This request is more complicated that most other requests, because it has to send the file descriptor to the
|
||||
/// compositor using a control message.
|
||||
///
|
||||
/// Returns the id of the newly create wl_shm_pool
|
||||
pub fn writeWlShmRequestCreatePool(socket: std.net.Stream, wl_shm_id: u32, next_id: *u32, fd: std.os.fd_t, fd_len: i32) !u32 {
|
||||
const wl_shm_pool_id = next_id.*;
|
||||
|
||||
const message = [_]u32{
|
||||
// id: new_id<wl_shm_pool>
|
||||
wl_shm_pool_id,
|
||||
// size: int
|
||||
@intCast(fd_len),
|
||||
};
|
||||
// If you're paying close attention, you'll notice that our message only has two parameters in it, despite the
|
||||
// documentation calling for 3: wl_shm_pool_id, fd, and size. This is because `fd` is sent in the control message,
|
||||
// and so not included in the regular message body.
|
||||
|
||||
// Create the message header as usual
|
||||
const message_bytes = std.mem.sliceAsBytes(&message);
|
||||
const header = Header{
|
||||
.object_id = wl_shm_id,
|
||||
.opcode = WL_SHM_REQUEST_CREATE_POOL,
|
||||
.size = @sizeOf(Header) + @as(u16, @intCast(message_bytes.len)),
|
||||
};
|
||||
const header_bytes = std.mem.asBytes(&header);
|
||||
|
||||
// we'll be using `std.os.sendmsg` to send a control message, so we may as well use the vectorized
|
||||
// IO to send the header and the message body while we're at it.
|
||||
const msg_iov = [_]std.os.iovec_const{
|
||||
.{
|
||||
.iov_base = header_bytes.ptr,
|
||||
.iov_len = header_bytes.len,
|
||||
},
|
||||
.{
|
||||
.iov_base = message_bytes.ptr,
|
||||
.iov_len = message_bytes.len,
|
||||
},
|
||||
};
|
||||
|
||||
// Send the file descriptor through a control message
|
||||
|
||||
// This is the control message! It is not a fixed size struct. Instead it varies depending on the message you want to send.
|
||||
// C uses macros to define it, here we make a comptime function instead.
|
||||
const control_message = cmsg(std.os.fd_t){
|
||||
.level = std.os.SOL.SOCKET,
|
||||
.type = 0x01, // value of SCM_RIGHTS
|
||||
.data = fd,
|
||||
};
|
||||
const control_message_bytes = std.mem.asBytes(&control_message);
|
||||
|
||||
const socket_message = std.os.msghdr_const{
|
||||
.name = null,
|
||||
.namelen = 0,
|
||||
.iov = &msg_iov,
|
||||
.iovlen = msg_iov.len,
|
||||
.control = control_message_bytes.ptr,
|
||||
// This is the size of the control message in bytes
|
||||
.controllen = control_message_bytes.len,
|
||||
.flags = 0,
|
||||
};
|
||||
|
||||
const bytes_sent = try std.os.sendmsg(socket.handle, &socket_message, 0);
|
||||
if (bytes_sent < header_bytes.len + message_bytes.len) {
|
||||
return error.ConnectionClosed;
|
||||
}
|
||||
|
||||
// Wait to increment until we know the message has been sent
|
||||
next_id.* += 1;
|
||||
return wl_shm_pool_id;
|
||||
}
|
||||
|
||||
fn cmsg(comptime T: type) type {
|
||||
const padding_size = (@sizeOf(T) + @sizeOf(c_long) - 1) & ~(@as(usize, @sizeOf(c_long)) - 1);
|
||||
return extern struct {
|
||||
len: c_ulong = @sizeOf(@This()) - padding_size,
|
||||
level: c_int,
|
||||
type: c_int,
|
||||
data: T,
|
||||
_padding: [padding_size]u8 align(1) = [_]u8{0} ** padding_size,
|
||||
};
|
||||
}
|
|
@ -25,15 +25,18 @@ pub fn main() !void {
|
|||
|
||||
// create a sync callback so we know when the registry is done listing extensions
|
||||
const registry_done_id = id_pool.create();
|
||||
std.debug.print("registry done id: {}\n", .{registry_done_id});
|
||||
{
|
||||
var buffer: [5]u32 = undefined;
|
||||
const message = try wayland.serialize(wayland.core.Display.Request, &buffer, 1, .{ .sync = .{ .callback = registry_done_id } });
|
||||
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||
}
|
||||
|
||||
var shm_id: u32 = id_pool.create();
|
||||
var compositor_id: u32 = id_pool.create();
|
||||
var xdg_wm_base_id: u32 = id_pool.create();
|
||||
var shm_id_opt: ?u32 = null;
|
||||
var compositor_id_opt: ?u32 = null;
|
||||
var xdg_wm_base_id_opt: ?u32 = null;
|
||||
var zxdg_decoration_manager_id_opt: ?u32 = null;
|
||||
var wl_seat_id_opt: ?u32 = null;
|
||||
|
||||
var message_buffer = std.ArrayList(u32).init(gpa);
|
||||
defer message_buffer.deinit();
|
||||
|
@ -54,6 +57,7 @@ pub fn main() !void {
|
|||
.global => |global| {
|
||||
var buffer: [20]u32 = undefined;
|
||||
if (std.mem.eql(u8, global.interface, "wl_shm")) {
|
||||
shm_id_opt = id_pool.create();
|
||||
const message = try wayland.serialize(
|
||||
wayland.core.Registry.Request,
|
||||
&buffer,
|
||||
|
@ -62,11 +66,12 @@ pub fn main() !void {
|
|||
.name = global.name,
|
||||
.interface = global.interface,
|
||||
.version = global.version,
|
||||
.new_id = shm_id,
|
||||
.new_id = shm_id_opt.?,
|
||||
} },
|
||||
);
|
||||
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||
} else if (std.mem.eql(u8, global.interface, "wl_compositor")) {
|
||||
} else if (std.mem.eql(u8, global.interface, wayland.core.Compositor.INTERFACE)) {
|
||||
compositor_id_opt = id_pool.create();
|
||||
const message = try wayland.serialize(
|
||||
wayland.core.Registry.Request,
|
||||
&buffer,
|
||||
|
@ -75,11 +80,12 @@ pub fn main() !void {
|
|||
.name = global.name,
|
||||
.interface = global.interface,
|
||||
.version = global.version,
|
||||
.new_id = compositor_id,
|
||||
.new_id = compositor_id_opt.?,
|
||||
} },
|
||||
);
|
||||
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||
} else if (std.mem.eql(u8, global.interface, "xdg_wm_base")) {
|
||||
xdg_wm_base_id_opt = id_pool.create();
|
||||
const message = try wayland.serialize(
|
||||
wayland.core.Registry.Request,
|
||||
&buffer,
|
||||
|
@ -88,7 +94,35 @@ pub fn main() !void {
|
|||
.name = global.name,
|
||||
.interface = global.interface,
|
||||
.version = global.version,
|
||||
.new_id = xdg_wm_base_id,
|
||||
.new_id = xdg_wm_base_id_opt.?,
|
||||
} },
|
||||
);
|
||||
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||
} else if (std.mem.eql(u8, global.interface, "zxdg_decoration_manager_v1")) {
|
||||
zxdg_decoration_manager_id_opt = id_pool.create();
|
||||
const message = try wayland.serialize(
|
||||
wayland.core.Registry.Request,
|
||||
&buffer,
|
||||
registry_id,
|
||||
.{ .bind = .{
|
||||
.name = global.name,
|
||||
.interface = global.interface,
|
||||
.version = 1,
|
||||
.new_id = zxdg_decoration_manager_id_opt.?,
|
||||
} },
|
||||
);
|
||||
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||
} else if (std.mem.eql(u8, global.interface, "wl_seat")) {
|
||||
wl_seat_id_opt = id_pool.create();
|
||||
const message = try wayland.serialize(
|
||||
wayland.core.Registry.Request,
|
||||
&buffer,
|
||||
registry_id,
|
||||
.{ .bind = .{
|
||||
.name = global.name,
|
||||
.interface = global.interface,
|
||||
.version = 8,
|
||||
.new_id = wl_seat_id_opt.?,
|
||||
} },
|
||||
);
|
||||
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||
|
@ -103,6 +137,11 @@ pub fn main() !void {
|
|||
}
|
||||
}
|
||||
|
||||
const shm_id = shm_id_opt orelse return error.NeccessaryWaylandExtensionMissing;
|
||||
const compositor_id = compositor_id_opt orelse return error.NeccessaryWaylandExtensionMissing;
|
||||
const xdg_wm_base_id = xdg_wm_base_id_opt orelse return error.NeccessaryWaylandExtensionMissing;
|
||||
const wl_seat_id = wl_seat_id_opt orelse return error.NeccessaryWaylandExtensionMissing;
|
||||
|
||||
const surface_id = id_pool.create();
|
||||
{
|
||||
var buffer: [10]u32 = undefined;
|
||||
|
@ -146,6 +185,24 @@ pub fn main() !void {
|
|||
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||
}
|
||||
|
||||
var zxdg_toplevel_decoration_id_opt: ?u32 = null;
|
||||
if (zxdg_decoration_manager_id_opt) |zxdg_decoration_manager_id| {
|
||||
zxdg_toplevel_decoration_id_opt = id_pool.create();
|
||||
{
|
||||
var buffer: [10]u32 = undefined;
|
||||
const message = try wayland.serialize(
|
||||
wayland.zxdg.DecorationManagerV1.Request,
|
||||
&buffer,
|
||||
zxdg_decoration_manager_id,
|
||||
.{ .get_toplevel_decoration = .{
|
||||
.new_id = zxdg_toplevel_decoration_id_opt.?,
|
||||
.toplevel = xdg_toplevel_id,
|
||||
} },
|
||||
);
|
||||
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var buffer: [10]u32 = undefined;
|
||||
const message = try wayland.serialize(
|
||||
|
@ -165,6 +222,7 @@ pub fn main() !void {
|
|||
|
||||
var done = false;
|
||||
var surface_configured = false;
|
||||
var seat_capabilties: ?wayland.core.Seat.Capability = null;
|
||||
while (!done or !surface_configured) {
|
||||
var header: wayland.Header = undefined;
|
||||
const header_bytes_read = try socket.readAll(std.mem.asBytes(&header));
|
||||
|
@ -193,10 +251,12 @@ pub fn main() !void {
|
|||
surface_configured = true;
|
||||
},
|
||||
}
|
||||
} else if (zxdg_toplevel_decoration_id_opt != null and header.object_id == zxdg_toplevel_decoration_id_opt.?) {
|
||||
const event = try wayland.deserialize(wayland.zxdg.ToplevelDecorationV1.Event, header, message_buffer.items);
|
||||
std.debug.print("<- zxdg_toplevel_decoration@{}\n", .{event});
|
||||
} else if (header.object_id == xdg_toplevel_id) {
|
||||
// const event = try wayland.deserialize(wayland.xdg.Toplevel.Event, header, message_buffer.items);
|
||||
// std.debug.print("<- {}\n", .{event});
|
||||
std.debug.print("<- xdg_toplevel@{} {s} {}\n", .{ header.object_id, @tagName(@as(std.meta.Tag(wayland.xdg.Toplevel.Event), @enumFromInt(header.size_and_opcode.opcode))), std.zig.fmtEscapes(std.mem.sliceAsBytes(message_buffer.items)) });
|
||||
const event = try wayland.deserialize(wayland.xdg.Toplevel.Event, header, message_buffer.items);
|
||||
std.debug.print("<- {}\n", .{event});
|
||||
} else if (header.object_id == registry_done_id) {
|
||||
done = true;
|
||||
} else if (header.object_id == shm_id) {
|
||||
|
@ -204,17 +264,82 @@ pub fn main() !void {
|
|||
switch (event) {
|
||||
.format => |format| std.debug.print("<- format {} {}\n", .{ format.format, std.zig.fmtEscapes(std.mem.asBytes(&format.format)) }),
|
||||
}
|
||||
} else if (header.object_id == wl_seat_id) {
|
||||
const event = try wayland.deserialize(wayland.core.Seat.Event, header, message_buffer.items);
|
||||
switch (event) {
|
||||
.capabilities => |capabilities| {
|
||||
const cap: wayland.core.Seat.Capability = @bitCast(capabilities.capability);
|
||||
std.debug.print("<- wl_seat.capabilties = {}\n", .{cap});
|
||||
seat_capabilties = cap;
|
||||
|
||||
// if (cap.keyboard) {
|
||||
// var buffer: [10]u32 = undefined;
|
||||
// wl_keyboard_id_opt = id_pool.create();
|
||||
// std.debug.print("wl keyboard id: {}\n", .{wl_keyboard_id_opt.?});
|
||||
// const message = try wayland.serialize(
|
||||
// wayland.core.Seat.Request,
|
||||
// &buffer,
|
||||
// wl_seat_id,
|
||||
// .{ .get_keyboard = .{
|
||||
// .new_id = wl_keyboard_id_opt.?,
|
||||
// } },
|
||||
// );
|
||||
// try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||
// }
|
||||
},
|
||||
.name => |name| {
|
||||
std.debug.print("<- wl_seat.name = {s}\n", .{name.name});
|
||||
},
|
||||
}
|
||||
} else if (header.object_id == 1) {
|
||||
const event = try wayland.deserialize(wayland.core.Display.Event, header, message_buffer.items);
|
||||
switch (event) {
|
||||
.@"error" => |err| std.debug.print("<- error({}): {} {s}\n", .{ err.object_id, err.code, err.message }),
|
||||
.delete_id => |id| id_pool.destroy(id.name),
|
||||
.delete_id => |id| {
|
||||
std.debug.print("id {} deleted\n", .{id});
|
||||
id_pool.destroy(id.id);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
std.debug.print("{} {x} \"{}\"\n", .{ header.object_id, header.size_and_opcode.opcode, std.zig.fmtEscapes(std.mem.sliceAsBytes(message_buffer.items)) });
|
||||
}
|
||||
}
|
||||
|
||||
var wl_pointer_id_opt: ?u32 = null;
|
||||
var wl_keyboard_id_opt: ?u32 = null;
|
||||
if (seat_capabilties) |caps| {
|
||||
if (caps.pointer) {
|
||||
var buffer: [10]u32 = undefined;
|
||||
wl_pointer_id_opt = id_pool.create();
|
||||
std.debug.print("wl pointer id: {}\n", .{wl_pointer_id_opt.?});
|
||||
const message = try wayland.serialize(
|
||||
wayland.core.Seat.Request,
|
||||
&buffer,
|
||||
wl_seat_id,
|
||||
.{ .get_pointer = .{
|
||||
.new_id = wl_pointer_id_opt.?,
|
||||
} },
|
||||
);
|
||||
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||
}
|
||||
if (caps.keyboard) {
|
||||
var buffer: [10]u32 = undefined;
|
||||
wl_keyboard_id_opt = id_pool.create();
|
||||
std.debug.print("wl keyboard id: {}\n", .{wl_keyboard_id_opt.?});
|
||||
const message = try wayland.serialize(
|
||||
wayland.core.Seat.Request,
|
||||
&buffer,
|
||||
wl_seat_id,
|
||||
.{ .get_keyboard = .{
|
||||
.new_id = wl_keyboard_id_opt.?,
|
||||
} },
|
||||
);
|
||||
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||
}
|
||||
}
|
||||
const wl_pointer_id = wl_pointer_id_opt orelse return error.MissingPointer;
|
||||
const wl_keyboard_id = wl_keyboard_id_opt orelse return error.MissingKeyboard;
|
||||
|
||||
// allocate a shared memory file for display purposes
|
||||
const Pixel = [4]u8;
|
||||
const framebuffer_size = [2]usize{ 128, 128 };
|
||||
|
@ -238,7 +363,7 @@ pub fn main() !void {
|
|||
}
|
||||
}
|
||||
|
||||
const wl_shm_pool_id = 10;
|
||||
const wl_shm_pool_id = id_pool.create();
|
||||
{
|
||||
var buffer: [10]u32 = undefined;
|
||||
const message = try wayland.serialize(
|
||||
|
@ -277,8 +402,9 @@ pub fn main() !void {
|
|||
_ = try std.os.sendmsg(socket.handle, &socket_message, 0);
|
||||
}
|
||||
|
||||
const wl_buffer_id = 11;
|
||||
const wl_buffer_id = id_pool.create();
|
||||
{
|
||||
std.debug.print("buffer id: {}\n", .{wl_buffer_id});
|
||||
var buffer: [10]u32 = undefined;
|
||||
const message = try wayland.serialize(
|
||||
wayland.core.ShmPool.Request,
|
||||
|
@ -364,6 +490,16 @@ pub fn main() !void {
|
|||
} },
|
||||
);
|
||||
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||
|
||||
// commit the configuration
|
||||
var buffer2: [10]u32 = undefined;
|
||||
const message2 = try wayland.serialize(
|
||||
wayland.core.Surface.Request,
|
||||
&buffer2,
|
||||
surface_id,
|
||||
wayland.core.Surface.Request.commit,
|
||||
);
|
||||
try socket.writeAll(std.mem.sliceAsBytes(message2));
|
||||
},
|
||||
}
|
||||
} else if (header.object_id == xdg_toplevel_id) {
|
||||
|
@ -378,11 +514,38 @@ pub fn main() !void {
|
|||
} else if (header.object_id == wl_buffer_id) {
|
||||
const event = try wayland.deserialize(wayland.core.Buffer.Event, header, message_buffer.items);
|
||||
std.debug.print("<- wl_buffer@{} {}\n", .{ header.object_id, event });
|
||||
} else if (header.object_id == xdg_wm_base_id) {
|
||||
const event = try wayland.deserialize(wayland.xdg.WmBase.Event, header, message_buffer.items);
|
||||
switch (event) {
|
||||
.ping => |ping| {
|
||||
var buffer: [10]u32 = undefined;
|
||||
const message = try wayland.serialize(
|
||||
wayland.xdg.WmBase.Request,
|
||||
&buffer,
|
||||
xdg_wm_base_id,
|
||||
.{ .pong = .{
|
||||
.serial = ping.serial,
|
||||
} },
|
||||
);
|
||||
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||
},
|
||||
}
|
||||
} else if (header.object_id == wl_pointer_id) {
|
||||
const event = try wayland.deserialize(wayland.core.Pointer.Event, header, message_buffer.items);
|
||||
std.debug.print("<- wl_pointer@{}\n", .{event});
|
||||
} else if (header.object_id == wl_keyboard_id) {
|
||||
const event = try wayland.deserialize(wayland.core.Keyboard.Event, header, message_buffer.items);
|
||||
switch (event) {
|
||||
// .keymap => |keymap| {},
|
||||
else => {
|
||||
std.debug.print("<- wl_keyboard@{}\n", .{event});
|
||||
},
|
||||
}
|
||||
} else if (header.object_id == 1) {
|
||||
const event = try wayland.deserialize(wayland.core.Display.Event, header, message_buffer.items);
|
||||
switch (event) {
|
||||
.@"error" => |err| std.debug.print("<- error({}): {} {s}\n", .{ err.object_id, err.code, err.message }),
|
||||
.delete_id => |id| id_pool.destroy(id.name),
|
||||
.delete_id => |id| id_pool.destroy(id.id),
|
||||
}
|
||||
} else {
|
||||
std.debug.print("{} {x} \"{}\"\n", .{ header.object_id, header.size_and_opcode.opcode, std.zig.fmtEscapes(std.mem.sliceAsBytes(message_buffer.items)) });
|
||||
|
|
330
src/core.zig
330
src/core.zig
|
@ -1,42 +1,24 @@
|
|||
pub const Display = struct {
|
||||
pub const Request = union(Request.Tag) {
|
||||
sync: Sync,
|
||||
get_registry: GetRegistry,
|
||||
|
||||
pub const Tag = enum(u16) {
|
||||
sync,
|
||||
get_registry,
|
||||
};
|
||||
|
||||
pub const Sync = struct {
|
||||
pub const Request = union(enum) {
|
||||
sync: struct {
|
||||
/// new_id<wl_callback>
|
||||
callback: u32,
|
||||
};
|
||||
|
||||
pub const GetRegistry = struct {
|
||||
},
|
||||
get_registry: struct {
|
||||
/// new_id<wl_registry>
|
||||
registry: u32,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
pub const Event = union(Event.Tag) {
|
||||
@"error": Event.Error,
|
||||
delete_id: DeleteId,
|
||||
|
||||
pub const Tag = enum(u16) {
|
||||
@"error",
|
||||
delete_id,
|
||||
};
|
||||
|
||||
pub const Error = struct {
|
||||
pub const Event = union(enum) {
|
||||
@"error": struct {
|
||||
object_id: u32,
|
||||
code: u32,
|
||||
message: []const u8,
|
||||
};
|
||||
|
||||
pub const DeleteId = struct {
|
||||
name: u32,
|
||||
};
|
||||
},
|
||||
delete_id: struct {
|
||||
id: u32,
|
||||
},
|
||||
};
|
||||
|
||||
pub const Error = enum(u32) {
|
||||
|
@ -48,64 +30,42 @@ pub const Display = struct {
|
|||
};
|
||||
|
||||
pub const Registry = struct {
|
||||
pub const Event = union(Event.Tag) {
|
||||
global: Global,
|
||||
global_remove: GlobalRemove,
|
||||
|
||||
pub const Tag = enum(u16) {
|
||||
global,
|
||||
global_remove,
|
||||
};
|
||||
|
||||
pub const Global = struct {
|
||||
pub const Event = union(enum) {
|
||||
global: struct {
|
||||
name: u32,
|
||||
interface: [:0]const u8,
|
||||
version: u32,
|
||||
};
|
||||
|
||||
pub const GlobalRemove = struct {
|
||||
},
|
||||
global_remove: struct {
|
||||
name: u32,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
pub const Request = union(Request.Tag) {
|
||||
bind: Bind,
|
||||
|
||||
pub const Tag = enum(u16) {
|
||||
bind,
|
||||
};
|
||||
|
||||
pub const Bind = struct {
|
||||
pub const Request = union(enum) {
|
||||
bind: struct {
|
||||
name: u32,
|
||||
interface: [:0]const u8,
|
||||
version: u32,
|
||||
new_id: u32,
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
pub const Compositor = struct {
|
||||
pub const Request = union(Request.Tag) {
|
||||
create_surface: CreateSurface,
|
||||
create_region: CreateRegion,
|
||||
pub const INTERFACE = "wl_compositor";
|
||||
|
||||
pub const Tag = enum(u16) {
|
||||
create_surface,
|
||||
create_region,
|
||||
};
|
||||
|
||||
pub const CreateSurface = struct {
|
||||
pub const Request = union(enum) {
|
||||
create_surface: struct {
|
||||
new_id: u32,
|
||||
};
|
||||
|
||||
pub const CreateRegion = struct {
|
||||
},
|
||||
create_region: struct {
|
||||
new_id: u32,
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
pub const ShmPool = struct {
|
||||
pub const Request = union(Request.Tag) {
|
||||
pub const Request = union(enum) {
|
||||
create_buffer: struct {
|
||||
new_id: u32,
|
||||
offset: i32,
|
||||
|
@ -118,29 +78,17 @@ pub const ShmPool = struct {
|
|||
resize: struct {
|
||||
size: i32,
|
||||
},
|
||||
|
||||
pub const Tag = enum(u16) {
|
||||
create_buffer,
|
||||
destroy,
|
||||
resize,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pub const Shm = struct {
|
||||
pub const Request = union(Request.Tag) {
|
||||
create_pool: CreatePool,
|
||||
|
||||
pub const Tag = enum(u16) {
|
||||
create_pool,
|
||||
};
|
||||
|
||||
pub const CreatePool = struct {
|
||||
pub const Request = union(enum) {
|
||||
create_pool: struct {
|
||||
new_id: u32,
|
||||
// file descriptors are sent through a control message
|
||||
// fd: u32,
|
||||
size: u32,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
pub const Event = union(enum) {
|
||||
|
@ -149,6 +97,12 @@ pub const Shm = struct {
|
|||
},
|
||||
};
|
||||
|
||||
pub const Error = enum(u32) {
|
||||
invalid_format,
|
||||
invalid_stride,
|
||||
invalid_fd,
|
||||
};
|
||||
|
||||
pub const Format = enum(u32) {
|
||||
argb8888,
|
||||
xrgb8888,
|
||||
|
@ -157,7 +111,7 @@ pub const Shm = struct {
|
|||
};
|
||||
|
||||
pub const Surface = struct {
|
||||
pub const Request = union(Request.Tag) {
|
||||
pub const Request = union(enum) {
|
||||
destroy: void,
|
||||
attach: struct {
|
||||
buffer: u32,
|
||||
|
@ -181,31 +135,29 @@ pub const Surface = struct {
|
|||
region: u32,
|
||||
},
|
||||
commit: void,
|
||||
|
||||
pub const Tag = enum(u16) {
|
||||
destroy,
|
||||
attach,
|
||||
damage,
|
||||
frame,
|
||||
set_opaque_region,
|
||||
set_input_region,
|
||||
commit,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Event = union(Event.Tag) {
|
||||
format: Format,
|
||||
pub const Event = union(enum) {
|
||||
enter: struct {
|
||||
output: u32,
|
||||
},
|
||||
leave: struct {
|
||||
output: u32,
|
||||
},
|
||||
preferred_buffer_scale: struct {
|
||||
factor: i32,
|
||||
},
|
||||
preferred_buffer_transform: struct {
|
||||
transform: u32,
|
||||
},
|
||||
};
|
||||
|
||||
pub const Tag = enum(u16) {
|
||||
@"error",
|
||||
delete_id,
|
||||
};
|
||||
|
||||
pub const Format = enum(u32) {
|
||||
argb8888,
|
||||
xrgb8888,
|
||||
_,
|
||||
};
|
||||
pub const Error = enum(u32) {
|
||||
invalid_scale,
|
||||
invalid_transform,
|
||||
invalid_size,
|
||||
invalid_offset,
|
||||
defunct_role_object,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -218,3 +170,173 @@ pub const Buffer = struct {
|
|||
release: void,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Seat = struct {
|
||||
pub const Request = union(enum) {
|
||||
get_pointer: struct {
|
||||
new_id: u32,
|
||||
},
|
||||
get_keyboard: struct {
|
||||
new_id: u32,
|
||||
},
|
||||
get_touch: struct {
|
||||
new_id: u32,
|
||||
},
|
||||
release: void,
|
||||
};
|
||||
|
||||
pub const Event = union(enum) {
|
||||
capabilities: struct {
|
||||
capability: u32,
|
||||
},
|
||||
name: struct {
|
||||
name: []const u8,
|
||||
},
|
||||
};
|
||||
|
||||
pub const Capability = packed struct(u32) {
|
||||
pointer: bool,
|
||||
keyboard: bool,
|
||||
touch: bool,
|
||||
_unused: u29,
|
||||
};
|
||||
|
||||
pub const Error = enum(u32) {};
|
||||
};
|
||||
|
||||
pub const Pointer = struct {
|
||||
pub const Request = union(enum) {
|
||||
set_cursor: struct {
|
||||
serial: u32,
|
||||
surface: u32,
|
||||
hotspot_x: i32,
|
||||
hotspot_y: i32,
|
||||
},
|
||||
release: void,
|
||||
};
|
||||
|
||||
pub const Event = union(enum) {
|
||||
enter: struct {
|
||||
serial: u32,
|
||||
surface: u32,
|
||||
surface_x: u32,
|
||||
surface_y: u32,
|
||||
},
|
||||
leave: struct {
|
||||
serial: u32,
|
||||
surface: u32,
|
||||
},
|
||||
motion: struct {
|
||||
time: u32,
|
||||
surface_x: i32, //i24.8
|
||||
surface_y: i32, //i24.8
|
||||
},
|
||||
button: struct {
|
||||
serial: u32,
|
||||
time: u32,
|
||||
button: u32,
|
||||
state: ButtonState,
|
||||
},
|
||||
axis: struct {
|
||||
time: u32,
|
||||
axis: Axis,
|
||||
value: i32, //i24.8
|
||||
},
|
||||
frame: void,
|
||||
axis_source: struct {
|
||||
axis_source: u32,
|
||||
},
|
||||
axis_stop: struct {
|
||||
time: u32,
|
||||
axis: Axis,
|
||||
},
|
||||
axis_discrete: struct {
|
||||
axis: Axis,
|
||||
discrete: i32,
|
||||
},
|
||||
axis_value120: struct {
|
||||
axis: Axis,
|
||||
value120: i32,
|
||||
},
|
||||
axis_relative_direction: struct {
|
||||
axis: Axis,
|
||||
direction: AxisRelativeDirection,
|
||||
},
|
||||
};
|
||||
|
||||
pub const Error = enum(u32) {
|
||||
role,
|
||||
};
|
||||
|
||||
pub const ButtonState = enum(u32) {
|
||||
released,
|
||||
pressed,
|
||||
};
|
||||
|
||||
pub const Axis = enum(u32) {
|
||||
vertical_scroll,
|
||||
horizontal_scroll,
|
||||
};
|
||||
|
||||
pub const AxisSource = enum(u32) {
|
||||
wheel,
|
||||
finger,
|
||||
continuous,
|
||||
wheel_tilt,
|
||||
};
|
||||
|
||||
pub const AxisRelativeDirection = enum(u32) {
|
||||
identical,
|
||||
inverted,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Keyboard = struct {
|
||||
pub const Request = union(enum) {
|
||||
release: void,
|
||||
};
|
||||
|
||||
pub const Event = union(enum) {
|
||||
keymap: struct {
|
||||
format: KeymapFormat,
|
||||
// fd: u32,
|
||||
size: u32,
|
||||
},
|
||||
enter: struct {
|
||||
serial: u32,
|
||||
surface: u32,
|
||||
keys: []const u32,
|
||||
},
|
||||
leave: struct {
|
||||
serial: u32,
|
||||
surface: u32,
|
||||
},
|
||||
key: struct {
|
||||
serial: u32,
|
||||
time: u32,
|
||||
key: u32,
|
||||
state: KeyState,
|
||||
},
|
||||
modifiers: struct {
|
||||
serial: u32,
|
||||
mods_depressed: u32,
|
||||
mods_latched: u32,
|
||||
mods_locked: u32,
|
||||
group: u32,
|
||||
},
|
||||
repeat_info: struct {
|
||||
rate: i32,
|
||||
delay: i32,
|
||||
},
|
||||
};
|
||||
|
||||
pub const KeymapFormat = enum(u32) {
|
||||
no_keymap,
|
||||
xkb_v1,
|
||||
};
|
||||
|
||||
pub const KeyState = enum(u32) {
|
||||
released,
|
||||
pressed,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,11 +2,15 @@ const std = @import("std");
|
|||
const testing = std.testing;
|
||||
pub const core = @import("./core.zig");
|
||||
pub const xdg = @import("./xdg.zig");
|
||||
pub const zxdg = @import("./zxdg.zig");
|
||||
|
||||
pub fn getDisplayPath(gpa: std.mem.Allocator) ![]u8 {
|
||||
const xdg_runtime_dir_path = try std.process.getEnvVarOwned(gpa, "XDG_RUNTIME_DIR");
|
||||
defer gpa.free(xdg_runtime_dir_path);
|
||||
const display_name = try std.process.getEnvVarOwned(gpa, "WAYLAND_DISPLAY");
|
||||
const display_name = std.process.getEnvVarOwned(gpa, "WAYLAND_DISPLAY") catch |err| switch (err) {
|
||||
error.EnvironmentVariableNotFound => return try std.fs.path.join(gpa, &.{ xdg_runtime_dir_path, "wayland-0" }),
|
||||
else => return err,
|
||||
};
|
||||
defer gpa.free(display_name);
|
||||
|
||||
return try std.fs.path.join(gpa, &.{ xdg_runtime_dir_path, display_name });
|
||||
|
@ -337,6 +341,9 @@ pub const IdPool = struct {
|
|||
}
|
||||
|
||||
pub fn destroy(this: *@This(), id: u32) void {
|
||||
for (this.free_ids.slice()) |existing_id| {
|
||||
if (existing_id == id) return;
|
||||
}
|
||||
this.free_ids.append(id) catch {};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
pub const DecorationManagerV1 = struct {
|
||||
pub const Request = union(Request.Tag) {
|
||||
destroy: void,
|
||||
get_toplevel_decoration: struct {
|
||||
new_id: u32,
|
||||
toplevel: u32,
|
||||
},
|
||||
|
||||
pub const Tag = enum(u16) {
|
||||
destroy,
|
||||
get_toplevel_decoration,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pub const ToplevelDecorationV1 = struct {
|
||||
pub const Request = union(Request.Tag) {
|
||||
destroy: void,
|
||||
unset_mode: void,
|
||||
set_mode: struct {
|
||||
mode: Mode,
|
||||
},
|
||||
|
||||
pub const Tag = enum(u16) {
|
||||
destroy,
|
||||
unset_mode,
|
||||
set_mode,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Event = union(Event.Tag) {
|
||||
configure: struct { mode: Mode },
|
||||
|
||||
pub const Tag = enum(u16) {
|
||||
configure,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Error = enum(u32) {
|
||||
unconfigured_buffer,
|
||||
already_constructed,
|
||||
orphaned,
|
||||
};
|
||||
|
||||
pub const Mode = enum(u32) {
|
||||
client_side = 1,
|
||||
server_side = 2,
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue