command: support repeating keyboard mappings

Repeating mappings are created using the -repeat option to the map
command:

    % riverctl map normal $mod+Mod1 K -repeat move up 10

- repeating is only supported for key press (not -release) mappings
- unlike -release, -repeat does not create distinct mappings: mapping a
  key with -repeat will replace an existing bare mapping and vice-versa

Resolves #306
This commit is contained in:
Keith Hubbard
2021-08-15 08:49:11 -04:00
committed by GitHub
parent 6e51a8fcdd
commit 2bdf9e20a5
9 changed files with 92 additions and 22 deletions

View File

@ -31,6 +31,7 @@ const DragIcon = @import("DragIcon.zig");
const Cursor = @import("Cursor.zig");
const InputManager = @import("InputManager.zig");
const Keyboard = @import("Keyboard.zig");
const Mapping = @import("Mapping.zig");
const LayerSurface = @import("LayerSurface.zig");
const Output = @import("Output.zig");
const SeatStatus = @import("SeatStatus.zig");
@ -60,6 +61,12 @@ mode_id: usize = 0,
/// ID of previous keymap mode, used when returning from "locked" mode
prev_mode_id: usize = 0,
/// Timer for repeating keyboard mappings
mapping_repeat_timer: *wl.EventSource,
/// Currently repeating mapping, if any
repeating_mapping: ?*const Mapping = null,
/// Currently focused output, may be the noop output if no real output
/// is currently available for focus.
focused_output: *Output,
@ -83,10 +90,15 @@ request_set_primary_selection: wl.Listener(*wlr.Seat.event.RequestSetPrimarySele
wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection).init(handleRequestSetPrimarySelection),
pub fn init(self: *Self, name: [*:0]const u8) !void {
const event_loop = server.wl_server.getEventLoop();
const mapping_repeat_timer = try event_loop.addTimer(*Self, handleMappingRepeatTimeout, self);
errdefer mapping_repeat_timer.remove();
self.* = .{
// This will be automatically destroyed when the display is destroyed
.wlr_seat = try wlr.Seat.create(server.wl_server, name),
.focused_output = &server.root.noop_output,
.mapping_repeat_timer = mapping_repeat_timer,
};
self.wlr_seat.data = @ptrToInt(self);
@ -100,6 +112,7 @@ pub fn init(self: *Self, name: [*:0]const u8) !void {
pub fn deinit(self: *Self) void {
self.cursor.deinit();
self.mapping_repeat_timer.remove();
while (self.keyboards.pop()) |node| {
node.data.deinit();
@ -318,32 +331,61 @@ pub fn handleMapping(
released: bool,
) bool {
const modes = &server.config.modes;
for (modes.items[self.mode_id].mappings.items) |mapping| {
for (modes.items[self.mode_id].mappings.items) |*mapping| {
if (std.meta.eql(modifiers, mapping.modifiers) and keysym == mapping.keysym and released == mapping.release) {
// Execute the bound command
const args = mapping.command_args;
var out: ?[]const u8 = null;
defer if (out) |s| util.gpa.free(s);
command.run(util.gpa, self, args, &out) catch |err| {
const failure_message = switch (err) {
command.Error.Other => out.?,
else => command.errToMsg(err),
};
std.log.scoped(.command).err("{s}: {s}", .{ args[0], failure_message });
return true;
};
if (out) |s| {
const stdout = std.io.getStdOut().writer();
stdout.print("{s}", .{s}) catch |err| {
std.log.scoped(.command).err("{s}: write to stdout failed {}", .{ args[0], err });
if (mapping.repeat) {
self.repeating_mapping = mapping;
self.mapping_repeat_timer.timerUpdate(server.config.repeat_delay) catch {
log.err("failed to update mapping repeat timer", .{});
};
}
self.runMappedCommand(mapping);
return true;
}
}
return false;
}
fn runMappedCommand(self: *Self, mapping: *const Mapping) void {
var out: ?[]const u8 = null;
defer if (out) |s| util.gpa.free(s);
const args = mapping.command_args;
command.run(util.gpa, self, args, &out) catch |err| {
const failure_message = switch (err) {
command.Error.Other => out.?,
else => command.errToMsg(err),
};
std.log.scoped(.command).err("{s}: {s}", .{ args[0], failure_message });
return;
};
if (out) |s| {
const stdout = std.io.getStdOut().writer();
stdout.print("{s}", .{s}) catch |err| {
std.log.scoped(.command).err("{s}: write to stdout failed {}", .{ args[0], err });
};
}
}
pub fn clearRepeatingMapping(self: *Self) void {
self.mapping_repeat_timer.timerUpdate(0) catch {
log.err("failed to clear mapping repeat timer", .{});
};
self.repeating_mapping = null;
}
/// Repeat key mapping
fn handleMappingRepeatTimeout(self: *Self) callconv(.C) c_int {
if (self.repeating_mapping) |mapping| {
const rate = server.config.repeat_rate;
const ms_delay = if (rate > 0) 1000 / rate else 0;
self.mapping_repeat_timer.timerUpdate(ms_delay) catch {
log.err("failed to update mapping repeat timer", .{});
};
self.runMappedCommand(mapping);
}
return 0;
}
/// Add a newly created input device to the seat and update the reported
/// capabilities.
pub fn addDevice(self: *Self, device: *wlr.InputDevice) void {