river: add keyboard groups
This commit is contained in:
		| @ -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); | ||||
|  | ||||
| device: InputDevice, | ||||
| 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; | ||||
| } | ||||
		Reference in New Issue
	
	Block a user