river: add keyboard groups
This commit is contained in:
parent
c0e64829f0
commit
01f49bbbc1
@ -3,6 +3,9 @@ function __riverctl_completion ()
|
||||
if [ "${COMP_CWORD}" -eq 1 ]
|
||||
then
|
||||
OPTS=" \
|
||||
keyboard-group-create \
|
||||
keyboard-group-destroy \
|
||||
keyboard-group-add-keyboard \
|
||||
csd-filter-add \
|
||||
exit \
|
||||
float-filter-add \
|
||||
|
@ -61,6 +61,10 @@ complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'hide-cursor'
|
||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'set-repeat' -d 'Set the keyboard repeat rate and repeat delay'
|
||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'set-cursor-warp' -d 'Set the cursor warp mode.'
|
||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'xcursor-theme' -d 'Set the xcursor theme'
|
||||
# Keyboardgroups
|
||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'keyboard-group-create' -d 'Create a keyboard group.'
|
||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'keyboard-group-destroy' -d 'Destroy a keyboard group.'
|
||||
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'keyboard-group-add-keyboard' -d 'Add a keyboard to a keyboard group.'
|
||||
|
||||
# Subcommands
|
||||
complete -c riverctl -x -n '__fish_seen_subcommand_from focus-output' -a 'next previous'
|
||||
|
@ -55,6 +55,10 @@ _riverctl_subcommands()
|
||||
'set-repeat:Set the keyboard repeat rate and repeat delay'
|
||||
'set-cursor-warp:Set the cursor warp mode.'
|
||||
'xcursor-theme:Set the xcursor theme'
|
||||
# Keyboard groups
|
||||
'keyboard-group-create:Create a keyboard group'
|
||||
'keyboard-group-destroy:Destroy a keyboard group'
|
||||
'keyboard-group-add-keyboard:Add a keyboard to a keyboard group'
|
||||
# Input
|
||||
'input:Configure input devices'
|
||||
'list-inputs:List all input devices'
|
||||
|
@ -330,6 +330,20 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_
|
||||
*list-input-configs*
|
||||
List all input configurations.
|
||||
|
||||
*keyboard-group-create* _keyboard_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
|
||||
modifiers, is shared between the keyboards in a group.
|
||||
|
||||
*keyboard-group-destroy* _keyboard_group_name_
|
||||
Destroy the keyboard group of the given name. All attached keyboards
|
||||
will be released, making them act as seperate devices again.
|
||||
|
||||
*keyboard-group-add-keyboard* _keyboard_group_name_ _input_device_identifier_
|
||||
Add a keyboard to a keyboard group, identified by the keyboards input
|
||||
device identifier. Any currently connected and future keyboards matching
|
||||
the identifier will be added to the group.
|
||||
|
||||
The _input_ command can be used to create a configuration rule for an input
|
||||
device identified by its _name_.
|
||||
The _name_ of an input device consists of its type, its numerical vendor id,
|
||||
|
@ -108,7 +108,7 @@ fn handleDestroy(listener: *wl.Listener(*wlr.InputDevice), _: *wlr.InputDevice)
|
||||
|
||||
switch (device.wlr_device.type) {
|
||||
.keyboard => {
|
||||
const keyboard = @fieldParentPtr(Keyboard, "device", device);
|
||||
const keyboard = @fieldParentPtr(Keyboard, "provider", @ptrCast(*Keyboard.Provider, device));
|
||||
keyboard.deinit();
|
||||
util.gpa.destroy(keyboard);
|
||||
},
|
||||
|
@ -25,13 +25,19 @@ const xkb = @import("xkbcommon");
|
||||
const server = &@import("main.zig").server;
|
||||
const util = @import("util.zig");
|
||||
|
||||
const KeycodeSet = @import("KeycodeSet.zig");
|
||||
const Seat = @import("Seat.zig");
|
||||
const InputDevice = @import("InputDevice.zig");
|
||||
const KeyboardGroup = @import("KeyboardGroup.zig");
|
||||
const KeycodeSet = @import("KeycodeSet.zig");
|
||||
|
||||
const log = std.log.scoped(.keyboard);
|
||||
|
||||
pub const Provider = union(enum) {
|
||||
device: InputDevice,
|
||||
group: *KeyboardGroup,
|
||||
};
|
||||
|
||||
provider: Provider,
|
||||
|
||||
/// Pressed keys for which a mapping was triggered on press
|
||||
eaten_keycodes: KeycodeSet = .{},
|
||||
@ -40,11 +46,8 @@ key: wl.Listener(*wlr.Keyboard.event.Key) = wl.Listener(*wlr.Keyboard.event.Key)
|
||||
modifiers: wl.Listener(*wlr.Keyboard) = wl.Listener(*wlr.Keyboard).init(handleModifiers),
|
||||
|
||||
pub fn init(self: *Self, seat: *Seat, wlr_device: *wlr.InputDevice) !void {
|
||||
self.* = .{
|
||||
.device = undefined,
|
||||
};
|
||||
try self.device.init(seat, wlr_device);
|
||||
errdefer self.device.deinit();
|
||||
const wlr_keyboard = wlr_device.device.keyboard;
|
||||
wlr_keyboard.data = @ptrToInt(self);
|
||||
|
||||
const context = xkb.Context.new(.no_flags) orelse return error.XkbContextFailed;
|
||||
defer context.unref();
|
||||
@ -54,34 +57,70 @@ pub fn init(self: *Self, seat: *Seat, wlr_device: *wlr.InputDevice) !void {
|
||||
const keymap = xkb.Keymap.newFromNames(context, null, .no_flags) orelse return error.XkbKeymapFailed;
|
||||
defer keymap.unref();
|
||||
|
||||
const wlr_keyboard = self.device.wlr_device.device.keyboard;
|
||||
wlr_keyboard.data = @ptrToInt(self);
|
||||
|
||||
if (!wlr_keyboard.setKeymap(keymap)) return error.SetKeymapFailed;
|
||||
|
||||
wlr_keyboard.setRepeatInfo(server.config.repeat_rate, server.config.repeat_delay);
|
||||
self.* = .{
|
||||
.provider = undefined,
|
||||
};
|
||||
if (wlr.KeyboardGroup.fromKeyboard(wlr_keyboard)) |grp| {
|
||||
const group = @intToPtr(*KeyboardGroup, grp.data);
|
||||
self.provider = .{ .group = group };
|
||||
} else {
|
||||
self.provider = .{ .device = undefined };
|
||||
try self.provider.device.init(seat, wlr_device);
|
||||
}
|
||||
|
||||
wlr_keyboard.events.key.add(&self.key);
|
||||
wlr_keyboard.events.modifiers.add(&self.modifiers);
|
||||
|
||||
wlr_keyboard.setRepeatInfo(server.config.repeat_rate, server.config.repeat_delay);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.key.link.remove();
|
||||
self.modifiers.link.remove();
|
||||
|
||||
self.device.deinit();
|
||||
switch (self.provider) {
|
||||
.device => {
|
||||
const wlr_keyboard = self.provider.device.wlr_device.device.keyboard;
|
||||
if (wlr_keyboard.group) |group| group.removeKeyboard(wlr_keyboard);
|
||||
self.provider.device.deinit();
|
||||
},
|
||||
.group => {},
|
||||
}
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// This event is raised when a key is pressed or released.
|
||||
fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboard.event.Key) void {
|
||||
// This event is raised when a key is pressed or released.
|
||||
const self = @fieldParentPtr(Self, "key", listener);
|
||||
const wlr_keyboard = self.device.wlr_device.device.keyboard;
|
||||
switch (self.provider) {
|
||||
.device => {
|
||||
if (self.provider.device.wlr_device.device.keyboard.group != null) return;
|
||||
self.handleKeyImpl(self.provider.device.seat, self.provider.device.wlr_device, event);
|
||||
},
|
||||
.group => self.handleKeyImpl(self.provider.group.seat, self.provider.group.group.input_device, event),
|
||||
}
|
||||
}
|
||||
|
||||
self.device.seat.handleActivity();
|
||||
/// Simply pass modifiers along to the client
|
||||
fn handleModifiers(listener: *wl.Listener(*wlr.Keyboard), _: *wlr.Keyboard) void {
|
||||
const self = @fieldParentPtr(Self, "modifiers", listener);
|
||||
switch (self.provider) {
|
||||
.device => {
|
||||
if (self.provider.device.wlr_device.device.keyboard.group != null) return;
|
||||
self.handleModifiersImpl(self.provider.device.seat, self.provider.device.wlr_device);
|
||||
},
|
||||
.group => self.handleModifiersImpl(self.provider.group.seat, self.provider.group.group.input_device),
|
||||
}
|
||||
}
|
||||
|
||||
self.device.seat.clearRepeatingMapping();
|
||||
fn handleKeyImpl(self: *Self, seat: *Seat, wlr_device: *wlr.InputDevice, event: *wlr.Keyboard.event.Key) void {
|
||||
const wlr_keyboard = wlr_device.device.keyboard;
|
||||
|
||||
seat.handleActivity();
|
||||
seat.clearRepeatingMapping();
|
||||
|
||||
// Translate libinput keycode -> xkbcommon
|
||||
const keycode = event.keycode + 8;
|
||||
@ -98,7 +137,7 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
|
||||
!released and
|
||||
!isModifier(sym))
|
||||
{
|
||||
self.device.seat.cursor.hide();
|
||||
seat.cursor.hide();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -109,11 +148,11 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
|
||||
}
|
||||
|
||||
// Handle user-defined mappings
|
||||
const mapped = self.device.seat.hasMapping(keycode, modifiers, released, xkb_state);
|
||||
const mapped = seat.hasMapping(keycode, modifiers, released, xkb_state);
|
||||
if (mapped) {
|
||||
if (!released) self.eaten_keycodes.add(event.keycode);
|
||||
|
||||
const handled = self.device.seat.handleMapping(keycode, modifiers, released, xkb_state);
|
||||
const handled = seat.handleMapping(keycode, modifiers, released, xkb_state);
|
||||
assert(handled);
|
||||
}
|
||||
|
||||
@ -121,8 +160,8 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
|
||||
|
||||
if (!eaten) {
|
||||
// If key was not handled, we pass it along to the client.
|
||||
const wlr_seat = self.device.seat.wlr_seat;
|
||||
wlr_seat.setKeyboard(self.device.wlr_device);
|
||||
const wlr_seat = seat.wlr_seat;
|
||||
wlr_seat.setKeyboard(wlr_device);
|
||||
wlr_seat.keyboardNotifyKey(event.time_msec, event.keycode, event.state);
|
||||
}
|
||||
}
|
||||
@ -131,14 +170,6 @@ fn isModifier(keysym: xkb.Keysym) bool {
|
||||
return @enumToInt(keysym) >= xkb.Keysym.Shift_L and @enumToInt(keysym) <= xkb.Keysym.Hyper_R;
|
||||
}
|
||||
|
||||
/// Simply pass modifiers along to the client
|
||||
fn handleModifiers(listener: *wl.Listener(*wlr.Keyboard), _: *wlr.Keyboard) void {
|
||||
const self = @fieldParentPtr(Self, "modifiers", listener);
|
||||
|
||||
self.device.seat.wlr_seat.setKeyboard(self.device.wlr_device);
|
||||
self.device.seat.wlr_seat.keyboardNotifyModifiers(&self.device.wlr_device.device.keyboard.modifiers);
|
||||
}
|
||||
|
||||
/// Handle any builtin, harcoded compsitor mappings such as VT switching.
|
||||
/// Returns true if the keysym was handled.
|
||||
fn handleBuiltinMapping(keysym: xkb.Keysym) bool {
|
||||
@ -158,3 +189,9 @@ fn handleBuiltinMapping(keysym: xkb.Keysym) bool {
|
||||
else => return false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Simply pass modifiers along to the client
|
||||
fn handleModifiersImpl(_: *Self, seat: *Seat, wlr_device: *wlr.InputDevice) void {
|
||||
seat.wlr_seat.setKeyboard(wlr_device);
|
||||
seat.wlr_seat.keyboardNotifyModifiers(&wlr_device.device.keyboard.modifiers);
|
||||
}
|
||||
|
113
river/KeyboardGroup.zig
Normal file
113
river/KeyboardGroup.zig
Normal file
@ -0,0 +1,113 @@
|
||||
// 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 Self = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const heap = std.heap;
|
||||
const mem = std.mem;
|
||||
const debug = std.debug;
|
||||
const wlr = @import("wlroots");
|
||||
const wl = @import("wayland").server.wl;
|
||||
const xkb = @import("xkbcommon");
|
||||
|
||||
const log = std.log.scoped(.keyboard_group);
|
||||
|
||||
const server = &@import("main.zig").server;
|
||||
const util = @import("util.zig");
|
||||
|
||||
const Seat = @import("Seat.zig");
|
||||
const Keyboard = @import("Keyboard.zig");
|
||||
|
||||
seat: *Seat,
|
||||
group: *wlr.KeyboardGroup,
|
||||
name: []const u8,
|
||||
keyboard_identifiers: std.ArrayListUnmanaged([]const u8) = .{},
|
||||
|
||||
pub fn init(self: *Self, seat: *Seat, _name: []const u8) !void {
|
||||
log.debug("new keyboard group: '{s}'", .{_name});
|
||||
|
||||
const group = try wlr.KeyboardGroup.create();
|
||||
errdefer group.destroy();
|
||||
group.data = @ptrToInt(self);
|
||||
|
||||
const name = try util.gpa.dupe(u8, _name);
|
||||
errdefer util.gpa.free(name);
|
||||
|
||||
self.* = .{
|
||||
.group = group,
|
||||
.name = name,
|
||||
.seat = seat,
|
||||
};
|
||||
|
||||
seat.addDevice(self.group.input_device);
|
||||
seat.wlr_seat.setKeyboard(self.group.input_device);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
log.debug("removing keyboard group: '{s}'", .{self.name});
|
||||
|
||||
util.gpa.free(self.name);
|
||||
for (self.keyboard_identifiers.items) |id| util.gpa.free(id);
|
||||
self.keyboard_identifiers.deinit(util.gpa);
|
||||
|
||||
// wlroots automatically removes all keyboards from the group.
|
||||
self.group.destroy();
|
||||
}
|
||||
|
||||
pub fn addKeyboardIdentifier(self: *Self, _id: []const u8) !void {
|
||||
if (containsIdentifier(self, _id)) return;
|
||||
log.debug("keyboard group '{s}' adding identifier: '{s}'", .{ self.name, _id });
|
||||
|
||||
const id = try util.gpa.dupe(u8, _id);
|
||||
errdefer util.gpa.free(id);
|
||||
try self.keyboard_identifiers.append(util.gpa, id);
|
||||
|
||||
// Add any existing matching keyboard to group.
|
||||
var it = server.input_manager.devices.iterator(.forward);
|
||||
while (it.next()) |device| {
|
||||
if (device.seat != self.seat) continue;
|
||||
if (device.wlr_device.type != .keyboard) continue;
|
||||
|
||||
if (mem.eql(u8, _id, device.identifier)) {
|
||||
log.debug("found existing matching keyboard; adding to group", .{});
|
||||
|
||||
const wlr_keyboard = device.wlr_device.device.keyboard;
|
||||
if (!self.group.addKeyboard(wlr_keyboard)) continue; // wlroots logs its own errors.
|
||||
}
|
||||
|
||||
// Continue, because we may have more than one device with the exact
|
||||
// same identifier. That is in fact the reason for the keyboard group
|
||||
// feature to exist in the first place.
|
||||
}
|
||||
}
|
||||
|
||||
pub fn containsIdentifier(self: *Self, id: []const u8) bool {
|
||||
for (self.keyboard_identifiers.items) |ki| {
|
||||
if (mem.eql(u8, ki, id)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn addKeyboard(self: *Self, keyboard: *Keyboard) !void {
|
||||
debug.assert(keyboard.provider != .group);
|
||||
const wlr_keyboard = keyboard.provider.device.wlr_device.device.keyboard;
|
||||
log.debug("keyboard group '{s}' adding keyboard: '{s}'", .{ self.name, keyboard.provider.device.identifier });
|
||||
if (!self.group.addKeyboard(wlr_keyboard)) {
|
||||
log.err("failed to add keyboard to group", .{});
|
||||
return error.OutOfMemory;
|
||||
}
|
||||
}
|
@ -41,6 +41,7 @@ const Switch = @import("Switch.zig");
|
||||
const View = @import("View.zig");
|
||||
const ViewStack = @import("view_stack.zig").ViewStack;
|
||||
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
|
||||
const KeyboardGroup = @import("KeyboardGroup.zig");
|
||||
|
||||
const log = std.log.scoped(.seat);
|
||||
const PointerConstraint = @import("PointerConstraint.zig");
|
||||
@ -69,6 +70,8 @@ mapping_repeat_timer: *wl.EventSource,
|
||||
/// Currently repeating mapping, if any
|
||||
repeating_mapping: ?*const Mapping = null,
|
||||
|
||||
keyboard_groups: std.TailQueue(KeyboardGroup) = .{},
|
||||
|
||||
/// Currently focused output, may be the noop output if no real output
|
||||
/// is currently available for focus.
|
||||
focused_output: *Output,
|
||||
@ -475,7 +478,18 @@ fn tryAddDevice(self: *Self, wlr_device: *wlr.InputDevice) !void {
|
||||
|
||||
try keyboard.init(self, wlr_device);
|
||||
|
||||
self.wlr_seat.setKeyboard(keyboard.device.wlr_device);
|
||||
// Add this keyboard to a keyboard group, if the group contains a
|
||||
// matching identifier and if the keyboard isn't a group itself.
|
||||
if (keyboard.provider == .device) {
|
||||
var it = self.keyboard_groups.first;
|
||||
while (it) |node| : (it = node.next) {
|
||||
if (node.data.containsIdentifier(keyboard.provider.device.identifier)) {
|
||||
try node.data.addKeyboard(keyboard);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self.wlr_seat.keyboard_state.focused_surface) |wlr_surface| {
|
||||
self.wlr_seat.keyboardNotifyClearFocus();
|
||||
self.keyboardNotifyEnter(wlr_surface);
|
||||
@ -508,6 +522,7 @@ pub fn updateCapabilities(self: *Self) void {
|
||||
|
||||
var it = server.input_manager.devices.iterator(.forward);
|
||||
while (it.next()) |device| {
|
||||
log.debug(">>>> '{s}'", .{device.identifier});
|
||||
if (device.seat == self) {
|
||||
switch (device.wlr_device.type) {
|
||||
.keyboard => capabilities.keyboard = true,
|
||||
|
@ -89,6 +89,9 @@ const command_impls = std.ComptimeStringMap(
|
||||
.{ "unmap-switch", @import("command/map.zig").unmapSwitch },
|
||||
.{ "xcursor-theme", @import("command/xcursor_theme.zig").xcursorTheme },
|
||||
.{ "zoom", @import("command/zoom.zig").zoom },
|
||||
.{ "keyboard-group-create", @import("command/keyboard_group.zig").keyboardGroupCreate},
|
||||
.{ "keyboard-group-destroy", @import("command/keyboard_group.zig").keyboardGroupDestroy},
|
||||
.{ "keyboard-group-add-keyboard", @import("command/keyboard_group.zig").keyboardGroupAddIdentifier},
|
||||
},
|
||||
);
|
||||
// zig fmt: on
|
||||
|
90
river/command/keyboard_group.zig
Normal file
90
river/command/keyboard_group.zig
Normal file
@ -0,0 +1,90 @@
|
||||
// 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 server = &@import("../main.zig").server;
|
||||
const util = @import("../util.zig");
|
||||
|
||||
const Error = @import("../command.zig").Error;
|
||||
const Seat = @import("../Seat.zig");
|
||||
const KeyboardGroup = @import("../KeyboardGroup.zig");
|
||||
|
||||
pub fn keyboardGroupCreate(
|
||||
seat: *Seat,
|
||||
args: []const [:0]const u8,
|
||||
out: *?[]const u8,
|
||||
) Error!void {
|
||||
if (args.len < 2) return Error.NotEnoughArguments;
|
||||
if (args.len > 2) return Error.TooManyArguments;
|
||||
|
||||
var it = seat.keyboard_groups.first;
|
||||
while (it) |node| : (it = node.next) {
|
||||
if (mem.eql(u8, node.data.name, args[1])) {
|
||||
const msg = try util.gpa.dupe(u8, "error: failed to create keybaord group: group of same name already exists\n");
|
||||
out.* = msg;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const node = try util.gpa.create(std.TailQueue(KeyboardGroup).Node);
|
||||
errdefer util.gpa.destroy(node);
|
||||
try node.data.init(seat, args[1]);
|
||||
seat.keyboard_groups.append(node);
|
||||
}
|
||||
|
||||
pub fn keyboardGroupDestroy(
|
||||
seat: *Seat,
|
||||
args: []const [:0]const u8,
|
||||
out: *?[]const u8,
|
||||
) Error!void {
|
||||
if (args.len < 2) return Error.NotEnoughArguments;
|
||||
if (args.len > 2) return Error.TooManyArguments;
|
||||
const kg = keyboardGroupFromName(seat, args[1]) orelse {
|
||||
const msg = try util.gpa.dupe(u8, "error: no keyboard group with that name exists\n");
|
||||
out.* = msg;
|
||||
return;
|
||||
};
|
||||
kg.deinit();
|
||||
const node = @fieldParentPtr(std.TailQueue(KeyboardGroup).Node, "data", kg);
|
||||
seat.keyboard_groups.remove(node);
|
||||
util.gpa.destroy(node);
|
||||
}
|
||||
|
||||
pub fn keyboardGroupAddIdentifier(
|
||||
seat: *Seat,
|
||||
args: []const [:0]const u8,
|
||||
out: *?[]const u8,
|
||||
) Error!void {
|
||||
if (args.len < 3) return Error.NotEnoughArguments;
|
||||
if (args.len > 3) return Error.TooManyArguments;
|
||||
|
||||
const kg = keyboardGroupFromName(seat, args[1]) orelse {
|
||||
const msg = try util.gpa.dupe(u8, "error: no keyboard group with that name exists\n");
|
||||
out.* = msg;
|
||||
return;
|
||||
};
|
||||
try kg.addKeyboardIdentifier(args[2]);
|
||||
}
|
||||
|
||||
fn keyboardGroupFromName(seat: *Seat, name: []const u8) ?*KeyboardGroup {
|
||||
var it = seat.keyboard_groups.first;
|
||||
while (it) |node| : (it = node.next) {
|
||||
if (mem.eql(u8, node.data.name, name)) return &node.data;
|
||||
}
|
||||
return null;
|
||||
}
|
Loading…
Reference in New Issue
Block a user