remove unneeded stuff

dev
Louis Pearson 2024-02-07 01:46:49 -07:00
parent 025ab53055
commit 76365322f2
12 changed files with 1286 additions and 5700 deletions

View File

@ -4,22 +4,6 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const xkbcommon_module = b.createModule(.{
.root_source_file = .{ .path = "deps/zig-xkbcommon/src/xkbcommon.zig" },
});
const module = b.addModule("wayland", .{
.root_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,
@ -31,33 +15,14 @@ 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" },
const exe = b.addExecutable(.{
.name = "pinephone-test",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
b.installArtifact(client_connect_raw_exe);
const client_connect_exe = b.addExecutable(.{
.name = "01_client_connect",
.root_source_file = .{ .path = "examples/01_client_connect.zig" },
.target = target,
.optimize = optimize,
});
client_connect_exe.root_module.addImport("wayland", module);
b.installArtifact(client_connect_exe);
exe.addIncludePath(.{ .path = "deps/font8x8/" });
const text_editor_exe = b.addExecutable(.{
.name = "02_text_editor",
.root_source_file = .{ .path = "examples/02_text_editor.zig" },
.target = target,
.optimize = optimize,
});
text_editor_exe.root_module.addImport("wayland", module);
text_editor_exe.root_module.addImport("xkbcommon", xkbcommon_module);
text_editor_exe.linkLibC();
text_editor_exe.linkSystemLibrary("xkbcommon");
text_editor_exe.addIncludePath(.{ .path = "deps/font8x8/" });
b.installArtifact(text_editor_exe);
b.installArtifact(exe);
}

View File

@ -1,19 +0,0 @@
Copyright 2020 Isaac Freund
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.

View File

@ -1,5 +0,0 @@
# zig-xkbcommon
[zig](https://ziglang.org/) 0.11 bindings for
[xkbcommon](https://xkbcommon.org) that are a little
nicer to use than the output of `zig translate-c`.

View File

@ -1,341 +0,0 @@
pub const names = @import("xkbcommon_names.zig");
pub const Keycode = u32;
pub const Keysym = @import("xkbcommon_keysyms.zig").Keysym;
pub const KeyDirection = enum(c_int) {
up,
down,
};
pub const LayoutIndex = u32;
pub const LayoutMask = u32;
pub const LevelIndex = u32;
pub const ModIndex = u32;
pub const ModMask = u32;
pub const LED_Index = u32;
pub const LED_Mask = u32;
pub const keycode_invalid = 0xffff_ffff;
pub const layout_invalid = 0xffff_ffff;
pub const level_invalid = 0xffff_ffff;
pub const mod_invalid = 0xffff_ffff;
pub const led_invalid = 0xffff_ffff;
pub const keycode_max = 0xffff_ffff - 1;
pub const RuleNames = extern struct {
rules: ?[*:0]const u8,
model: ?[*:0]const u8,
layout: ?[*:0]const u8,
variant: ?[*:0]const u8,
options: ?[*:0]const u8,
};
pub const LogLevel = enum(c_int) {
crit = 10,
err = 20,
warn = 30,
info = 40,
debug = 50,
};
pub const Context = opaque {
pub const Flags = enum(c_int) {
no_flags = 0,
no_default_includes = 1 << 0,
no_environment_names = 1 << 1,
};
extern fn xkb_context_new(flags: Flags) ?*Context;
pub const new = xkb_context_new;
extern fn xkb_context_ref(context: *Context) *Context;
pub const ref = xkb_context_ref;
extern fn xkb_context_unref(context: *Context) void;
pub const unref = xkb_context_unref;
extern fn xkb_context_set_user_data(context: *Context, user_data: ?*anyopaque) void;
pub const setUserData = xkb_context_set_user_data;
extern fn xkb_context_get_user_data(context: *Context) ?*anyopaque;
pub const getUserData = xkb_context_get_user_data;
extern fn xkb_context_include_path_append(context: *Context, path: [*:0]const u8) c_int;
pub const includePathAppend = xkb_context_include_path_append;
extern fn xkb_context_include_path_append_default(context: *Context) c_int;
pub const includePathAppendDefault = xkb_context_include_path_append_default;
extern fn xkb_context_include_path_reset_defaults(context: *Context) c_int;
pub const includePathResetDefaults = xkb_context_include_path_reset_defaults;
extern fn xkb_context_include_path_clear(context: *Context) void;
pub const includePathClear = xkb_context_include_path_clear;
extern fn xkb_context_num_include_paths(context: *Context) c_uint;
pub const numIncludePaths = xkb_context_num_include_paths;
extern fn xkb_context_include_path_get(context: *Context, index: c_uint) ?[*:0]const u8;
pub const includePathGet = xkb_context_include_path_get;
extern fn xkb_context_set_log_level(context: *Context, level: LogLevel) void;
pub const setLogLevel = xkb_context_set_log_level;
extern fn xkb_context_get_log_level(context: *Context) LogLevel;
pub const getLogLevel = xkb_context_get_log_level;
extern fn xkb_context_set_log_verbosity(context: *Context, verbosity: c_int) void;
pub const setLogVerbosity = xkb_context_set_log_verbosity;
extern fn xkb_context_get_log_verbosity(context: *Context) c_int;
pub const getLogVerbosity = xkb_context_get_log_verbosity;
// TODO
//extern fn xkb_context_set_log_fn(context: *Context, log_fn: ?fn (?*Context, enum_xkb_log_level, [*c]const u8, [*c]struct___va_list_tag) callconv(.C) void) void;
};
pub const Keymap = opaque {
pub const CompileFlags = enum(c_int) {
no_flags = 0,
};
pub const Format = enum(c_int) {
text_v1 = 1,
};
extern fn xkb_keymap_new_from_names(context: *Context, names: ?*const RuleNames, flags: CompileFlags) ?*Keymap;
pub const newFromNames = xkb_keymap_new_from_names;
// TODO
//extern fn xkb_keymap_new_from_file(context: *Context, file: *FILE, format: Format, flags: CompileFlags) ?*Keymap;
//pub const newFromFile = xkb_keymap_new_from_file;
extern fn xkb_keymap_new_from_string(context: *Context, string: [*:0]const u8, format: Format, flags: CompileFlags) ?*Keymap;
pub const newFromString = xkb_keymap_new_from_string;
extern fn xkb_keymap_new_from_buffer(context: *Context, buffer: [*]const u8, length: usize, format: Format, flags: CompileFlags) ?*Keymap;
pub const newFromBuffer = xkb_keymap_new_from_buffer;
extern fn xkb_keymap_ref(keymap: *Keymap) *Keymap;
pub const ref = xkb_keymap_ref;
extern fn xkb_keymap_unref(keymap: *Keymap) void;
pub const unref = xkb_keymap_unref;
extern fn xkb_keymap_get_as_string(keymap: *Keymap, format: Format) [*c]u8;
pub const getAsString = xkb_keymap_get_as_string;
extern fn xkb_keymap_min_keycode(keymap: *Keymap) Keycode;
pub const minKeycode = xkb_keymap_min_keycode;
extern fn xkb_keymap_max_keycode(keymap: *Keymap) Keycode;
pub const maxKeycode = xkb_keymap_max_keycode;
extern fn xkb_keymap_key_for_each(
keymap: *Keymap,
iter: fn (keymap: *Keymap, key: Keycode, data: ?*anyopaque) callconv(.C) void,
data: ?*anyopaque,
) void;
pub inline fn keyForEach(
keymap: *Keymap,
comptime T: type,
comptime iter: fn (keymap: *Keymap, key: Keycode, data: T) void,
data: T,
) void {
xkb_keymap_key_for_each(
keymap,
struct {
fn _wrapper(_keymap: *Keymap, _key: Keycode, _data: ?*anyopaque) callconv(.C) void {
iter(_keymap, _key, @ptrCast(@alignCast(_data)));
}
}._wrapper,
data,
);
}
extern fn xkb_keymap_key_get_name(keymap: *Keymap, key: Keycode) ?[*:0]const u8;
pub const keyGetName = xkb_keymap_key_get_name;
extern fn xkb_keymap_key_by_name(keymap: *Keymap, name: [*:0]const u8) Keycode;
pub const keyByName = xkb_keymap_key_by_name;
extern fn xkb_keymap_num_mods(keymap: *Keymap) ModIndex;
pub const numMods = xkb_keymap_num_mods;
extern fn xkb_keymap_mod_get_name(keymap: *Keymap, idx: ModIndex) ?[*:0]const u8;
pub const modGetName = xkb_keymap_mod_get_name;
extern fn xkb_keymap_mod_get_index(keymap: *Keymap, name: [*:0]const u8) ModIndex;
pub const modGetIndex = xkb_keymap_mod_get_index;
extern fn xkb_keymap_num_layouts(keymap: *Keymap) LayoutIndex;
pub const numLayouts = xkb_keymap_num_layouts;
extern fn xkb_keymap_layout_get_name(keymap: *Keymap, idx: LayoutIndex) ?[*:0]const u8;
pub const layoutGetName = xkb_keymap_layout_get_name;
extern fn xkb_keymap_layout_get_index(keymap: *Keymap, name: [*:0]const u8) LayoutIndex;
pub const layoutGetIndex = xkb_keymap_layout_get_index;
extern fn xkb_keymap_num_leds(keymap: *Keymap) LED_Index;
pub const numLeds = xkb_keymap_num_leds;
extern fn xkb_keymap_led_get_name(keymap: *Keymap, idx: LED_Index) ?[*:0]const u8;
pub const ledGetName = xkb_keymap_led_get_name;
extern fn xkb_keymap_led_get_index(keymap: *Keymap, name: [*:0]const u8) LED_Index;
pub const ledGetIndex = xkb_keymap_led_get_index;
extern fn xkb_keymap_num_layouts_for_key(keymap: *Keymap, key: Keycode) LayoutIndex;
pub const numLayoutsForKey = xkb_keymap_num_layouts_for_key;
extern fn xkb_keymap_num_levels_for_key(keymap: *Keymap, key: Keycode, layout: LayoutIndex) LevelIndex;
pub const numLevelsForKey = xkb_keymap_num_levels_for_key;
extern fn xkb_keymap_key_get_mods_for_level(keymap: *Keymap, key: Keycode, layout: LayoutIndex, level: LevelIndex, masks_out: [*]ModMask, masks_size: usize) usize;
pub const keyGetModsForLevel = xkb_keymap_key_get_mods_for_level;
extern fn xkb_keymap_key_get_syms_by_level(keymap: *Keymap, key: Keycode, layout: LayoutIndex, level: LevelIndex, syms_out: *?[*]const Keysym) c_int;
pub fn keyGetSymsByLevel(keymap: *Keymap, key: Keycode, layout: LayoutIndex, level: LevelIndex) []const Keysym {
var ptr: ?[*]const Keysym = undefined;
const len = xkb_keymap_key_get_syms_by_level(keymap, key, layout, level, &ptr);
return if (len == 0) &[0]Keysym{} else ptr.?[0..@intCast(len)];
}
extern fn xkb_keymap_key_repeats(keymap: *Keymap, key: Keycode) c_int;
pub const keyRepeats = xkb_keymap_key_repeats;
};
pub const ConsumedMode = enum(c_int) {
xkb,
gtk,
};
pub const State = opaque {
pub const Component = enum(c_int) {
_,
pub const mods_depressed = 1 << 0;
pub const mods_latched = 1 << 1;
pub const mods_locked = 1 << 2;
pub const mods_effective = 1 << 3;
pub const layout_depressed = 1 << 4;
pub const layout_latched = 1 << 5;
pub const layout_locked = 1 << 6;
pub const layout_effective = 1 << 7;
pub const leds = 1 << 8;
};
pub const Match = enum(c_int) {
_,
pub const any = 1 << 0;
pub const all = 1 << 1;
pub const non_exclusive = 1 << 16;
};
extern fn xkb_state_new(keymap: *Keymap) ?*State;
pub const new = xkb_state_new;
extern fn xkb_state_ref(state: *State) *State;
pub const ref = xkb_state_ref;
extern fn xkb_state_unref(state: *State) void;
pub const unref = xkb_state_unref;
extern fn xkb_state_get_keymap(state: *State) *Keymap;
pub const getKeymap = xkb_state_get_keymap;
extern fn xkb_state_update_key(state: *State, key: Keycode, direction: KeyDirection) Component;
pub const updateKey = xkb_state_update_key;
extern fn xkb_state_update_mask(
state: *State,
depressed_mods: ModMask,
latched_mods: ModMask,
locked_mods: ModMask,
depressed_layout: LayoutIndex,
latched_layout: LayoutIndex,
locked_layout: LayoutIndex,
) Component;
pub const updateMask = xkb_state_update_mask;
extern fn xkb_state_key_get_syms(state: *State, key: Keycode, syms_out: *?[*]const Keysym) c_int;
pub fn keyGetSyms(state: *State, key: Keycode) []const Keysym {
var ptr: ?[*]const Keysym = undefined;
const len = xkb_state_key_get_syms(state, key, &ptr);
return if (len == 0) &[0]Keysym{} else ptr.?[0..@intCast(len)];
}
extern fn xkb_state_key_get_utf8(state: *State, key: Keycode, buffer: [*]u8, size: usize) c_int;
pub fn keyGetUtf8(state: *State, key: Keycode, buffer: []u8) usize {
return @intCast(xkb_state_key_get_utf8(state, key, buffer.ptr, buffer.len));
}
extern fn xkb_state_key_get_utf32(state: *State, key: Keycode) u32;
pub const keyGetUtf32 = xkb_state_key_get_utf32;
extern fn xkb_state_key_get_one_sym(state: *State, key: Keycode) Keysym;
pub const keyGetOneSym = xkb_state_key_get_one_sym;
extern fn xkb_state_key_get_layout(state: *State, key: Keycode) LayoutIndex;
pub const keyGetLayout = xkb_state_key_get_layout;
extern fn xkb_state_key_get_level(state: *State, key: Keycode, layout: LayoutIndex) LevelIndex;
pub const keyGetLevel = xkb_state_key_get_level;
extern fn xkb_state_serialize_mods(state: *State, components: Component) ModMask;
pub const serializeMods = xkb_state_serialize_mods;
extern fn xkb_state_serialize_layout(state: *State, components: Component) LayoutIndex;
pub const serializeLayout = xkb_state_serialize_layout;
extern fn xkb_state_mod_name_is_active(state: *State, name: [*:0]const u8, kind: Component) c_int;
pub const modNameIsActive = xkb_state_mod_name_is_active;
extern fn xkb_state_mod_names_are_active(state: *State, kind: Component, match: Match, ...) c_int;
pub const modNamesAreActive = xkb_state_mod_names_are_active;
extern fn xkb_state_mod_index_is_active(state: *State, idx: ModIndex, kind: Component) c_int;
pub const modIndexIsActive = xkb_state_mod_index_is_active;
extern fn xkb_state_mod_indices_are_active(state: *State, kind: Component, match: Match, ...) c_int;
pub const modIndicesAreActive = xkb_state_mod_indices_are_active;
extern fn xkb_state_key_get_consumed_mods2(state: *State, key: Keycode, mode: ConsumedMode) ModMask;
pub const keyGetConsumedMods2 = xkb_state_key_get_consumed_mods2;
extern fn xkb_state_key_get_consumed_mods(state: *State, key: Keycode) ModMask;
pub const keyGetConsumedMods = xkb_state_key_get_consumed_mods;
extern fn xkb_state_mod_index_is_consumed2(state: *State, key: Keycode, idx: ModIndex, mode: ConsumedMode) c_int;
pub const modIndexIsConsumed2 = xkb_state_mod_index_is_consumed2;
extern fn xkb_state_mod_index_is_consumed(state: *State, key: Keycode, idx: ModIndex) c_int;
pub const modIndexIsConsumed = xkb_state_mod_index_is_consumed;
extern fn xkb_state_mod_mask_remove_consumed(state: *State, key: Keycode, mask: ModMask) ModMask;
pub const modMaskRemoveConsumed = xkb_state_mod_mask_remove_consumed;
extern fn xkb_state_layout_name_is_active(state: *State, name: [*c]const u8, kind: Component) c_int;
pub const layoutNameIsActive = xkb_state_layout_name_is_active;
extern fn xkb_state_layout_index_is_active(state: *State, idx: LayoutIndex, kind: Component) c_int;
pub const layoutIndexIsActive = xkb_state_layout_index_is_active;
extern fn xkb_state_led_name_is_active(state: *State, name: [*c]const u8) c_int;
pub const ledNameIsActive = xkb_state_led_name_is_active;
extern fn xkb_state_led_index_is_active(state: *State, idx: LED_Index) c_int;
pub const ledIndexIsActive = xkb_state_led_index_is_active;
};
test {
const std = @import("std");
@setEvalBranchQuota(4000);
std.testing.refAllDeclsRecursive(@This());
}

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +0,0 @@
pub const mod = struct {
pub const shift = "Shift";
pub const caps = "Lock";
pub const ctrl = "Control";
pub const alt = "Mod1";
pub const num = "Mod2";
pub const logo = "Mod4";
};
pub const led = struct {
pub const caps = "Caps Lock";
pub const num = "Num Lock";
pub const scroll = "Scroll Lock";
};

View File

@ -1,556 +0,0 @@
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,
};
}

View File

@ -1,375 +0,0 @@
const std = @import("std");
const wayland = @import("wayland");
const Pixel = [4]u8;
const Theme = struct {
const background = 0x282A36;
const current_line = 0x44475A;
const foreground = 0xF8F8F2;
const comment = 0x6272A4;
const cyan = 0x8BE9FD;
const green = 0x50FA7B;
const orange = 0xFFB86C;
const pink = 0xFF79C6;
const purple = 0xBD93F9;
const red = 0xFF5555;
const yellow = 0xF1FA8C;
};
fn toPixel(color: u24) Pixel {
return .{
@intCast(color & 0xFF),
@intCast(color >> 8 & 0xFF),
@intCast(color >> 16 & 0xFF),
0xFF,
};
}
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);
var conn = try wayland.Conn.init(gpa, display_path);
defer conn.deinit();
// Create an id pool to allocate ids for us
var id_pool = wayland.IdPool{};
const ids = try wayland.registerGlobals(gpa, &id_pool, conn.socket, &.{
wayland.core.Shm,
wayland.core.Compositor,
wayland.xdg.WmBase,
wayland.core.Seat,
wayland.zxdg.DecorationManagerV1,
wayland.zwp.TextInputManagerV3,
});
const DISPLAY_ID = 1;
const shm_id = ids[0] orelse return error.NeccessaryWaylandExtensionMissing;
const compositor_id = ids[1] orelse return error.NeccessaryWaylandExtensionMissing;
const xdg_wm_base_id = ids[2] orelse return error.NeccessaryWaylandExtensionMissing;
const surface_id = id_pool.create();
try conn.send(
wayland.core.Compositor.Request,
compositor_id,
.{ .create_surface = .{
.new_id = surface_id,
} },
);
const xdg_surface_id = id_pool.create();
try conn.send(
wayland.xdg.WmBase.Request,
xdg_wm_base_id,
.{ .get_xdg_surface = .{
.id = xdg_surface_id,
.surface = surface_id,
} },
);
const xdg_toplevel_id = id_pool.create();
try conn.send(
wayland.xdg.Surface.Request,
xdg_surface_id,
.{ .get_toplevel = .{
.id = xdg_toplevel_id,
} },
);
var zxdg_toplevel_decoration_id_opt: ?u32 = null;
if (ids[4]) |zxdg_decoration_manager_id| {
zxdg_toplevel_decoration_id_opt = id_pool.create();
try conn.send(
wayland.zxdg.DecorationManagerV1.Request,
zxdg_decoration_manager_id,
.{ .get_toplevel_decoration = .{
.new_id = zxdg_toplevel_decoration_id_opt.?,
.toplevel = xdg_toplevel_id,
} },
);
}
try conn.send(
wayland.core.Surface.Request,
surface_id,
wayland.core.Surface.Request.commit,
);
const registry_done_id = id_pool.create();
try conn.send(
wayland.core.Display.Request,
DISPLAY_ID,
.{ .sync = .{ .callback = registry_done_id } },
);
var done = false;
var surface_configured = false;
while (!done or !surface_configured) {
const header, const body = try conn.recv();
if (header.object_id == xdg_surface_id) {
const event = try wayland.deserialize(wayland.xdg.Surface.Event, header, body);
switch (event) {
.configure => |conf| {
try conn.send(
wayland.xdg.Surface.Request,
xdg_surface_id,
.{ .ack_configure = .{
.serial = conf.serial,
} },
);
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, body);
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, body);
std.debug.print("<- {}\n", .{event});
} else if (header.object_id == registry_done_id) {
done = true;
} else if (header.object_id == shm_id) {
const event = try wayland.deserialize(wayland.core.Shm.Event, header, body);
switch (event) {
.format => |format| std.debug.print("<- format {} {}\n", .{ format.format, std.zig.fmtEscapes(std.mem.asBytes(&format.format)) }),
}
} else if (header.object_id == DISPLAY_ID) {
const event = try wayland.deserialize(wayland.core.Display.Event, header, body);
switch (event) {
.@"error" => |err| std.debug.print("<- error({}): {} {s}\n", .{ err.object_id, err.code, err.message }),
.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(body)) });
}
}
// allocate a shared memory file for display purposes
const framebuffer_size = [2]u32{ 128, 128 };
const pool_file_len = 1024 * framebuffer_size[0] * framebuffer_size[1] * @sizeOf(Pixel);
const pool_fd = try std.os.memfd_create("my-wayland-framebuffer", 0);
try std.os.ftruncate(pool_fd, pool_file_len);
const pool_bytes = try std.os.mmap(null, pool_file_len, std.os.PROT.READ | std.os.PROT.WRITE, std.os.MAP.SHARED, pool_fd, 0);
var pool_fixed_buffer_allocator = std.heap.FixedBufferAllocator.init(pool_bytes);
var pool_general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){ .backing_allocator = pool_fixed_buffer_allocator.allocator() };
const pool_alloc = pool_general_purpose_allocator.allocator();
const framebuffer = try pool_alloc.alloc(Pixel, framebuffer_size[0] * framebuffer_size[1]);
// put some interesting colors into the framebuffer
renderGradient(framebuffer, framebuffer_size);
const wl_shm_pool_id = id_pool.create();
{
std.debug.print("framebuffer_fd: {}\n", .{pool_fd});
try conn.send(
wayland.core.Shm.Request,
shm_id,
.{ .create_pool = .{
.new_id = wl_shm_pool_id,
.fd = @enumFromInt(pool_fd),
.size = pool_file_len,
} },
);
}
var framebuffers = std.AutoHashMap(u32, []Pixel).init(gpa);
defer framebuffers.deinit();
try framebuffers.put(wl_shm_pool_id, framebuffer);
const wl_buffer_id = id_pool.create();
try conn.send(
wayland.core.ShmPool.Request,
wl_shm_pool_id,
.{ .create_buffer = .{
.new_id = wl_buffer_id,
.offset = 0,
.width = framebuffer_size[0],
.height = framebuffer_size[1],
.stride = framebuffer_size[0] * @sizeOf([4]u8),
.format = .argb8888,
} },
);
try conn.send(
wayland.core.Surface.Request,
surface_id,
.{ .attach = .{
.buffer = wl_buffer_id,
.x = 0,
.y = 0,
} },
);
try conn.send(
wayland.core.Surface.Request,
surface_id,
.{ .damage = .{
.x = 0,
.y = 0,
.width = std.math.maxInt(i32),
.height = std.math.maxInt(i32),
} },
);
try conn.send(
wayland.core.Surface.Request,
surface_id,
wayland.core.Surface.Request.commit,
);
var window_size: [2]u32 = [2]u32{ @intCast(framebuffer_size[0]), @intCast(framebuffer_size[1]) };
var running = true;
while (running) {
const header, const body = try conn.recv();
if (header.object_id == xdg_surface_id) {
const event = try wayland.deserialize(wayland.xdg.Surface.Event, header, body);
switch (event) {
.configure => |conf| {
try conn.send(
wayland.xdg.Surface.Request,
xdg_surface_id,
.{ .ack_configure = .{
.serial = conf.serial,
} },
);
const new_buffer_id = id_pool.create();
const new_framebuffer = try pool_alloc.alloc(Pixel, window_size[0] * window_size[1]);
try framebuffers.put(new_buffer_id, new_framebuffer);
// put some interesting colors into the new_framebuffer
renderGradient(new_framebuffer, window_size);
try conn.send(
wayland.core.ShmPool.Request,
wl_shm_pool_id,
.{ .create_buffer = .{
.new_id = new_buffer_id,
.offset = @intCast(@intFromPtr(new_framebuffer.ptr) - @intFromPtr(pool_bytes.ptr)),
.width = @intCast(window_size[0]),
.height = @intCast(window_size[1]),
.stride = @as(i32, @intCast(window_size[0])) * @sizeOf([4]u8),
.format = .argb8888,
} },
);
try conn.send(
wayland.core.Surface.Request,
surface_id,
.{ .attach = .{
.buffer = new_buffer_id,
.x = 0,
.y = 0,
} },
);
try conn.send(
wayland.core.Surface.Request,
surface_id,
.{ .damage = .{
.x = 0,
.y = 0,
.width = std.math.maxInt(i32),
.height = std.math.maxInt(i32),
} },
);
// commit the configuration
try conn.send(
wayland.core.Surface.Request,
surface_id,
wayland.core.Surface.Request.commit,
);
},
}
} else if (header.object_id == xdg_toplevel_id) {
const event = try wayland.deserialize(wayland.xdg.Toplevel.Event, header, body);
switch (event) {
.configure => |conf| {
std.debug.print("<- xdg_toplevel@{} configure <{}, {}> {any}\n", .{ header.object_id, conf.width, conf.height, conf.states });
window_size = .{
@intCast(conf.width),
@intCast(conf.height),
};
},
.close => running = false,
else => |tag| std.debug.print("<- xdg_toplevel@{} {s} {}\n", .{ header.object_id, @tagName(tag), event }),
}
} else if (header.object_id == xdg_wm_base_id) {
const event = try wayland.deserialize(wayland.xdg.WmBase.Event, header, body);
switch (event) {
.ping => |ping| {
try conn.send(
wayland.xdg.WmBase.Request,
xdg_wm_base_id,
.{ .pong = .{
.serial = ping.serial,
} },
);
},
}
} else if (framebuffers.get(header.object_id)) |framebuffer_slice| {
const event = try wayland.deserialize(wayland.core.Buffer.Event, header, body);
switch (event) {
.release => {
_ = framebuffers.remove(header.object_id);
pool_alloc.free(framebuffer_slice);
},
}
} else if (header.object_id == DISPLAY_ID) {
const event = try wayland.deserialize(wayland.core.Display.Event, header, body);
switch (event) {
.@"error" => |err| std.debug.print("<- error({}): {} {s}\n", .{ err.object_id, err.code, err.message }),
.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(body)) });
}
}
}
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,
};
}
fn getFramebuffer(framebuffers: *std.AutoHashMap(u32, []Pixel), id_pool: *wayland.IdPool, pool_alloc: std.mem.Allocator, fb_size: [2]u32) !struct { u32, []Pixel } {
const new_buffer_id = id_pool.create();
const new_framebuffer = try pool_alloc.alloc(Pixel, fb_size[0] * fb_size[1]);
try framebuffers.put(new_buffer_id, new_framebuffer);
return .{ new_buffer_id, new_framebuffer };
}
fn renderGradient(framebuffer: []Pixel, fb_size: [2]u32) void {
for (0..fb_size[1]) |y| {
const row = framebuffer[y * fb_size[0] .. (y + 1) * fb_size[0]];
for (row, 0..fb_size[0]) |*pixel, x| {
pixel.* = .{
@truncate(x),
@truncate(y),
0x00,
0xFF,
};
}
}
}

View File

@ -1,689 +0,0 @@
const std = @import("std");
const wayland = @import("wayland");
const xkbcommon = @import("xkbcommon");
const font8x8 = @cImport({
@cInclude("font8x8.h");
});
const PieceTable = @import("PieceTable.zig").PieceTable;
const Pixel = [4]u8;
const Theme = struct {
const background = 0x282A36;
const current_line = 0x44475A;
const foreground = 0xF8F8F2;
const comment = 0x6272A4;
const cyan = 0x8BE9FD;
const green = 0x50FA7B;
const orange = 0xFFB86C;
const pink = 0xFF79C6;
const purple = 0xBD93F9;
const red = 0xFF5555;
const yellow = 0xF1FA8C;
};
fn toPixel(color: u24) Pixel {
return .{
@intCast(color & 0xFF),
@intCast(color >> 8 & 0xFF),
@intCast(color >> 16 & 0xFF),
0xFF,
};
}
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);
var conn = try wayland.Conn.init(gpa, display_path);
defer conn.deinit();
// Create an id pool to allocate ids for us
var id_pool = wayland.IdPool{};
const ids = try wayland.registerGlobals(gpa, &id_pool, conn.socket, &.{
wayland.core.Shm,
wayland.core.Compositor,
wayland.xdg.WmBase,
wayland.core.Seat,
wayland.zxdg.DecorationManagerV1,
wayland.zwp.TextInputManagerV3,
});
const DISPLAY_ID = 1;
const shm_id = ids[0] orelse return error.NeccessaryWaylandExtensionMissing;
const compositor_id = ids[1] orelse return error.NeccessaryWaylandExtensionMissing;
const xdg_wm_base_id = ids[2] orelse return error.NeccessaryWaylandExtensionMissing;
const wl_seat_id = ids[3] orelse return error.NeccessaryWaylandExtensionMissing;
const zwp_text_input_manager_v3 = ids[5] orelse return error.NeccessaryWaylandExtensionMissing;
const surface_id = id_pool.create();
try conn.send(
wayland.core.Compositor.Request,
compositor_id,
.{ .create_surface = .{
.new_id = surface_id,
} },
);
const xdg_surface_id = id_pool.create();
try conn.send(
wayland.xdg.WmBase.Request,
xdg_wm_base_id,
.{ .get_xdg_surface = .{
.id = xdg_surface_id,
.surface = surface_id,
} },
);
const xdg_toplevel_id = id_pool.create();
try conn.send(
wayland.xdg.Surface.Request,
xdg_surface_id,
.{ .get_toplevel = .{
.id = xdg_toplevel_id,
} },
);
const zwp_text_input_v3_id = id_pool.create();
try conn.send(
wayland.zwp.TextInputManagerV3.Request,
zwp_text_input_manager_v3,
.{ .get_text_input = .{
.id = zwp_text_input_v3_id,
.seat = wl_seat_id,
} },
);
var zxdg_toplevel_decoration_id_opt: ?u32 = null;
if (ids[4]) |zxdg_decoration_manager_id| {
zxdg_toplevel_decoration_id_opt = id_pool.create();
try conn.send(
wayland.zxdg.DecorationManagerV1.Request,
zxdg_decoration_manager_id,
.{ .get_toplevel_decoration = .{
.new_id = zxdg_toplevel_decoration_id_opt.?,
.toplevel = xdg_toplevel_id,
} },
);
}
try conn.send(
wayland.core.Surface.Request,
surface_id,
wayland.core.Surface.Request.commit,
);
const registry_done_id = id_pool.create();
try conn.send(
wayland.core.Display.Request,
DISPLAY_ID,
.{ .sync = .{ .callback = registry_done_id } },
);
var done = false;
var surface_configured = false;
var seat_capabilties: ?wayland.core.Seat.Capability = null;
while (!done or !surface_configured) {
const header, const body = try conn.recv();
if (header.object_id == xdg_surface_id) {
const event = try wayland.deserialize(wayland.xdg.Surface.Event, header, body);
switch (event) {
.configure => |conf| {
try conn.send(
wayland.xdg.Surface.Request,
xdg_surface_id,
.{ .ack_configure = .{
.serial = conf.serial,
} },
);
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, body);
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, body);
std.debug.print("<- {}\n", .{event});
} else if (header.object_id == registry_done_id) {
done = true;
} else if (header.object_id == shm_id) {
const event = try wayland.deserialize(wayland.core.Shm.Event, header, body);
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, body);
switch (event) {
.capabilities => |capabilities| {
const cap: wayland.core.Seat.Capability = @bitCast(capabilities.capability);
std.debug.print("<- wl_seat.capabilties = {}\n", .{cap});
seat_capabilties = cap;
},
.name => |name| {
std.debug.print("<- wl_seat.name = {s}\n", .{name.name});
},
}
} else if (header.object_id == DISPLAY_ID) {
const event = try wayland.deserialize(wayland.core.Display.Event, header, body);
switch (event) {
.@"error" => |err| std.debug.print("<- error({}): {} {s}\n", .{ err.object_id, err.code, err.message }),
.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(body)) });
}
}
var wl_pointer_id_opt: ?u32 = null;
var wl_keyboard_id_opt: ?u32 = null;
if (seat_capabilties) |caps| {
if (caps.pointer) {
wl_pointer_id_opt = id_pool.create();
std.debug.print("wl pointer id: {}\n", .{wl_pointer_id_opt.?});
try conn.send(
wayland.core.Seat.Request,
wl_seat_id,
.{ .get_pointer = .{
.new_id = wl_pointer_id_opt.?,
} },
);
}
if (caps.keyboard) {
wl_keyboard_id_opt = id_pool.create();
std.debug.print("wl keyboard id: {}\n", .{wl_keyboard_id_opt.?});
try conn.send(
wayland.core.Seat.Request,
wl_seat_id,
.{ .get_keyboard = .{
.new_id = wl_keyboard_id_opt.?,
} },
);
}
}
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 framebuffer_size = [2]u32{ 128, 128 };
const pool_file_len = 1024 * framebuffer_size[0] * framebuffer_size[1] * @sizeOf(Pixel);
const pool_fd = try std.os.memfd_create("my-wayland-framebuffer", 0);
try std.os.ftruncate(pool_fd, pool_file_len);
const pool_bytes = try std.os.mmap(null, pool_file_len, std.os.PROT.READ | std.os.PROT.WRITE, std.os.MAP.SHARED, pool_fd, 0);
var pool_fixed_buffer_allocator = std.heap.FixedBufferAllocator.init(pool_bytes);
var pool_general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){ .backing_allocator = pool_fixed_buffer_allocator.allocator() };
const pool_alloc = pool_general_purpose_allocator.allocator();
const framebuffer = try pool_alloc.alloc(Pixel, framebuffer_size[0] * framebuffer_size[1]);
// put some interesting colors into the framebuffer
renderGradient(framebuffer, framebuffer_size);
const wl_shm_pool_id = id_pool.create();
{
std.debug.print("framebuffer_fd: {}\n", .{pool_fd});
try conn.send(
wayland.core.Shm.Request,
shm_id,
.{ .create_pool = .{
.new_id = wl_shm_pool_id,
.fd = @enumFromInt(pool_fd),
.size = pool_file_len,
} },
);
}
var framebuffers = std.AutoHashMap(u32, []Pixel).init(gpa);
defer framebuffers.deinit();
try framebuffers.put(wl_shm_pool_id, framebuffer);
const wl_buffer_id = id_pool.create();
try conn.send(
wayland.core.ShmPool.Request,
wl_shm_pool_id,
.{ .create_buffer = .{
.new_id = wl_buffer_id,
.offset = 0,
.width = framebuffer_size[0],
.height = framebuffer_size[1],
.stride = framebuffer_size[0] * @sizeOf([4]u8),
.format = .argb8888,
} },
);
try conn.send(
wayland.core.Surface.Request,
surface_id,
.{ .attach = .{
.buffer = wl_buffer_id,
.x = 0,
.y = 0,
} },
);
try conn.send(
wayland.core.Surface.Request,
surface_id,
.{ .damage = .{
.x = 0,
.y = 0,
.width = std.math.maxInt(i32),
.height = std.math.maxInt(i32),
} },
);
try conn.send(
wayland.core.Surface.Request,
surface_id,
wayland.core.Surface.Request.commit,
);
var window_size: [2]u32 = [2]u32{ @intCast(framebuffer_size[0]), @intCast(framebuffer_size[1]) };
const xkb_ctx = xkbcommon.Context.new(.no_flags) orelse return error.XKBInit;
defer xkb_ctx.unref();
var xkb_keymap_opt: ?*xkbcommon.Keymap = null;
defer if (xkb_keymap_opt) |xkb_keymap| {
xkb_keymap.unref();
};
var xkb_state_opt: ?*xkbcommon.State = null;
defer if (xkb_state_opt) |xkb_state| {
xkb_state.unref();
};
var piece_table = try PieceTable.init(gpa, "Hello, World!");
defer piece_table.deinit();
var edit_buffer: [1024]u8 = [1]u8{0} ** 1024;
var edit_slice: ?[]u8 = null;
var delete_before: usize = 0;
var delete_after: usize = 0;
// var preedit_buffer: [1024]u8 = [1]u8{0} ** 1024;
// var preedit_slice: ?[]u8 = null;
var cursor_pos: usize = piece_table.getTotalSize();
var running = true;
while (running) {
const header, const body = try conn.recv();
if (header.object_id == xdg_surface_id) {
const event = try wayland.deserialize(wayland.xdg.Surface.Event, header, body);
switch (event) {
.configure => |conf| {
try conn.send(
wayland.xdg.Surface.Request,
xdg_surface_id,
.{ .ack_configure = .{
.serial = conf.serial,
} },
);
const new_buffer_id = id_pool.create();
const new_framebuffer = try pool_alloc.alloc(Pixel, window_size[0] * window_size[1]);
try framebuffers.put(new_buffer_id, new_framebuffer);
// put some interesting colors into the new_framebuffer
renderGradient(new_framebuffer, window_size);
const text = try piece_table.writeAllAlloc();
defer gpa.free(text);
// blit some characters
renderText(new_framebuffer, window_size, .{ 10, 10 }, text);
try conn.send(
wayland.core.ShmPool.Request,
wl_shm_pool_id,
.{ .create_buffer = .{
.new_id = new_buffer_id,
.offset = @intCast(@intFromPtr(new_framebuffer.ptr) - @intFromPtr(pool_bytes.ptr)),
.width = @intCast(window_size[0]),
.height = @intCast(window_size[1]),
.stride = @as(i32, @intCast(window_size[0])) * @sizeOf([4]u8),
.format = .argb8888,
} },
);
try conn.send(
wayland.core.Surface.Request,
surface_id,
.{ .attach = .{
.buffer = new_buffer_id,
.x = 0,
.y = 0,
} },
);
try conn.send(
wayland.core.Surface.Request,
surface_id,
.{ .damage = .{
.x = 0,
.y = 0,
.width = std.math.maxInt(i32),
.height = std.math.maxInt(i32),
} },
);
// commit the configuration
try conn.send(
wayland.core.Surface.Request,
surface_id,
wayland.core.Surface.Request.commit,
);
},
}
} else if (header.object_id == xdg_toplevel_id) {
const event = try wayland.deserialize(wayland.xdg.Toplevel.Event, header, body);
switch (event) {
.configure => |conf| {
std.debug.print("<- xdg_toplevel@{} configure <{}, {}> {any}\n", .{ header.object_id, conf.width, conf.height, conf.states });
window_size = .{
@intCast(conf.width),
@intCast(conf.height),
};
},
.close => running = false,
else => |tag| std.debug.print("<- xdg_toplevel@{} {s} {}\n", .{ header.object_id, @tagName(tag), event }),
}
} else if (header.object_id == xdg_wm_base_id) {
const event = try wayland.deserialize(wayland.xdg.WmBase.Event, header, body);
switch (event) {
.ping => |ping| {
try conn.send(
wayland.xdg.WmBase.Request,
xdg_wm_base_id,
.{ .pong = .{
.serial = ping.serial,
} },
);
},
}
} else if (header.object_id == wl_pointer_id) {
const event = try wayland.deserialize(wayland.core.Pointer.Event, header, body);
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, body);
switch (event) {
.keymap => |keymap| {
const fd = conn.fd_queue.orderedRemove(0);
std.debug.print("keymap format={}, size={}, fd={}\n", .{
keymap.format,
keymap.size,
fd,
});
const mem = try std.os.mmap(
null,
keymap.size,
std.os.PROT.READ,
std.os.MAP.PRIVATE,
fd,
0,
);
std.debug.print("---START xkb file---\n{s}\n---END xkb file---\n", .{mem});
xkb_keymap_opt = xkbcommon.Keymap.newFromString(xkb_ctx, @ptrCast(mem), .text_v1, .no_flags) orelse return error.XKBKeymap;
xkb_state_opt = xkbcommon.State.new(xkb_keymap_opt.?) orelse return error.XKBStateInit;
},
.modifiers => |mods| {
if (xkb_state_opt) |xkb_state| {
_ = xkb_state.updateMask(
mods.mods_depressed,
mods.mods_latched,
mods.mods_locked,
0,
0,
0,
);
}
},
.key => |key| {
if (xkb_state_opt) |xkb_state| {
const keycode: xkbcommon.Keycode = key.key + 8;
const keysym: xkbcommon.Keysym = xkb_state.keyGetOneSym(keycode);
var buf: [64]u8 = undefined;
// const name_len = keysym.getName(&buf, buf.len);
// std.debug.print("{s}\n", .{buf[0..@intCast(name_len)]});
if (key.state == .pressed) {
const sym = xkbcommon.Keysym;
switch (@as(u32, @intFromEnum(keysym))) {
sym.BackSpace => {
try piece_table.delete(cursor_pos - 1, 1);
cursor_pos -= 1;
},
sym.Delete => {
piece_table.delete(cursor_pos, 1) catch |e| switch (e) {
error.OutOfBounds => {},
else => return e,
};
},
sym.Left => {
cursor_pos -|= 1;
},
sym.Right => {
cursor_pos += 1;
cursor_pos = @min(cursor_pos, piece_table.getTotalSize());
},
else => if (key.state == .pressed) {
const size = xkb_state.keyGetUtf8(keycode, &buf);
try piece_table.insert(cursor_pos, buf[0..size]);
cursor_pos += size;
},
}
const new_buffer_id, const new_framebuffer = try getFramebuffer(&framebuffers, &id_pool, pool_alloc, window_size);
// put some interesting colors into the new_framebuffer
renderGradient(new_framebuffer, window_size);
const text = try piece_table.writeAllAlloc();
defer gpa.free(text);
// blit some characters
renderText(new_framebuffer, window_size, .{ 10, 10 }, text);
try conn.send(
wayland.core.ShmPool.Request,
wl_shm_pool_id,
.{ .create_buffer = .{
.new_id = new_buffer_id,
.offset = @intCast(@intFromPtr(new_framebuffer.ptr) - @intFromPtr(pool_bytes.ptr)),
.width = @intCast(window_size[0]),
.height = @intCast(window_size[1]),
.stride = @as(i32, @intCast(window_size[0])) * @sizeOf([4]u8),
.format = .argb8888,
} },
);
try conn.send(
wayland.core.Surface.Request,
surface_id,
.{ .attach = .{
.buffer = new_buffer_id,
.x = 0,
.y = 0,
} },
);
try conn.send(
wayland.core.Surface.Request,
surface_id,
.{ .damage = .{
.x = 0,
.y = 0,
.width = std.math.maxInt(i32),
.height = std.math.maxInt(i32),
} },
);
// commit the configuration
try conn.send(
wayland.core.Surface.Request,
surface_id,
wayland.core.Surface.Request.commit,
);
}
}
},
else => {
std.debug.print("<- wl_keyboard@{}\n", .{event});
},
}
} else if (header.object_id == zwp_text_input_v3_id) {
const event = try wayland.deserialize(wayland.zwp.TextInputV3.Event, header, body);
std.debug.print("<- zwp_text_input_v3@{} event {}\n", .{ zwp_text_input_v3_id, event });
switch (event) {
.enter => |e| {
_ = e;
// if (e.surface == surface_id) {
try conn.send(
wayland.zwp.TextInputV3.Request,
zwp_text_input_v3_id,
.enable,
);
try conn.send(
wayland.zwp.TextInputV3.Request,
zwp_text_input_v3_id,
.{ .set_content_type = .{
.hint = .multiline,
.purpose = .normal,
} },
);
try conn.send(
wayland.zwp.TextInputV3.Request,
zwp_text_input_v3_id,
.commit,
);
// }
},
.leave => |e| {
_ = e;
// if (e.surface == surface_id) {
try conn.send(
wayland.zwp.TextInputV3.Request,
zwp_text_input_v3_id,
.disable,
);
// }
},
.preedit_string => {},
.commit_string => |commit| {
edit_slice = edit_buffer[0..commit.text.len];
@memcpy(edit_slice.?, commit.text);
},
.delete_surrounding_text => |offset| {
delete_before = offset.before_length;
delete_after = offset.after_length;
},
.done => |_| {
// 1 replace existing pre-edit string with cursor
// 2 delete requested surrounding text
const start = cursor_pos - delete_before;
const end = cursor_pos + delete_after;
const length = end - start;
if (length != 0) {
try piece_table.delete(start, length);
}
// 3 insert commit string with cursor at its end
if (edit_slice) |slice| {
try piece_table.insert(cursor_pos, slice);
cursor_pos += slice.len;
edit_slice = null;
}
// 4 calculate surrounding text to send
// 5 insert new preedit text in cursor position
// 6 place cursor inside predit text
},
}
} else if (framebuffers.get(header.object_id)) |framebuffer_slice| {
const event = try wayland.deserialize(wayland.core.Buffer.Event, header, body);
switch (event) {
.release => {
_ = framebuffers.remove(header.object_id);
pool_alloc.free(framebuffer_slice);
},
}
} else if (header.object_id == DISPLAY_ID) {
const event = try wayland.deserialize(wayland.core.Display.Event, header, body);
switch (event) {
.@"error" => |err| std.debug.print("<- error({}): {} {s}\n", .{ err.object_id, err.code, err.message }),
.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(body)) });
}
}
}
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,
};
}
fn getFramebuffer(framebuffers: *std.AutoHashMap(u32, []Pixel), id_pool: *wayland.IdPool, pool_alloc: std.mem.Allocator, fb_size: [2]u32) !struct { u32, []Pixel } {
const new_buffer_id = id_pool.create();
const new_framebuffer = try pool_alloc.alloc(Pixel, fb_size[0] * fb_size[1]);
try framebuffers.put(new_buffer_id, new_framebuffer);
return .{ new_buffer_id, new_framebuffer };
}
fn renderGradient(framebuffer: []Pixel, fb_size: [2]u32) void {
for (0..fb_size[1]) |y| {
const row = framebuffer[y * fb_size[0] .. (y + 1) * fb_size[0]];
for (row, 0..fb_size[0]) |*pixel, x| {
pixel.* = .{
@truncate(x),
@truncate(y),
0x00,
0xFF,
};
}
}
}
fn textWidth(str: []const u8) usize {
return std.unicode.utf8CountCodepoints(str) catch 0; // incorrect, but I'm going with it
}
fn renderText(framebuffer: []Pixel, fb_size: [2]u32, pos: [2]usize, str: []const u8) void {
const top, const left = pos;
const bot = @min(top + 8, fb_size[1]);
const right = @min(left + textWidth(str) * 8, fb_size[0]);
for (top..bot) |y| {
const row = framebuffer[y * fb_size[0] .. (y + 1) * fb_size[0]];
for (row[left..right], left..right) |*pixel, x| {
const col = ((x - left) / 8);
const which_char = str[col];
if (!std.ascii.isPrint(which_char)) continue;
const char = font8x8.font8x8_basic[which_char];
const line = char[(y - top) % 8];
if ((line >> @intCast((x - left) % 8)) & 0x1 != 0) {
pixel.* = toPixel(Theme.foreground);
} else {
pixel.* = toPixel(Theme.background);
}
}
}
}

View File

@ -1,461 +0,0 @@
const std = @import("std");
const testing = std.testing;
/// Represents buffer and a set of changes to the buffer.
///
/// All insertions are copied internally. Deletions to the buffer will not free memory.
///
/// To initialize without text, use struct initialization syntax and specify an allocator
/// like so: `var table = PieceTable{ .allocator = allocator };`
pub const PieceTable = struct {
allocator: std.mem.Allocator,
original: []const u8 = &.{},
buffer: std.ArrayListUnmanaged(u8) = .{},
pieces: std.ArrayListUnmanaged(Piece) = .{},
pub const Piece = struct {
start: usize,
length: usize,
tag: Tag = .added,
pub const Tag = enum {
original,
added,
};
};
pub fn init(allocator: std.mem.Allocator, original: []const u8) !PieceTable {
if (original.len == 0) {
// An empty string was passed, skip making a copy of it
return .{
.allocator = allocator,
};
}
const original_copy = try allocator.dupe(u8, original);
// Create piece pointing to original text
var pieces = try std.ArrayListUnmanaged(Piece).initCapacity(allocator, 1);
pieces.appendAssumeCapacity(.{
.start = 0,
.length = original_copy.len,
.tag = .original,
});
return .{
.allocator = allocator,
.original = original_copy,
.buffer = .{},
.pieces = pieces,
};
}
pub fn deinit(table: *PieceTable) void {
table.allocator.free(table.original);
table.buffer.deinit(table.allocator);
table.pieces.deinit(table.allocator);
}
/// Inserts `new_text` into buffer at `index`.
///
/// `new_text` is owned by caller.
///
/// It is an error to insert outside of the bounds of the piece table. If the
/// table is empty, 0 is the only valid argument for index.
pub fn insert(table: *PieceTable, index: usize, text: []const u8) !void {
const start = table.buffer.items.len;
const length = text.len;
try table.buffer.appendSlice(table.allocator, text);
// Insert at the start of the file, catches empty tables
if (index == 0) {
try table.pieces.insert(table.allocator, 0, .{ .start = start, .length = length, .tag = .added });
return;
}
var p_i: usize = 0;
var b_i: usize = 0;
while (p_i < table.pieces.items.len) : (p_i += 1) {
const p = table.pieces.items[p_i];
if (index == b_i + p.length) {
if (p_i + 1 == table.pieces.items.len) {
// The new index is the end of the file
try table.pieces.append(table.allocator, .{ .start = start, .length = length, .tag = .added });
return;
} else {
// The new index is directly after an existing node, but not at the end of the file.
try table.pieces.insert(table.allocator, p_i + 1, .{ .start = start, .length = length, .tag = .added });
return;
}
} else if (index < b_i + p.length) {
// new piece is within another piece; split the old one into 2
// and insert the new piece between
// ignore the returned slice since we will also want the
// piece right before the insertion
_ = try table.pieces.addManyAt(table.allocator, p_i + 1, 2);
const pieces = table.pieces.items[p_i..][0..3];
const sub_i = index - b_i;
const start0 = pieces[0].start;
const length_original = pieces[0].length;
const length_rest = length_original - sub_i;
const length_first = length_original - length_rest;
std.debug.assert(pieces[0].length == length_first + length_rest);
pieces[0].length = length_first;
pieces[1].start = start;
pieces[1].length = length;
pieces[2].start = start0 + (length_first);
pieces[2].length = length_rest;
// set the tag for the pieces 1 and 2
// elide setting the tag for pieces[0], it should be correct already
pieces[1].tag = .added;
switch (p.tag) {
.original => pieces[2].tag = .original,
.added => pieces[2].tag = .added,
}
return;
} else {
b_i += p.length;
}
}
@panic("Impossible state while inserting into PieceTable");
}
/// Deletes the data from start to start+length from the piece table.
/// Will not free any memory.
pub fn delete(table: *PieceTable, start: usize, length: usize) !void {
if (length == 0) return error.InvalidLength;
const endi = start + length;
var b_i: usize = 0; // buffer index
const p_start, const start_subi, const start_piece = for (table.pieces.items, 0..) |piece, i| {
if (start < b_i + piece.length) {
// start found
break .{ i, start - b_i, piece };
}
b_i += piece.length;
} else return error.OutOfBounds;
// reuse b_i
const p_end, const end_subi, const end_piece = for (table.pieces.items[p_start..], p_start..) |piece, i| {
if (endi < b_i + piece.length) {
break .{ i, endi - b_i, piece };
}
b_i += piece.length;
} else .{ p_start, start_piece.length, start_piece };
// Removal cases:
// 1. the deletion starts on one piece boundary and ends on another piece boundary
// - Delete all pieces between start and end
// 2. the deletion starts within a piece and ends on a boundary
// - Delete all but the start piece
// - modify slice end in start piece
// 3. the deletion starts on a bondary and ends within a piece
// - Delete all but the end piece
// - modify slice start in end piece
// 4. the deletion starts within a piece and ends within a piece
// - Delet all the start and end pieces
// - modify slice end in start piece
// - modify slice end in end piece
const is_start_on_boundary = start_subi == 0;
const is_end_on_boundary = end_subi == end_piece.length;
const remove_len = (p_end + 1) - p_start;
if (is_start_on_boundary and is_end_on_boundary) {
table.pieces.replaceRange(table.allocator, p_start, remove_len, &.{}) catch unreachable;
} else {
if (is_start_on_boundary) {
const new = &[_]PieceTable.Piece{
.{ .start = end_piece.start + end_subi, .length = end_piece.length - end_subi, .tag = end_piece.tag },
};
table.pieces.replaceRange(table.allocator, p_start, remove_len, new) catch unreachable;
} else if (is_end_on_boundary) {
const new = &[_]PieceTable.Piece{
.{ .start = start_piece.start, .length = start_piece.length - start_subi, .tag = start_piece.tag },
};
table.pieces.replaceRange(table.allocator, p_start, remove_len, new) catch unreachable;
} else {
const new = &[_]PieceTable.Piece{
.{ .start = start_piece.start, .length = start_subi, .tag = start_piece.tag },
.{ .start = end_piece.start + end_subi, .length = end_piece.length - end_subi, .tag = end_piece.tag },
};
table.pieces.replaceRange(table.allocator, p_start, remove_len, new) catch unreachable;
}
}
}
pub fn getTotalSize(table: PieceTable) usize {
var length: usize = 0;
for (table.pieces.items) |piece| {
length += piece.length;
}
return length;
}
fn getPiece(table: PieceTable, piece: Piece) []const u8 {
return switch (piece.tag) {
.original => table.original[piece.start..][0..piece.length],
.added => table.buffer.items[piece.start..][0..piece.length],
};
}
pub fn writeAll(table: PieceTable, buffer: []u8) void {
std.debug.assert(table.getTotalSize() == buffer.len);
var current_buffer = buffer[0..];
for (table.pieces.items) |piece| {
const str = table.getPiece(piece);
@memcpy(current_buffer[0..piece.length], str);
current_buffer = current_buffer[piece.length..];
}
}
pub fn writeAllAlloc(table: PieceTable) ![]u8 {
const size = table.getTotalSize();
const buffer = try table.allocator.alloc(u8, size);
table.writeAll(buffer);
return buffer;
}
};
test "Init empty PieceTable" {
var table = try PieceTable.init(testing.allocator, "");
defer table.deinit();
var out_buf: [0]u8 = undefined;
table.writeAll(&out_buf);
try testing.expectEqualStrings("", &out_buf);
}
test "Insert into empty PieceTable" {
var table = try PieceTable.init(testing.allocator, "");
defer table.deinit();
try table.insert(0, "the quick brown fox\njumped over the lazy dog");
var out_buf: [44]u8 = undefined;
table.writeAll(&out_buf);
try testing.expectEqualStrings(
\\the quick brown fox
\\jumped over the lazy dog
, &out_buf);
}
test "Init Piecetable" {
const original = "the quick brown fox\njumped over the lazy dog";
var table = try PieceTable.init(testing.allocator, original);
defer table.deinit();
var out_buf: [44]u8 = undefined;
table.writeAll(&out_buf);
try testing.expectEqualStrings(
\\the quick brown fox
\\jumped over the lazy dog
, &out_buf);
}
test "Insert into PieceTable" {
const original = "the quick brown fox\njumped over the lazy dog";
var table = try PieceTable.init(testing.allocator, original);
defer table.deinit();
try table.insert(20, "went to the park and\n");
try testing.expectEqual(@as(usize, 3), table.pieces.items.len);
try testing.expectEqual(PieceTable.Piece.Tag.original, table.pieces.items[0].tag);
try testing.expectEqual(PieceTable.Piece.Tag.added, table.pieces.items[1].tag);
try testing.expectEqual(PieceTable.Piece.Tag.original, table.pieces.items[2].tag);
try testing.expectEqualStrings("the quick brown fox\n", table.getPiece(table.pieces.items[0]));
try testing.expectEqualStrings("went to the park and\n", table.getPiece(table.pieces.items[1]));
try testing.expectEqualStrings("jumped over the lazy dog", table.getPiece(table.pieces.items[2]));
try testing.expectEqual(@as(usize, 65), table.getTotalSize());
var out_buf: [65]u8 = undefined;
table.writeAll(&out_buf);
try testing.expectEqualStrings(
\\the quick brown fox
\\went to the park and
\\jumped over the lazy dog
, &out_buf);
}
test "Insert at end of Piece" {
const original = "the quick brown fox\njumped over the lazy dog";
var table = try PieceTable.init(testing.allocator, original);
defer table.deinit();
try table.insert(20, "went to the park and\n");
try table.insert(41, "ate a burger and\n");
try testing.expectEqual(@as(usize, 4), table.pieces.items.len);
try testing.expectEqual(PieceTable.Piece.Tag.original, table.pieces.items[0].tag);
try testing.expectEqual(PieceTable.Piece.Tag.added, table.pieces.items[1].tag);
try testing.expectEqual(PieceTable.Piece.Tag.added, table.pieces.items[2].tag);
try testing.expectEqual(PieceTable.Piece.Tag.original, table.pieces.items[3].tag);
// try testing.expectEqualStrings("the quick brown fox\n", table.pieces.items[0].slice);
// try testing.expectEqualStrings("went to the park and\n", table.pieces.items[1].slice);
// try testing.expectEqualStrings("ate a burger and\n", table.pieces.items[2].slice);
// try testing.expectEqualStrings("jumped over the lazy dog", table.pieces.items[3].slice);
try testing.expectEqual(@as(usize, 82), table.getTotalSize());
var out_buf: [82]u8 = undefined;
table.writeAll(&out_buf);
try testing.expectEqualStrings(
\\the quick brown fox
\\went to the park and
\\ate a burger and
\\jumped over the lazy dog
, &out_buf);
}
test "Insert at end of file" {
const original = "the quick brown fox";
var table = try PieceTable.init(testing.allocator, original);
defer table.deinit();
try table.insert(19, "\njumped over the lazy dog");
try testing.expectEqual(@as(usize, 2), table.pieces.items.len);
try testing.expectEqual(PieceTable.Piece.Tag.original, table.pieces.items[0].tag);
try testing.expectEqual(PieceTable.Piece.Tag.added, table.pieces.items[1].tag);
// try testing.expectEqualStrings("the quick brown fox", table.pieces.items[0].slice);
// try testing.expectEqualStrings("\njumped over the lazy dog", table.pieces.items[1].slice);
try testing.expectEqual(@as(usize, 44), table.getTotalSize());
var out_buf: [44]u8 = undefined;
table.writeAll(&out_buf);
try testing.expectEqualStrings(
\\the quick brown fox
\\jumped over the lazy dog
, &out_buf);
}
test "Delete one entire Piece" {
const original = "the quick brown fox";
var table = try PieceTable.init(testing.allocator, original);
defer table.deinit();
try table.delete(0, 19);
try testing.expectEqual(@as(usize, 0), table.pieces.items.len);
}
test "Delete multiple entire Pieces" {
const original = "the quick brown fox\njumped over the lazy dog";
var table = try PieceTable.init(testing.allocator, original);
defer table.deinit();
try table.insert(20, "went to the park and\n");
try table.insert(41, "ate a burger and\n");
try table.delete(20, 38);
try testing.expectEqual(@as(usize, 2), table.pieces.items.len);
}
test "Delete inside a Piece" {
const original = "the quick brown fox";
var table = try PieceTable.init(testing.allocator, original);
defer table.deinit();
// delete "brown "
try table.delete(10, 6);
try testing.expectEqual(@as(usize, 2), table.pieces.items.len);
// try testing.expectEqualStrings("the quick ", table.pieces.items[0].slice);
// try testing.expectEqualStrings("fox", table.pieces.items[1].slice);
try testing.expectEqual(@as(usize, 13), table.getTotalSize());
var out_buf: [13]u8 = undefined;
table.writeAll(&out_buf);
try testing.expectEqualStrings(
\\the quick fox
, &out_buf);
}
test "Delete from within one piece to within another piece" {
const original = "the quick brown fox\njumped over the lazy dog";
var table = try PieceTable.init(testing.allocator, original);
defer table.deinit();
try table.insert(20, "went to the park and\n");
try table.insert(41, "ate a burger and\n");
try table.delete(45, 13 + 12);
try testing.expectEqual(@as(usize, 4), table.pieces.items.len);
try testing.expectEqual(@as(usize, 57), table.getTotalSize());
var out_buf: [57]u8 = undefined;
table.writeAll(&out_buf);
try testing.expectEqualStrings(
\\the quick brown fox
\\went to the park and
\\ate the lazy dog
, &out_buf);
}
test "Delete from start of piece to within piece" {
const original = "the quick brown fox\njumped over the lazy dog";
var table = try PieceTable.init(testing.allocator, original);
defer table.deinit();
try table.insert(20, "went to the park and\n");
try table.insert(41, "ate a burger and\n");
try table.delete(41, 13);
try testing.expectEqual(@as(usize, 4), table.pieces.items.len);
try testing.expectEqual(@as(usize, 69), table.getTotalSize());
var out_buf: [69]u8 = undefined;
table.writeAll(&out_buf);
try testing.expectEqualStrings(
\\the quick brown fox
\\went to the park and
\\and
\\jumped over the lazy dog
, &out_buf);
}
test "Delete from within piece to end of piece" {
const original = "the quick brown fox\njumped over the lazy dog";
var table = try PieceTable.init(testing.allocator, original);
defer table.deinit();
try table.insert(20, "went to the park and\n");
try table.insert(41, "ate a burger and\n");
try table.delete(45, 13);
try testing.expectEqual(@as(usize, 4), table.pieces.items.len);
try testing.expectEqual(@as(usize, 69), table.getTotalSize());
var out_buf: [69]u8 = undefined;
table.writeAll(&out_buf);
try testing.expectEqualStrings(
\\the quick brown fox
\\went to the park and
\\ate jumped over the lazy dog
, &out_buf);
}

File diff suppressed because it is too large Load Diff

646
src/root.zig Normal file
View File

@ -0,0 +1,646 @@
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 const zwp = @import("./zwp.zig");
pub const types = @import("./types.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 = 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 });
}
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 readUInt(buffer: []const u32, parent_pos: *usize) !u32 {
var pos = parent_pos.*;
if (pos >= buffer.len) return error.EndOfStream;
const uint: u32 = @bitCast(buffer[pos]);
pos += 1;
parent_pos.* = pos;
return uint;
}
pub fn readInt(buffer: []const u32, parent_pos: *usize) !i32 {
var pos = parent_pos.*;
if (pos >= buffer.len) return error.EndOfStream;
const int: i32 = @bitCast(buffer[pos]);
pos += 1;
parent_pos.* = pos;
return int;
}
pub fn readString(buffer: []const u32, parent_pos: *usize) !?[:0]const u8 {
var pos = parent_pos.*;
const len = try readUInt(buffer, &pos);
if (len == 0) return null;
const wordlen = std.mem.alignForward(usize, len, @sizeOf(u32)) / @sizeOf(u32);
if (pos + wordlen > buffer.len) return error.EndOfStream;
const string = std.mem.sliceAsBytes(buffer[pos..])[0 .. len - 1 :0];
pos += std.mem.alignForward(usize, len, @sizeOf(u32)) / @sizeOf(u32);
parent_pos.* = pos;
return string;
}
pub fn readArray(comptime T: type, buffer: []const u32, parent_pos: *usize) ![]const T {
var pos = parent_pos.*;
const byte_size = try readUInt(buffer, &pos);
const array = @as([*]const T, @ptrCast(buffer[pos..].ptr))[0 .. byte_size / @sizeOf(T)];
pos += byte_size / @sizeOf(u32);
parent_pos.* = pos;
return array;
}
pub fn deserializeArguments(comptime Signature: type, buffer: []const u32) !Signature {
if (Signature == void) return {};
var result: Signature = undefined;
var pos: usize = 0;
inline for (std.meta.fields(Signature)) |field| {
if (field.type == types.Fd) continue;
switch (@typeInfo(field.type)) {
.Int => |int_info| switch (int_info.signedness) {
.signed => @field(result, field.name) = try readInt(buffer, &pos),
.unsigned => @field(result, field.name) = try readUInt(buffer, &pos),
},
.Enum => |enum_info| if (@sizeOf(enum_info.tag_type) == @sizeOf(u32)) {
@field(result, field.name) = @enumFromInt(try readInt(buffer, &pos));
} else {
@compileError("Unsupported type " ++ @typeName(field.type));
},
.Pointer => |ptr| switch (ptr.size) {
.Slice => if (ptr.child == u8) {
@field(result, field.name) = try readString(buffer, &pos) orelse return error.UnexpectedNullString;
} else {
@field(result, field.name) = try readArray(ptr.child, buffer, &pos);
},
else => @compileError("Unsupported type " ++ @typeName(field.type)),
},
.Optional => |opt| switch (@typeInfo(opt.child)) {
.Pointer => |ptr| switch (ptr.size) {
.Slice => if (ptr.child == u8) {
@field(result, field.name) = try readString(buffer, &pos);
} else @compileError("Unsupported type " ++ @typeName(field.type)),
else => @compileError("Unsupported type " ++ @typeName(field.type)),
},
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;
}
pub fn countFds(comptime Signature: type) usize {
if (Signature == void) return 0;
var count: usize = 0;
inline for (std.meta.fields(Signature)) |field| {
if (field.type == types.Fd) {
count += 1;
}
}
return count;
}
pub fn extractFds(comptime Signature: type, message: *const Signature) [countFds(Signature)]*const types.Fd {
if (Signature == void) return [_]*const types.Fd{};
var fds: [countFds(Signature)]*const types.Fd = undefined;
var i: usize = 0;
inline for (std.meta.fields(Signature)) |field| {
if (field.type == types.Fd) {
fds[i] = &@field(message, field.name);
i += 1;
}
}
return fds;
}
/// Message must live until the iovec array is written.
pub fn serializeArguments(comptime Signature: type, buffer: []u32, message: Signature) ![]u32 {
if (Signature == void) return buffer[0..0];
var pos: usize = 0;
inline for (std.meta.fields(Signature)) |field| {
if (field.type == types.Fd) continue;
switch (@typeInfo(field.type)) {
.Int => {
if (pos >= buffer.len) return error.OutOfMemory;
buffer[pos] = @bitCast(@field(message, field.name));
pos += 1;
},
.Enum => |enum_info| if (enum_info.tag_type == u32) {
if (pos >= buffer.len) return error.OutOfMemory;
buffer[pos] = @intFromEnum(@field(message, field.name));
pos += 1;
} else {
@compileError("Unsupported type " ++ @typeName(field.type));
},
.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)),
},
.Optional => |opt| switch (@typeInfo(opt.child)) {
.Pointer => |ptr| switch (ptr.size) {
.Slice => if (ptr.child == u8) {
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)),
},
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,
);
}
pub const IdPool = struct {
next_id: u32 = 2,
free_ids: std.BoundedArray(u32, 1024) = .{},
pub fn create(this: *@This()) u32 {
if (this.free_ids.popOrNull()) |id| {
return id;
}
defer this.next_id += 1;
return this.next_id;
}
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 {};
}
};
pub fn registerGlobals(alloc: std.mem.Allocator, id_pool: *IdPool, socket: std.net.Stream, comptime T: []const type) ![T.len]?u32 {
const Item = struct { version: u32, index: u32 };
const Pair = struct { []const u8, Item };
comptime var kvs_list: []const Pair = &[_]Pair{};
inline for (T, 0..) |t, i| {
kvs_list = kvs_list ++ &[_]Pair{.{ t.INTERFACE, .{ .version = t.VERSION, .index = i } }};
}
const map = std.ComptimeStringMap(Item, kvs_list);
const registry_id = id_pool.create();
{
var buffer: [5]u32 = undefined;
const message = try serialize(core.Display.Request, &buffer, 1, .{ .get_registry = .{ .registry = registry_id } });
try socket.writeAll(std.mem.sliceAsBytes(message));
}
const registry_done_id = id_pool.create();
{
var buffer: [5]u32 = undefined;
const message = try serialize(core.Display.Request, &buffer, 1, .{ .sync = .{ .callback = registry_done_id } });
try socket.writeAll(std.mem.sliceAsBytes(message));
}
var ids: [T.len]?u32 = [_]?u32{null} ** T.len;
var message_buffer = std.ArrayList(u32).init(alloc);
defer message_buffer.deinit();
while (true) {
var header: Header = undefined;
const header_bytes_read = try socket.readAll(std.mem.asBytes(&header));
if (header_bytes_read < @sizeOf(Header)) break;
try message_buffer.resize((header.size_and_opcode.size - @sizeOf(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 deserialize(core.Registry.Event, header, message_buffer.items);
switch (event) {
.global => |global| {
var buffer: [20]u32 = undefined;
if (map.get(global.interface)) |item| {
if (global.version < item.version) {
// TODO: Add diagnostics API
return error.OutdatedCompositorProtocol;
}
const new_id = id_pool.create();
ids[item.index] = new_id;
const message = try serialize(core.Registry.Request, &buffer, registry_id, .{ .bind = .{
.name = global.name,
.interface = global.interface,
.version = item.version,
.new_id = new_id,
} });
try socket.writeAll(std.mem.sliceAsBytes(message));
}
},
.global_remove => {},
}
} else if (header.object_id == registry_done_id) {
break;
} else {
std.log.info("{} {x} \"{}\"", .{
header.object_id,
header.size_and_opcode.opcode,
std.zig.fmtEscapes(std.mem.sliceAsBytes(message_buffer.items)),
});
}
}
return ids;
}
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,
};
}
pub const Conn = struct {
allocator: std.mem.Allocator,
send_buffer: []u32,
recv_buffer: []u32,
fd_queue: std.ArrayListUnmanaged(std.os.fd_t),
socket: std.net.Stream,
pub fn init(alloc: std.mem.Allocator, display_path: []const u8) !Conn {
const send_buffer = try alloc.alloc(u32, 16);
const recv_buffer = try alloc.alloc(u32, 16);
return .{
.allocator = alloc,
.send_buffer = send_buffer,
.recv_buffer = recv_buffer,
.fd_queue = .{},
.socket = try std.net.connectUnixSocket(display_path),
};
}
pub fn deinit(conn: *Conn) void {
conn.allocator.free(conn.send_buffer);
conn.allocator.free(conn.recv_buffer);
conn.fd_queue.deinit(conn.allocator);
conn.socket.close();
}
pub fn send(conn: *Conn, comptime Signature: type, id: u32, message: Signature) !void {
const msg = while (true) {
const msg = serialize(
Signature,
conn.send_buffer,
id,
message,
) catch |e| switch (e) {
error.OutOfMemory => {
conn.send_buffer = try conn.allocator.realloc(conn.send_buffer, conn.send_buffer.len * 2);
continue;
},
else => return e,
};
break msg;
};
const msg_bytes = std.mem.sliceAsBytes(msg);
const msg_iov = [_]std.os.iovec_const{
.{
.iov_base = msg_bytes.ptr,
.iov_len = msg_bytes.len,
},
};
const fds = switch (message) {
inline else => |*payload| extractFds(@TypeOf(payload.*), payload),
};
var ctrl_msgs: [fds.len]cmsg(std.os.fd_t) = undefined;
for (fds, 0..) |fdp, i| {
std.debug.print("fd {}: {}\n", .{ i, fdp.* });
ctrl_msgs[i] = .{
.level = std.os.SOL.SOCKET,
.type = 0x01,
.data = @intFromEnum(fdp.*),
};
}
const socket_msg = std.os.msghdr_const{
.name = null,
.namelen = 0,
.iov = &msg_iov,
.iovlen = msg_iov.len,
.control = &ctrl_msgs,
.controllen = @intCast(@sizeOf(cmsg(std.os.fd_t)) * ctrl_msgs.len),
.flags = 0,
};
_ = try std.os.sendmsg(conn.socket.handle, &socket_msg, 0);
}
pub const Message = struct { Header, []const u32 };
pub fn recv(conn: *Conn) !Message {
// TODO: make this less messy
// Read header
@memset(conn.recv_buffer, 0);
var iov: [1]std.os.iovec = .{.{
.iov_base = std.mem.sliceAsBytes(conn.recv_buffer).ptr,
.iov_len = @sizeOf(Header),
}};
var control_msg: cmsg(std.os.fd_t) = undefined;
const control_bytes = std.mem.asBytes(&control_msg);
var socket_msg = std.os.msghdr{
.name = null,
.namelen = 0,
.iov = &iov,
.iovlen = iov.len,
.control = control_bytes.ptr,
.controllen = @intCast(control_bytes.len),
.flags = 0,
};
const size = std.os.linux.recvmsg(conn.socket.handle, &socket_msg, 0);
if (size < @sizeOf(Header)) return error.SocketClosed;
var header: Header = undefined;
@memcpy(std.mem.asBytes(&header), iov[0].iov_base[0..@sizeOf(Header)]);
if (socket_msg.controllen != 0) {
try conn.fd_queue.append(conn.allocator, control_msg.data);
}
// Read body
const body_size = (header.size_and_opcode.size - @sizeOf(Header)) / @sizeOf(u32);
iov[0] = .{
.iov_base = std.mem.sliceAsBytes(conn.recv_buffer).ptr,
.iov_len = body_size * @sizeOf(u32),
};
socket_msg = std.os.msghdr{
.name = null,
.namelen = 0,
.iov = &iov,
.iovlen = iov.len,
.control = control_bytes.ptr,
.controllen = @intCast(control_bytes.len),
.flags = 0,
};
const size2 = std.os.linux.recvmsg(conn.socket.handle, &socket_msg, 0);
const message = conn.recv_buffer[0 .. size2 / @sizeOf(u32)];
if (socket_msg.controllen != 0) {
try conn.fd_queue.append(conn.allocator, control_msg.data);
}
return .{ header, message };
}
};
// pub const XKBKeymap = struct {
// keycodes: Keycodes,
// types: Types,
// compatability: Compatability,
// symbols: Symbols,
//
// const Keycodes = struct {
// min: usize,
// max: usize,
// map: std.AutoHashMap(usize, usize),
// indicators: std.StringHashMap(usize),
// };
//
// const ISOKEY = [4]u8;
//
// const Types = struct {};
// const Compatability = struct {};
// const Symbols = struct {};
//
// pub fn parse() {}
// };