Create example of connecting to a wayland server
commit
00dd8167ae
|
@ -0,0 +1,2 @@
|
||||||
|
zig-out/
|
||||||
|
zig-cache/
|
|
@ -0,0 +1,38 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const module = b.addModule("wayland", .{
|
||||||
|
.source_file = .{ .path = "src/main.zig" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const lib = b.addStaticLibrary(.{
|
||||||
|
.name = "zig-wayland-wire",
|
||||||
|
.root_source_file = .{ .path = "src/main.zig" },
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
b.installArtifact(lib);
|
||||||
|
|
||||||
|
const main_tests = b.addTest(.{
|
||||||
|
.root_source_file = .{ .path = "src/main.zig" },
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const run_main_tests = b.addRunArtifact(main_tests);
|
||||||
|
|
||||||
|
const test_step = b.step("test", "Run library tests");
|
||||||
|
test_step.dependOn(&run_main_tests.step);
|
||||||
|
|
||||||
|
const client_connect_exe = b.addExecutable(.{
|
||||||
|
.name = "client_connect",
|
||||||
|
.root_source_file = .{ .path = "examples/01_client_connect.zig" },
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
client_connect_exe.addModule("wayland", module);
|
||||||
|
b.installArtifact(client_connect_exe);
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const wayland = @import("wayland");
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var general_allocator = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer _ = general_allocator.deinit();
|
||||||
|
const gpa = general_allocator.allocator();
|
||||||
|
|
||||||
|
const display_path = try wayland.getDisplayPath(gpa);
|
||||||
|
defer gpa.free(display_path);
|
||||||
|
|
||||||
|
const socket = try std.net.connectUnixSocket(display_path);
|
||||||
|
defer socket.close();
|
||||||
|
|
||||||
|
// reserve an object id for the registry
|
||||||
|
const registry_id = 2;
|
||||||
|
{
|
||||||
|
var buffer: [5]u32 = undefined;
|
||||||
|
const message = try wayland.serialize(wayland.core.Display.Request, &buffer, 1, .{ .get_registry = .{ .registry = registry_id } });
|
||||||
|
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a sync callback so we know when the registry is done listing extensions
|
||||||
|
const registry_done_id = 3;
|
||||||
|
{
|
||||||
|
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 = 4;
|
||||||
|
var compositor_id: u32 = 5;
|
||||||
|
var xdg_wm_base_id: u32 = 6;
|
||||||
|
var wp_single_pixel_buffer_manager_id: u32 = 7;
|
||||||
|
|
||||||
|
var message_buffer = std.ArrayList(u32).init(gpa);
|
||||||
|
defer message_buffer.deinit();
|
||||||
|
while (true) {
|
||||||
|
var header: wayland.Header = undefined;
|
||||||
|
const header_bytes_read = try socket.readAll(std.mem.asBytes(&header));
|
||||||
|
if (header_bytes_read < @sizeOf(wayland.Header)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try message_buffer.resize((header.size_and_opcode.size - @sizeOf(wayland.Header)) / @sizeOf(u32));
|
||||||
|
const bytes_read = try socket.readAll(std.mem.sliceAsBytes(message_buffer.items));
|
||||||
|
message_buffer.shrinkRetainingCapacity(bytes_read / @sizeOf(u32));
|
||||||
|
|
||||||
|
if (header.object_id == registry_id) {
|
||||||
|
const event = try wayland.deserialize(wayland.core.Registry.Event, header, message_buffer.items);
|
||||||
|
std.debug.print("{} {s} ", .{ header.object_id, @tagName(event) });
|
||||||
|
switch (event) {
|
||||||
|
.global => |global| {
|
||||||
|
std.debug.print("{} \"{}\" v{}\n", .{ global.name, std.zig.fmtEscapes(global.interface), global.version });
|
||||||
|
|
||||||
|
var buffer: [20]u32 = undefined;
|
||||||
|
if (std.mem.eql(u8, global.interface, "wl_shm")) {
|
||||||
|
const message = try wayland.serialize(
|
||||||
|
wayland.core.Registry.Request,
|
||||||
|
&buffer,
|
||||||
|
registry_id,
|
||||||
|
.{ .bind = .{
|
||||||
|
.name = global.name,
|
||||||
|
.interface = global.interface,
|
||||||
|
.version = global.version,
|
||||||
|
.new_id = shm_id,
|
||||||
|
} },
|
||||||
|
);
|
||||||
|
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||||
|
} else if (std.mem.eql(u8, global.interface, "wl_compositor")) {
|
||||||
|
const message = try wayland.serialize(
|
||||||
|
wayland.core.Registry.Request,
|
||||||
|
&buffer,
|
||||||
|
registry_id,
|
||||||
|
.{ .bind = .{
|
||||||
|
.name = global.name,
|
||||||
|
.interface = global.interface,
|
||||||
|
.version = global.version,
|
||||||
|
.new_id = compositor_id,
|
||||||
|
} },
|
||||||
|
);
|
||||||
|
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||||
|
} else if (std.mem.eql(u8, global.interface, "xdg_wm_base")) {
|
||||||
|
const message = try wayland.serialize(
|
||||||
|
wayland.core.Registry.Request,
|
||||||
|
&buffer,
|
||||||
|
registry_id,
|
||||||
|
.{ .bind = .{
|
||||||
|
.name = global.name,
|
||||||
|
.interface = global.interface,
|
||||||
|
.version = global.version,
|
||||||
|
.new_id = xdg_wm_base_id,
|
||||||
|
} },
|
||||||
|
);
|
||||||
|
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||||
|
} else if (std.mem.eql(u8, global.interface, "wp_single_pixel_buffer_manager_v1")) {
|
||||||
|
const message = try wayland.serialize(
|
||||||
|
wayland.core.Registry.Request,
|
||||||
|
&buffer,
|
||||||
|
registry_id,
|
||||||
|
.{ .bind = .{
|
||||||
|
.name = global.name,
|
||||||
|
.interface = global.interface,
|
||||||
|
.version = global.version,
|
||||||
|
.new_id = wp_single_pixel_buffer_manager_id,
|
||||||
|
} },
|
||||||
|
);
|
||||||
|
try socket.writeAll(std.mem.sliceAsBytes(message));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.global_remove => std.debug.print("{}\n", .{std.zig.fmtEscapes(std.mem.sliceAsBytes(message_buffer.items))}),
|
||||||
|
}
|
||||||
|
} else if (header.object_id == registry_done_id) {
|
||||||
|
std.debug.print("<-", .{});
|
||||||
|
for (message_buffer.items) |word| {
|
||||||
|
std.debug.print(" {}", .{std.fmt.fmtSliceHexLower(std.mem.asBytes(&word))});
|
||||||
|
}
|
||||||
|
std.debug.print(" (sync event id)\n", .{});
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
std.debug.print("{} {x} \"{}\"\n", .{ header.object_id, header.size_and_opcode.opcode, std.zig.fmtEscapes(std.mem.sliceAsBytes(message_buffer.items)) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
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 {
|
||||||
|
/// new_id<wl_callback>
|
||||||
|
callback: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const GetRegistry = 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 {
|
||||||
|
object_id: u32,
|
||||||
|
code: u32,
|
||||||
|
message: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DeleteId = struct {
|
||||||
|
name: u32,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Error = enum(u32) {
|
||||||
|
invalid_object,
|
||||||
|
invalid_method,
|
||||||
|
no_memory,
|
||||||
|
implementation,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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 {
|
||||||
|
name: u32,
|
||||||
|
interface: [:0]const u8,
|
||||||
|
version: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const GlobalRemove = struct {
|
||||||
|
name: u32,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Request = union(Request.Tag) {
|
||||||
|
bind: Bind,
|
||||||
|
|
||||||
|
pub const Tag = enum(u16) {
|
||||||
|
bind,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Bind = struct {
|
||||||
|
name: u32,
|
||||||
|
interface: [:0]const u8,
|
||||||
|
version: u32,
|
||||||
|
new_id: u32,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,263 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const testing = std.testing;
|
||||||
|
pub const core = @import("./core.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");
|
||||||
|
defer gpa.free(display_name);
|
||||||
|
|
||||||
|
return try std.fs.path.join(gpa, &.{ xdg_runtime_dir_path, display_name });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Header = extern struct {
|
||||||
|
object_id: u32 align(1),
|
||||||
|
size_and_opcode: SizeAndOpcode align(1),
|
||||||
|
|
||||||
|
pub const SizeAndOpcode = packed struct(u32) {
|
||||||
|
opcode: u16,
|
||||||
|
size: u16,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
test "[]u32 from header" {
|
||||||
|
try std.testing.expectEqualSlices(
|
||||||
|
u32,
|
||||||
|
&[_]u32{
|
||||||
|
1,
|
||||||
|
(@as(u32, 12) << 16) | (4),
|
||||||
|
},
|
||||||
|
&@as([2]u32, @bitCast(Header{
|
||||||
|
.object_id = 1,
|
||||||
|
.size_and_opcode = .{
|
||||||
|
.size = 12,
|
||||||
|
.opcode = 4,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "header from []u32" {
|
||||||
|
try std.testing.expectEqualDeep(
|
||||||
|
Header{
|
||||||
|
.object_id = 1,
|
||||||
|
.size_and_opcode = .{
|
||||||
|
.size = 12,
|
||||||
|
.opcode = 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
@as(Header, @bitCast([2]u32{
|
||||||
|
1,
|
||||||
|
(@as(u32, 12) << 16) | (4),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserializeArguments(comptime Signature: type, buffer: []const u32) !Signature {
|
||||||
|
var result: Signature = undefined;
|
||||||
|
var pos: usize = 0;
|
||||||
|
inline for (std.meta.fields(Signature)) |field| {
|
||||||
|
switch (@typeInfo(field.type)) {
|
||||||
|
.Int => {
|
||||||
|
@field(result, field.name) = @bitCast(buffer[pos]);
|
||||||
|
pos += 1;
|
||||||
|
},
|
||||||
|
.Pointer => |ptr| switch (ptr.size) {
|
||||||
|
.Slice => {
|
||||||
|
const len = buffer[pos];
|
||||||
|
pos += 1;
|
||||||
|
const byte_pos = pos * @sizeOf(u32);
|
||||||
|
@field(result, field.name) = std.mem.sliceAsBytes(buffer)[byte_pos..][0 .. len - 1 :0];
|
||||||
|
pos += std.mem.alignForward(usize, len, @sizeOf(u32)) / @sizeOf(u32);
|
||||||
|
},
|
||||||
|
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
|
},
|
||||||
|
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize(comptime Union: type, header: Header, buffer: []const u32) !Union {
|
||||||
|
const op = try std.meta.intToEnum(std.meta.Tag(Union), header.size_and_opcode.opcode);
|
||||||
|
switch (op) {
|
||||||
|
inline else => |f| {
|
||||||
|
const Payload = std.meta.TagPayload(Union, f);
|
||||||
|
const payload = try deserializeArguments(Payload, buffer);
|
||||||
|
return @unionInit(Union, @tagName(f), payload);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length of the serialized message in `u32` words.
|
||||||
|
pub fn calculateSerializedWordLen(comptime Signature: type, message: Signature) usize {
|
||||||
|
var pos: usize = 0;
|
||||||
|
inline for (std.meta.fields(Signature)) |field| {
|
||||||
|
switch (@typeInfo(field.type)) {
|
||||||
|
.Int => pos += 1,
|
||||||
|
.Pointer => |ptr| switch (ptr.size) {
|
||||||
|
.Slice => {
|
||||||
|
// for size of string in bytes
|
||||||
|
pos += 1;
|
||||||
|
|
||||||
|
const str = @field(message, field.name);
|
||||||
|
pos += std.mem.alignForward(usize, str.len + 1, @sizeOf(u32)) / @sizeOf(u32);
|
||||||
|
},
|
||||||
|
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
|
},
|
||||||
|
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message must live until the iovec array is written.
|
||||||
|
pub fn serializeArguments(comptime Signature: type, buffer: []u32, message: Signature) ![]u32 {
|
||||||
|
var pos: usize = 0;
|
||||||
|
inline for (std.meta.fields(Signature)) |field| {
|
||||||
|
switch (@typeInfo(field.type)) {
|
||||||
|
.Int => {
|
||||||
|
if (pos >= buffer.len) return error.OutOfMemory;
|
||||||
|
buffer[pos] = @field(message, field.name);
|
||||||
|
pos += 1;
|
||||||
|
},
|
||||||
|
.Pointer => |ptr| switch (ptr.size) {
|
||||||
|
.Slice => {
|
||||||
|
const str = @field(message, field.name);
|
||||||
|
if (str.len >= std.math.maxInt(u32)) return error.StringTooLong;
|
||||||
|
|
||||||
|
buffer[pos] = @intCast(str.len + 1);
|
||||||
|
pos += 1;
|
||||||
|
|
||||||
|
const str_len_aligned = std.mem.alignForward(usize, str.len + 1, @sizeOf(u32));
|
||||||
|
const padding_len = str_len_aligned - str.len;
|
||||||
|
if (str_len_aligned / @sizeOf(u32) >= buffer[pos..].len) return error.OutOfMemory;
|
||||||
|
const buffer_bytes = std.mem.sliceAsBytes(buffer[pos..]);
|
||||||
|
@memcpy(buffer_bytes[0..str.len], str);
|
||||||
|
@memset(buffer_bytes[str.len..][0..padding_len], 0);
|
||||||
|
pos += str_len_aligned / @sizeOf(u32);
|
||||||
|
},
|
||||||
|
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
|
},
|
||||||
|
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer[0..pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize(comptime Union: type, buffer: []u32, object_id: u32, message: Union) ![]u32 {
|
||||||
|
const header_wordlen = @sizeOf(Header) / @sizeOf(u32);
|
||||||
|
const header: *Header = @ptrCast(buffer[0..header_wordlen]);
|
||||||
|
header.object_id = object_id;
|
||||||
|
|
||||||
|
const tag = std.meta.activeTag(message);
|
||||||
|
header.size_and_opcode.opcode = @intFromEnum(tag);
|
||||||
|
|
||||||
|
const arguments = switch (message) {
|
||||||
|
inline else => |payload| try serializeArguments(@TypeOf(payload), buffer[header_wordlen..], payload),
|
||||||
|
};
|
||||||
|
|
||||||
|
header.size_and_opcode.size = @intCast(@sizeOf(Header) + arguments.len * @sizeOf(u32));
|
||||||
|
return buffer[0 .. header.size_and_opcode.size / @sizeOf(u32)];
|
||||||
|
}
|
||||||
|
|
||||||
|
test "deserialize Registry.Event.Global" {
|
||||||
|
const words = [_]u32{
|
||||||
|
1,
|
||||||
|
7,
|
||||||
|
@bitCast(@as([4]u8, "wl_s".*)),
|
||||||
|
@bitCast(@as([4]u8, "hm\x00\x00".*)),
|
||||||
|
3,
|
||||||
|
};
|
||||||
|
const parsed = try deserializeArguments(core.Registry.Event.Global, &words);
|
||||||
|
try std.testing.expectEqualDeep(core.Registry.Event.Global{
|
||||||
|
.name = 1,
|
||||||
|
.interface = "wl_shm",
|
||||||
|
.version = 3,
|
||||||
|
}, parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "deserialize Registry.Event" {
|
||||||
|
const header = Header{
|
||||||
|
.object_id = 123,
|
||||||
|
.size_and_opcode = .{
|
||||||
|
.size = 28,
|
||||||
|
.opcode = @intFromEnum(core.Registry.Event.Tag.global),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const words = [_]u32{
|
||||||
|
1,
|
||||||
|
7,
|
||||||
|
@bitCast(@as([4]u8, "wl_s".*)),
|
||||||
|
@bitCast(@as([4]u8, "hm\x00\x00".*)),
|
||||||
|
3,
|
||||||
|
};
|
||||||
|
const parsed = try deserialize(core.Registry.Event, header, &words);
|
||||||
|
try std.testing.expectEqualDeep(
|
||||||
|
core.Registry.Event{
|
||||||
|
.global = .{
|
||||||
|
.name = 1,
|
||||||
|
.interface = "wl_shm",
|
||||||
|
.version = 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parsed,
|
||||||
|
);
|
||||||
|
|
||||||
|
const header2 = Header{
|
||||||
|
.object_id = 1,
|
||||||
|
.size_and_opcode = .{
|
||||||
|
.size = 14 * @sizeOf(u32),
|
||||||
|
.opcode = @intFromEnum(core.Display.Event.Tag.@"error"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const words2 = [_]u32{
|
||||||
|
1,
|
||||||
|
15,
|
||||||
|
40,
|
||||||
|
@bitCast(@as([4]u8, "inva".*)),
|
||||||
|
@bitCast(@as([4]u8, "lid ".*)),
|
||||||
|
@bitCast(@as([4]u8, "argu".*)),
|
||||||
|
@bitCast(@as([4]u8, "ment".*)),
|
||||||
|
@bitCast(@as([4]u8, "s to".*)),
|
||||||
|
@bitCast(@as([4]u8, " wl_".*)),
|
||||||
|
@bitCast(@as([4]u8, "regi".*)),
|
||||||
|
@bitCast(@as([4]u8, "stry".*)),
|
||||||
|
@bitCast(@as([4]u8, "@2.b".*)),
|
||||||
|
@bitCast(@as([4]u8, "ind\x00".*)),
|
||||||
|
};
|
||||||
|
const parsed2 = try deserialize(core.Display.Event, header2, &words2);
|
||||||
|
try std.testing.expectEqualDeep(
|
||||||
|
core.Display.Event{
|
||||||
|
.@"error" = .{
|
||||||
|
.object_id = 1,
|
||||||
|
.code = 15,
|
||||||
|
.message = "invalid arguments to wl_registry@2.bind",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parsed2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "serialize Registry.Event.Global" {
|
||||||
|
const message = core.Registry.Event.Global{
|
||||||
|
.name = 1,
|
||||||
|
.interface = "wl_shm",
|
||||||
|
.version = 3,
|
||||||
|
};
|
||||||
|
var buffer: [5]u32 = undefined;
|
||||||
|
const serialized = try serializeArguments(core.Registry.Event.Global, &buffer, message);
|
||||||
|
|
||||||
|
try std.testing.expectEqualSlices(
|
||||||
|
u32,
|
||||||
|
&[_]u32{
|
||||||
|
1,
|
||||||
|
7,
|
||||||
|
@bitCast(@as([4]u8, "wl_s".*)),
|
||||||
|
@bitCast(@as([4]u8, "hm\x00\x00".*)),
|
||||||
|
3,
|
||||||
|
},
|
||||||
|
serialized,
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue