git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commitb6784759240af0e79643136a45c2869b297a918a
parent77f52fb807
authorIsaac Freund <[email protected]>
date2020-05-19 22:59
Implement command execution through riverctl

 src/Command.zig                                    | 141 +++++++++++++++++++++
 src/Config.zig                                     |  77 +++++------
 src/Keybind.zig                                    |   5 +-
 src/Mode.zig                                       |   1 +
 src/Seat.zig                                       |   2 +-
 src/Server.zig                                     |   1 +
 src/WindowManagement.zig                           |  11 +-
 src/command.zig                                    |  50 --------
 src/command/{close_view.zig => close.zig}          |   4 +-
 src/command/{exit_compositor.zig => exit.zig}      |   4 +-
 src/command/{focus_view.zig => focus.zig}          |   4 +-
 src/command/focus_all_tags.zig                     |  25 ++++
 src/command/focus_output.zig                       |   2 +-
 src/command/{focus_tags.zig => focus_tag.zig}      |  10 +-
 ...odify_master_count.zig => mod_master_count.zig} |   4 +-
 ...ify_master_factor.zig => mod_master_factor.zig} |   4 +-
 src/command/mode.zig                               |   2 +-
 src/command/send_to_output.zig                     |   2 +-
 src/command/spawn.zig                              |   2 +-
 src/command/{set_view_tags.zig => tag_view.zig}    |   8 +-
 src/command/tag_view_all_tags.zig                  |  31 +++++
 src/command/toggle_float.zig                       |   2 +-
 .../{toggle_tags.zig => toggle_tag_focus.zig}      |   6 +-
 .../{toggle_view_tags.zig => toggle_view_tag.zig}  |   8 +-
 src/command/zoom.zig                               |   2 +-
 25 files changed, 271 insertions(+), 137 deletions(-)

diff --git a/src/Command.zig b/src/Command.zig
new file mode 100644
index 0000000..a7c2e1d
--- /dev/null
+++ b/src/Command.zig
@@ -0,0 +1,141 @@
+// 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 Seat = @import("Seat.zig");
+
+const command = struct {
+    const close = @import("command/close.zig").close;
+    const exit = @import("command/exit.zig").exit;
+    const focus = @import("command/focus.zig").focus;
+    const focusAllTags = @import("command/focus_all_tags.zig").focusAllTags;
+    const focusOutput = @import("command/focus_output.zig").focusOutput;
+    const focusTag = @import("command/focus_tag.zig").focusTag;
+    const modMasterCount = @import("command/mod_master_count.zig").modMasterCount;
+    const modMasterFactor = @import("command/mod_master_factor.zig").modMasterFactor;
+    const mode = @import("command/mode.zig").mode;
+    const sendToOutput = @import("command/send_to_output.zig").sendToOutput;
+    const spawn = @import("command/spawn.zig").spawn;
+    const tagView = @import("command/tag_view.zig").tagView;
+    const tagViewAllTags = @import("command/tag_view_all_tags.zig").tagViewAllTags;
+    const toggleFloat = @import("command/toggle_float.zig").toggleFloat;
+    const toggleTagFocus = @import("command/toggle_tag_focus.zig").toggleTagFocus;
+    const toggleViewTag = @import("command/toggle_view_tag.zig").toggleViewTag;
+    const zoom = @import("command/zoom.zig").zoom;
+};
+
+const Direction = enum {
+    Next,
+    Prev,
+};
+
+pub const Arg = union(enum) {
+    int: i32,
+    uint: u32,
+    float: f64,
+    str: []const u8,
+    direction: Direction,
+    none: void,
+
+    fn parse(
+        arg_type: @TagType(Arg),
+        args: []const []const u8,
+        allocator: *std.mem.Allocator,
+    ) !Arg {
+        switch (arg_type) {
+            .int, .uint, .float, .direction => {
+                if (args.len == 0) return error.NotEnoughArguments;
+                if (args.len > 1) return error.TooManyArguments;
+                return switch (arg_type) {
+                    .int => .{ .int = try std.fmt.parseInt(i32, args[0], 10) },
+                    .uint => .{ .uint = try std.fmt.parseInt(u32, args[0], 10) },
+                    .float => .{ .float = try std.fmt.parseFloat(f64, args[0]) },
+                    .direction => if (std.mem.eql(u8, args[0], "next"))
+                        Arg{ .direction = .Next }
+                    else if (std.mem.eql(u8, args[0], "previous"))
+                        Arg{ .direction = .Prev }
+                    else
+                        error.InvalidDirection,
+                    else => unreachable,
+                };
+            },
+            .str => {
+                if (args.len == 0) return error.NotEnoughArguments;
+                return Arg{ .str = try std.mem.join(allocator, " ", args) };
+            },
+            .none => return if (args.len == 0) .{ .none = {} } else error.TooManyArguments,
+        }
+    }
+};
+
+const ImplFn = fn (seat: *Seat, arg: Arg) void;
+
+const Definition = struct {
+    name: []const u8,
+    arg_type: @TagType(Arg),
+    impl: ImplFn,
+};
+
+// zig fmt: off
+const str_to_read_fn = [_]Definition{
+    .{ .name = "close",             .arg_type = .none,      .impl = command.close },
+    .{ .name = "exit",              .arg_type = .none,      .impl = command.exit },
+    .{ .name = "focus",             .arg_type = .direction, .impl = command.focus },
+    .{ .name = "focus_all_tags",    .arg_type = .none,      .impl = command.focusAllTags },
+    .{ .name = "focus_output",      .arg_type = .direction, .impl = command.focusOutput },
+    .{ .name = "focus_tag",         .arg_type = .uint,      .impl = command.focusTag },
+    .{ .name = "mod_master_count",  .arg_type = .int,       .impl = command.modMasterCount },
+    .{ .name = "mod_master_factor", .arg_type = .float,     .impl = command.modMasterFactor },
+    .{ .name = "mode",              .arg_type = .str,       .impl = command.mode },
+    .{ .name = "send_to_output",    .arg_type = .direction, .impl = command.sendToOutput },
+    .{ .name = "spawn",             .arg_type = .str,       .impl = command.spawn },
+    .{ .name = "tag_view",          .arg_type = .uint,      .impl = command.tagView },
+    .{ .name = "tag_view_all_tags", .arg_type = .none,      .impl = command.tagViewAllTags },
+    .{ .name = "toggle_float",      .arg_type = .none,      .impl = command.toggleFloat },
+    .{ .name = "toggle_tag_focus",  .arg_type = .uint,      .impl = command.toggleTagFocus },
+    .{ .name = "toggle_view_tag",   .arg_type = .uint,      .impl = command.toggleViewTag },
+    .{ .name = "zoom",              .arg_type = .none,      .impl = command.zoom },
+};
+// zig fmt: on
+
+impl: ImplFn,
+arg: Arg,
+
+pub fn init(args: []const []const u8, allocator: *std.mem.Allocator) !Self {
+    if (args.len == 0) return error.NoCommand;
+    const name = args[0];
+
+    const definition = for (str_to_read_fn) |definition| {
+        if (std.mem.eql(u8, name, definition.name)) break definition;
+    } else return error.UnknownCommand;
+
+    return Self{
+        .impl = definition.impl,
+        .arg = try Arg.parse(definition.arg_type, args[1..], allocator),
+    };
+}
+
+pub fn deinit(self: Self, allocator: *std.mem.Allocator) void {
+    if (self.arg == .str) allocator.free(self.arg.str);
+}
+
+pub fn run(self: Self, seat: *Seat) void {
+    self.impl(seat, self.arg);
+}
diff --git a/src/Config.zig b/src/Config.zig
index 85c9b32..a1a8b05 100644
--- a/src/Config.zig
+++ b/src/Config.zig
@@ -20,8 +20,8 @@ const Self = @This();
 const std = @import("std");
 
 const c = @import("c.zig");
-const command = @import("command.zig");
 
+const Command = @import("Command.zig");
 const Log = @import("log.zig").Log;
 const Mode = @import("Mode.zig");
 const Server = @import("Server.zig");
@@ -58,38 +58,33 @@ pub fn init(self: *Self, allocator: *std.mem.Allocator) !void {
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_Return,
         .modifiers = mod | c.WLR_MODIFIER_SHIFT,
-        .command = command.spawn,
-        .arg = .{ .str = "alacritty" },
+        .command = try Command.init(&[_][]const u8{ "spawn", "alacritty" }, allocator),
     });
 
     // Mod+Q to close the focused view
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_q,
         .modifiers = mod,
-        .command = command.close_view,
-        .arg = .{ .none = {} },
+        .command = try Command.init(&[_][]const u8{"close"}, allocator),
     });
 
     // Mod+E to exit river
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_e,
         .modifiers = mod,
-        .command = command.exitCompositor,
-        .arg = .{ .none = {} },
+        .command = try Command.init(&[_][]const u8{"exit"}, allocator),
     });
 
     // Mod+J and Mod+K to focus the next/previous view in the layout stack
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_j,
         .modifiers = mod,
-        .command = command.focusView,
-        .arg = .{ .direction = .Next },
+        .command = try Command.init(&[_][]const u8{ "focus", "next" }, allocator),
     });
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_k,
         .modifiers = mod,
-        .command = command.focusView,
-        .arg = .{ .direction = .Prev },
+        .command = try Command.init(&[_][]const u8{ "focus", "previous" }, allocator),
     });
 
     // Mod+Return to bump the focused view to the top of the layout stack,
@@ -97,22 +92,19 @@ pub fn init(self: *Self, allocator: *std.mem.Allocator) !void {
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_Return,
         .modifiers = mod,
-        .command = command.zoom,
-        .arg = .{ .none = {} },
+        .command = try Command.init(&[_][]const u8{"zoom"}, allocator),
     });
 
     // Mod+H and Mod+L to increase/decrease the width of the master column
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_h,
         .modifiers = mod,
-        .command = command.modifyMasterFactor,
-        .arg = .{ .float = 0.05 },
+        .command = try Command.init(&[_][]const u8{ "mod_master_factor", "+0.05" }, allocator),
     });
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_l,
         .modifiers = mod,
-        .command = command.modifyMasterFactor,
-        .arg = .{ .float = -0.05 },
+        .command = try Command.init(&[_][]const u8{ "mod_master_factor", "-0.05" }, allocator),
     });
 
     // Mod+Shift+H and Mod+Shift+L to increment/decrement the number of
@@ -120,45 +112,40 @@ pub fn init(self: *Self, allocator: *std.mem.Allocator) !void {
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_h,
         .modifiers = mod | c.WLR_MODIFIER_SHIFT,
-        .command = command.modifyMasterCount,
-        .arg = .{ .int = 1 },
+        .command = try Command.init(&[_][]const u8{ "mod_master_count", "+1" }, allocator),
     });
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_l,
         .modifiers = mod | c.WLR_MODIFIER_SHIFT,
-        .command = command.modifyMasterCount,
-        .arg = .{ .int = -1 },
+        .command = try Command.init(&[_][]const u8{ "mod_master_count", "+1" }, allocator),
     });
 
     comptime var i = 0;
     inline while (i < 9) : (i += 1) {
+        const str = &[_]u8{i + '0' + 1};
         // Mod+[1-9] to focus tag [1-9]
         try normal.keybinds.append(.{
             .keysym = c.XKB_KEY_1 + i,
             .modifiers = mod,
-            .command = command.focusTags,
-            .arg = .{ .uint = 1 << i },
+            .command = try Command.init(&[_][]const u8{ "focus_tag", str }, allocator),
         });
         // Mod+Shift+[1-9] to tag focused view with tag [1-9]
         try normal.keybinds.append(.{
             .keysym = c.XKB_KEY_1 + i,
             .modifiers = mod | c.WLR_MODIFIER_SHIFT,
-            .command = command.setViewTags,
-            .arg = .{ .uint = 1 << i },
+            .command = try Command.init(&[_][]const u8{ "tag_view", str }, allocator),
         });
         // Mod+Ctrl+[1-9] to toggle focus of tag [1-9]
         try normal.keybinds.append(.{
             .keysym = c.XKB_KEY_1 + i,
             .modifiers = mod | c.WLR_MODIFIER_CTRL,
-            .command = command.toggleTags,
-            .arg = .{ .uint = 1 << i },
+            .command = try Command.init(&[_][]const u8{ "toggle_tag_focus", str }, allocator),
         });
         // Mod+Shift+Ctrl+[1-9] to toggle tag [1-9] of focused view
         try normal.keybinds.append(.{
             .keysym = c.XKB_KEY_1 + i,
             .modifiers = mod | c.WLR_MODIFIER_CTRL | c.WLR_MODIFIER_SHIFT,
-            .command = command.toggleViewTags,
-            .arg = .{ .uint = 1 << i },
+            .command = try Command.init(&[_][]const u8{ "toggle_view_tag", str }, allocator),
         });
     }
 
@@ -166,30 +153,26 @@ pub fn init(self: *Self, allocator: *std.mem.Allocator) !void {
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_0,
         .modifiers = mod,
-        .command = command.focusTags,
-        .arg = .{ .uint = 0xFFFFFFFF },
+        .command = try Command.init(&[_][]const u8{"focus_all_tags"}, allocator),
     });
 
     // Mod+Shift+0 to tag focused view with all tags
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_0,
         .modifiers = mod | c.WLR_MODIFIER_SHIFT,
-        .command = command.setViewTags,
-        .arg = .{ .uint = 0xFFFFFFFF },
+        .command = try Command.init(&[_][]const u8{"tag_view_all_tags"}, allocator),
     });
 
     // Mod+Period and Mod+Comma to focus the next/previous output
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_period,
         .modifiers = mod,
-        .command = command.focusOutput,
-        .arg = .{ .direction = .Next },
+        .command = try Command.init(&[_][]const u8{ "focus_output", "next" }, allocator),
     });
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_comma,
         .modifiers = mod,
-        .command = command.focusOutput,
-        .arg = .{ .direction = .Prev },
+        .command = try Command.init(&[_][]const u8{ "focus_output", "previous" }, allocator),
     });
 
     // Mod+Shift+Period/Comma to send the focused view to the the
@@ -197,30 +180,26 @@ pub fn init(self: *Self, allocator: *std.mem.Allocator) !void {
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_period,
         .modifiers = mod | c.WLR_MODIFIER_SHIFT,
-        .command = command.sendToOutput,
-        .arg = .{ .direction = .Next },
+        .command = try Command.init(&[_][]const u8{ "send_to_output", "next" }, allocator),
     });
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_comma,
         .modifiers = mod | c.WLR_MODIFIER_SHIFT,
-        .command = command.sendToOutput,
-        .arg = .{ .direction = .Prev },
+        .command = try Command.init(&[_][]const u8{ "send_to_output", "previous" }, allocator),
     });
 
     // Mod+Space to toggle float
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_space,
         .modifiers = mod,
-        .command = command.toggleFloat,
-        .arg = .{ .none = {} },
+        .command = try Command.init(&[_][]const u8{"toggle_float"}, allocator),
     });
 
     // Mod+F11 to enter passthrough mode
     try normal.keybinds.append(.{
         .keysym = c.XKB_KEY_F11,
         .modifiers = mod,
-        .command = command.mode,
-        .arg = .{ .str = "passthrough" },
+        .command = try Command.init(&[_][]const u8{ "mode", "passthrough" }, allocator),
     });
 
     try self.modes.append(try Mode.init("passthrough", allocator));
@@ -229,14 +208,18 @@ pub fn init(self: *Self, allocator: *std.mem.Allocator) !void {
     try self.modes.items[1].keybinds.append(.{
         .keysym = c.XKB_KEY_F11,
         .modifiers = mod,
-        .command = command.mode,
-        .arg = .{ .str = "normal" },
+        .command = try Command.init(&[_][]const u8{ "mode", "normal" }, allocator),
     });
 
     // Float views with app_id "float"
     try self.float_filter.append("float");
 }
 
+pub fn deinit(self: Self, allocator: *std.mem.Allocator) void {
+    for (self.modes.items) |*mode| mode.deinit();
+    self.modes.deinit();
+}
+
 pub fn getMode(self: Self, name: []const u8) *Mode {
     for (self.modes.items) |*mode|
         if (std.mem.eql(u8, mode.name, name)) return mode;
diff --git a/src/Keybind.zig b/src/Keybind.zig
index 9a156ad..d82be7a 100644
--- a/src/Keybind.zig
+++ b/src/Keybind.zig
@@ -16,9 +16,8 @@
 // along with this program. If not, see <https://www.gnu.org/licenses/>.
 
 const c = @import("c.zig");
-const command = @import("command.zig");
+const Command = @import("Command.zig");
 
 keysym: c.xkb_keysym_t,
 modifiers: u32,
-command: command.Command,
-arg: command.Arg,
+command: Command,
diff --git a/src/Mode.zig b/src/Mode.zig
index 9210db6..dfc4f54 100644
--- a/src/Mode.zig
+++ b/src/Mode.zig
@@ -38,5 +38,6 @@ pub fn init(name: []const u8, allocator: *std.mem.Allocator) !Self {
 pub fn deinit(self: Self) void {
     const allocator = self.keybinds.allocator;
     allocator.free(self.name);
+    for (self.keybinds.items) |keybind| keybind.command.deinit(allocator);
     self.keybinds.deinit();
 }
diff --git a/src/Seat.zig b/src/Seat.zig
index c697010..3ff3667 100644
--- a/src/Seat.zig
+++ b/src/Seat.zig
@@ -252,7 +252,7 @@ pub fn handleKeybinding(self: *Self, keysym: c.xkb_keysym_t, modifiers: u32) boo
     for (self.mode.keybinds.items) |keybind| {
         if (modifiers == keybind.modifiers and keysym == keybind.keysym) {
             // Execute the bound command
-            keybind.command(self, keybind.arg);
+            keybind.command.run(self);
             return true;
         }
     }
diff --git a/src/Server.zig b/src/Server.zig
index 622b76f..a75ead0 100644
--- a/src/Server.zig
+++ b/src/Server.zig
@@ -143,6 +143,7 @@ pub fn deinit(self: *Self) void {
     c.wl_display_destroy(self.wl_display);
     self.input_manager.deinit();
     self.root.deinit();
+    self.config.deinit(self.allocator);
 }
 
 /// Create the socket, set WAYLAND_DISPLAY, and start the backend
diff --git a/src/WindowManagement.zig b/src/WindowManagement.zig
index 7c14838..c95aa23 100644
--- a/src/WindowManagement.zig
+++ b/src/WindowManagement.zig
@@ -21,6 +21,7 @@ const std = @import("std");
 
 const c = @import("c.zig");
 
+const Command = @import("Command.zig");
 const Log = @import("log.zig").Log;
 const Server = @import("Server.zig");
 
@@ -73,15 +74,15 @@ fn resourceDestroy(wl_resource: ?*c.wl_resource) callconv(.C) void {
     // TODO
 }
 
-fn runCommand(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource, command: ?*c.wl_array) callconv(.C) void {
+fn runCommand(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource, wl_array: ?*c.wl_array) callconv(.C) void {
     const self = @ptrCast(*Self, @alignCast(@alignOf(*Self), c.wl_resource_get_user_data(wl_resource)));
     const allocator = self.server.allocator;
 
     var args = std.ArrayList([]const u8).init(allocator);
 
     var i: usize = 0;
-    const data = @ptrCast([*]const u8, command.?.data);
-    while (i < command.?.size) {
+    const data = @ptrCast([*]const u8, wl_array.?.data);
+    while (i < wl_array.?.size) {
         const slice = std.mem.spanZ(@ptrCast([*:0]const u8, &data[i]));
         args.append(std.mem.dupe(allocator, u8, slice) catch unreachable) catch unreachable;
 
@@ -91,4 +92,8 @@ fn runCommand(wl_client: ?*c.wl_client, wl_resource: ?*c.wl_resource, command: ?
     for (args.items) |x| {
         std.debug.warn("{}\n", .{x});
     }
+
+    // TODO: send the error event on failure instead of crashing
+    const command = Command.init(args.items, allocator) catch unreachable;
+    command.run(self.server.input_manager.default_seat);
 }
diff --git a/src/command.zig b/src/command.zig
deleted file mode 100644
index 95baf20..0000000
--- a/src/command.zig
+++ /dev/null
@@ -1,50 +0,0 @@
-// 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 Seat = @import("Seat.zig");
-
-pub const Direction = enum {
-    Next,
-    Prev,
-};
-
-pub const Arg = union {
-    int: i32,
-    uint: u32,
-    float: f64,
-    str: []const u8,
-    direction: Direction,
-    none: void,
-};
-
-pub const Command = fn (seat: *Seat, arg: Arg) void;
-
-pub usingnamespace @import("command/close_view.zig");
-pub usingnamespace @import("command/exit_compositor.zig");
-pub usingnamespace @import("command/focus_output.zig");
-pub usingnamespace @import("command/focus_tags.zig");
-pub usingnamespace @import("command/focus_view.zig");
-pub usingnamespace @import("command/mode.zig");
-pub usingnamespace @import("command/modify_master_count.zig");
-pub usingnamespace @import("command/modify_master_factor.zig");
-pub usingnamespace @import("command/send_to_output.zig");
-pub usingnamespace @import("command/set_view_tags.zig");
-pub usingnamespace @import("command/spawn.zig");
-pub usingnamespace @import("command/toggle_float.zig");
-pub usingnamespace @import("command/toggle_tags.zig");
-pub usingnamespace @import("command/toggle_view_tags.zig");
-pub usingnamespace @import("command/zoom.zig");
diff --git a/src/command/close_view.zig b/src/command/close.zig
similarity index 91%
rename from src/command/close_view.zig
rename to src/command/close.zig
index 7bee0b6..c5be01c 100644
--- a/src/command/close_view.zig
+++ b/src/command/close.zig
@@ -17,11 +17,11 @@
 
 const c = @import("../c.zig");
 
-const Arg = @import("../command.zig").Arg;
+const Arg = @import("../Command.zig").Arg;
 const Seat = @import("../Seat.zig");
 
 /// Close the focused view, if any.
-pub fn close_view(seat: *Seat, arg: Arg) void {
+pub fn close(seat: *Seat, arg: Arg) void {
     if (seat.focused_view) |view| {
         // Note: we don't call arrange() here as it will be called
         // automatically when the view is unmapped.
diff --git a/src/command/exit_compositor.zig b/src/command/exit.zig
similarity index 90%
rename from src/command/exit_compositor.zig
rename to src/command/exit.zig
index 7736452..799b12c 100644
--- a/src/command/exit_compositor.zig
+++ b/src/command/exit.zig
@@ -17,10 +17,10 @@
 
 const c = @import("../c.zig");
 
-const Arg = @import("../command.zig").Arg;
+const Arg = @import("../Command.zig").Arg;
 const Seat = @import("../Seat.zig");
 
 /// Exit the compositor, terminating the wayland session.
-pub fn exitCompositor(seat: *Seat, arg: Arg) void {
+pub fn exit(seat: *Seat, arg: Arg) void {
     c.wl_display_terminate(seat.input_manager.server.wl_display);
 }
diff --git a/src/command/focus_view.zig b/src/command/focus.zig
similarity index 96%
rename from src/command/focus_view.zig
rename to src/command/focus.zig
index 225fa5e..930a36c 100644
--- a/src/command/focus_view.zig
+++ b/src/command/focus.zig
@@ -17,14 +17,14 @@
 
 const c = @import("../c.zig");
 
-const Arg = @import("../command.zig").Arg;
+const Arg = @import("../Command.zig").Arg;
 const Seat = @import("../Seat.zig");
 const View = @import("../View.zig");
 const ViewStack = @import("../view_stack.zig").ViewStack;
 
 /// Focus either the next or the previous visible view, depending on the enum
 /// passed. Does nothing if there are 1 or 0 views in the stack.
-pub fn focusView(seat: *Seat, arg: Arg) void {
+pub fn focus(seat: *Seat, arg: Arg) void {
     const direction = arg.direction;
     const output = seat.focused_output;
     if (seat.focused_view) |current_focus| {
diff --git a/src/command/focus_all_tags.zig b/src/command/focus_all_tags.zig
new file mode 100644
index 0000000..d2eef88
--- /dev/null
+++ b/src/command/focus_all_tags.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 <https://www.gnu.org/licenses/>.
+
+const Arg = @import("../Command.zig").Arg;
+const Seat = @import("../Seat.zig");
+
+/// Set focus to all tags
+pub fn focusAllTags(seat: *Seat, arg: Arg) void {
+    seat.focused_output.pending_focused_tags = 0xFFFFFFFF;
+    seat.input_manager.server.root.arrange();
+}
diff --git a/src/command/focus_output.zig b/src/command/focus_output.zig
index 5140943..02b7999 100644
--- a/src/command/focus_output.zig
+++ b/src/command/focus_output.zig
@@ -19,7 +19,7 @@ const std = @import("std");
 
 const c = @import("../c.zig");
 
-const Arg = @import("../command.zig").Arg;
+const Arg = @import("../Command.zig").Arg;
 const Output = @import("../Output.zig");
 const Seat = @import("../Seat.zig");
 
diff --git a/src/command/focus_tags.zig b/src/command/focus_tag.zig
similarity index 82%
rename from src/command/focus_tags.zig
rename to src/command/focus_tag.zig
index aa29b7c..edb3292 100644
--- a/src/command/focus_tags.zig
+++ b/src/command/focus_tag.zig
@@ -15,14 +15,12 @@
 // 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 c = @import("../c.zig");
-
-const Arg = @import("../command.zig").Arg;
+const Arg = @import("../Command.zig").Arg;
 const Seat = @import("../Seat.zig");
 
-/// Switch focus to the passed tags.
-pub fn focusTags(seat: *Seat, arg: Arg) void {
-    const tags = arg.uint;
+/// Switch focus to the passed tag.
+pub fn focusTag(seat: *Seat, arg: Arg) void {
+    const tags = @as(u32, 1) << @intCast(u5, arg.uint - 1);
     seat.focused_output.pending_focused_tags = tags;
     seat.input_manager.server.root.arrange();
 }
diff --git a/src/command/modify_master_count.zig b/src/command/mod_master_count.zig
similarity index 92%
rename from src/command/modify_master_count.zig
rename to src/command/mod_master_count.zig
index c7a5ac1..bd2fde0 100644
--- a/src/command/modify_master_count.zig
+++ b/src/command/mod_master_count.zig
@@ -19,11 +19,11 @@ const std = @import("std");
 
 const c = @import("../c.zig");
 
-const Arg = @import("../command.zig").Arg;
+const Arg = @import("../Command.zig").Arg;
 const Seat = @import("../Seat.zig");
 
 /// Modify the number of master views
-pub fn modifyMasterCount(seat: *Seat, arg: Arg) void {
+pub fn modMasterCount(seat: *Seat, arg: Arg) void {
     const delta = arg.int;
     const output = seat.focused_output;
     output.master_count = @intCast(
diff --git a/src/command/modify_master_factor.zig b/src/command/mod_master_factor.zig
similarity index 92%
rename from src/command/modify_master_factor.zig
rename to src/command/mod_master_factor.zig
index b2af06f..2e2a73c 100644
--- a/src/command/modify_master_factor.zig
+++ b/src/command/mod_master_factor.zig
@@ -19,11 +19,11 @@ const std = @import("std");
 
 const c = @import("../c.zig");
 
-const Arg = @import("../command.zig").Arg;
+const Arg = @import("../Command.zig").Arg;
 const Seat = @import("../Seat.zig");
 
 /// Modify the percent of the width of the screen that the master views occupy.
-pub fn modifyMasterFactor(seat: *Seat, arg: Arg) void {
+pub fn modMasterFactor(seat: *Seat, arg: Arg) void {
     const delta = arg.float;
     const output = seat.focused_output;
     const new_master_factor = std.math.min(
diff --git a/src/command/mode.zig b/src/command/mode.zig
index cf0d709..706533c 100644
--- a/src/command/mode.zig
+++ b/src/command/mode.zig
@@ -15,7 +15,7 @@
 // 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 Arg = @import("../command.zig").Arg;
+const Arg = @import("../Command.zig").Arg;
 const Seat = @import("../Seat.zig");
 
 /// Switch to the given mode
diff --git a/src/command/send_to_output.zig b/src/command/send_to_output.zig
index f0658ca..7e0bbbf 100644
--- a/src/command/send_to_output.zig
+++ b/src/command/send_to_output.zig
@@ -19,7 +19,7 @@ const std = @import("std");
 
 const c = @import("../c.zig");
 
-const Arg = @import("../command.zig").Arg;
+const Arg = @import("../Command.zig").Arg;
 const Output = @import("../Output.zig");
 const Seat = @import("../Seat.zig");
 
diff --git a/src/command/spawn.zig b/src/command/spawn.zig
index 53566fa..61aedab 100644
--- a/src/command/spawn.zig
+++ b/src/command/spawn.zig
@@ -19,7 +19,7 @@ const std = @import("std");
 
 const c = @import("../c.zig");
 
-const Arg = @import("../command.zig").Arg;
+const Arg = @import("../Command.zig").Arg;
 const Log = @import("../log.zig").Log;
 const Seat = @import("../Seat.zig");
 
diff --git a/src/command/set_view_tags.zig b/src/command/tag_view.zig
similarity index 84%
rename from src/command/set_view_tags.zig
rename to src/command/tag_view.zig
index 562d311..fd74285 100644
--- a/src/command/set_view_tags.zig
+++ b/src/command/tag_view.zig
@@ -17,12 +17,12 @@
 
 const c = @import("../c.zig");
 
-const Arg = @import("../command.zig").Arg;
+const Arg = @import("../Command.zig").Arg;
 const Seat = @import("../Seat.zig");
 
-/// Set the tags of the focused view.
-pub fn setViewTags(seat: *Seat, arg: Arg) void {
-    const tags = arg.uint;
+/// Set the tag of the focused view.
+pub fn tagView(seat: *Seat, arg: Arg) void {
+    const tags = @as(u32, 1) << @intCast(u5, arg.uint - 1);
     if (seat.focused_view) |view| {
         if (view.current_tags != tags) {
             view.pending_tags = tags;
diff --git a/src/command/tag_view_all_tags.zig b/src/command/tag_view_all_tags.zig
new file mode 100644
index 0000000..1b93233
--- /dev/null
+++ b/src/command/tag_view_all_tags.zig
@@ -0,0 +1,31 @@
+// 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 c = @import("../c.zig");
+
+const Arg = @import("../Command.zig").Arg;
+const Seat = @import("../Seat.zig");
+
+/// Tag the focused view with all tags.
+pub fn tagViewAllTags(seat: *Seat, arg: Arg) void {
+    if (seat.focused_view) |view| {
+        if (view.current_tags != 0xFFFFFFFF) {
+            view.pending_tags = 0xFFFFFFFF;
+            seat.input_manager.server.root.arrange();
+        }
+    }
+}
diff --git a/src/command/toggle_float.zig b/src/command/toggle_float.zig
index 27e3186..e890d73 100644
--- a/src/command/toggle_float.zig
+++ b/src/command/toggle_float.zig
@@ -17,7 +17,7 @@
 
 const c = @import("../c.zig");
 
-const Arg = @import("../command.zig").Arg;
+const Arg = @import("../Command.zig").Arg;
 const Seat = @import("../Seat.zig");
 
 /// Make the focused view float or stop floating, depending on its current
diff --git a/src/command/toggle_tags.zig b/src/command/toggle_tag_focus.zig
similarity index 87%
rename from src/command/toggle_tags.zig
rename to src/command/toggle_tag_focus.zig
index 2d9f86e..8a1a707 100644
--- a/src/command/toggle_tags.zig
+++ b/src/command/toggle_tag_focus.zig
@@ -17,12 +17,12 @@
 
 const c = @import("../c.zig");
 
-const Arg = @import("../command.zig").Arg;
+const Arg = @import("../Command.zig").Arg;
 const Seat = @import("../Seat.zig");
 
 /// Toggle focus of the passsed tags.
-pub fn toggleTags(seat: *Seat, arg: Arg) void {
-    const tags = arg.uint;
+pub fn toggleTagFocus(seat: *Seat, arg: Arg) void {
+    const tags = @as(u32, 1) << @intCast(u5, arg.uint - 1);
     const output = seat.focused_output;
     const new_focused_tags = output.current_focused_tags ^ tags;
     if (new_focused_tags != 0) {
diff --git a/src/command/toggle_view_tags.zig b/src/command/toggle_view_tag.zig
similarity index 84%
rename from src/command/toggle_view_tags.zig
rename to src/command/toggle_view_tag.zig
index 2f5ba3e..fa869b5 100644
--- a/src/command/toggle_view_tags.zig
+++ b/src/command/toggle_view_tag.zig
@@ -17,12 +17,12 @@
 
 const c = @import("../c.zig");
 
-const Arg = @import("../command.zig").Arg;
+const Arg = @import("../Command.zig").Arg;
 const Seat = @import("../Seat.zig");
 
-/// Toggle the passed tags of the focused view
-pub fn toggleViewTags(seat: *Seat, arg: Arg) void {
-    const tags = arg.uint;
+/// Toggle the passed tag of the focused view
+pub fn toggleViewTag(seat: *Seat, arg: Arg) void {
+    const tags = @as(u32, 1) << @intCast(u5, arg.uint - 1);
     if (seat.focused_view) |view| {
         const new_tags = view.current_tags ^ tags;
         if (new_tags != 0) {
diff --git a/src/command/zoom.zig b/src/command/zoom.zig
index b1f7ba6..b603a69 100644
--- a/src/command/zoom.zig
+++ b/src/command/zoom.zig
@@ -17,7 +17,7 @@
 
 const c = @import("../c.zig");
 
-const Arg = @import("../command.zig").Arg;
+const Arg = @import("../Command.zig").Arg;
 const Seat = @import("../Seat.zig");
 const View = @import("../View.zig");
 const ViewStack = @import("../view_stack.zig").ViewStack;