river: add keyboard-layout command

This allows switching river's keyboard layout at runtime.
This commit is contained in:
Leon Henrik Plickat 2022-10-16 21:49:40 +02:00 committed by Isaac Freund
parent 2eb0a7a75c
commit ad1dbb1180
No known key found for this signature in database
GPG Key ID: 86DED400DDFD7A11
5 changed files with 109 additions and 1 deletions

View File

@ -334,6 +334,11 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_
*list-input-configs* *list-input-configs*
List all input configurations. List all input configurations.
*keyboard-layout* _layout_ [-variant _variant_] [-model _model_] [-options _options_] [-rules _rules_]
Set the XKB layout for all keyboards. All values other than the layout
name are optional. XKB will fill in unspecified values based on
heuristics and the environment. Duplicate flags are not allowed.
*keyboard-group-create* _group_name_ *keyboard-group-create* _group_name_
Create a keyboard group. A keyboard group collects multiple keyboards in Create a keyboard group. A keyboard group collects multiple keyboards in
a single logical keyboard. This means that all state, like the active a single logical keyboard. This means that all state, like the active

View File

@ -17,6 +17,8 @@
const Self = @This(); const Self = @This();
const std = @import("std"); const std = @import("std");
const mem = std.mem;
const xkb = @import("xkbcommon");
const util = @import("util.zig"); const util = @import("util.zig");
@ -100,6 +102,9 @@ cursor_hide_timeout: u31 = 0,
/// Hide the cursor while typing /// Hide the cursor while typing
cursor_hide_when_typing: HideCursorWhenTypingMode = .disabled, cursor_hide_when_typing: HideCursorWhenTypingMode = .disabled,
/// Keyboard layout configuration
keyboard_layout: ?xkb.RuleNames = null,
pub fn init() !Self { pub fn init() !Self {
var self = Self{ var self = Self{
.mode_to_id = std.StringHashMap(u32).init(util.gpa), .mode_to_id = std.StringHashMap(u32).init(util.gpa),
@ -129,6 +134,14 @@ pub fn deinit(self: *Self) void {
for (self.modes.items) |*mode| mode.deinit(); for (self.modes.items) |*mode| mode.deinit();
self.modes.deinit(util.gpa); self.modes.deinit(util.gpa);
if (self.keyboard_layout) |kl| {
if (kl.rules) |s| util.gpa.free(mem.span(s));
if (kl.model) |s| util.gpa.free(mem.span(s));
if (kl.layout) |s| util.gpa.free(mem.span(s));
if (kl.variant) |s| util.gpa.free(mem.span(s));
if (kl.options) |s| util.gpa.free(mem.span(s));
}
{ {
var it = self.float_filter_app_ids.keyIterator(); var it = self.float_filter_app_ids.keyIterator();
while (it.next()) |key| util.gpa.free(key.*); while (it.next()) |key| util.gpa.free(key.*);

View File

@ -51,7 +51,8 @@ pub fn init(self: *Self, seat: *Seat, wlr_device: *wlr.InputDevice) !void {
// Passing null here indicates that defaults from libxkbcommon and // Passing null here indicates that defaults from libxkbcommon and
// its XKB_DEFAULT_LAYOUT, XKB_DEFAULT_OPTIONS, etc. should be used. // its XKB_DEFAULT_LAYOUT, XKB_DEFAULT_OPTIONS, etc. should be used.
const keymap = xkb.Keymap.newFromNames(context, null, .no_flags) orelse return error.XkbKeymapFailed; const layout_config = if (server.config.keyboard_layout) |kl| &kl else null;
const keymap = xkb.Keymap.newFromNames(context, layout_config, .no_flags) orelse return error.XkbKeymapFailed;
defer keymap.unref(); defer keymap.unref();
const wlr_keyboard = self.device.wlr_device.toKeyboard(); const wlr_keyboard = self.device.wlr_device.toKeyboard();

View File

@ -89,6 +89,7 @@ const command_impls = std.ComptimeStringMap(
.{ "unmap-switch", @import("command/map.zig").unmapSwitch }, .{ "unmap-switch", @import("command/map.zig").unmapSwitch },
.{ "xcursor-theme", @import("command/xcursor_theme.zig").xcursorTheme }, .{ "xcursor-theme", @import("command/xcursor_theme.zig").xcursorTheme },
.{ "zoom", @import("command/zoom.zig").zoom }, .{ "zoom", @import("command/zoom.zig").zoom },
.{ "keyboard-layout", @import("command/keyboard.zig").keyboardLayout },
.{ "keyboard-group-create", @import("command/keyboard_group.zig").keyboardGroupCreate }, .{ "keyboard-group-create", @import("command/keyboard_group.zig").keyboardGroupCreate },
.{ "keyboard-group-destroy", @import("command/keyboard_group.zig").keyboardGroupDestroy }, .{ "keyboard-group-destroy", @import("command/keyboard_group.zig").keyboardGroupDestroy },
.{ "keyboard-group-add", @import("command/keyboard_group.zig").keyboardGroupAdd }, .{ "keyboard-group-add", @import("command/keyboard_group.zig").keyboardGroupAdd },

View File

@ -0,0 +1,88 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
// Copyright 2022 The River Developers
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const mem = std.mem;
const xkb = @import("xkbcommon");
const server = &@import("../main.zig").server;
const util = @import("../util.zig");
const Error = @import("../command.zig").Error;
const Seat = @import("../Seat.zig");
pub fn keyboardLayout(
_: *Seat,
args: []const [:0]const u8,
_: *?[]const u8,
) Error!void {
if (args.len < 2) return Error.NotEnoughArguments;
if (args.len % 2 != 0) return Error.InvalidValue;
// Do not carry over any previous keyboard layout configuration, always
// start fresh.
if (server.config.keyboard_layout) |kl| {
if (kl.rules) |s| util.gpa.free(mem.span(s));
if (kl.model) |s| util.gpa.free(mem.span(s));
if (kl.layout) |s| util.gpa.free(mem.span(s));
if (kl.variant) |s| util.gpa.free(mem.span(s));
if (kl.options) |s| util.gpa.free(mem.span(s));
}
server.config.keyboard_layout = xkb.RuleNames{
.layout = try util.gpa.dupeZ(u8, args[1]),
.rules = null,
.model = null,
.variant = null,
.options = null,
};
// TODO[zig]: this can be solved more elegantly with an inline for loop, but
// on version 0.9.1 that crashes the compiler.
var i: usize = 2;
while (i < args.len - 1) : (i += 2) {
if (mem.eql(u8, args[i], "-variant")) {
// Do not allow duplicate flags.
if (server.config.keyboard_layout.?.variant != null) return error.InvalidValue;
server.config.keyboard_layout.?.variant = try util.gpa.dupeZ(u8, args[i + 1]);
} else if (mem.eql(u8, args[i], "-model")) {
if (server.config.keyboard_layout.?.model != null) return error.InvalidValue;
server.config.keyboard_layout.?.model = try util.gpa.dupeZ(u8, args[i + 1]);
} else if (mem.eql(u8, args[i], "-options")) {
if (server.config.keyboard_layout.?.options != null) return error.InvalidValue;
server.config.keyboard_layout.?.options = try util.gpa.dupeZ(u8, args[i + 1]);
} else if (mem.eql(u8, args[i], "-rules")) {
if (server.config.keyboard_layout.?.rules != null) return error.InvalidValue;
server.config.keyboard_layout.?.rules = try util.gpa.dupeZ(u8, args[i + 1]);
} else {
return error.InvalidValue;
}
}
const context = xkb.Context.new(.no_flags) orelse return error.OutOfMemory;
defer context.unref();
const keymap = xkb.Keymap.newFromNames(context, &server.config.keyboard_layout.?, .no_flags) orelse return error.InvalidValue;
defer keymap.unref();
var it = server.input_manager.devices.iterator(.forward);
while (it.next()) |device| {
if (device.wlr_device.type != .keyboard) continue;
const wlr_keyboard = device.wlr_device.toKeyboard();
if (!wlr_keyboard.setKeymap(keymap)) return error.OutOfMemory;
}
}