git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commitd6c24e083718e3bd4151991b9807d10a1fac4bd0
parent06597a4612
authorIstván Donkó <[email protected]>
date2023-08-12 21:30
keyboard: add the ability to load layout from file

 completions/bash/riverctl      |  1 +
 completions/fish/riverctl.fish |  1 +
 completions/zsh/_riverctl      |  1 +
 doc/riverctl.1.scd             |  6 ++++++
 river/command.zig              |  5 +++++
 river/command/keyboard.zig     | 36 ++++++++++++++++++++++++++++++++++++
 6 files changed, 50 insertions(+)

diff --git a/completions/bash/riverctl b/completions/bash/riverctl
index c756321..ccee6a0 100644
--- a/completions/bash/riverctl
+++ b/completions/bash/riverctl
@@ -9,6 +9,7 @@ function __riverctl_completion ()
 			keyboard-group-add \
 			keyboard-group-remove \
 			keyboard-layout \
+			keyboard-layout-file \
 			exit \
 			focus-output \
 			focus-view \
diff --git a/completions/fish/riverctl.fish b/completions/fish/riverctl.fish
index 9b321f3..7accc4d 100644
--- a/completions/fish/riverctl.fish
+++ b/completions/fish/riverctl.fish
@@ -77,6 +77,7 @@ complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-group-dest
 complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-group-add'     -d 'Add a keyboard to a keyboard group'
 complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-group-remove'  -d 'Remove a keyboard from a keyboard group'
 complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-layout'        -d 'Set the keyboard layout'
+complete -c riverctl -n '__fish_riverctl_complete_arg 1' -a 'keyboard-layout-file'   -d 'Set the keyboard layout from a file.'
 
 # Subcommands
 complete -c riverctl -n '__fish_seen_subcommand_from focus-output send-to-output' -n '__fish_riverctl_complete_arg 2' -a 'next previous'
diff --git a/completions/zsh/_riverctl b/completions/zsh/_riverctl
index 6467fbc..0c90bf9 100644
--- a/completions/zsh/_riverctl
+++ b/completions/zsh/_riverctl
@@ -67,6 +67,7 @@ _riverctl_commands()
         'keyboard-group-add:Add a keyboard to a keyboard group'
         'keyboard-group-remove:Remove a keyboard from a keyboard group'
         'keyboard-layout:Set the keyboard layout'
+        'keyboard-layout-file:Set the keyboard layout from a file'
         # Input
         'input:Configure input devices'
         'list-inputs:List all input devices'
diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd
index b43678b..5553306 100644
--- a/doc/riverctl.1.scd
+++ b/doc/riverctl.1.scd
@@ -424,6 +424,12 @@ matches everything while _\*\*_ and the empty string are invalid.
 	"grp:ctrl_space_toggle"). See *xkeyboard-config*(7) for possible values and
 	more information.
 
+*keyboard-layout-file* _path_
+	Set the XKB layout for all keyboards from an XKB keymap file at the provided
+	path. Documentation for the XKB keymap file format can be found at the
+	following URL:
+	https://xkbcommon.org/doc/current/keymap-text-format-v1.html
+
 *keyboard-group-create* _group_name_
 	Create a keyboard group. A keyboard group collects multiple keyboards in
 	a single logical keyboard. This means that all state, like the active
diff --git a/river/command.zig b/river/command.zig
index dfebe9b..4a2a647 100644
--- a/river/command.zig
+++ b/river/command.zig
@@ -62,6 +62,7 @@ const command_impls = std.ComptimeStringMap(
         .{ "keyboard-group-destroy",    @import("command/keyboard_group.zig").keyboardGroupDestroy },
         .{ "keyboard-group-remove",     @import("command/keyboard_group.zig").keyboardGroupRemove },
         .{ "keyboard-layout",           @import("command/keyboard.zig").keyboardLayout },
+        .{ "keyboard-layout-file",      @import("command/keyboard.zig").keyboardLayoutFile },
         .{ "list-input-configs",        @import("command/input.zig").listInputConfigs},
         .{ "list-inputs",               @import("command/input.zig").listInputs },
         .{ "list-rules",                @import("command/rule.zig").listRules},
@@ -112,6 +113,8 @@ pub const Error = error{
     InvalidOrientation,
     InvalidRgba,
     InvalidValue,
+    CannotReadFile,
+    CannotParseFile,
     UnknownOption,
     ConflictingOptions,
     OutOfMemory,
@@ -155,6 +158,8 @@ pub fn errToMsg(err: Error) [:0]const u8 {
         Error.InvalidOrientation => "invalid orientation. Must be 'horizontal', or 'vertical'",
         Error.InvalidRgba => "invalid color format, must be hexadecimal 0xRRGGBB or 0xRRGGBBAA",
         Error.InvalidValue => "invalid value",
+        Error.CannotReadFile => "cannot read file",
+        Error.CannotParseFile => "cannot parse file",
         Error.OutOfMemory => "out of memory",
         Error.Other => unreachable,
     };
diff --git a/river/command/keyboard.zig b/river/command/keyboard.zig
index 93f5cb9..c09db1f 100644
--- a/river/command/keyboard.zig
+++ b/river/command/keyboard.zig
@@ -58,6 +58,42 @@ pub fn keyboardLayout(
     ) orelse return error.InvalidValue;
     defer new_keymap.unref();
 
+    applyLayout(new_keymap);
+}
+
+pub fn keyboardLayoutFile(
+    _: *Seat,
+    args: []const [:0]const u8,
+    _: *?[]const u8,
+) Error!void {
+    if (args.len < 2) return Error.NotEnoughArguments;
+    if (args.len > 2) return Error.TooManyArguments;
+
+    const file = std.fs.cwd().openFile(args[1], .{}) catch return error.CannotReadFile;
+    defer file.close();
+
+    // 1 GiB is arbitrarily chosen as an exceedingly large but not infinite upper bound.
+    const file_bytes = file.readToEndAlloc(util.gpa, 1024 * 1024 * 1024) catch |err| {
+        switch (err) {
+            error.FileTooBig, error.OutOfMemory => return error.OutOfMemory,
+            else => return error.CannotReadFile,
+        }
+    };
+    defer util.gpa.free(file_bytes);
+
+    const new_keymap = xkb.Keymap.newFromBuffer(
+        server.config.xkb_context,
+        file_bytes.ptr,
+        file_bytes.len,
+        .text_v1,
+        .no_flags,
+    ) orelse return error.CannotParseFile;
+    defer new_keymap.unref();
+
+    applyLayout(new_keymap);
+}
+
+fn applyLayout(new_keymap: *xkb.Keymap) void {
     server.config.keymap.unref();
     server.config.keymap = new_keymap.ref();