remove unneeded stuff
parent
025ab53055
commit
76365322f2
45
build.zig
45
build.zig
|
@ -4,22 +4,6 @@ pub fn build(b: *std.Build) void {
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
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(.{
|
const main_tests = b.addTest(.{
|
||||||
.root_source_file = .{ .path = "src/main.zig" },
|
.root_source_file = .{ .path = "src/main.zig" },
|
||||||
.target = target,
|
.target = target,
|
||||||
|
@ -31,33 +15,14 @@ pub fn build(b: *std.Build) void {
|
||||||
const test_step = b.step("test", "Run library tests");
|
const test_step = b.step("test", "Run library tests");
|
||||||
test_step.dependOn(&run_main_tests.step);
|
test_step.dependOn(&run_main_tests.step);
|
||||||
|
|
||||||
const client_connect_raw_exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "00_client_connect",
|
.name = "pinephone-test",
|
||||||
.root_source_file = .{ .path = "examples/00_client_connect.zig" },
|
.root_source_file = .{ .path = "src/main.zig" },
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
b.installArtifact(client_connect_raw_exe);
|
|
||||||
|
|
||||||
const client_connect_exe = b.addExecutable(.{
|
exe.addIncludePath(.{ .path = "deps/font8x8/" });
|
||||||
.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);
|
|
||||||
|
|
||||||
const text_editor_exe = b.addExecutable(.{
|
b.installArtifact(exe);
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
|
|
@ -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`.
|
|
|
@ -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
|
@ -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";
|
|
||||||
};
|
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
1233
src/main.zig
1233
src/main.zig
File diff suppressed because it is too large
Load Diff
|
@ -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() {}
|
||||||
|
// };
|
Loading…
Reference in New Issue