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 ]
|
if [ "${COMP_CWORD}" -eq 1 ]
|
||||||
then
|
then
|
||||||
OPTS=" \
|
OPTS=" \
|
||||||
|
keyboard-group-create \
|
||||||
|
keyboard-group-destroy \
|
||||||
|
keyboard-group-add-keyboard \
|
||||||
csd-filter-add \
|
csd-filter-add \
|
||||||
exit \
|
exit \
|
||||||
float-filter-add \
|
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-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 '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'
|
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
|
# Subcommands
|
||||||
complete -c riverctl -x -n '__fish_seen_subcommand_from focus-output' -a 'next previous'
|
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-repeat:Set the keyboard repeat rate and repeat delay'
|
||||||
'set-cursor-warp:Set the cursor warp mode.'
|
'set-cursor-warp:Set the cursor warp mode.'
|
||||||
'xcursor-theme:Set the xcursor theme'
|
'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
|
||||||
'input:Configure input devices'
|
'input:Configure input devices'
|
||||||
'list-inputs:List all 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-input-configs*
|
||||||
List all input configurations.
|
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
|
The _input_ command can be used to create a configuration rule for an input
|
||||||
device identified by its _name_.
|
device identified by its _name_.
|
||||||
The _name_ of an input device consists of its type, its numerical vendor id,
|
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) {
|
switch (device.wlr_device.type) {
|
||||||
.keyboard => {
|
.keyboard => {
|
||||||
const keyboard = @fieldParentPtr(Keyboard, "device", device);
|
const keyboard = @fieldParentPtr(Keyboard, "provider", @ptrCast(*Keyboard.Provider, device));
|
||||||
keyboard.deinit();
|
keyboard.deinit();
|
||||||
util.gpa.destroy(keyboard);
|
util.gpa.destroy(keyboard);
|
||||||
},
|
},
|
||||||
|
@ -25,13 +25,19 @@ const xkb = @import("xkbcommon");
|
|||||||
const server = &@import("main.zig").server;
|
const server = &@import("main.zig").server;
|
||||||
const util = @import("util.zig");
|
const util = @import("util.zig");
|
||||||
|
|
||||||
const KeycodeSet = @import("KeycodeSet.zig");
|
|
||||||
const Seat = @import("Seat.zig");
|
const Seat = @import("Seat.zig");
|
||||||
const InputDevice = @import("InputDevice.zig");
|
const InputDevice = @import("InputDevice.zig");
|
||||||
|
const KeyboardGroup = @import("KeyboardGroup.zig");
|
||||||
|
const KeycodeSet = @import("KeycodeSet.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.keyboard);
|
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
|
/// Pressed keys for which a mapping was triggered on press
|
||||||
eaten_keycodes: KeycodeSet = .{},
|
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),
|
modifiers: wl.Listener(*wlr.Keyboard) = wl.Listener(*wlr.Keyboard).init(handleModifiers),
|
||||||
|
|
||||||
pub fn init(self: *Self, seat: *Seat, wlr_device: *wlr.InputDevice) !void {
|
pub fn init(self: *Self, seat: *Seat, wlr_device: *wlr.InputDevice) !void {
|
||||||
self.* = .{
|
const wlr_keyboard = wlr_device.device.keyboard;
|
||||||
.device = undefined,
|
wlr_keyboard.data = @ptrToInt(self);
|
||||||
};
|
|
||||||
try self.device.init(seat, wlr_device);
|
|
||||||
errdefer self.device.deinit();
|
|
||||||
|
|
||||||
const context = xkb.Context.new(.no_flags) orelse return error.XkbContextFailed;
|
const context = xkb.Context.new(.no_flags) orelse return error.XkbContextFailed;
|
||||||
defer context.unref();
|
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;
|
const keymap = xkb.Keymap.newFromNames(context, null, .no_flags) orelse return error.XkbKeymapFailed;
|
||||||
defer keymap.unref();
|
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;
|
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.key.add(&self.key);
|
||||||
wlr_keyboard.events.modifiers.add(&self.modifiers);
|
wlr_keyboard.events.modifiers.add(&self.modifiers);
|
||||||
|
|
||||||
|
wlr_keyboard.setRepeatInfo(server.config.repeat_rate, server.config.repeat_delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
self.key.link.remove();
|
self.key.link.remove();
|
||||||
self.modifiers.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;
|
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 {
|
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 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
|
// Translate libinput keycode -> xkbcommon
|
||||||
const keycode = event.keycode + 8;
|
const keycode = event.keycode + 8;
|
||||||
@ -98,7 +137,7 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
|
|||||||
!released and
|
!released and
|
||||||
!isModifier(sym))
|
!isModifier(sym))
|
||||||
{
|
{
|
||||||
self.device.seat.cursor.hide();
|
seat.cursor.hide();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,11 +148,11 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle user-defined mappings
|
// 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 (mapped) {
|
||||||
if (!released) self.eaten_keycodes.add(event.keycode);
|
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);
|
assert(handled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,8 +160,8 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
|
|||||||
|
|
||||||
if (!eaten) {
|
if (!eaten) {
|
||||||
// If key was not handled, we pass it along to the client.
|
// If key was not handled, we pass it along to the client.
|
||||||
const wlr_seat = self.device.seat.wlr_seat;
|
const wlr_seat = seat.wlr_seat;
|
||||||
wlr_seat.setKeyboard(self.device.wlr_device);
|
wlr_seat.setKeyboard(wlr_device);
|
||||||
wlr_seat.keyboardNotifyKey(event.time_msec, event.keycode, event.state);
|
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;
|
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.
|
/// Handle any builtin, harcoded compsitor mappings such as VT switching.
|
||||||
/// Returns true if the keysym was handled.
|
/// Returns true if the keysym was handled.
|
||||||
fn handleBuiltinMapping(keysym: xkb.Keysym) bool {
|
fn handleBuiltinMapping(keysym: xkb.Keysym) bool {
|
||||||
@ -158,3 +189,9 @@ fn handleBuiltinMapping(keysym: xkb.Keysym) bool {
|
|||||||
else => return false,
|
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 View = @import("View.zig");
|
||||||
const ViewStack = @import("view_stack.zig").ViewStack;
|
const ViewStack = @import("view_stack.zig").ViewStack;
|
||||||
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
|
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
|
||||||
|
const KeyboardGroup = @import("KeyboardGroup.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.seat);
|
const log = std.log.scoped(.seat);
|
||||||
const PointerConstraint = @import("PointerConstraint.zig");
|
const PointerConstraint = @import("PointerConstraint.zig");
|
||||||
@ -69,6 +70,8 @@ mapping_repeat_timer: *wl.EventSource,
|
|||||||
/// Currently repeating mapping, if any
|
/// Currently repeating mapping, if any
|
||||||
repeating_mapping: ?*const Mapping = null,
|
repeating_mapping: ?*const Mapping = null,
|
||||||
|
|
||||||
|
keyboard_groups: std.TailQueue(KeyboardGroup) = .{},
|
||||||
|
|
||||||
/// Currently focused output, may be the noop output if no real output
|
/// Currently focused output, may be the noop output if no real output
|
||||||
/// is currently available for focus.
|
/// is currently available for focus.
|
||||||
focused_output: *Output,
|
focused_output: *Output,
|
||||||
@ -475,7 +478,18 @@ fn tryAddDevice(self: *Self, wlr_device: *wlr.InputDevice) !void {
|
|||||||
|
|
||||||
try keyboard.init(self, wlr_device);
|
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| {
|
if (self.wlr_seat.keyboard_state.focused_surface) |wlr_surface| {
|
||||||
self.wlr_seat.keyboardNotifyClearFocus();
|
self.wlr_seat.keyboardNotifyClearFocus();
|
||||||
self.keyboardNotifyEnter(wlr_surface);
|
self.keyboardNotifyEnter(wlr_surface);
|
||||||
@ -508,6 +522,7 @@ pub fn updateCapabilities(self: *Self) void {
|
|||||||
|
|
||||||
var it = server.input_manager.devices.iterator(.forward);
|
var it = server.input_manager.devices.iterator(.forward);
|
||||||
while (it.next()) |device| {
|
while (it.next()) |device| {
|
||||||
|
log.debug(">>>> '{s}'", .{device.identifier});
|
||||||
if (device.seat == self) {
|
if (device.seat == self) {
|
||||||
switch (device.wlr_device.type) {
|
switch (device.wlr_device.type) {
|
||||||
.keyboard => capabilities.keyboard = true,
|
.keyboard => capabilities.keyboard = true,
|
||||||
|
@ -89,6 +89,9 @@ 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-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
|
// 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