From 989e7aaeda44b02bdb92455dfe7b58dafc28435c Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Mon, 24 Aug 2020 14:52:47 +0200 Subject: [PATCH] config: implement map-pointer command This command takes a mode, modifiers, button/event name, and pointer action as arguments. It stores these in the config data structure. The currently available pointer actions are move-view and resize-view, which replace the previously hard-coded functionality. Closing the hovered view with middle click has temorarily been removed until it is decided if we wish to make this another special pointer action or perhaps allow running any arbitrary command (which would of course include close). --- README.md | 1 + build.zig | 1 + contrib/config.sh | 6 ++ doc/riverctl.1.scd | 17 ++++ river/Config.zig | 33 ++++---- river/Cursor.zig | 42 ++++++---- river/Keyboard.zig | 8 +- river/Mode.zig | 41 ++++++++++ river/PointerMapping.zig | 25 ++++++ river/Seat.zig | 5 +- river/c.zig | 1 + river/command.zig | 1 + river/command/declare_mode.zig | 5 +- river/command/map.zig | 140 ++++++++++++++++++++++++--------- 14 files changed, 240 insertions(+), 86 deletions(-) create mode 100644 river/Mode.zig create mode 100644 river/PointerMapping.zig diff --git a/README.md b/README.md index 0cdb98f..3900eed 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ installed: - wayland-protocols - [wlroots](https://github.com/swaywm/wlroots) 0.11.0 - xkbcommon +- libevdev - pixman - pkg-config - scdoc (optional, but required for man page generation) diff --git a/build.zig b/build.zig index 8a9572b..23aa3e6 100644 --- a/build.zig +++ b/build.zig @@ -112,6 +112,7 @@ fn addServerDeps(exe: *std.build.LibExeObjStep) void { exe.addIncludeDir("."); exe.linkLibC(); + exe.linkSystemLibrary("libevdev"); exe.linkSystemLibrary("pixman-1"); exe.linkSystemLibrary("wayland-server"); exe.linkSystemLibrary("wlroots"); diff --git a/contrib/config.sh b/contrib/config.sh index 6b2cd72..261ed4b 100755 --- a/contrib/config.sh +++ b/contrib/config.sh @@ -37,6 +37,12 @@ riverctl map normal $mod L mod-master-factor +0.05 riverctl map normal $mod+Shift H mod-master-count +1 riverctl map normal $mod+Shift L mod-master-count -1 +# Mod + Left Mouse Button to move views +riverctl map-pointer normal $mod BTN_LEFT move-view + +# Mod + Right Mouse Button to resize views +riverctl map-pointer normal $mod BTN_RIGHT resize-view + for i in $(seq 1 9); do tagmask=$((1 << ($i - 1))) diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd index 3b7da3c..3632827 100644 --- a/doc/riverctl.1.scd +++ b/doc/riverctl.1.scd @@ -136,6 +136,23 @@ that tag 1 through 9 are visible. A mapping without modifiers can be created by passing an empty string as the modifiers argument. +*map-pointer* _mode_ _modifiers_ _button_ _action_ + _mode_ and _modifiers_ are the same as for *map*. + + _button_ is the name of a linux input event code. The most commonly used + values are: + + - BTN_LEFT - left mouse button + - BTN_RIGHT - right mouse button + - BTN_MIDDLE - middle mouse button + + A complete list may be found in _/usr/include/linux/input-event-codes.h_ + + _action_ is one of the following values: + + - move-view + - resize-view + *outer-padding* _pixels_ Set the padding around the edge of the screen to _pixels_. diff --git a/river/Config.zig b/river/Config.zig index a2814f1..900dabc 100644 --- a/river/Config.zig +++ b/river/Config.zig @@ -23,7 +23,7 @@ const c = @import("c.zig"); const util = @import("util.zig"); const Server = @import("Server.zig"); -const Mapping = @import("Mapping.zig"); +const Mode = @import("Mode.zig"); /// Color of background in RGBA (alpha should only affect nested sessions) background_color: [4]f32 = [_]f32{ 0.0, 0.16862745, 0.21176471, 1.0 }, // Solarized base03 @@ -47,7 +47,7 @@ outer_padding: u32 = 8, mode_to_id: std.StringHashMap(usize), /// All user-defined keymap modes, indexed by mode id -modes: std.ArrayList(std.ArrayList(Mapping)), +modes: std.ArrayList(Mode), /// List of app_ids which will be started floating float_filter: std.ArrayList([]const u8), @@ -56,22 +56,20 @@ float_filter: std.ArrayList([]const u8), csd_filter: std.ArrayList([]const u8), pub fn init() !Self { - var mode_to_id = std.StringHashMap(usize).init(util.gpa); - errdefer mode_to_id.deinit(); - const owned_slice = try std.mem.dupe(util.gpa, u8, "normal"); - errdefer util.gpa.free(owned_slice); - try mode_to_id.putNoClobber(owned_slice, 0); - - var modes = std.ArrayList(std.ArrayList(Mapping)).init(util.gpa); - errdefer modes.deinit(); - try modes.append(std.ArrayList(Mapping).init(util.gpa)); - - return Self{ - .mode_to_id = mode_to_id, - .modes = modes, + var self = Self{ + .mode_to_id = std.StringHashMap(usize).init(util.gpa), + .modes = std.ArrayList(Mode).init(util.gpa), .float_filter = std.ArrayList([]const u8).init(util.gpa), .csd_filter = std.ArrayList([]const u8).init(util.gpa), }; + + // Start with a single, empty mode called normal + errdefer self.deinit(); + const owned_slice = try std.mem.dupe(util.gpa, u8, "normal"); + try self.mode_to_id.putNoClobber(owned_slice, 0); + try self.modes.append(Mode.init()); + + return self; } pub fn deinit(self: Self) void { @@ -79,10 +77,7 @@ pub fn deinit(self: Self) void { while (it.next()) |kv| util.gpa.free(kv.key); self.mode_to_id.deinit(); - for (self.modes.items) |mode| { - for (mode.items) |mapping| mapping.deinit(util.gpa); - mode.deinit(); - } + for (self.modes.items) |mode| mode.deinit(); self.modes.deinit(); self.float_filter.deinit(); diff --git a/river/Cursor.zig b/river/Cursor.zig index 1ca75d7..c84c3af 100644 --- a/river/Cursor.zig +++ b/river/Cursor.zig @@ -397,23 +397,11 @@ fn handleButton(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { // perhaps enter move/resize mode. if (View.fromWlrSurface(wlr_surface)) |view| { if (event.state == .WLR_BUTTON_PRESSED and self.pressed_count == 1) { - // If the button is pressed and the pointer modifier is - // active, enter cursor mode or close view and return. - const fullscreen = view.current.fullscreen or view.pending.fullscreen; - if (self.seat.pointer_modifier) { - switch (event.button) { - c.BTN_LEFT => if (!fullscreen) Mode.enter(self, .move, event, view), - c.BTN_MIDDLE => view.close(), - c.BTN_RIGHT => if (!fullscreen) Mode.enter(self, .resize, event, view), - - // TODO Some mice have additional buttons. These - // could also be bound to some useful action. - else => {}, - } - return; - } else { - Mode.enter(self, .down, event, view); - } + // If there is an active mapping for this button which is + // handled we are done here + if (self.handlePointerMapping(event, view)) return; + // Otherwise enter cursor down mode + Mode.enter(self, .down, event, view); } } @@ -426,6 +414,26 @@ fn handleButton(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { } } +/// Handle the mapping for the passed button if any. Returns true if there +/// was a mapping and the button was handled. +fn handlePointerMapping(self: *Self, event: *c.wlr_event_pointer_button, view: *View) bool { + const wlr_keyboard = c.wlr_seat_get_keyboard(self.seat.wlr_seat); + const modifiers = c.wlr_keyboard_get_modifiers(wlr_keyboard); + + const fullscreen = view.current.fullscreen or view.pending.fullscreen; + + const config = self.seat.input_manager.server.config; + return for (config.modes.items[self.seat.mode_id].pointer_mappings.items) |mapping| { + if (event.button == mapping.event_code and modifiers == mapping.modifiers) { + switch (mapping.action) { + .move => if (!fullscreen) Mode.enter(self, .move, event, view), + .resize => if (!fullscreen) Mode.enter(self, .resize, event, view), + } + break true; + } + } else false; +} + fn handleFrame(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { // This event is forwarded by the cursor when a pointer emits an frame // event. Frame events are sent after regular pointer events to group diff --git a/river/Keyboard.zig b/river/Keyboard.zig index cb8dc12..f5c065f 100644 --- a/river/Keyboard.zig +++ b/river/Keyboard.zig @@ -153,13 +153,7 @@ fn handleModifiers(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void c.wlr_seat_set_keyboard(self.seat.wlr_seat, self.wlr_input_device); // Send modifiers to the client. - c.wlr_seat_keyboard_notify_modifiers( - self.seat.wlr_seat, - &self.wlr_keyboard.modifiers, - ); - - const modifiers = c.wlr_keyboard_get_modifiers(self.wlr_keyboard); - self.seat.pointer_modifier = modifiers == c.WLR_MODIFIER_LOGO; + c.wlr_seat_keyboard_notify_modifiers(self.seat.wlr_seat, &self.wlr_keyboard.modifiers); } /// Handle any builtin, harcoded compsitor mappings such as VT switching. diff --git a/river/Mode.zig b/river/Mode.zig new file mode 100644 index 0000000..136e19d --- /dev/null +++ b/river/Mode.zig @@ -0,0 +1,41 @@ +// This file is part of river, a dynamic tiling wayland compositor. +// +// Copyright 2020 Isaac Freund +// +// 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, either version 3 of the License, or +// (at your option) any later version. +// +// 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 . + +const Self = @This(); + +const std = @import("std"); +const util = @import("util.zig"); + +const Mapping = @import("Mapping.zig"); +const PointerMapping = @import("PointerMapping.zig"); + +// TODO: use unmanaged array lists here to save memory +mappings: std.ArrayList(Mapping), +pointer_mappings: std.ArrayList(PointerMapping), + +pub fn init() Self { + return .{ + .mappings = std.ArrayList(Mapping).init(util.gpa), + .pointer_mappings = std.ArrayList(PointerMapping).init(util.gpa), + }; +} + +pub fn deinit(self: Self) void { + for (self.mappings.items) |m| m.deinit(util.gpa); + self.mappings.deinit(); + self.pointer_mappings.deinit(); +} diff --git a/river/PointerMapping.zig b/river/PointerMapping.zig new file mode 100644 index 0000000..161d17a --- /dev/null +++ b/river/PointerMapping.zig @@ -0,0 +1,25 @@ +// This file is part of river, a dynamic tiling wayland compositor. +// +// Copyright 2020 Isaac Freund +// +// 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, either version 3 of the License, or +// (at your option) any later version. +// +// 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 . + +pub const Action = enum { + move, + resize, +}; + +event_code: u32, +modifiers: u32, +action: Action, diff --git a/river/Seat.zig b/river/Seat.zig index 7ab0fb6..6a2c15e 100644 --- a/river/Seat.zig +++ b/river/Seat.zig @@ -65,9 +65,6 @@ focus_stack: ViewStack(*View) = ViewStack(*View){}, /// List of status tracking objects relaying changes to this seat to clients. status_trackers: std.SinglyLinkedList(SeatStatus) = std.SinglyLinkedList(SeatStatus).init(), -/// State of pointer modifier; Used for pointer operations such as move ans resize. -pointer_modifier: bool = false, - listen_request_set_selection: c.wl_listener = undefined, pub fn init(self: *Self, input_manager: *InputManager, name: [*:0]const u8) !void { @@ -258,7 +255,7 @@ pub fn handleViewUnmap(self: *Self, view: *View) void { /// Returns true if the key was handled pub fn handleMapping(self: *Self, keysym: c.xkb_keysym_t, modifiers: u32) bool { const modes = &self.input_manager.server.config.modes; - for (modes.items[self.mode_id].items) |mapping| { + for (modes.items[self.mode_id].mappings.items) |mapping| { if (modifiers == mapping.modifiers and keysym == mapping.keysym) { // Execute the bound command const args = mapping.command_args; diff --git a/river/c.zig b/river/c.zig index 5a09aaa..113df91 100644 --- a/river/c.zig +++ b/river/c.zig @@ -24,6 +24,7 @@ pub usingnamespace @cImport({ @cInclude("unistd.h"); @cInclude("linux/input-event-codes.h"); + @cInclude("libevdev/libevdev.h"); @cInclude("wayland-server-core.h"); //@cInclude("wlr/backend.h"); diff --git a/river/command.zig b/river/command.zig index 8527c9f..cd9489c 100644 --- a/river/command.zig +++ b/river/command.zig @@ -45,6 +45,7 @@ const str_to_impl_fn = [_]struct { .{ .name = "focus-view", .impl = @import("command/focus_view.zig").focusView }, .{ .name = "layout", .impl = @import("command/layout.zig").layout }, .{ .name = "map", .impl = @import("command/map.zig").map }, + .{ .name = "map-pointer", .impl = @import("command/map.zig").mapPointer }, .{ .name = "mod-master-count", .impl = @import("command/mod_master_count.zig").modMasterCount }, .{ .name = "mod-master-factor", .impl = @import("command/mod_master_factor.zig").modMasterFactor }, .{ .name = "outer-padding", .impl = @import("command/config.zig").outerPadding }, diff --git a/river/command/declare_mode.zig b/river/command/declare_mode.zig index 27e5483..9dc2dfc 100644 --- a/river/command/declare_mode.zig +++ b/river/command/declare_mode.zig @@ -19,6 +19,7 @@ const std = @import("std"); const util = @import("../util.zig"); +const Mode = @import("../Mode.zig"); const Error = @import("../command.zig").Error; const Mapping = @import("../Mapping.zig"); const Seat = @import("../Seat.zig"); @@ -45,9 +46,9 @@ pub fn declareMode( return Error.Other; } + try config.modes.ensureCapacity(config.modes.items.len + 1); const owned_name = try std.mem.dupe(util.gpa, u8, new_mode_name); errdefer util.gpa.free(owned_name); try config.mode_to_id.putNoClobber(owned_name, config.modes.items.len); - errdefer _ = config.mode_to_id.remove(owned_name); - try config.modes.append(std.ArrayList(Mapping).init(util.gpa)); + config.modes.appendAssumeCapacity(Mode.init()); } diff --git a/river/command/map.zig b/river/command/map.zig index 9946ce6..d21cb33 100644 --- a/river/command/map.zig +++ b/river/command/map.zig @@ -22,6 +22,7 @@ const util = @import("../util.zig"); const Error = @import("../command.zig").Error; const Mapping = @import("../Mapping.zig"); +const PointerMapping = @import("../PointerMapping.zig"); const Seat = @import("../Seat.zig"); const modifier_names = [_]struct { @@ -49,22 +50,114 @@ pub fn map( args: []const []const u8, out: *?[]const u8, ) Error!void { - if (args.len < 4) return Error.NotEnoughArguments; + if (args.len < 5) return Error.NotEnoughArguments; - // Parse the mode - const config = seat.input_manager.server.config; - const target_mode = args[1]; - const mode_id = config.mode_to_id.getValue(target_mode) orelse { + const mode_id = try modeNameToId(allocator, seat, args[1], out); + const modifiers = try parseModifiers(allocator, args[2], out); + + // Parse the keysym + const keysym_name = try std.cstr.addNullByte(allocator, args[3]); + 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, - "cannot add mapping to non-existant mode '{}p'", - .{target_mode}, + "invalid keysym '{}'", + .{args[3]}, + ); + 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) { + out.* = try std.fmt.allocPrint( + allocator, + "a mapping for modifiers '{}' and keysym '{}' already exists", + .{ args[2], args[3] }, + ); + return Error.Other; + } + } + + try mode_mappings.append(try Mapping.init(util.gpa, keysym, modifiers, args[4..])); +} + +/// Create a new pointer mapping for a given mode +/// +/// Example: +/// map-pointer normal Mod4 BTN_LEFT move-view +pub fn mapPointer( + allocator: *std.mem.Allocator, + seat: *Seat, + args: []const []const u8, + out: *?[]const u8, +) Error!void { + if (args.len < 5) return Error.NotEnoughArguments; + if (args.len > 5) return Error.TooManyArguments; + + const mode_id = try modeNameToId(allocator, seat, args[1], out); + const modifiers = try parseModifiers(allocator, args[2], out); + + const event_code = blk: { + const event_code_name = try std.cstr.addNullByte(allocator, args[3]); + 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 {}", .{args[3]}); + return Error.Other; + } + break :blk @intCast(u32, ret); + }; + + // Check if the mapping already exists + const mode_pointer_mappings = &seat.input_manager.server.config.modes.items[mode_id].pointer_mappings; + for (mode_pointer_mappings.items) |existing| { + if (existing.event_code == event_code and existing.modifiers == modifiers) { + out.* = try std.fmt.allocPrint( + allocator, + "a pointer mapping for modifiers '{}' and button '{}' already exists", + .{ args[2], args[3] }, + ); + return Error.Other; + } + } + + const action = if (std.mem.eql(u8, args[4], "move-view")) + PointerMapping.Action.move + else if (std.mem.eql(u8, args[4], "resize-view")) + PointerMapping.Action.resize + else { + out.* = try std.fmt.allocPrint( + allocator, + "invalid pointer action {}, must be move-view or resize-view", + .{args[4]}, ); return Error.Other; }; - // Parse the modifiers - var it = std.mem.split(args[2], "+"); + try mode_pointer_mappings.append(.{ + .event_code = event_code, + .modifiers = modifiers, + .action = action, + }); +} + +fn modeNameToId(allocator: *std.mem.Allocator, seat: *Seat, mode_name: []const u8, out: *?[]const u8) !usize { + const config = seat.input_manager.server.config; + return config.mode_to_id.getValue(mode_name) orelse { + out.* = try std.fmt.allocPrint( + allocator, + "cannot add mapping to non-existant mode '{}p'", + .{mode_name}, + ); + return Error.Other; + }; +} + +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; while (it.next()) |mod_name| { for (modifier_names) |def| { @@ -81,32 +174,5 @@ pub fn map( return Error.Other; } } - - // Parse the keysym - const keysym_name = try std.cstr.addNullByte(allocator, args[3]); - 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]}, - ); - return Error.Other; - } - - // Check if the mapping already exists - const mode_mappings = &config.modes.items[mode_id]; - for (mode_mappings.items) |existant_mapping| { - if (existant_mapping.modifiers == modifiers and existant_mapping.keysym == keysym) { - out.* = try std.fmt.allocPrint( - allocator, - "a mapping for modifiers '{}' and keysym '{}' already exists", - .{ args[2], args[3] }, - ); - return Error.Other; - } - } - - try mode_mappings.append(try Mapping.init(util.gpa, keysym, modifiers, args[4..])); + return modifiers; }