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).
This commit is contained in:
parent
fd8c5e6410
commit
989e7aaeda
@ -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)
|
||||
|
@ -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");
|
||||
|
@ -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)))
|
||||
|
||||
|
@ -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_.
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
41
river/Mode.zig
Normal file
41
river/Mode.zig
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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();
|
||||
}
|
25
river/PointerMapping.zig
Normal file
25
river/PointerMapping.zig
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
pub const Action = enum {
|
||||
move,
|
||||
resize,
|
||||
};
|
||||
|
||||
event_code: u32,
|
||||
modifiers: u32,
|
||||
action: Action,
|
@ -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;
|
||||
|
@ -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");
|
||||
|
@ -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 },
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user