diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd index f233ad1..8dbb5bb 100644 --- a/doc/riverctl.1.scd +++ b/doc/riverctl.1.scd @@ -123,9 +123,11 @@ that tag 1 through 9 are visible. but let the currently focused window in focus. When set to _strict_ this is not the case. The focus will be updated on every cursor movement. -*map* _mode_ _modifiers_ _key_ _command_ +*map* [-release] _mode_ _modifiers_ _key_ _command_ _mode_ is either “normal” (the default mode) or a mode created with - *declare-mode*. _modifiers_ is a list of one or more of the following + *declare-mode*. + If _-release_ is specified the mapping is executed on key release rather than key press. + _modifiers_ is a list of one or more of the following modifiers separated with a plus sign: - Shift diff --git a/river/Keyboard.zig b/river/Keyboard.zig index f5c065f..6044257 100644 --- a/river/Keyboard.zig +++ b/river/Keyboard.zig @@ -103,27 +103,29 @@ fn handleKey(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { var handled = false; // TODO: These modifiers aren't properly handled, see sway's code const modifiers = c.wlr_keyboard_get_modifiers(wlr_keyboard); - if (event.state == .WLR_KEY_PRESSED) { - var i: usize = 0; - while (i < translated_keysyms_len) : (i += 1) { - if (self.handleBuiltinMapping(translated_keysyms.?[i])) { - handled = true; - break; - } else if (self.seat.handleMapping(translated_keysyms.?[i], modifiers)) { - handled = true; - break; - } + const released = event.state == .WLR_KEY_RELEASED; + + var i: usize = 0; + while (i < translated_keysyms_len) : (i += 1) { + // Handle builtin mapping only when keys are pressed + if (!released and self.handleBuiltinMapping(translated_keysyms.?[i])) { + handled = true; + break; + } else if (self.seat.handleMapping(translated_keysyms.?[i], modifiers, released)) { + handled = true; + break; } - if (!handled) { - i = 0; - while (i < raw_keysyms_len) : (i += 1) { - if (self.handleBuiltinMapping(raw_keysyms.?[i])) { - handled = true; - break; - } else if (self.seat.handleMapping(raw_keysyms.?[i], modifiers)) { - handled = true; - break; - } + } + if (!handled) { + i = 0; + while (i < raw_keysyms_len) : (i += 1) { + // Handle builtin mapping only when keys are pressed + if (!released and self.handleBuiltinMapping(raw_keysyms.?[i])) { + handled = true; + break; + } else if (self.seat.handleMapping(raw_keysyms.?[i], modifiers, released)) { + handled = true; + break; } } } diff --git a/river/Mapping.zig b/river/Mapping.zig index 3d80624..c253693 100644 --- a/river/Mapping.zig +++ b/river/Mapping.zig @@ -25,10 +25,14 @@ keysym: c.xkb_keysym_t, modifiers: u32, command_args: []const []const u8, +/// When set to true the mapping will be executed on key release rather than on press +release: bool, + pub fn init( allocator: *std.mem.Allocator, keysym: c.xkb_keysym_t, modifiers: u32, + release: bool, command_args: []const []const u8, ) !Self { const owned_args = try allocator.alloc([]u8, command_args.len); @@ -40,6 +44,7 @@ pub fn init( return Self{ .keysym = keysym, .modifiers = modifiers, + .release = release, .command_args = owned_args, }; } diff --git a/river/Seat.zig b/river/Seat.zig index 2527466..59f9eab 100644 --- a/river/Seat.zig +++ b/river/Seat.zig @@ -262,10 +262,10 @@ pub fn handleViewUnmap(self: *Self, view: *View) void { /// Handle any user-defined mapping for the passed keysym and modifiers /// Returns true if the key was handled -pub fn handleMapping(self: *Self, keysym: c.xkb_keysym_t, modifiers: u32) bool { +pub fn handleMapping(self: *Self, keysym: c.xkb_keysym_t, modifiers: u32, released: bool) bool { const modes = &self.input_manager.server.config.modes; for (modes.items[self.mode_id].mappings.items) |mapping| { - if (modifiers == mapping.modifiers and keysym == mapping.keysym) { + if (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; diff --git a/river/command/map.zig b/river/command/map.zig index d21cb33..9d5c137 100644 --- a/river/command/map.zig +++ b/river/command/map.zig @@ -50,20 +50,23 @@ pub fn map( args: []const []const u8, out: *?[]const u8, ) Error!void { - if (args.len < 5) return Error.NotEnoughArguments; + const optionals = parseOptionalArgs(args[1..]); + // offset caused by optional arguments + const offset = optionals.i; + if (args.len - offset < 5) return Error.NotEnoughArguments; - const mode_id = try modeNameToId(allocator, seat, args[1], out); - const modifiers = try parseModifiers(allocator, args[2], out); + const mode_id = try modeNameToId(allocator, seat, args[1 + offset], out); + const modifiers = try parseModifiers(allocator, args[2 + offset], out); // Parse the keysym - const keysym_name = try std.cstr.addNullByte(allocator, args[3]); + const keysym_name = try std.cstr.addNullByte(allocator, args[3 + offset]); defer allocator.free(keysym_name); const keysym = c.xkb_keysym_from_name(keysym_name, .XKB_KEYSYM_CASE_INSENSITIVE); if (keysym == c.XKB_KEY_NoSymbol) { out.* = try std.fmt.allocPrint( allocator, "invalid keysym '{}'", - .{args[3]}, + .{args[3 + offset]}, ); return Error.Other; } @@ -71,17 +74,17 @@ pub fn map( // Check if the mapping already exists const mode_mappings = &seat.input_manager.server.config.modes.items[mode_id].mappings; for (mode_mappings.items) |existant_mapping| { - if (existant_mapping.modifiers == modifiers and existant_mapping.keysym == keysym) { + if (existant_mapping.modifiers == modifiers and existant_mapping.keysym == keysym and existant_mapping.release == optionals.release) { out.* = try std.fmt.allocPrint( allocator, "a mapping for modifiers '{}' and keysym '{}' already exists", - .{ args[2], args[3] }, + .{ args[2 + offset], args[3 + offset] }, ); return Error.Other; } } - try mode_mappings.append(try Mapping.init(util.gpa, keysym, modifiers, args[4..])); + try mode_mappings.append(try Mapping.init(util.gpa, keysym, modifiers, optionals.release, args[4 + offset ..])); } /// Create a new pointer mapping for a given mode @@ -176,3 +179,35 @@ fn parseModifiers(allocator: *std.mem.Allocator, modifiers_str: []const u8, out: } return modifiers; } + +const OptionalArgsContainer = struct { + i: usize, + release: bool, +}; + +/// Parses optional args (such as -release) and return the index of the first argument that is +/// not an optional argument +/// Returns an OptionalArgsContainer with the settings set according to the args +/// Errors cant occur because it returns as soon as it gets an unknown argument +fn parseOptionalArgs(args: []const []const u8) OptionalArgsContainer { + // Set to defaults + var parsed = OptionalArgsContainer{ + // i is the number of arguments consumed + .i = 0, + .release = false, + }; + + var i: usize = 0; + for (args) |arg| { + if (std.mem.eql(u8, arg, "-release")) { + parsed.release = true; + i += 1; + } else { + // Break if the arg is not an option + parsed.i = i; + break; + } + } + + return parsed; +}