diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd index d5e4aec..044af09 100644 --- a/doc/riverctl.1.scd +++ b/doc/riverctl.1.scd @@ -196,6 +196,10 @@ that tag 1 through 9 are visible. *outer-padding* _pixels_ Set the padding around the edge of the screen to _pixels_. +*unmap* [-release] _mode_ _modifiers_ _key_ + Removes the mapping defined by the arguments *-release*, *modifiers* and *key* from *mode*. + See *map* for an explanation of the arguments. + *view-padding* _pixels_ Set the padding around the edge of each view to _pixels_. diff --git a/river/command.zig b/river/command.zig index 257bd08..998a9e0 100644 --- a/river/command.zig +++ b/river/command.zig @@ -75,6 +75,7 @@ const str_to_impl_fn = [_]struct { .{ .name = "toggle-focused-tags", .impl = @import("command/tags.zig").toggleFocusedTags }, .{ .name = "toggle-fullscreen", .impl = @import("command/toggle_fullscreen.zig").toggleFullscreen }, .{ .name = "toggle-view-tags", .impl = @import("command/tags.zig").toggleViewTags }, + .{ .name = "unmap", .impl = @import("command/map.zig").unmap }, .{ .name = "view-padding", .impl = @import("command/config.zig").viewPadding }, .{ .name = "xcursor-theme", .impl = @import("command/xcursor_theme.zig").xcursorTheme }, .{ .name = "zoom", .impl = @import("command/zoom.zig").zoom }, diff --git a/river/command/map.zig b/river/command/map.zig index 46b6a8c..f97162e 100644 --- a/river/command/map.zig +++ b/river/command/map.zig @@ -1,6 +1,7 @@ // This file is part of river, a dynamic tiling wayland compositor. // // Copyright 2020 Isaac Freund +// Copyright 2020 Marten Ringwelski // // 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 @@ -57,33 +58,19 @@ pub fn map( const mode_id = try modeNameToId(allocator, seat, args[1 + offset], out); const modifiers = try parseModifiers(allocator, args[2 + offset], out); + const keysym = try parseKeysym(allocator, args[3 + offset], out); - // Parse the keysym - 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) { + const mode_mappings = &seat.input_manager.server.config.modes.items[mode_id].mappings; + + if (mappingExists(mode_mappings, modifiers, keysym, optionals.release)) |_| { out.* = try std.fmt.allocPrint( allocator, - "invalid keysym '{}'", - .{args[3 + offset]}, + "a mapping for modifiers '{}' and keysym '{}' already exists", + .{ args[2 + offset], args[3 + offset] }, ); return Error.Other; } - // 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 and existant_mapping.release == optionals.release) { - out.* = try std.fmt.allocPrint( - allocator, - "a mapping for modifiers '{}' and keysym '{}' already exists", - .{ args[2 + offset], args[3 + offset] }, - ); - return Error.Other; - } - } - try mode_mappings.append(try Mapping.init(keysym, modifiers, optionals.release, args[4 + offset ..])); } @@ -152,13 +139,52 @@ fn modeNameToId(allocator: *std.mem.Allocator, seat: *Seat, mode_name: []const u return config.mode_to_id.get(mode_name) orelse { out.* = try std.fmt.allocPrint( allocator, - "cannot add mapping to non-existant mode '{}p'", + "cannot add mapping to non-existant mode '{}'", .{mode_name}, ); return Error.Other; }; } +/// Returns the index of the Mapping with matching modifiers, keysym and release, if any. +fn mappingExists(mappings: *std.ArrayList(Mapping), modifiers: u32, keysym: u32, release: bool) ?usize { + for (mappings.items) |mapping, i| { + if (mapping.modifiers == modifiers and mapping.keysym == keysym and mapping.release == release) { + return i; + } + } + + return null; +} + +fn parseEventCode(allocator: *std.mem.Allocator, event_code_str: []const u8, out: *?[]const u8) !u32 { + const event_code_name = try std.cstr.addNullByte(allocator, event_code_str); + defer allocator.free(event_code_name); + const ret = c.libevdev_event_code_from_name(c.EV_KEY, event_code_name); + if (ret < 1) { + out.* = try std.fmt.allocPrint(allocator, "unknown button {}", .{event_code_str}); + return Error.Other; + } + + return @intCast(u32, ret); +} + +fn parseKeysym(allocator: *std.mem.Allocator, keysym_str: []const u8, out: *?[]const u8) !u32 { + const keysym_name = try std.cstr.addNullByte(allocator, keysym_str); + 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 '{}'", + .{keysym_str}, + ); + return Error.Other; + } + + return keysym; +} + fn parseModifiers(allocator: *std.mem.Allocator, modifiers_str: []const u8, out: *?[]const u8) !u32 { var it = std.mem.split(modifiers_str, "+"); var modifiers: u32 = 0; @@ -211,3 +237,36 @@ fn parseOptionalArgs(args: []const []const u8) OptionalArgsContainer { return parsed; } + +/// Remove a mapping from a given mode +/// +/// Example: +/// unmap normal Mod4+Shift Return +pub fn unmap( + allocator: *std.mem.Allocator, + seat: *Seat, + args: []const []const u8, + out: *?[]const u8, +) Error!void { + const optionals = parseOptionalArgs(args[1..]); + // offset caused by optional arguments + const offset = optionals.i; + if (args.len - offset < 4) return Error.NotEnoughArguments; + + const mode_id = try modeNameToId(allocator, seat, args[1 + offset], out); + const modifiers = try parseModifiers(allocator, args[2 + offset], out); + const keysym = try parseKeysym(allocator, args[3 + offset], out); + + const mode_mappings = &seat.input_manager.server.config.modes.items[mode_id].mappings; + const mapping_idx = mappingExists(mode_mappings, modifiers, keysym, optionals.release) orelse { + out.* = try std.fmt.allocPrint( + allocator, + "there is no mapping for modifiers '{}' and keysym '{}'", + .{ args[2 + offset], args[3 + offset] }, + ); + return Error.Other; + }; + + var mapping = mode_mappings.swapRemove(mapping_idx); + mapping.deinit(); +}