Diff
diff --git a/build.zig b/build.zig
index 30e883d..7b6237e 100644
--- a/build.zig
+++ b/build.zig
@@ -40,6 +40,22 @@ pub fn build(b: *std.Build) void {
});
test_step.dependOn(&b.addRunArtifact(unit_tests).step);
+ const lua_config_tests = b.addTest(.{
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("tests/lua_config_tests.zig"),
+ .target = target,
+ .optimize = optimize,
+ }),
+ });
+ lua_config_tests.root_module.addImport("lua", b.createModule(.{
+ .root_source_file = b.path("src/config/lua.zig"),
+ .target = target,
+ .optimize = optimize,
+ }));
+ lua_config_tests.linkSystemLibrary("lua");
+ lua_config_tests.linkLibC();
+ test_step.dependOn(&b.addRunArtifact(lua_config_tests).step);
+
const xephyr_step = b.step("xephyr", "Run in Xephyr (1280x800 on :2)");
xephyr_step.dependOn(&add_xephyr_run(b, exe, false).step);
diff --git a/resources/test-config.lua b/resources/test-config.lua
index c52da7f..a99dd82 100644
--- a/resources/test-config.lua
+++ b/resources/test-config.lua
@@ -107,50 +107,50 @@ oxwm.key.bind({ modkey, "Shift" }, "J", oxwm.client.move_stack(1))
oxwm.key.bind({ modkey, "Shift" }, "K", oxwm.client.move_stack(-1))
-- View tag (switch workspace)
-oxwm.key.bind({ modkey }, "1", oxwm.tag.view(1))
-oxwm.key.bind({ modkey }, "2", oxwm.tag.view(2))
-oxwm.key.bind({ modkey }, "3", oxwm.tag.view(3))
-oxwm.key.bind({ modkey }, "4", oxwm.tag.view(4))
-oxwm.key.bind({ modkey }, "5", oxwm.tag.view(5))
-oxwm.key.bind({ modkey }, "6", oxwm.tag.view(6))
-oxwm.key.bind({ modkey }, "7", oxwm.tag.view(7))
-oxwm.key.bind({ modkey }, "8", oxwm.tag.view(8))
-oxwm.key.bind({ modkey }, "9", oxwm.tag.view(9))
+oxwm.key.bind({ modkey }, "1", oxwm.tag.view(0))
+oxwm.key.bind({ modkey }, "2", oxwm.tag.view(1))
+oxwm.key.bind({ modkey }, "3", oxwm.tag.view(2))
+oxwm.key.bind({ modkey }, "4", oxwm.tag.view(3))
+oxwm.key.bind({ modkey }, "5", oxwm.tag.view(4))
+oxwm.key.bind({ modkey }, "6", oxwm.tag.view(5))
+oxwm.key.bind({ modkey }, "7", oxwm.tag.view(6))
+oxwm.key.bind({ modkey }, "8", oxwm.tag.view(7))
+oxwm.key.bind({ modkey }, "9", oxwm.tag.view(8))
-- Move window to tag
-oxwm.key.bind({ modkey, "Shift" }, "1", oxwm.tag.move_to(1))
-oxwm.key.bind({ modkey, "Shift" }, "2", oxwm.tag.move_to(2))
-oxwm.key.bind({ modkey, "Shift" }, "3", oxwm.tag.move_to(3))
-oxwm.key.bind({ modkey, "Shift" }, "4", oxwm.tag.move_to(4))
-oxwm.key.bind({ modkey, "Shift" }, "5", oxwm.tag.move_to(5))
-oxwm.key.bind({ modkey, "Shift" }, "6", oxwm.tag.move_to(6))
-oxwm.key.bind({ modkey, "Shift" }, "7", oxwm.tag.move_to(7))
-oxwm.key.bind({ modkey, "Shift" }, "8", oxwm.tag.move_to(8))
-oxwm.key.bind({ modkey, "Shift" }, "9", oxwm.tag.move_to(9))
+oxwm.key.bind({ modkey, "Shift" }, "1", oxwm.tag.move_to(0))
+oxwm.key.bind({ modkey, "Shift" }, "2", oxwm.tag.move_to(1))
+oxwm.key.bind({ modkey, "Shift" }, "3", oxwm.tag.move_to(2))
+oxwm.key.bind({ modkey, "Shift" }, "4", oxwm.tag.move_to(3))
+oxwm.key.bind({ modkey, "Shift" }, "5", oxwm.tag.move_to(4))
+oxwm.key.bind({ modkey, "Shift" }, "6", oxwm.tag.move_to(5))
+oxwm.key.bind({ modkey, "Shift" }, "7", oxwm.tag.move_to(6))
+oxwm.key.bind({ modkey, "Shift" }, "8", oxwm.tag.move_to(7))
+oxwm.key.bind({ modkey, "Shift" }, "9", oxwm.tag.move_to(8))
-- Toggle view (view multiple tags at once) - dwm-style multi-tag viewing
-- Example: Mod+Ctrl+2 while on tag 1 will show BOTH tags 1 and 2
-oxwm.key.bind({ modkey, "Control" }, "1", oxwm.tag.toggleview(1))
-oxwm.key.bind({ modkey, "Control" }, "2", oxwm.tag.toggleview(2))
-oxwm.key.bind({ modkey, "Control" }, "3", oxwm.tag.toggleview(3))
-oxwm.key.bind({ modkey, "Control" }, "4", oxwm.tag.toggleview(4))
-oxwm.key.bind({ modkey, "Control" }, "5", oxwm.tag.toggleview(5))
-oxwm.key.bind({ modkey, "Control" }, "6", oxwm.tag.toggleview(6))
-oxwm.key.bind({ modkey, "Control" }, "7", oxwm.tag.toggleview(7))
-oxwm.key.bind({ modkey, "Control" }, "8", oxwm.tag.toggleview(8))
-oxwm.key.bind({ modkey, "Control" }, "9", oxwm.tag.toggleview(9))
+oxwm.key.bind({ modkey, "Control" }, "1", oxwm.tag.toggleview(0))
+oxwm.key.bind({ modkey, "Control" }, "2", oxwm.tag.toggleview(1))
+oxwm.key.bind({ modkey, "Control" }, "3", oxwm.tag.toggleview(2))
+oxwm.key.bind({ modkey, "Control" }, "4", oxwm.tag.toggleview(3))
+oxwm.key.bind({ modkey, "Control" }, "5", oxwm.tag.toggleview(4))
+oxwm.key.bind({ modkey, "Control" }, "6", oxwm.tag.toggleview(5))
+oxwm.key.bind({ modkey, "Control" }, "7", oxwm.tag.toggleview(6))
+oxwm.key.bind({ modkey, "Control" }, "8", oxwm.tag.toggleview(7))
+oxwm.key.bind({ modkey, "Control" }, "9", oxwm.tag.toggleview(8))
-- Toggle tag (window on multiple tags) - dwm-style sticky windows
-- Example: Mod+Ctrl+Shift+2 puts focused window on BOTH current tag and tag 2
-oxwm.key.bind({ modkey, "Control", "Shift" }, "1", oxwm.tag.toggletag(1))
-oxwm.key.bind({ modkey, "Control", "Shift" }, "2", oxwm.tag.toggletag(2))
-oxwm.key.bind({ modkey, "Control", "Shift" }, "3", oxwm.tag.toggletag(3))
-oxwm.key.bind({ modkey, "Control", "Shift" }, "4", oxwm.tag.toggletag(4))
-oxwm.key.bind({ modkey, "Control", "Shift" }, "5", oxwm.tag.toggletag(5))
-oxwm.key.bind({ modkey, "Control", "Shift" }, "6", oxwm.tag.toggletag(6))
-oxwm.key.bind({ modkey, "Control", "Shift" }, "7", oxwm.tag.toggletag(7))
-oxwm.key.bind({ modkey, "Control", "Shift" }, "8", oxwm.tag.toggletag(8))
-oxwm.key.bind({ modkey, "Control", "Shift" }, "9", oxwm.tag.toggletag(9))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "1", oxwm.tag.toggletag(0))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "2", oxwm.tag.toggletag(1))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "3", oxwm.tag.toggletag(2))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "4", oxwm.tag.toggletag(3))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "5", oxwm.tag.toggletag(4))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "6", oxwm.tag.toggletag(5))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "7", oxwm.tag.toggletag(6))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "8", oxwm.tag.toggletag(7))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "9", oxwm.tag.toggletag(8))
oxwm.key.bind({ modkey }, "Tab", oxwm.tag.view_next())
oxwm.key.bind({ modkey, "Shift" }, "Tab", oxwm.tag.view_previous())
diff --git a/src/config/config.zig b/src/config/config.zig
index 775e1e0..7c7f918 100644
--- a/src/config/config.zig
+++ b/src/config/config.zig
@@ -36,9 +36,14 @@ pub const Action = enum {
scroll_right,
};
+pub const Key_Press = struct {
+ mod_mask: u32 = 0,
+ keysym: u64 = 0,
+};
+
pub const Keybind = struct {
- mod_mask: u32,
- keysym: u64,
+ keys: [4]Key_Press = [_]Key_Press{.{}} ** 4,
+ key_count: u8 = 1,
action: Action,
int_arg: i32 = 0,
str_arg: ?[]const u8 = null,
diff --git a/src/config/lua.zig b/src/config/lua.zig
index 6296639..2969749 100644
--- a/src/config/lua.zig
+++ b/src/config/lua.zig
@@ -1,5 +1,5 @@
const std = @import("std");
-const config_mod = @import("config.zig");
+pub const config_mod = @import("config.zig");
const Config = config_mod.Config;
const Keybind = config_mod.Keybind;
const Action = config_mod.Action;
@@ -392,19 +392,85 @@ fn lua_key_bind(state: ?*c.lua_State) callconv(.c) c_int {
}
c.lua_settop(s, -2);
- cfg.add_keybind(.{
- .mod_mask = mod_mask,
- .keysym = keysym,
+ var keybind: config_mod.Keybind = .{
.action = action,
.int_arg = int_arg,
.str_arg = str_arg,
- }) catch return 0;
+ };
+ keybind.keys[0] = .{ .mod_mask = mod_mask, .keysym = keysym };
+ keybind.key_count = 1;
+
+ cfg.add_keybind(keybind) catch return 0;
return 0;
}
fn lua_key_chord(state: ?*c.lua_State) callconv(.c) c_int {
- _ = state;
+ const s = state orelse return 0;
+ const cfg = config orelse return 0;
+
+ if (c.lua_type(s, 1) != c.LUA_TTABLE) return 0;
+ if (c.lua_type(s, 2) != c.LUA_TTABLE) return 0;
+
+ var keybind: config_mod.Keybind = .{
+ .action = .quit,
+ .int_arg = 0,
+ .str_arg = null,
+ };
+ keybind.key_count = 0;
+
+ const num_keys = c.lua_rawlen(s, 1);
+ if (num_keys == 0 or num_keys > 4) return 0;
+
+ var i: usize = 1;
+ while (i <= num_keys) : (i += 1) {
+ _ = c.lua_rawgeti(s, 1, @intCast(i));
+ if (c.lua_type(s, -1) != c.LUA_TTABLE) {
+ c.lua_settop(s, -2);
+ return 0;
+ }
+
+ _ = c.lua_rawgeti(s, -1, 1);
+ const mod_mask = parse_modifiers_at_top(s);
+ c.lua_settop(s, -2);
+
+ _ = c.lua_rawgeti(s, -1, 2);
+ const key_str = get_lua_string(s, -1) orelse {
+ c.lua_settop(s, -3);
+ return 0;
+ };
+ c.lua_settop(s, -2);
+
+ const keysym = key_name_to_keysym(key_str) orelse {
+ c.lua_settop(s, -2);
+ return 0;
+ };
+
+ keybind.keys[keybind.key_count] = .{ .mod_mask = mod_mask, .keysym = keysym };
+ keybind.key_count += 1;
+
+ c.lua_settop(s, -2);
+ }
+
+ _ = c.lua_getfield(s, 2, "__action");
+ const action_str = get_lua_string(s, -1) orelse {
+ c.lua_settop(s, -2);
+ return 0;
+ };
+ c.lua_settop(s, -2);
+
+ keybind.action = parse_action(action_str) orelse return 0;
+
+ _ = c.lua_getfield(s, 2, "__arg");
+ if (c.lua_type(s, -1) == c.LUA_TNUMBER) {
+ keybind.int_arg = @intCast(c.lua_tointegerx(s, -1, null));
+ } else if (c.lua_type(s, -1) == c.LUA_TSTRING) {
+ keybind.str_arg = get_lua_string(s, -1);
+ }
+ c.lua_settop(s, -2);
+
+ cfg.add_keybind(keybind) catch return 0;
+
return 0;
}
@@ -532,7 +598,7 @@ fn lua_layout_scroll_right(state: ?*c.lua_State) callconv(.c) c_int {
fn lua_tag_view(state: ?*c.lua_State) callconv(.c) c_int {
const s = state orelse return 0;
const idx: i32 = @intCast(c.lua_tointegerx(s, 1, null));
- create_action_table_with_int(s, "ViewTag", idx - 1);
+ create_action_table_with_int(s, "ViewTag", idx);
return 1;
}
@@ -563,21 +629,21 @@ fn lua_tag_view_previous_nonempty(state: ?*c.lua_State) callconv(.c) c_int {
fn lua_tag_toggleview(state: ?*c.lua_State) callconv(.c) c_int {
const s = state orelse return 0;
const idx: i32 = @intCast(c.lua_tointegerx(s, 1, null));
- create_action_table_with_int(s, "ToggleView", idx - 1);
+ create_action_table_with_int(s, "ToggleView", idx);
return 1;
}
fn lua_tag_move_to(state: ?*c.lua_State) callconv(.c) c_int {
const s = state orelse return 0;
const idx: i32 = @intCast(c.lua_tointegerx(s, 1, null));
- create_action_table_with_int(s, "MoveToTag", idx - 1);
+ create_action_table_with_int(s, "MoveToTag", idx);
return 1;
}
fn lua_tag_toggletag(state: ?*c.lua_State) callconv(.c) c_int {
const s = state orelse return 0;
const idx: i32 = @intCast(c.lua_tointegerx(s, 1, null));
- create_action_table_with_int(s, "ToggleTag", idx - 1);
+ create_action_table_with_int(s, "ToggleTag", idx);
return 1;
}
@@ -1081,6 +1147,10 @@ fn parse_modifiers(state: *c.lua_State, idx: c_int) u32 {
return mod_mask;
}
+fn parse_modifiers_at_top(state: *c.lua_State) u32 {
+ return parse_modifiers(state, -1);
+}
+
fn parse_single_modifier(name: []const u8) u32 {
if (std.mem.eql(u8, name, "Mod4") or std.mem.eql(u8, name, "mod4") or std.mem.eql(u8, name, "super")) {
return (1 << 6);
diff --git a/src/main.zig b/src/main.zig
index 21c5fdc..6563ceb 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -13,6 +13,7 @@ const bar_mod = @import("bar/bar.zig");
const blocks_mod = @import("bar/blocks/blocks.zig");
const config_mod = @import("config/config.zig");
const lua = @import("config/lua.zig");
+const overlay_mod = @import("overlay.zig");
const Display = display_mod.Display;
const Client = client_mod.Client;
@@ -67,6 +68,87 @@ var config_path_global: ?[]const u8 = null;
var scroll_animation: animations.Scroll_Animation = .{};
var animation_config: animations.Animation_Config = .{ .duration_ms = 150, .easing = .ease_out };
+var chord_keys: [4]config_mod.Key_Press = [_]config_mod.Key_Press{.{}} ** 4;
+var chord_index: u8 = 0;
+var chord_timestamp: i64 = 0;
+const chord_timeout_ms: i64 = 1000;
+var keyboard_grabbed: bool = false;
+
+var keybind_overlay: ?*overlay_mod.Keybind_Overlay = null;
+
+fn print_help() void {
+ std.debug.print(
+ \\oxwm - A window manager
+ \\
+ \\USAGE:
+ \\ oxwm [OPTIONS]
+ \\
+ \\OPTIONS:
+ \\ --init Create default config in ~/.config/oxwm/config.lua
+ \\ --config <PATH> Use custom config file
+ \\ --version Print version information
+ \\ --help Print this help message
+ \\
+ \\CONFIG:
+ \\ Location: ~/.config/oxwm/config.lua
+ \\ Edit the config file and use Mod+Shift+R to reload
+ \\ No compilation needed - instant hot-reload!
+ \\
+ \\FIRST RUN:
+ \\ Run 'oxwm --init' to create a config file
+ \\ Or just start oxwm and it will create one automatically
+ \\
+ , .{});
+}
+
+fn init_config(allocator: std.mem.Allocator) void {
+ const home = std.posix.getenv("HOME") orelse {
+ std.debug.print("error: HOME environment variable not set\n", .{});
+ return;
+ };
+
+ var path_buf: [512]u8 = undefined;
+ const config_dir = std.fmt.bufPrint(&path_buf, "{s}/.config/oxwm", .{home}) catch {
+ std.debug.print("error: path too long\n", .{});
+ return;
+ };
+
+ std.fs.makeDirAbsolute(config_dir) catch |err| {
+ if (err != error.PathAlreadyExists) {
+ std.debug.print("error: could not create config directory: {}\n", .{err});
+ return;
+ }
+ };
+
+ var config_path_buf: [512]u8 = undefined;
+ const config_path = std.fmt.bufPrint(&config_path_buf, "{s}/config.lua", .{config_dir}) catch {
+ std.debug.print("error: path too long\n", .{});
+ return;
+ };
+
+ const template = std.fs.cwd().readFileAlloc(allocator, "templates/config.lua", 64 * 1024) catch |err| {
+ std.debug.print("error: could not read template (templates/config.lua): {}\n", .{err});
+ std.debug.print("hint: run from the oxwm source directory, or copy templates/config.lua manually\n", .{});
+ return;
+ };
+ defer allocator.free(template);
+
+ const file = std.fs.createFileAbsolute(config_path, .{}) catch |err| {
+ std.debug.print("error: could not create config file: {}\n", .{err});
+ return;
+ };
+ defer file.close();
+
+ _ = file.writeAll(template) catch |err| {
+ std.debug.print("error: could not write config file: {}\n", .{err});
+ return;
+ };
+
+ std.debug.print("Config created at {s}\n", .{config_path});
+ std.debug.print("Edit the file and reload with Mod+Shift+R\n", .{});
+ std.debug.print("No compilation needed - changes take effect immediately!\n", .{});
+}
+
pub fn main() !void {
const allocator = gpa.allocator();
defer _ = gpa.deinit();
@@ -80,7 +162,13 @@ pub fn main() !void {
if (std.mem.eql(u8, arg, "-c") or std.mem.eql(u8, arg, "--config")) {
config_path = args.next();
} else if (std.mem.eql(u8, arg, "-h") or std.mem.eql(u8, arg, "--help")) {
- std.debug.print("usage: oxwm [-c config.lua]\n", .{});
+ print_help();
+ return;
+ } else if (std.mem.eql(u8, arg, "-v") or std.mem.eql(u8, arg, "--version")) {
+ std.debug.print("oxwm 0.1.0\n", .{});
+ return;
+ } else if (std.mem.eql(u8, arg, "--init")) {
+ init_config(allocator);
return;
}
}
@@ -140,6 +228,7 @@ pub fn main() !void {
setup_monitors(&display);
setup_bars(allocator, &display);
+ setup_overlay(allocator, &display);
grab_keybinds(&display);
scan_existing_windows(&display);
@@ -222,6 +311,10 @@ fn setup_bars(allocator: std.mem.Allocator, display: *Display) void {
}
}
+fn setup_overlay(allocator: std.mem.Allocator, display: *Display) void {
+ keybind_overlay = overlay_mod.Keybind_Overlay.init(display.handle, display.screen, display.root, config.font, allocator);
+}
+
fn config_block_to_bar_block(cfg: config_mod.Block) blocks_mod.Block {
return switch (cfg.block_type) {
.static => blocks_mod.Block.init_static(cfg.format, cfg.color, cfg.underline),
@@ -335,41 +428,60 @@ fn apply_config_values() void {
tags = config.tags;
}
+fn make_keybind(mod: u32, key: u64, action: config_mod.Action) config_mod.Keybind {
+ var kb: config_mod.Keybind = .{ .action = action };
+ kb.keys[0] = .{ .mod_mask = mod, .keysym = key };
+ kb.key_count = 1;
+ return kb;
+}
+
+fn make_keybind_int(mod: u32, key: u64, action: config_mod.Action, int_arg: i32) config_mod.Keybind {
+ var kb = make_keybind(mod, key, action);
+ kb.int_arg = int_arg;
+ return kb;
+}
+
+fn make_keybind_str(mod: u32, key: u64, action: config_mod.Action, str_arg: []const u8) config_mod.Keybind {
+ var kb = make_keybind(mod, key, action);
+ kb.str_arg = str_arg;
+ return kb;
+}
+
fn setup_default_keybinds() void {
const mod_key: u32 = 1 << 6;
const shift_key: u32 = 1 << 0;
const control_key: u32 = 1 << 2;
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 0xff0d, .action = .spawn_terminal }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'd', .action = .spawn, .str_arg = "rofi -show drun" }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 's', .action = .spawn, .str_arg = "maim -s | xclip -selection clipboard -t image/png" }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'q', .action = .kill_client }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key | shift_key, .keysym = 'q', .action = .quit }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key | shift_key, .keysym = 'r', .action = .reload_config }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'j', .action = .focus_next }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'k', .action = .focus_prev }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key | shift_key, .keysym = 'j', .action = .move_next }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key | shift_key, .keysym = 'k', .action = .move_prev }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'h', .action = .resize_master, .int_arg = -50 }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'l', .action = .resize_master, .int_arg = 50 }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'i', .action = .inc_master }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'p', .action = .dec_master }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'a', .action = .toggle_gaps }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'f', .action = .toggle_fullscreen }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 0x0020, .action = .toggle_floating }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 'n', .action = .cycle_layout }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 0x002c, .action = .focus_monitor, .int_arg = -1 }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = 0x002e, .action = .focus_monitor, .int_arg = 1 }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key | shift_key, .keysym = 0x002c, .action = .send_to_monitor, .int_arg = -1 }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key | shift_key, .keysym = 0x002e, .action = .send_to_monitor, .int_arg = 1 }) catch {};
+ config.add_keybind(make_keybind(mod_key, 0xff0d, .spawn_terminal)) catch {};
+ config.add_keybind(make_keybind_str(mod_key, 'd', .spawn, "rofi -show drun")) catch {};
+ config.add_keybind(make_keybind_str(mod_key, 's', .spawn, "maim -s | xclip -selection clipboard -t image/png")) catch {};
+ config.add_keybind(make_keybind(mod_key, 'q', .kill_client)) catch {};
+ config.add_keybind(make_keybind(mod_key | shift_key, 'q', .quit)) catch {};
+ config.add_keybind(make_keybind(mod_key | shift_key, 'r', .reload_config)) catch {};
+ config.add_keybind(make_keybind(mod_key, 'j', .focus_next)) catch {};
+ config.add_keybind(make_keybind(mod_key, 'k', .focus_prev)) catch {};
+ config.add_keybind(make_keybind(mod_key | shift_key, 'j', .move_next)) catch {};
+ config.add_keybind(make_keybind(mod_key | shift_key, 'k', .move_prev)) catch {};
+ config.add_keybind(make_keybind_int(mod_key, 'h', .resize_master, -50)) catch {};
+ config.add_keybind(make_keybind_int(mod_key, 'l', .resize_master, 50)) catch {};
+ config.add_keybind(make_keybind(mod_key, 'i', .inc_master)) catch {};
+ config.add_keybind(make_keybind(mod_key, 'p', .dec_master)) catch {};
+ config.add_keybind(make_keybind(mod_key, 'a', .toggle_gaps)) catch {};
+ config.add_keybind(make_keybind(mod_key, 'f', .toggle_fullscreen)) catch {};
+ config.add_keybind(make_keybind(mod_key, 0x0020, .toggle_floating)) catch {};
+ config.add_keybind(make_keybind(mod_key, 'n', .cycle_layout)) catch {};
+ config.add_keybind(make_keybind_int(mod_key, 0x002c, .focus_monitor, -1)) catch {};
+ config.add_keybind(make_keybind_int(mod_key, 0x002e, .focus_monitor, 1)) catch {};
+ config.add_keybind(make_keybind_int(mod_key | shift_key, 0x002c, .send_to_monitor, -1)) catch {};
+ config.add_keybind(make_keybind_int(mod_key | shift_key, 0x002e, .send_to_monitor, 1)) catch {};
var tag_index: i32 = 0;
while (tag_index < 9) : (tag_index += 1) {
const keysym: u64 = @as(u64, '1') + @as(u64, @intCast(tag_index));
- config.add_keybind(.{ .mod_mask = mod_key, .keysym = keysym, .action = .view_tag, .int_arg = tag_index }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key | shift_key, .keysym = keysym, .action = .move_to_tag, .int_arg = tag_index }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key | control_key, .keysym = keysym, .action = .toggle_view_tag, .int_arg = tag_index }) catch {};
- config.add_keybind(.{ .mod_mask = mod_key | control_key | shift_key, .keysym = keysym, .action = .toggle_tag, .int_arg = tag_index }) catch {};
+ config.add_keybind(make_keybind_int(mod_key, keysym, .view_tag, tag_index)) catch {};
+ config.add_keybind(make_keybind_int(mod_key | shift_key, keysym, .move_to_tag, tag_index)) catch {};
+ config.add_keybind(make_keybind_int(mod_key | control_key, keysym, .toggle_view_tag, tag_index)) catch {};
+ config.add_keybind(make_keybind_int(mod_key | control_key | shift_key, keysym, .toggle_tag, tag_index)) catch {};
}
}
@@ -380,13 +492,15 @@ fn grab_keybinds(display: *Display) void {
_ = xlib.XUngrabKey(display.handle, xlib.AnyKey, xlib.AnyModifier, display.root);
for (config.keybinds.items) |keybind| {
- const keycode = xlib.XKeysymToKeycode(display.handle, @intCast(keybind.keysym));
+ if (keybind.key_count == 0) continue;
+ const first_key = keybind.keys[0];
+ const keycode = xlib.XKeysymToKeycode(display.handle, @intCast(first_key.keysym));
if (keycode != 0) {
for (modifiers) |modifier| {
_ = xlib.XGrabKey(
display.handle,
keycode,
- keybind.mod_mask | modifier,
+ first_key.mod_mask | modifier,
display.root,
xlib.True,
xlib.GrabModeAsync,
@@ -708,15 +822,94 @@ fn handle_configure_request(display: *Display, event: *xlib.XConfigureRequestEve
_ = xlib.XSync(display.handle, xlib.False);
}
+fn reset_chord_state() void {
+ chord_index = 0;
+ chord_keys = [_]config_mod.Key_Press{.{}} ** 4;
+ chord_timestamp = 0;
+ if (keyboard_grabbed) {
+ if (display_global) |dsp| {
+ _ = xlib.XUngrabKeyboard(dsp.handle, xlib.CurrentTime);
+ }
+ keyboard_grabbed = false;
+ }
+}
+
fn handle_key_press(display: *Display, event: *xlib.XKeyEvent) void {
const keysym = xlib.XKeycodeToKeysym(display.handle, @intCast(event.keycode), 0);
+
+ if (keybind_overlay) |overlay| {
+ if (overlay.handle_key(keysym)) {
+ return;
+ }
+ }
+
const clean_state = event.state & ~@as(c_uint, xlib.LockMask | xlib.Mod2Mask);
+ const current_time = std.time.milliTimestamp();
+
+ if (chord_index > 0 and (current_time - chord_timestamp) > chord_timeout_ms) {
+ reset_chord_state();
+ }
+
+ chord_keys[chord_index] = .{ .mod_mask = clean_state, .keysym = keysym };
+ chord_index += 1;
+ chord_timestamp = current_time;
for (config.keybinds.items) |keybind| {
- if (keysym == keybind.keysym and clean_state == keybind.mod_mask) {
- execute_action(display, keybind.action, keybind.int_arg, keybind.str_arg);
- return;
+ if (keybind.key_count == 0) continue;
+
+ if (keybind.key_count == chord_index) {
+ var matches = true;
+ var i: u8 = 0;
+ while (i < keybind.key_count) : (i += 1) {
+ if (chord_keys[i].keysym != keybind.keys[i].keysym or
+ chord_keys[i].mod_mask != keybind.keys[i].mod_mask)
+ {
+ matches = false;
+ break;
+ }
+ }
+ if (matches) {
+ execute_action(display, keybind.action, keybind.int_arg, keybind.str_arg);
+ reset_chord_state();
+ return;
+ }
+ }
+ }
+
+ var has_partial_match = false;
+ for (config.keybinds.items) |keybind| {
+ if (keybind.key_count > chord_index) {
+ var matches = true;
+ var i: u8 = 0;
+ while (i < chord_index) : (i += 1) {
+ if (chord_keys[i].keysym != keybind.keys[i].keysym or
+ chord_keys[i].mod_mask != keybind.keys[i].mod_mask)
+ {
+ matches = false;
+ break;
+ }
+ }
+ if (matches) {
+ has_partial_match = true;
+ break;
+ }
+ }
+ }
+
+ if (has_partial_match and !keyboard_grabbed) {
+ const grab_result = xlib.XGrabKeyboard(
+ display.handle,
+ display.root,
+ xlib.True,
+ xlib.GrabModeAsync,
+ xlib.GrabModeAsync,
+ xlib.CurrentTime,
+ );
+ if (grab_result == xlib.GrabSuccess) {
+ keyboard_grabbed = true;
}
+ } else if (!has_partial_match) {
+ reset_chord_state();
}
}
@@ -734,8 +927,15 @@ fn execute_action(display: *Display, action: config_mod.Action, int_arg: i32, st
running = false;
},
.reload_config => reload_config(display),
- .restart => {},
- .show_keybinds => {},
+ .restart => reload_config(display),
+ .show_keybinds => {
+ if (keybind_overlay) |overlay| {
+ const mon = monitor_mod.selected_monitor orelse monitor_mod.monitors;
+ if (mon) |m| {
+ overlay.toggle(m.mon_x, m.mon_y, m.mon_w, m.mon_h);
+ }
+ }
+ },
.focus_next => focusstack(display, 1),
.focus_prev => focusstack(display, -1),
.move_next => movestack(display, 1),
@@ -748,8 +948,8 @@ fn execute_action(display: *Display, action: config_mod.Action, int_arg: i32, st
.toggle_gaps => toggle_gaps(),
.cycle_layout => cycle_layout(),
.set_layout => set_layout(str_arg),
- .set_layout_tiling => {},
- .set_layout_floating => {},
+ .set_layout_tiling => set_layout_index(0),
+ .set_layout_floating => set_layout_index(2),
.view_tag => {
const tag_mask: u32 = @as(u32, 1) << @intCast(int_arg);
view(display, tag_mask);
@@ -1323,6 +1523,20 @@ fn set_layout(layout_name: ?[]const u8) void {
}
}
+fn set_layout_index(index: u32) void {
+ const monitor = monitor_mod.selected_monitor orelse return;
+ monitor.sel_lt = index;
+ monitor.pertag.sellts[monitor.pertag.curtag] = index;
+ if (index != 3) {
+ monitor.scroll_offset = 0;
+ }
+ arrange(monitor);
+ bar_mod.invalidate_bars();
+ if (monitor.lt[monitor.sel_lt]) |layout| {
+ std.debug.print("set_layout_index: {s}\n", .{layout.symbol});
+ }
+}
+
fn focusmon(display: *Display, direction: i32) void {
const selmon = monitor_mod.selected_monitor orelse return;
const target = monitor_mod.dir_to_monitor(direction) orelse return;
diff --git a/src/overlay.zig b/src/overlay.zig
new file mode 100644
index 0000000..82b2164
--- /dev/null
+++ b/src/overlay.zig
@@ -0,0 +1,371 @@
+const std = @import("std");
+const xlib = @import("x11/xlib.zig");
+const config_mod = @import("config/config.zig");
+
+const padding: i32 = 32;
+const line_spacing: i32 = 12;
+const key_action_spacing: i32 = 32;
+const border_width: i32 = 4;
+const border_color: c_ulong = 0x7fccff;
+const bg_color: c_ulong = 0x1a1a1a;
+const fg_color: c_ulong = 0xffffff;
+const key_bg_color: c_ulong = 0x2a2a2a;
+
+const max_lines: usize = 12;
+
+pub const Keybind_Overlay = struct {
+ window: xlib.Window = 0,
+ pixmap: xlib.Pixmap = 0,
+ gc: xlib.GC = null,
+ xft_draw: ?*xlib.XftDraw = null,
+ font: ?*xlib.XftFont = null,
+ font_height: i32 = 0,
+ width: i32 = 0,
+ height: i32 = 0,
+ visible: bool = false,
+ display: ?*xlib.Display = null,
+ root: xlib.Window = 0,
+ screen: c_int = 0,
+
+ key_bufs: [max_lines][64]u8 = undefined,
+ key_lens: [max_lines]usize = undefined,
+ descs: [max_lines][]const u8 = undefined,
+ line_count: usize = 0,
+
+ pub fn init(display: *xlib.Display, screen: c_int, root: xlib.Window, font_name: []const u8, allocator: std.mem.Allocator) ?*Keybind_Overlay {
+ const overlay = allocator.create(Keybind_Overlay) catch return null;
+
+ const font_name_z = allocator.dupeZ(u8, font_name) catch {
+ allocator.destroy(overlay);
+ return null;
+ };
+ defer allocator.free(font_name_z);
+
+ const font = xlib.XftFontOpenName(display, screen, font_name_z);
+ if (font == null) {
+ allocator.destroy(overlay);
+ return null;
+ }
+
+ const font_height = font.*.ascent + font.*.descent;
+
+ overlay.* = .{
+ .display = display,
+ .root = root,
+ .font = font,
+ .font_height = font_height,
+ .screen = screen,
+ };
+
+ return overlay;
+ }
+
+ pub fn deinit(self: *Keybind_Overlay, allocator: std.mem.Allocator) void {
+ if (self.display) |display| {
+ self.destroy_window(display);
+ if (self.font) |font| {
+ xlib.XftFontClose(display, font);
+ }
+ }
+ allocator.destroy(self);
+ }
+
+ fn destroy_window(self: *Keybind_Overlay, display: *xlib.Display) void {
+ if (self.xft_draw) |xft_draw| {
+ xlib.XftDrawDestroy(xft_draw);
+ self.xft_draw = null;
+ }
+ if (self.gc) |gc| {
+ _ = xlib.XFreeGC(display, gc);
+ self.gc = null;
+ }
+ if (self.pixmap != 0) {
+ _ = xlib.XFreePixmap(display, self.pixmap);
+ self.pixmap = 0;
+ }
+ if (self.window != 0) {
+ _ = xlib.c.XDestroyWindow(display, self.window);
+ self.window = 0;
+ }
+ }
+
+ pub fn toggle(self: *Keybind_Overlay, mon_x: i32, mon_y: i32, mon_w: i32, mon_h: i32) void {
+ if (self.visible) {
+ self.hide();
+ } else {
+ self.show(mon_x, mon_y, mon_w, mon_h);
+ }
+ }
+
+ pub fn show(self: *Keybind_Overlay, mon_x: i32, mon_y: i32, mon_w: i32, mon_h: i32) void {
+ const display = self.display orelse return;
+ const cfg = config_mod.get_config() orelse return;
+
+ self.collect_keybinds(cfg);
+ if (self.line_count == 0) return;
+
+ var max_key_width: i32 = 0;
+ var max_desc_width: i32 = 0;
+
+ for (0..self.line_count) |i| {
+ const key_slice = self.key_bufs[i][0..self.key_lens[i]];
+ const key_w = self.text_width(display, key_slice);
+ const desc_w = self.text_width(display, self.descs[i]);
+ if (key_w > max_key_width) max_key_width = key_w;
+ if (desc_w > max_desc_width) max_desc_width = desc_w;
+ }
+
+ const title = "Keybindings";
+ const title_width = self.text_width(display, title);
+
+ const content_width = max_key_width + key_action_spacing + max_desc_width;
+ const min_width = @max(title_width, content_width);
+
+ self.width = min_width + padding * 2;
+ const line_height = self.font_height + line_spacing;
+ const title_height = self.font_height + 20;
+ self.height = title_height + @as(i32, @intCast(self.line_count)) * line_height + padding * 2;
+
+ self.destroy_window(display);
+
+ const x: i32 = mon_x + @divTrunc(mon_w - self.width, 2);
+ const y: i32 = mon_y + @divTrunc(mon_h - self.height, 2);
+
+ const visual = xlib.XDefaultVisual(display, self.screen);
+ const colormap = xlib.XDefaultColormap(display, self.screen);
+ const depth = xlib.XDefaultDepth(display, self.screen);
+
+ self.window = xlib.c.XCreateSimpleWindow(
+ display,
+ self.root,
+ x,
+ y,
+ @intCast(self.width),
+ @intCast(self.height),
+ @intCast(border_width),
+ border_color,
+ bg_color,
+ );
+
+ var attrs: xlib.c.XSetWindowAttributes = undefined;
+ attrs.override_redirect = xlib.True;
+ attrs.event_mask = xlib.c.ExposureMask | xlib.c.KeyPressMask | xlib.c.ButtonPressMask;
+ _ = xlib.c.XChangeWindowAttributes(display, self.window, xlib.c.CWOverrideRedirect | xlib.c.CWEventMask, &attrs);
+
+ self.pixmap = xlib.XCreatePixmap(display, self.window, @intCast(self.width), @intCast(self.height), @intCast(depth));
+ self.gc = xlib.XCreateGC(display, self.pixmap, 0, null);
+ self.xft_draw = xlib.XftDrawCreate(display, self.pixmap, visual, colormap);
+
+ _ = xlib.XMapWindow(display, self.window);
+ _ = xlib.XRaiseWindow(display, self.window);
+
+ self.draw(display, max_key_width, title);
+
+ _ = xlib.XGrabKeyboard(display, self.window, xlib.True, xlib.GrabModeAsync, xlib.GrabModeAsync, xlib.CurrentTime);
+
+ _ = xlib.XSync(display, xlib.False);
+
+ self.visible = true;
+ }
+
+ pub fn hide(self: *Keybind_Overlay) void {
+ if (!self.visible) return;
+ if (self.display) |display| {
+ _ = xlib.XUngrabKeyboard(display, xlib.CurrentTime);
+ if (self.window != 0) {
+ _ = xlib.c.XUnmapWindow(display, self.window);
+ }
+ }
+ self.visible = false;
+ }
+
+ pub fn handle_key(self: *Keybind_Overlay, keysym: u64) bool {
+ if (!self.visible) return false;
+ if (keysym == 0xff1b or keysym == 'q' or keysym == 'Q') {
+ self.hide();
+ return true;
+ }
+ return false;
+ }
+
+ pub fn is_overlay_window(self: *Keybind_Overlay, win: xlib.Window) bool {
+ return self.visible and self.window != 0 and self.window == win;
+ }
+
+ fn draw(self: *Keybind_Overlay, display: *xlib.Display, max_key_width: i32, title: []const u8) void {
+ self.fill_rect(display, 0, 0, self.width, self.height, bg_color);
+
+ const title_x = @divTrunc(self.width - self.text_width(display, title), 2);
+ const title_y = padding + self.font.?.*.ascent;
+ self.draw_text(display, title_x, title_y, title, fg_color);
+
+ const line_height = self.font_height + line_spacing;
+ var y = padding + self.font_height + 20 + self.font.?.*.ascent;
+
+ for (0..self.line_count) |i| {
+ const key_slice = self.key_bufs[i][0..self.key_lens[i]];
+ const key_w = self.text_width(display, key_slice);
+ self.fill_rect(display, padding - 4, y - self.font.?.*.ascent - 2, key_w + 8, self.font_height + 4, key_bg_color);
+ self.draw_text(display, padding, y, key_slice, fg_color);
+
+ const desc_x = padding + max_key_width + key_action_spacing;
+ self.draw_text(display, desc_x, y, self.descs[i], fg_color);
+
+ y += line_height;
+ }
+
+ _ = xlib.c.XCopyArea(display, self.pixmap, self.window, self.gc, 0, 0, @intCast(self.width), @intCast(self.height), 0, 0);
+ _ = xlib.c.XFlush(display);
+ }
+
+ fn fill_rect(self: *Keybind_Overlay, display: *xlib.Display, x: i32, y: i32, w: i32, h: i32, color: c_ulong) void {
+ _ = xlib.XSetForeground(display, self.gc, color);
+ _ = xlib.XFillRectangle(display, self.pixmap, self.gc, x, y, @intCast(w), @intCast(h));
+ }
+
+ fn draw_text(self: *Keybind_Overlay, display: *xlib.Display, x: i32, y: i32, text: []const u8, color: c_ulong) void {
+ if (self.xft_draw == null or self.font == null) return;
+ if (text.len == 0) return;
+
+ var xft_color: xlib.XftColor = undefined;
+ var render_color: xlib.XRenderColor = undefined;
+ render_color.red = @intCast((color >> 16 & 0xff) * 257);
+ render_color.green = @intCast((color >> 8 & 0xff) * 257);
+ render_color.blue = @intCast((color & 0xff) * 257);
+ render_color.alpha = 0xffff;
+
+ const visual = xlib.XDefaultVisual(display, self.screen);
+ const colormap = xlib.XDefaultColormap(display, self.screen);
+
+ _ = xlib.XftColorAllocValue(display, visual, colormap, &render_color, &xft_color);
+ xlib.XftDrawStringUtf8(self.xft_draw, &xft_color, self.font, x, y, text.ptr, @intCast(text.len));
+ xlib.XftColorFree(display, visual, colormap, &xft_color);
+ }
+
+ fn text_width(self: *Keybind_Overlay, display: *xlib.Display, text: []const u8) i32 {
+ if (self.font == null or text.len == 0) return 0;
+ var extents: xlib.XGlyphInfo = undefined;
+ xlib.XftTextExtentsUtf8(display, self.font, text.ptr, @intCast(text.len), &extents);
+ return extents.xOff;
+ }
+
+ fn collect_keybinds(self: *Keybind_Overlay, cfg: *config_mod.Config) void {
+ const priority_actions = [_]config_mod.Action{
+ .show_keybinds,
+ .quit,
+ .reload_config,
+ .kill_client,
+ .spawn_terminal,
+ .toggle_fullscreen,
+ .toggle_floating,
+ .cycle_layout,
+ .focus_next,
+ .focus_prev,
+ .view_tag,
+ .move_to_tag,
+ };
+
+ self.line_count = 0;
+
+ for (priority_actions) |action| {
+ if (self.line_count >= max_lines) break;
+
+ for (cfg.keybinds.items) |kb| {
+ if (kb.action == action and kb.key_count > 0) {
+ self.format_key_to_buf(self.line_count, &kb.keys[0]);
+ self.descs[self.line_count] = action_desc(action);
+ self.line_count += 1;
+ break;
+ }
+ }
+ }
+ }
+
+ fn format_key_to_buf(self: *Keybind_Overlay, idx: usize, key: *const config_mod.Key_Press) void {
+ var len: usize = 0;
+ var buf = &self.key_bufs[idx];
+
+ if (key.mod_mask & (1 << 6) != 0) {
+ const s = "Mod + ";
+ @memcpy(buf[len .. len + s.len], s);
+ len += s.len;
+ }
+ if (key.mod_mask & (1 << 0) != 0) {
+ const s = "Shift + ";
+ @memcpy(buf[len .. len + s.len], s);
+ len += s.len;
+ }
+ if (key.mod_mask & (1 << 2) != 0) {
+ const s = "Ctrl + ";
+ @memcpy(buf[len .. len + s.len], s);
+ len += s.len;
+ }
+
+ const key_name = keysym_to_name(key.keysym);
+ if (len + key_name.len < buf.len) {
+ @memcpy(buf[len .. len + key_name.len], key_name);
+ len += key_name.len;
+ }
+
+ self.key_lens[idx] = len;
+ }
+
+ fn keysym_to_name(keysym: u64) []const u8 {
+ return switch (keysym) {
+ 0xff0d => "Return",
+ 0x0020 => "Space",
+ 0xff1b => "Escape",
+ 0xff08 => "BackSpace",
+ 0xff09 => "Tab",
+ 0xffbe => "F1",
+ 0xffbf => "F2",
+ 0xffc0 => "F3",
+ 0xffc1 => "F4",
+ 0xffc2 => "F5",
+ 0xffc3 => "F6",
+ 0xffc4 => "F7",
+ 0xffc5 => "F8",
+ 0xffc6 => "F9",
+ 0xffc7 => "F10",
+ 0xffc8 => "F11",
+ 0xffc9 => "F12",
+ 0xff51 => "Left",
+ 0xff52 => "Up",
+ 0xff53 => "Right",
+ 0xff54 => "Down",
+ 0x002c => ",",
+ 0x002e => ".",
+ 0x002f => "/",
+ 'a'...'z' => |c| &[_]u8{@intCast(c - 32)},
+ 'A'...'Z' => |c| &[_]u8{@intCast(c)},
+ '0'...'9' => |c| &[_]u8{@intCast(c)},
+ else => "?",
+ };
+ }
+
+ fn action_desc(action: config_mod.Action) []const u8 {
+ return switch (action) {
+ .show_keybinds => "Show Keybinds",
+ .quit => "Quit WM",
+ .reload_config => "Reload Config",
+ .restart => "Restart WM",
+ .kill_client => "Close Window",
+ .spawn_terminal => "Open Terminal",
+ .spawn => "Launch Program",
+ .toggle_fullscreen => "Toggle Fullscreen",
+ .toggle_floating => "Toggle Floating",
+ .toggle_gaps => "Toggle Gaps",
+ .cycle_layout => "Cycle Layout",
+ .set_layout => "Set Layout",
+ .focus_next => "Focus Next",
+ .focus_prev => "Focus Previous",
+ .move_next => "Move Next",
+ .move_prev => "Move Previous",
+ .view_tag => "View Tag",
+ .move_to_tag => "Move to Tag",
+ .focus_monitor => "Focus Monitor",
+ .send_to_monitor => "Send to Monitor",
+ else => "Action",
+ };
+ }
+};
diff --git a/src/x11/xlib.zig b/src/x11/xlib.zig
index 50065d1..9de9d74 100644
--- a/src/x11/xlib.zig
+++ b/src/x11/xlib.zig
@@ -252,6 +252,8 @@ pub const XGrabServer = c.XGrabServer;
pub const XUngrabServer = c.XUngrabServer;
pub const XUngrabButton = c.XUngrabButton;
pub const XUngrabKey = c.XUngrabKey;
+pub const XGrabKeyboard = c.XGrabKeyboard;
+pub const XUngrabKeyboard = c.XUngrabKeyboard;
pub const AnyKey = c.AnyKey;
pub const AnyModifier = c.AnyModifier;
diff --git a/tests/lua_config_tests.zig b/tests/lua_config_tests.zig
new file mode 100644
index 0000000..f1faa18
--- /dev/null
+++ b/tests/lua_config_tests.zig
@@ -0,0 +1,18 @@
+const std = @import("std");
+const testing = std.testing;
+const lua = @import("lua");
+const Config = lua.config_mod.Config;
+
+test "test-config.lua loads without errors" {
+ var cfg = Config.init(testing.allocator);
+ defer cfg.deinit();
+
+ const initialized = lua.init(&cfg);
+ if (!initialized) {
+ return error.LuaInitFailed;
+ }
+ defer lua.deinit();
+
+ const loaded = lua.load_file("resources/test-config.lua");
+ try testing.expect(loaded);
+}