- list all files as copyright "The River Developers" - add an AUTHORS file to acknowledge contributors
300 lines
10 KiB
Zig
300 lines
10 KiB
Zig
// This file is part of river, a dynamic tiling wayland compositor.
|
|
//
|
|
// Copyright 2020 The River Developers
|
|
//
|
|
// 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 std = @import("std");
|
|
|
|
const c = @import("../c.zig");
|
|
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 {
|
|
name: []const u8,
|
|
modifier: u32,
|
|
}{
|
|
.{ .name = "None", .modifier = 0 },
|
|
.{ .name = "Shift", .modifier = c.WLR_MODIFIER_SHIFT },
|
|
.{ .name = "Lock", .modifier = c.WLR_MODIFIER_CAPS },
|
|
.{ .name = "Control", .modifier = c.WLR_MODIFIER_CTRL },
|
|
.{ .name = "Mod1", .modifier = c.WLR_MODIFIER_ALT },
|
|
.{ .name = "Mod2", .modifier = c.WLR_MODIFIER_MOD2 },
|
|
.{ .name = "Mod3", .modifier = c.WLR_MODIFIER_MOD3 },
|
|
.{ .name = "Mod4", .modifier = c.WLR_MODIFIER_LOGO },
|
|
.{ .name = "Mod5", .modifier = c.WLR_MODIFIER_MOD5 },
|
|
};
|
|
|
|
/// Create a new mapping for a given mode
|
|
///
|
|
/// Example:
|
|
/// map normal Mod4+Shift Return spawn foot
|
|
pub fn map(
|
|
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 < 5) 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;
|
|
|
|
if (mappingExists(mode_mappings, modifiers, keysym, 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 ..]));
|
|
}
|
|
|
|
/// 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 = try parseEventCode(allocator, args[3], out);
|
|
|
|
const mode_pointer_mappings = &seat.input_manager.server.config.modes.items[mode_id].pointer_mappings;
|
|
if (pointerMappingExists(mode_pointer_mappings, modifiers, event_code)) |_| {
|
|
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;
|
|
};
|
|
|
|
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.get(mode_name) orelse {
|
|
out.* = try std.fmt.allocPrint(
|
|
allocator,
|
|
"cannot add/remove mapping to/from 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;
|
|
}
|
|
|
|
/// Returns the index of the PointerMapping with matching modifiers and event code, if any.
|
|
fn pointerMappingExists(pointer_mappings: *std.ArrayList(PointerMapping), modifiers: u32, event_code: u32) ?usize {
|
|
for (pointer_mappings.items) |mapping, i| {
|
|
if (mapping.modifiers == modifiers and mapping.event_code == event_code) {
|
|
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;
|
|
while (it.next()) |mod_name| {
|
|
for (modifier_names) |def| {
|
|
if (std.mem.eql(u8, def.name, mod_name)) {
|
|
modifiers |= def.modifier;
|
|
break;
|
|
}
|
|
} else {
|
|
out.* = try std.fmt.allocPrint(
|
|
allocator,
|
|
"invalid modifier '{}'",
|
|
.{mod_name},
|
|
);
|
|
return Error.Other;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
/// 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();
|
|
}
|
|
|
|
/// Remove a pointer mapping for a given mode
|
|
///
|
|
/// Example:
|
|
/// unmap-pointer normal Mod4 BTN_LEFT
|
|
pub fn unmapPointer(
|
|
allocator: *std.mem.Allocator,
|
|
seat: *Seat,
|
|
args: []const []const u8,
|
|
out: *?[]const u8,
|
|
) Error!void {
|
|
if (args.len < 4) return Error.NotEnoughArguments;
|
|
if (args.len > 4) return Error.TooManyArguments;
|
|
|
|
const mode_id = try modeNameToId(allocator, seat, args[1], out);
|
|
const modifiers = try parseModifiers(allocator, args[2], out);
|
|
const event_code = try parseEventCode(allocator, args[3], out);
|
|
|
|
const mode_pointer_mappings = &seat.input_manager.server.config.modes.items[mode_id].pointer_mappings;
|
|
const mapping_idx = pointerMappingExists(mode_pointer_mappings, modifiers, event_code) orelse {
|
|
out.* = try std.fmt.allocPrint(
|
|
allocator,
|
|
"there is no mapping for modifiers '{}' and button '{}'",
|
|
.{ args[2], args[3] },
|
|
);
|
|
return Error.Other;
|
|
};
|
|
|
|
_ = mode_pointer_mappings.swapRemove(mapping_idx);
|
|
}
|