aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--completions/bash/riverctl2
-rw-r--r--completions/fish/riverctl.fish2
-rw-r--r--completions/zsh/_riverctl2
m---------deps/zig-wlroots0
-rw-r--r--doc/riverctl.1.scd21
-rw-r--r--river/Mode.zig3
-rw-r--r--river/Seat.zig37
-rw-r--r--river/Switch.zig108
-rw-r--r--river/SwitchMapping.zig47
-rw-r--r--river/command.zig2
-rw-r--r--river/command/map.zig124
11 files changed, 344 insertions, 4 deletions
diff --git a/completions/bash/riverctl b/completions/bash/riverctl
index bfee3f1..692dcaa 100644
--- a/completions/bash/riverctl
+++ b/completions/bash/riverctl
@@ -34,8 +34,10 @@ function __riverctl_completion ()
enter-mode \
map \
map-pointer \
+ map-switch \
unmap \
unmap-pointer \
+ unmap-switch \
attach-mode \
background-color \
border-color-focused \
diff --git a/completions/fish/riverctl.fish b/completions/fish/riverctl.fish
index 7b2ee76..36cf750 100644
--- a/completions/fish/riverctl.fish
+++ b/completions/fish/riverctl.fish
@@ -45,8 +45,10 @@ complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'declare-mode'
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'enter-mode' -d 'Switch to given mode if it exists'
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'map' -d 'Run command when key is pressed while modifiers are held down and in the specified mode'
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'map-pointer' -d 'Move or resize views when button and modifers are held down while in the specified mode'
+complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'map-switch ' -d 'Run command when river receives a switch event in the specified mode'
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'unmap' -d 'Remove the mapping defined by the arguments'
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'unmap-pointer' -d 'Remove the pointer mapping defined by the arguments'
+complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'unmap-switch' -d 'Remove the switch mapping defined by the arguments'
# Configuration
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'attach-mode' -d 'Configure where new views should attach to the view stack'
complete -c riverctl -x -n '__fish_riverctl_complete_arg 1' -a 'background-color' -d 'Set the background color'
diff --git a/completions/zsh/_riverctl b/completions/zsh/_riverctl
index 2aa663e..745484d 100644
--- a/completions/zsh/_riverctl
+++ b/completions/zsh/_riverctl
@@ -39,8 +39,10 @@ _riverctl_subcommands()
'enter-mode:Switch to given mode if it exists'
'map:Run command when key is pressed while modifiers are held down and in the specified mode'
'map-pointer:Move or resize views when button and modifiers are held down while in the specified mode'
+ 'map-switch:Run command when river receives a switch event in the specified mode'
'unmap:Remove the mapping defined by the arguments'
'unmap-pointer:Remove the pointer mapping defined by the arguments'
+ 'unmap-switch:Remove the switch mapping defined by the arguments'
# Configuration
'attach-mode:Configure where new views should attach to the view stack'
'background-color:Set the background color'
diff --git a/deps/zig-wlroots b/deps/zig-wlroots
-Subproject 49a5f81a71f7b14a3b0e52a5d5d8aa1a9e893bd
+Subproject 42d08b0b1e50f7f0d142275f89c7e899ca8c78d
diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd
index d4ba768..fec47dd 100644
--- a/doc/riverctl.1.scd
+++ b/doc/riverctl.1.scd
@@ -218,6 +218,20 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_
- move-view
- resize-view
+*map-switch* _mode_ *lid*|*tablet* _state_ _command_
+ Run _command_ when river receives a certain switch event.
+
+ - _mode_: name of the mode for which to create the mapping
+ - _lid_|_tablet_: 'lid switch' and 'tablet mode switch' are supported
+ - _state_:
+ - possible states for _lid_:
+ - close
+ - open
+ - possible states for _tablet_:
+ - on
+ - off
+ - _command_: any command that may be run with riverctl
+
*unmap* [_-release_] _mode_ _modifiers_ _key_
Remove the mapping defined by the arguments:
@@ -235,6 +249,13 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_
by a plus sign (+).
- _button_: the name of a linux input event code as described above
+*unmap-switch* _mode_ *lid*|*tablet* _state_
+ Remove the switch mapping defined by the arguments:
+
+ - _mode_: name of the mode for which to remove the mapping
+ - _lid_|_tablet_: the switch for which to remove the mapping
+ - _state_: a state as listed above
+
## CONFIGURATION
*attach-mode* *top*|*bottom*
diff --git a/river/Mode.zig b/river/Mode.zig
index cb4a4a6..5793c50 100644
--- a/river/Mode.zig
+++ b/river/Mode.zig
@@ -21,12 +21,15 @@ const util = @import("util.zig");
const Mapping = @import("Mapping.zig");
const PointerMapping = @import("PointerMapping.zig");
+const SwitchMapping = @import("SwitchMapping.zig");
mappings: std.ArrayListUnmanaged(Mapping) = .{},
pointer_mappings: std.ArrayListUnmanaged(PointerMapping) = .{},
+switch_mappings: std.ArrayListUnmanaged(SwitchMapping) = .{},
pub fn deinit(self: *Self) void {
for (self.mappings.items) |m| m.deinit();
self.mappings.deinit(util.gpa);
self.pointer_mappings.deinit(util.gpa);
+ self.switch_mappings.deinit(util.gpa);
}
diff --git a/river/Seat.zig b/river/Seat.zig
index 76d4d84..8929813 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -31,6 +31,7 @@ const DragIcon = @import("DragIcon.zig");
const Cursor = @import("Cursor.zig");
const InputManager = @import("InputManager.zig");
const Keyboard = @import("Keyboard.zig");
+const Switch = @import("Switch.zig");
const Mapping = @import("Mapping.zig");
const LayerSurface = @import("LayerSurface.zig");
const Output = @import("Output.zig");
@@ -55,6 +56,9 @@ cursor: Cursor = undefined,
/// Mulitple keyboards are handled separately
keyboards: std.TailQueue(Keyboard) = .{},
+/// There are two kind of switches: lid switches and tablet mode switches
+switches: std.TailQueue(Switch) = .{},
+
/// ID of the current keymap mode
mode_id: usize = 0,
@@ -123,6 +127,11 @@ pub fn deinit(self: *Self) void {
util.gpa.destroy(node);
}
+ while (self.switches.pop()) |node| {
+ node.data.deinit();
+ util.gpa.destroy(node);
+ }
+
while (self.focus_stack.first) |node| {
self.focus_stack.remove(node);
util.gpa.destroy(node);
@@ -350,17 +359,30 @@ pub fn handleMapping(
log.err("failed to update mapping repeat timer", .{});
};
}
- self.runMappedCommand(mapping);
+ self.runCommand(mapping.command_args);
return true;
}
}
return false;
}
-fn runMappedCommand(self: *Self, mapping: *const Mapping) void {
+/// Handle any user-defined mapping for switches
+pub fn handleSwitchMapping(
+ self: *Self,
+ switch_type: Switch.Type,
+ switch_state: Switch.State,
+) void {
+ const modes = &server.config.modes;
+ for (modes.items[self.mode_id].switch_mappings.items) |mapping| {
+ if (std.meta.eql(mapping.switch_type, switch_type) and std.meta.eql(mapping.switch_state, switch_state)) {
+ self.runCommand(mapping.command_args);
+ }
+ }
+}
+
+fn runCommand(self: *Self, args: []const [:0]const u8) void {
var out: ?[]const u8 = null;
defer if (out) |s| util.gpa.free(s);
- const args = mapping.command_args;
command.run(self, args, &out) catch |err| {
const failure_message = switch (err) {
command.Error.Other => out.?,
@@ -392,7 +414,7 @@ fn handleMappingRepeatTimeout(self: *Self) callconv(.C) c_int {
self.mapping_repeat_timer.timerUpdate(ms_delay) catch {
log.err("failed to update mapping repeat timer", .{});
};
- self.runMappedCommand(mapping);
+ self.runCommand(mapping.command_args);
}
return 0;
}
@@ -403,6 +425,7 @@ pub fn addDevice(self: *Self, device: *wlr.InputDevice) void {
switch (device.type) {
.keyboard => self.addKeyboard(device) catch return,
.pointer => self.addPointer(device),
+ .switch_device => self.addSwitch(device) catch return,
else => return,
}
@@ -438,6 +461,12 @@ fn addPointer(self: Self, device: *wlr.InputDevice) void {
self.cursor.wlr_cursor.attachInputDevice(device);
}
+fn addSwitch(self: *Self, device: *wlr.InputDevice) !void {
+ const node = try util.gpa.create(std.TailQueue(Switch).Node);
+ node.data.init(self, device);
+ self.switches.append(node);
+}
+
fn handleRequestSetSelection(
listener: *wl.Listener(*wlr.Seat.event.RequestSetSelection),
event: *wlr.Seat.event.RequestSetSelection,
diff --git a/river/Switch.zig b/river/Switch.zig
new file mode 100644
index 0000000..23667a3
--- /dev/null
+++ b/river/Switch.zig
@@ -0,0 +1,108 @@
+// This file is part of river, a dynamic tiling wayland compositor.
+//
+// Copyright 2022 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, version 3.
+//
+// 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 wlr = @import("wlroots");
+const wl = @import("wayland").server.wl;
+
+const server = &@import("main.zig").server;
+const util = @import("util.zig");
+
+const Seat = @import("Seat.zig");
+
+const log = std.log.scoped(.switch_device);
+
+pub const Type = enum {
+ lid,
+ tablet,
+};
+
+pub const State = union(Type) {
+ lid: LidState,
+ tablet: TabletState,
+};
+
+pub const LidState = enum {
+ open,
+ close,
+};
+
+pub const TabletState = enum {
+ off,
+ on,
+};
+
+seat: *Seat,
+input_device: *wlr.InputDevice,
+
+switch_device: wl.Listener(*wlr.Switch.event.Toggle) = wl.Listener(*wlr.Switch.event.Toggle).init(handleToggle),
+destroy: wl.Listener(*wlr.Switch) = wl.Listener(*wlr.Switch).init(handleDestroy),
+
+pub fn init(self: *Self, seat: *Seat, input_device: *wlr.InputDevice) void {
+ self.* = .{
+ .seat = seat,
+ .input_device = input_device,
+ };
+
+ const wlr_switch = self.input_device.device.switch_device;
+
+ wlr_switch.events.toggle.add(&self.switch_device);
+}
+
+pub fn deinit(self: *Self) void {
+ self.destroy.link.remove();
+}
+
+fn handleToggle(listener: *wl.Listener(*wlr.Switch.event.Toggle), event: *wlr.Switch.event.Toggle) void {
+ // This event is raised when the lid witch or the tablet mode switch is toggled.
+ const self = @fieldParentPtr(Self, "switch_device", listener);
+
+ self.seat.handleActivity();
+
+ var switch_type: Type = undefined;
+ var switch_state: State = undefined;
+ switch (event.switch_type) {
+ .lid => {
+ switch_type = .lid;
+ switch_state = switch (event.switch_state) {
+ .off => .{ .lid = .open },
+ .on => .{ .lid = .close },
+ .toggle => unreachable,
+ };
+ },
+ .tablet_mode => {
+ switch_type = .tablet;
+ switch_state = switch (event.switch_state) {
+ .off => .{ .tablet = .off },
+ .on => .{ .tablet = .on },
+ .toggle => unreachable,
+ };
+ },
+ }
+
+ self.seat.handleSwitchMapping(switch_type, switch_state);
+}
+
+fn handleDestroy(listener: *wl.Listener(*wlr.Switch), _: *wlr.Switch) void {
+ const self = @fieldParentPtr(Self, "destroy", listener);
+ const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
+
+ self.seat.switches.remove(node);
+ self.deinit();
+ util.gpa.destroy(node);
+}
diff --git a/river/SwitchMapping.zig b/river/SwitchMapping.zig
new file mode 100644
index 0000000..9346780
--- /dev/null
+++ b/river/SwitchMapping.zig
@@ -0,0 +1,47 @@
+// This file is part of river, a dynamic tiling wayland compositor.
+//
+// Copyright 2022 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, version 3.
+//
+// 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 Switch = @import("Switch.zig");
+const util = @import("util.zig");
+
+switch_type: Switch.Type,
+switch_state: Switch.State,
+command_args: []const [:0]const u8,
+
+pub fn init(
+ switch_type: Switch.Type,
+ switch_state: Switch.State,
+ command_args: []const []const u8,
+) !Self {
+ const owned_args = try util.gpa.alloc([:0]u8, command_args.len);
+ errdefer util.gpa.free(owned_args);
+ for (command_args) |arg, i| {
+ errdefer for (owned_args[0..i]) |a| util.gpa.free(a);
+ owned_args[i] = try util.gpa.dupeZ(u8, arg);
+ }
+ return Self{
+ .switch_type = switch_type,
+ .switch_state = switch_state,
+ .command_args = owned_args,
+ };
+}
+
+pub fn deinit(self: Self) void {
+ for (self.command_args) |arg| util.gpa.free(arg);
+ util.gpa.free(self.command_args);
+}
diff --git a/river/command.zig b/river/command.zig
index 0a0bcc4..1606f06 100644
--- a/river/command.zig
+++ b/river/command.zig
@@ -64,6 +64,7 @@ const command_impls = std.ComptimeStringMap(
.{ "list-inputs", @import("command/input.zig").listInputs },
.{ "map", @import("command/map.zig").map },
.{ "map-pointer", @import("command/map.zig").mapPointer },
+ .{ "map-switch", @import("command/map.zig").mapSwitch },
.{ "move", @import("command/move.zig").move },
.{ "output-layout", @import("command/layout.zig").outputLayout },
.{ "resize", @import("command/move.zig").resize },
@@ -84,6 +85,7 @@ const command_impls = std.ComptimeStringMap(
.{ "toggle-view-tags", @import("command/tags.zig").toggleViewTags },
.{ "unmap", @import("command/map.zig").unmap },
.{ "unmap-pointer", @import("command/map.zig").unmapPointer },
+ .{ "unmap-switch", @import("command/map.zig").unmapSwitch },
.{ "xcursor-theme", @import("command/xcursor_theme.zig").xcursorTheme },
.{ "zoom", @import("command/zoom.zig").zoom },
},
diff --git a/river/command/map.zig b/river/command/map.zig
index 80abda8..3d549d8 100644
--- a/river/command/map.zig
+++ b/river/command/map.zig
@@ -27,6 +27,8 @@ const util = @import("../util.zig");
const Error = @import("../command.zig").Error;
const Mapping = @import("../Mapping.zig");
const PointerMapping = @import("../PointerMapping.zig");
+const SwitchMapping = @import("../SwitchMapping.zig");
+const Switch = @import("../Switch.zig");
const Seat = @import("../Seat.zig");
/// Create a new mapping for a given mode
@@ -77,6 +79,40 @@ pub fn map(
}
}
+/// Create a new switch mapping for a given mode
+///
+/// Example:
+/// map-switch normal lid close spawn "wlr-randr --output eDP-1 --off"
+pub fn mapSwitch(
+ _: *Seat,
+ args: []const [:0]const u8,
+ out: *?[]const u8,
+) Error!void {
+ if (args.len < 5) return Error.NotEnoughArguments;
+
+ const mode_id = try modeNameToId(args[1], out);
+ const switch_type = try parseSwitchType(args[2], out);
+ const switch_state = try parseSwitchState(switch_type, args[3], out);
+
+ const new = try SwitchMapping.init(switch_type, switch_state, args[4..]);
+ errdefer new.deinit();
+
+ const mode_mappings = &server.config.modes.items[mode_id].switch_mappings;
+
+ if (switchMappingExists(mode_mappings, switch_type, switch_state)) |current| {
+ mode_mappings.items[current].deinit();
+ mode_mappings.items[current] = new;
+ // Warn user if they overwrote an existing keybinding using riverctl.
+ out.* = try std.fmt.allocPrint(
+ util.gpa,
+ "overwrote an existing keybinding: map-switch {s} {s} {s}",
+ .{ args[1], args[2], args[3] },
+ );
+ } else {
+ try mode_mappings.append(util.gpa, new);
+ }
+}
+
/// Create a new pointer mapping for a given mode
///
/// Example:
@@ -148,6 +184,21 @@ fn mappingExists(
return null;
}
+/// Returns the index of the SwitchMapping with matching switch_type and switch_state, if any.
+fn switchMappingExists(
+ switch_mappings: *std.ArrayListUnmanaged(SwitchMapping),
+ switch_type: Switch.Type,
+ switch_state: Switch.State,
+) ?usize {
+ for (switch_mappings.items) |mapping, i| {
+ if (mapping.switch_type == switch_type and std.meta.eql(mapping.switch_state, switch_state)) {
+ return i;
+ }
+ }
+
+ return null;
+}
+
/// Returns the index of the PointerMapping with matching modifiers and event code, if any.
fn pointerMappingExists(
pointer_mappings: *std.ArrayListUnmanaged(PointerMapping),
@@ -210,6 +261,57 @@ fn parseModifiers(modifiers_str: []const u8, out: *?[]const u8) !wlr.Keyboard.Mo
return modifiers;
}
+fn parseSwitchType(
+ switch_type_str: []const u8,
+ out: *?[]const u8,
+) !Switch.Type {
+ return std.meta.stringToEnum(Switch.Type, switch_type_str) orelse {
+ out.* = try std.fmt.allocPrint(
+ util.gpa,
+ "invalid switch '{s}', must be 'lid' or 'tablet'",
+ .{switch_type_str},
+ );
+ return Error.Other;
+ };
+}
+
+fn parseSwitchState(
+ switch_type: Switch.Type,
+ switch_state_str: []const u8,
+ out: *?[]const u8,
+) !Switch.State {
+ switch (switch_type) {
+ .lid => {
+ const lid_state = std.meta.stringToEnum(
+ Switch.LidState,
+ switch_state_str,
+ ) orelse {
+ out.* = try std.fmt.allocPrint(
+ util.gpa,
+ "invalid lid state '{s}', must be 'close' or 'open'",
+ .{switch_state_str},
+ );
+ return Error.Other;
+ };
+ return Switch.State{ .lid = lid_state };
+ },
+ .tablet => {
+ const tablet_state = std.meta.stringToEnum(
+ Switch.TabletState,
+ switch_state_str,
+ ) orelse {
+ out.* = try std.fmt.allocPrint(
+ util.gpa,
+ "invalid tablet state '{s}', must be 'on' or 'off'",
+ .{switch_state_str},
+ );
+ return Error.Other;
+ };
+ return Switch.State{ .tablet = tablet_state };
+ },
+ }
+}
+
const OptionalArgsContainer = struct {
i: usize,
release: bool,
@@ -273,6 +375,28 @@ pub fn unmap(seat: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!v
mapping.deinit();
}
+/// Remove a switch mapping from a given mode
+///
+/// Example:
+/// unmap-switch normal tablet on
+pub fn unmapSwitch(
+ _: *Seat,
+ args: []const [:0]const u8,
+ out: *?[]const u8,
+) Error!void {
+ if (args.len < 4) return Error.NotEnoughArguments;
+
+ const mode_id = try modeNameToId(args[1], out);
+ const switch_type = try parseSwitchType(args[2], out);
+ const switch_state = try parseSwitchState(switch_type, args[3], out);
+
+ const mode_mappings = &server.config.modes.items[mode_id].switch_mappings;
+ const mapping_idx = switchMappingExists(mode_mappings, switch_type, switch_state) orelse return;
+
+ var mapping = mode_mappings.swapRemove(mapping_idx);
+ mapping.deinit();
+}
+
/// Remove a pointer mapping for a given mode
///
/// Example: