diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd index be665e1..3dc8330 100644 --- a/doc/riverctl.1.scd +++ b/doc/riverctl.1.scd @@ -32,8 +32,8 @@ over the Wayland protocol. Add _app-id_ to the float filter list. Views with this _app-id_ will start floating. -*focus-output* *next*|*previous* - Focus the next or previous output. +*focus-output* *next*|*previous*|*up*|*right*|*down*|*left* + Focus the next or previous output or the closest output in any direction. *focus-view* *next*|*previous* Focus the next or previous view in the stack. @@ -50,8 +50,9 @@ over the Wayland protocol. Snap the focused view to the specified screen edge. The view will be set to floating. -*send-to-output* *next*|*previous* - Send the focused view to the next or the previous output. +*send-to-output* *next*|*previous*|*up*|*right*|*down*|*left* + Send the focused view to the next or previous output or the closest + output in any direction. *spawn* _shell_command_ Run _shell_command_ using _/bin/sh -c_. Put single quotes around diff --git a/river/command.zig b/river/command.zig index b9ef7c6..313e11b 100644 --- a/river/command.zig +++ b/river/command.zig @@ -55,7 +55,7 @@ const str_to_impl_fn = [_]struct { .{ .name = "exit", .impl = @import("command/exit.zig").exit }, .{ .name = "float-filter-add", .impl = @import("command/filter.zig").floatFilterAdd }, .{ .name = "focus-follows-cursor", .impl = @import("command/focus_follows_cursor.zig").focusFollowsCursor }, - .{ .name = "focus-output", .impl = @import("command/focus_output.zig").focusOutput }, + .{ .name = "focus-output", .impl = @import("command/output.zig").focusOutput }, .{ .name = "focus-view", .impl = @import("command/focus_view.zig").focusView }, .{ .name = "input", .impl = @import("command/input.zig").input }, .{ .name = "list-inputs", .impl = @import("command/input.zig").listInputs }, @@ -67,7 +67,7 @@ const str_to_impl_fn = [_]struct { .{ .name = "opacity", .impl = @import("command/opacity.zig").opacity }, .{ .name = "output-layout", .impl = @import("command/layout.zig").outputLayout }, .{ .name = "resize", .impl = @import("command/move.zig").resize }, - .{ .name = "send-to-output", .impl = @import("command/send_to_output.zig").sendToOutput }, + .{ .name = "send-to-output", .impl = @import("command/output.zig").sendToOutput }, .{ .name = "set-focused-tags", .impl = @import("command/tags.zig").setFocusedTags }, .{ .name = "set-layout-value", .impl = @import("command/layout.zig").setLayoutValue }, .{ .name = "set-repeat", .impl = @import("command/set_repeat.zig").setRepeat }, diff --git a/river/command/focus_output.zig b/river/command/focus_output.zig deleted file mode 100644 index 6d0f088..0000000 --- a/river/command/focus_output.zig +++ /dev/null @@ -1,55 +0,0 @@ -// 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 . - -const std = @import("std"); - -const server = &@import("../main.zig").server; - -const Direction = @import("../command.zig").Direction; -const Error = @import("../command.zig").Error; -const Output = @import("../Output.zig"); -const Seat = @import("../Seat.zig"); - -/// Focus either the next or the previous output, depending on the bool passed. -/// Does nothing if there is only one output. -pub fn focusOutput( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - out: *?[]const u8, -) Error!void { - if (args.len < 2) return Error.NotEnoughArguments; - if (args.len > 2) return Error.TooManyArguments; - - const direction = std.meta.stringToEnum(Direction, args[1]) orelse return Error.InvalidDirection; - - // If the noop output is focused, there are no other outputs to switch to - if (seat.focused_output == &server.root.noop_output) { - std.debug.assert(server.root.outputs.len == 0); - return; - } - - // Focus the next/prev output in the list if there is one, else wrap - const focused_node = @fieldParentPtr(std.TailQueue(Output).Node, "data", seat.focused_output); - seat.focusOutput(switch (direction) { - .next => if (focused_node.next) |node| &node.data else &server.root.outputs.first.?.data, - .previous => if (focused_node.prev) |node| &node.data else &server.root.outputs.last.?.data, - }); - - seat.focus(null); - server.root.startTransaction(); -} diff --git a/river/command/output.zig b/river/command/output.zig new file mode 100644 index 0000000..5535b88 --- /dev/null +++ b/river/command/output.zig @@ -0,0 +1,99 @@ +// This file is part of river, a dynamic tiling wayland compositor. +// +// Copyright 2021 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 . + +const std = @import("std"); + +const wlr = @import("wlroots"); + +const server = &@import("../main.zig").server; + +const Direction = @import("../command.zig").Direction; +const PhysicalDirectionDirection = @import("../command.zig").PhysicalDirection; +const Error = @import("../command.zig").Error; +const Output = @import("../Output.zig"); +const Seat = @import("../Seat.zig"); + +pub fn focusOutput( + allocator: *std.mem.Allocator, + seat: *Seat, + args: []const []const u8, + out: *?[]const u8, +) Error!void { + if (args.len < 2) return Error.NotEnoughArguments; + if (args.len > 2) return Error.TooManyArguments; + + // If the noop output is focused, there are no other outputs to switch to + if (seat.focused_output == &server.root.noop_output) { + std.debug.assert(server.root.outputs.len == 0); + return; + } + + seat.focusOutput((try getOutput(seat, args[1])) orelse return); + seat.focus(null); + server.root.startTransaction(); +} + +pub fn sendToOutput( + allocator: *std.mem.Allocator, + seat: *Seat, + args: []const []const u8, + out: *?[]const u8, +) Error!void { + if (args.len < 2) return Error.NotEnoughArguments; + if (args.len > 2) return Error.TooManyArguments; + + // If the noop output is focused, there is nowhere to send the view + if (seat.focused_output == &server.root.noop_output) { + std.debug.assert(server.root.outputs.len == 0); + return; + } + + if (seat.focused == .view) { + const destination_output = (try getOutput(seat, args[1])) orelse return; + seat.focused.view.sendToOutput(destination_output); + + // Handle the change and focus whatever's next in the focus stack + seat.focus(null); + seat.focused_output.arrangeViews(); + destination_output.arrangeViews(); + server.root.startTransaction(); + } +} + +/// Find an output adjacent to the currently focused based on either logical or +/// spacial direction +fn getOutput(seat: *Seat, str: []const u8) !?*Output { + if (std.meta.stringToEnum(Direction, str)) |direction| { // Logical direction + // Return the next/prev output in the list if there is one, else wrap + const focused_node = @fieldParentPtr(std.TailQueue(Output).Node, "data", seat.focused_output); + return switch (direction) { + .next => if (focused_node.next) |node| &node.data else &server.root.outputs.first.?.data, + .previous => if (focused_node.prev) |node| &node.data else &server.root.outputs.last.?.data, + }; + } else if (std.meta.stringToEnum(wlr.OutputLayout.Direction, str)) |direction| { // Spacial direction + const focus_box = server.root.output_layout.getBox(seat.focused_output.wlr_output) orelse return null; + const wlr_output = server.root.output_layout.adjacentOutput( + direction, + seat.focused_output.wlr_output, + @intToFloat(f64, focus_box.x + @divFloor(focus_box.width, 2)), + @intToFloat(f64, focus_box.y + @divFloor(focus_box.height, 2)), + ) orelse return null; + return @intToPtr(*Output, wlr_output.data); + } else { + return Error.InvalidDirection; + } +} diff --git a/river/command/send_to_output.zig b/river/command/send_to_output.zig deleted file mode 100644 index e3ba90b..0000000 --- a/river/command/send_to_output.zig +++ /dev/null @@ -1,63 +0,0 @@ -// 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 . - -const std = @import("std"); - -const server = &@import("../main.zig").server; - -const Direction = @import("../command.zig").Direction; -const Error = @import("../command.zig").Error; -const Output = @import("../Output.zig"); -const Seat = @import("../Seat.zig"); - -/// Send the focused view to the the next or the previous output, depending on -/// the bool passed. Does nothing if there is only one output. -pub fn sendToOutput( - allocator: *std.mem.Allocator, - seat: *Seat, - args: []const []const u8, - out: *?[]const u8, -) Error!void { - if (args.len < 2) return Error.NotEnoughArguments; - if (args.len > 2) return Error.TooManyArguments; - - const direction = std.meta.stringToEnum(Direction, args[1]) orelse return Error.InvalidDirection; - - if (seat.focused == .view) { - // If the noop output is focused, there is nowhere to send the view - if (seat.focused_output == &server.root.noop_output) { - std.debug.assert(server.root.outputs.len == 0); - return; - } - - // Send to the next/prev output in the list if there is one, else wrap - const current_node = @fieldParentPtr(std.TailQueue(Output).Node, "data", seat.focused_output); - const destination_output = switch (direction) { - .next => if (current_node.next) |node| &node.data else &server.root.outputs.first.?.data, - .previous => if (current_node.prev) |node| &node.data else &server.root.outputs.last.?.data, - }; - - // Move the view to the target output - seat.focused.view.sendToOutput(destination_output); - - // Handle the change and focus whatever's next in the focus stack - seat.focus(null); - seat.focused_output.arrangeViews(); - destination_output.arrangeViews(); - server.root.startTransaction(); - } -}