river-layout: update to v2

This implements the changes to the river-layout protocol proposed
in the previous commit removing river-options.
This commit is contained in:
Isaac Freund 2021-04-26 21:03:28 +02:00
parent 871fc7c8de
commit e80b883a47
14 changed files with 275 additions and 61 deletions

View file

@ -65,7 +65,7 @@ pub fn build(b: *zbs.Builder) !void {
scanner.addSystemProtocol("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"); scanner.addSystemProtocol("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml");
scanner.addProtocolPath("protocol/river-control-unstable-v1.xml"); scanner.addProtocolPath("protocol/river-control-unstable-v1.xml");
scanner.addProtocolPath("protocol/river-status-unstable-v1.xml"); scanner.addProtocolPath("protocol/river-status-unstable-v1.xml");
scanner.addProtocolPath("protocol/river-layout-v1.xml"); scanner.addProtocolPath("protocol/river-layout-v2.xml");
scanner.addProtocolPath("protocol/wlr-layer-shell-unstable-v1.xml"); scanner.addProtocolPath("protocol/wlr-layer-shell-unstable-v1.xml");
scanner.addProtocolPath("protocol/wlr-output-power-management-unstable-v1.xml"); scanner.addProtocolPath("protocol/wlr-output-power-management-unstable-v1.xml");

View file

@ -19,6 +19,8 @@ function __riverctl_completion ()
zoom \ zoom \
default-layout \ default-layout \
output-layout \ output-layout \
set-layout-value \
mod-layout-value \
set-focused-tags \ set-focused-tags \
set-view-tags \ set-view-tags \
toggle-focused-tags \ toggle-focused-tags \

View file

@ -1,6 +1,6 @@
function __fish_riverctl_complete_no_subcommand function __fish_riverctl_complete_no_subcommand
for i in (commandline -opc) for i in (commandline -opc)
if contains -- $i close csd-filter-add exit float-filter-add focus-output focus-view move resize snap send-to-output spawn swap toggle-float toggle-fullscreen zoom default-layout output-layout set-focused-tags set-view-tags toggle-focused-tags toggle-view-tags spawn-tagmask declare-mode enter-mode map map-pointer unmap unmap-pointer attach-mode background-color border-color-focused border-color-unfocused border-width focus-follows-cursor opacity set-repeat xcursor-theme if contains -- $i close csd-filter-add exit float-filter-add focus-output focus-view move resize snap send-to-output spawn swap toggle-float toggle-fullscreen zoom default-layout output-layout set-layout-value mod-layout-value set-focused-tags set-view-tags toggle-focused-tags toggle-view-tags spawn-tagmask declare-mode enter-mode map map-pointer unmap unmap-pointer attach-mode background-color border-color-focused border-color-unfocused border-width focus-follows-cursor opacity set-repeat xcursor-theme
return 1 return 1
end end
end end
@ -25,6 +25,8 @@ complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a toggle-fu
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a zoom -d 'Bump the focused view to the top of the layout stack' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a zoom -d 'Bump the focused view to the top of the layout stack'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a default-layout -d 'Set the layout namespace to be used by all outputs by default.' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a default-layout -d 'Set the layout namespace to be used by all outputs by default.'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a output-layout -d 'Set the layout namespace of currently focused output.' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a output-layout -d 'Set the layout namespace of currently focused output.'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a set-layout-value -d 'Set the value with name _name_ of the layout on the focused output with matching namespace.'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a mod-layout-value -d 'Modify the value with name _name_ of the layout on the focused output with matching namespace.'
# Tag managements # Tag managements
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a set-focused-tags -d 'Show views with tags corresponding to the set bits of tags' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a set-focused-tags -d 'Show views with tags corresponding to the set bits of tags'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a set-view-tags -d 'Assign the currently focused view the tags corresponding to the set bits of tags' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a set-view-tags -d 'Assign the currently focused view the tags corresponding to the set bits of tags'

View file

@ -31,6 +31,8 @@ _riverctl() {
'zoom:Bump the focused view to the top of the layout stack' 'zoom:Bump the focused view to the top of the layout stack'
'default-layout:Set the layout namespace to be used by all outputs by default.' 'default-layout:Set the layout namespace to be used by all outputs by default.'
'output-layout:Set the layout namespace of currently focused output.' 'output-layout:Set the layout namespace of currently focused output.'
'set-layout-value:Set the value with name _name_ of the layout on the focused output with matching namespace.'
'mod-layout-value:Modify the value with name _name_ of the layout on the focused output with matching namespace.'
# Tag management # Tag management
'set-focused-tags:Show views with tags corresponding to the set bits of tags' 'set-focused-tags:Show views with tags corresponding to the set bits of tags'
'set-view-tags:Assign the currently focused view the tags corresponding to the set bits of tags' 'set-view-tags:Assign the currently focused view the tags corresponding to the set bits of tags'

View file

@ -79,6 +79,16 @@ over the Wayland protocol.
Set the layout namespace of currently focused output, overriding Set the layout namespace of currently focused output, overriding
the value set with *default-layout* if any. the value set with *default-layout* if any.
*set-layout-value* _namespace_ _type_ _name_ _value_
Set the value with name _name_ of the layout on the focused output
with matching namespace. If there is no matching layout, this command
does nothing.
*mod-layout-value* _namespace_ _type_ _name_ _value_
Modify the value with name _name_ of the layout on the focused
output with matching namespace. If there is no matching layout,
this command does nothing.
## TAG MANAGEMENT ## TAG MANAGEMENT
Tags are similar to workspaces but more flexible. You can assign views multiple Tags are similar to workspaces but more flexible. You can assign views multiple

View file

@ -13,6 +13,17 @@ rivertile - Tiled layout generator for river
*rivertile* is a layout client for river. It provides a simple tiled layout *rivertile* is a layout client for river. It provides a simple tiled layout
split main/secondary stacks. split main/secondary stacks.
# VALUES
_main_location_ (string: top, bottom, left, or right)
The location of the main area in the layout.
_main_count_ (int)
The number of views in the main area of the layout.
_main_factor_ (fixed: [0.1, 0.9])
The ratio of main area to total layout area.
# AUTHORS # AUTHORS
Maintained by Isaac Freund <ifreund@ifreund.xyz> who is assisted by open Maintained by Isaac Freund <ifreund@ifreund.xyz> who is assisted by open

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<protocol name="river_layout_v1"> <protocol name="river_layout_v2">
<copyright> <copyright>
Copyright 2020-2021 The River Developers Copyright 2020-2021 The River Developers
@ -19,7 +19,7 @@
<description summary="let clients propose view positions and dimensions"> <description summary="let clients propose view positions and dimensions">
This protocol specifies a way for clients to propose arbitrary positions and This protocol specifies a way for clients to propose arbitrary positions and
dimensions for a set of views on a specific output of a compositor through dimensions for a set of views on a specific output of a compositor through
the river_layout_v1 object. the river_layout_v2 object.
This set of views is logically structured as a simple list. Views This set of views is logically structured as a simple list. Views
in this list cannot be individually addressed, instead the order of in this list cannot be individually addressed, instead the order of
@ -32,7 +32,7 @@
intentional limitation. intentional limitation.
Note that the client may need to handle multiple layout demands per Note that the client may need to handle multiple layout demands per
river_layout_v1 object simultaneously. river_layout_v2 object simultaneously.
Warning! The protocol described in this file is currently in the testing Warning! The protocol described in this file is currently in the testing
phase. Backward compatible changes may be added together with the phase. Backward compatible changes may be added together with the
@ -40,9 +40,9 @@
only be done by creating a new major version of the extension. only be done by creating a new major version of the extension.
</description> </description>
<interface name="river_layout_manager_v1" version="1"> <interface name="river_layout_manager_v2" version="1">
<description summary="manage river layout objects"> <description summary="manage river layout objects">
A global factory for river_layout_v1 objects. A global factory for river_layout_v2 objects.
</description> </description>
<request name="destroy" type="destructor"> <request name="destroy" type="destructor">
@ -54,26 +54,26 @@
</request> </request>
<request name="get_layout"> <request name="get_layout">
<description summary="create a river_layout_v1 object"> <description summary="create a river_layout_v2 object">
This creates a new river_layout_v1 object for the given wl_output. This creates a new river_layout_v2 object for the given wl_output.
All layout related communication is done through this interface. All layout related communication is done through this interface.
The namespace is used by the compositor to decide which river_layout_v1 The namespace is used by the compositor to decide which river_layout_v2
object will receive layout demands for the output. object will receive layout demands for the output.
The namespace is required to be be unique per-output. Furthermore, The namespace is required to be be unique per-output. Furthermore,
two separate clients may not share a namespace on separate outputs. If two separate clients may not share a namespace on separate outputs. If
these conditions are not upheld, the the namespace_in_use event will these conditions are not upheld, the the namespace_in_use event will
be sent directly after creation of the river_layout_v1 object. be sent directly after creation of the river_layout_v2 object.
</description> </description>
<arg name="id" type="new_id" interface="river_layout_v1"/> <arg name="id" type="new_id" interface="river_layout_v2"/>
<arg name="output" type="object" interface="wl_output"/> <arg name="output" type="object" interface="wl_output"/>
<arg name="namespace" type="string" summary="namespace of the layout object"/> <arg name="namespace" type="string" summary="namespace of the layout object"/>
</request> </request>
</interface> </interface>
<interface name="river_layout_v1" version="1"> <interface name="river_layout_v2" version="1">
<description summary="receive and respond to layout demands"> <description summary="receive and respond to layout demands">
This interface allows clients to receive layout demands from the This interface allows clients to receive layout demands from the
compositor for a specific output and subsequently propose positions and compositor for a specific output and subsequently propose positions and
@ -88,8 +88,8 @@
</enum> </enum>
<request name="destroy" type="destructor"> <request name="destroy" type="destructor">
<description summary="destroy the river_layout_v1 object"> <description summary="destroy the river_layout_v2 object">
This request indicates that the client will not use the river_layout_v1 This request indicates that the client will not use the river_layout_v2
object any more. object any more.
</description> </description>
</request> </request>
@ -98,7 +98,7 @@
<description summary="the requested namespace is already in use"> <description summary="the requested namespace is already in use">
After this event is sent, all requests aside from the destroy event After this event is sent, all requests aside from the destroy event
will be ignored by the server. If the client wishes to try again with will be ignored by the server. If the client wishes to try again with
a different namespace they must create a new river_layout_v1 object. a different namespace they must create a new river_layout_v2 object.
</description> </description>
</event> </event>
@ -188,14 +188,69 @@
<arg name="serial" type="uint" summary="serial of layout demand"/> <arg name="serial" type="uint" summary="serial of layout demand"/>
</request> </request>
<request name="parameters_changed"> <event name="set_int_value">
<description summary="parameters of layout have changed"> <description summary="an int value has been set">
The client may use this request to inform the compositor that one or This event indicates that the value of this river_layout_v2 object
muliple of the parameters it uses to generate layouts have changed. with the given name has been set to the given value.
If the client is responsible for the current view layout, the compositor This event will be followed by a layout_demand if necessary (i.e. if
may decide to send a new layout demand to update the layout. this layout object is currently being used by the compositor to
layout an output)
</description> </description>
</request> <arg name="name" type="string"/>
<arg name="value" type="int"/>
</event>
<event name="mod_int_value">
<description summary="an int value has been modified">
This event indicates that the value of this river_layout_v2 object
with the given name has been modifed by the given delta.
This event will be followed by a layout_demand if necessary (i.e. if
this layout object is currently being used by the compositor to
layout an output)
</description>
<arg name="name" type="string"/>
<arg name="delta" type="int"/>
</event>
<event name="set_fixed_value">
<description summary="a fixed value has been set">
This event indicates that the value of this river_layout_v2 object
with the given name has been set to the given value.
This event will be followed by a layout_demand if necessary (i.e. if
this layout object is currently being used by the compositor to
layout an output)
</description>
<arg name="name" type="string"/>
<arg name="value" type="fixed"/>
</event>
<event name="mod_fixed_value">
<description summary="a fixed value has been modified">
This event indicates that the value of this river_layout_v2 object
with the given name has been modifed by the given delta.
This event will be followed by a layout_demand if necessary (i.e. if
this layout object is currently being used by the compositor to
layout an output)
</description>
<arg name="name" type="string"/>
<arg name="delta" type="fixed"/>
</event>
<event name="set_string_value">
<description summary="a string value has been set">
This event indicates that the value of this river_layout_v2 object
with the given name has been set to the given value.
This event will be followed by a layout_demand if necessary (i.e. if
this layout object is currently being used by the compositor to
layout an output)
</description>
<arg name="name" type="string"/>
<arg name="value" type="string"/>
</event>
</interface> </interface>
</protocol> </protocol>

View file

@ -35,12 +35,12 @@ const LayoutDemand = @import("LayoutDemand.zig");
const log = std.log.scoped(.layout); const log = std.log.scoped(.layout);
layout: *river.LayoutV1, layout: *river.LayoutV2,
namespace: []const u8, namespace: []const u8,
output: *Output, output: *Output,
pub fn create(client: *wl.Client, version: u32, id: u32, output: *Output, namespace: []const u8) !void { pub fn create(client: *wl.Client, version: u32, id: u32, output: *Output, namespace: []const u8) !void {
const layout = try river.LayoutV1.create(client, version, id); const layout = try river.LayoutV2.create(client, version, id);
if (namespaceInUse(namespace, output, client)) { if (namespaceInUse(namespace, output, client)) {
layout.sendNamespaceInUse(); layout.sendNamespaceInUse();
@ -91,7 +91,7 @@ fn namespaceInUse(namespace: []const u8, output: *Output, client: *wl.Client) bo
/// This exists to handle layouts that have been rendered inert (due to the /// This exists to handle layouts that have been rendered inert (due to the
/// namespace already being in use) until the client destroys them. /// namespace already being in use) until the client destroys them.
fn handleRequestInert(layout: *river.LayoutV1, request: river.LayoutV1.Request, _: ?*c_void) void { fn handleRequestInert(layout: *river.LayoutV2, request: river.LayoutV2.Request, _: ?*c_void) void {
if (request == .destroy) layout.destroy(); if (request == .destroy) layout.destroy();
} }
@ -128,14 +128,10 @@ pub fn startLayoutDemand(self: *Self, views: u32) void {
self.output.root.trackLayoutDemands(); self.output.root.trackLayoutDemands();
} }
fn handleRequest(layout: *river.LayoutV1, request: river.LayoutV1.Request, self: *Self) void { fn handleRequest(layout: *river.LayoutV2, request: river.LayoutV2.Request, self: *Self) void {
switch (request) { switch (request) {
.destroy => layout.destroy(), .destroy => layout.destroy(),
// Parameters of the layout changed. We only care about this, if the
// layout is currently in use, in which case we rearrange the output.
.parameters_changed => if (self == self.output.pending.layout) self.output.arrangeViews(),
// We receive this event when the client wants to push a view dimension proposal // We receive this event when the client wants to push a view dimension proposal
// to the layout demand matching the serial. // to the layout demand matching the serial.
.push_view_dimensions => |req| { .push_view_dimensions => |req| {
@ -171,7 +167,7 @@ fn handleRequest(layout: *river.LayoutV1, request: river.LayoutV1.Request, self:
} }
} }
fn handleDestroy(layout: *river.LayoutV1, self: *Self) void { fn handleDestroy(layout: *river.LayoutV2, self: *Self) void {
log.debug( log.debug(
"destroying layout '{}' on output '{}'", "destroying layout '{}' on output '{}'",
.{ self.namespace, self.output.wlr_output.name }, .{ self.namespace, self.output.wlr_output.name },

View file

@ -21,7 +21,6 @@ const std = @import("std");
const wlr = @import("wlroots"); const wlr = @import("wlroots");
const wayland = @import("wayland"); const wayland = @import("wayland");
const wl = wayland.server.wl; const wl = wayland.server.wl;
const zriver = wayland.server.zriver;
const util = @import("util.zig"); const util = @import("util.zig");

View file

@ -37,7 +37,7 @@ server_destroy: wl.Listener(*wl.Server) = wl.Listener(*wl.Server).init(handleSer
pub fn init(self: *Self, server: *Server) !void { pub fn init(self: *Self, server: *Server) !void {
self.* = .{ self.* = .{
.global = try wl.Global.create(server.wl_server, river.LayoutManagerV1, 1, *Self, self, bind), .global = try wl.Global.create(server.wl_server, river.LayoutManagerV2, 1, *Self, self, bind),
}; };
server.wl_server.addDestroyListener(&self.server_destroy); server.wl_server.addDestroyListener(&self.server_destroy);
@ -49,7 +49,7 @@ fn handleServerDestroy(listener: *wl.Listener(*wl.Server), wl_server: *wl.Server
} }
fn bind(client: *wl.Client, self: *Self, version: u32, id: u32) callconv(.C) void { fn bind(client: *wl.Client, self: *Self, version: u32, id: u32) callconv(.C) void {
const layout_manager = river.LayoutManagerV1.create(client, 1, id) catch { const layout_manager = river.LayoutManagerV2.create(client, 1, id) catch {
client.postNoMemory(); client.postNoMemory();
log.crit("out of memory", .{}); log.crit("out of memory", .{});
return; return;
@ -57,7 +57,7 @@ fn bind(client: *wl.Client, self: *Self, version: u32, id: u32) callconv(.C) voi
layout_manager.setHandler(*Self, handleRequest, null, self); layout_manager.setHandler(*Self, handleRequest, null, self);
} }
fn handleRequest(layout_manager: *river.LayoutManagerV1, request: river.LayoutManagerV1.Request, self: *Self) void { fn handleRequest(layout_manager: *river.LayoutManagerV2, request: river.LayoutManagerV2.Request, self: *Self) void {
switch (request) { switch (request) {
.destroy => layout_manager.destroy(), .destroy => layout_manager.destroy(),

View file

@ -75,7 +75,8 @@ drag_icons: std.SinglyLinkedList(DragIcon) = .{},
xwayland_unmanaged_views: if (build_options.xwayland) xwayland_unmanaged_views: if (build_options.xwayland)
std.TailQueue(XwaylandUnmanaged) std.TailQueue(XwaylandUnmanaged)
else else
void = if (build_options.xwayland) .{}, void = if (build_options.xwayland)
.{},
/// Number of layout demands pending before the transaction may be started. /// Number of layout demands pending before the transaction may be started.
pending_layout_demands: u32 = 0, pending_layout_demands: u32 = 0,

View file

@ -59,12 +59,14 @@ const str_to_impl_fn = [_]struct {
.{ .name = "focus-view", .impl = @import("command/focus_view.zig").focusView }, .{ .name = "focus-view", .impl = @import("command/focus_view.zig").focusView },
.{ .name = "map", .impl = @import("command/map.zig").map }, .{ .name = "map", .impl = @import("command/map.zig").map },
.{ .name = "map-pointer", .impl = @import("command/map.zig").mapPointer }, .{ .name = "map-pointer", .impl = @import("command/map.zig").mapPointer },
.{ .name = "mod-layout-value", .impl = @import("command/layout.zig").modLayoutValue },
.{ .name = "move", .impl = @import("command/move.zig").move }, .{ .name = "move", .impl = @import("command/move.zig").move },
.{ .name = "opacity", .impl = @import("command/opacity.zig").opacity }, .{ .name = "opacity", .impl = @import("command/opacity.zig").opacity },
.{ .name = "output-layout", .impl = @import("command/layout.zig").outputLayout }, .{ .name = "output-layout", .impl = @import("command/layout.zig").outputLayout },
.{ .name = "resize", .impl = @import("command/move.zig").resize }, .{ .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/send_to_output.zig").sendToOutput },
.{ .name = "set-focused-tags", .impl = @import("command/tags.zig").setFocusedTags }, .{ .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 }, .{ .name = "set-repeat", .impl = @import("command/set_repeat.zig").setRepeat },
.{ .name = "set-view-tags", .impl = @import("command/tags.zig").setViewTags }, .{ .name = "set-view-tags", .impl = @import("command/tags.zig").setViewTags },
.{ .name = "snap", .impl = @import("command/move.zig").snap }, .{ .name = "snap", .impl = @import("command/move.zig").snap },
@ -92,6 +94,7 @@ pub const Error = error{
InvalidDirection, InvalidDirection,
InvalidPhysicalDirection, InvalidPhysicalDirection,
InvalidOrientation, InvalidOrientation,
InvalidType,
InvalidRgba, InvalidRgba,
InvalidValue, InvalidValue,
UnknownOption, UnknownOption,
@ -135,6 +138,7 @@ pub fn errToMsg(err: Error) [:0]const u8 {
Error.InvalidDirection => "invalid direction. Must be 'next' or 'previous'", Error.InvalidDirection => "invalid direction. Must be 'next' or 'previous'",
Error.InvalidPhysicalDirection => "invalid direction. Must be 'up', 'down', 'left' or 'right'", Error.InvalidPhysicalDirection => "invalid direction. Must be 'up', 'down', 'left' or 'right'",
Error.InvalidOrientation => "invalid orientation. Must be 'horizontal', or 'vertical'", Error.InvalidOrientation => "invalid orientation. Must be 'horizontal', or 'vertical'",
Error.InvalidType => "invalid type",
Error.InvalidRgba => "invalid color format, must be #RRGGBB or #RRGGBBAA", Error.InvalidRgba => "invalid color format, must be #RRGGBB or #RRGGBBAA",
Error.InvalidValue => "invalid value", Error.InvalidValue => "invalid value",
Error.OutOfMemory => "out of memory", Error.OutOfMemory => "out of memory",

View file

@ -16,6 +16,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const mem = std.mem;
const wl = @import("wayland").server.wl;
const util = @import("../util.zig"); const util = @import("../util.zig");
const Error = @import("../command.zig").Error; const Error = @import("../command.zig").Error;
@ -52,3 +54,98 @@ pub fn defaultLayout(
if (output.layout_namespace == null) output.handleLayoutNamespaceChange(); if (output.layout_namespace == null) output.handleLayoutNamespaceChange();
} }
} }
const SetType = enum {
int,
fixed,
string,
};
/// riverctl set-layout-value rivertile int main_count 42
/// riverctl set-layout-value rivertile fixed main_factor 42.0
/// riverctl set-layout-value rivertile string main_location top
pub fn setLayoutValue(
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 target_namespace = args[1];
const kind = std.meta.stringToEnum(SetType, args[2]) orelse return Error.InvalidType;
const output = seat.focused_output;
var it = output.layouts.first;
const layout = while (it) |node| : (it = node.next) {
const layout = &node.data;
if (mem.eql(u8, layout.namespace, target_namespace)) break layout;
} else return;
const null_terminated_name = try util.gpa.dupeZ(u8, args[3]);
defer util.gpa.free(null_terminated_name);
switch (kind) {
.int => {
const value = try std.fmt.parseInt(i32, args[4], 10);
layout.layout.sendSetIntValue(null_terminated_name, value);
},
.fixed => {
const value = try std.fmt.parseFloat(f64, args[4]);
layout.layout.sendSetFixedValue(null_terminated_name, wl.Fixed.fromDouble(value));
},
.string => {
const null_terminated_value = try util.gpa.dupeZ(u8, args[4]);
defer util.gpa.free(null_terminated_value);
layout.layout.sendSetStringValue(null_terminated_name, null_terminated_value);
},
}
output.arrangeViews();
}
const ModType = enum {
int,
fixed,
};
/// riverctl mode-layout-value rivertile int main_count 42
/// riverctl set-layout-value rivertile fixed main_factor 42.0
pub fn modLayoutValue(
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 target_namespace = args[1];
const kind = std.meta.stringToEnum(ModType, args[2]) orelse return Error.InvalidType;
const output = seat.focused_output;
var it = output.layouts.first;
const layout = while (it) |node| : (it = node.next) {
const layout = &node.data;
if (mem.eql(u8, layout.namespace, target_namespace)) break layout;
} else return;
const null_terminated_name = try util.gpa.dupeZ(u8, args[3]);
defer util.gpa.free(null_terminated_name);
switch (kind) {
.int => {
const value = try std.fmt.parseInt(i32, args[4], 10);
layout.layout.sendModIntValue(null_terminated_name, value);
},
.fixed => {
const value = try std.fmt.parseFloat(f64, args[4]);
layout.layout.sendModFixedValue(null_terminated_name, wl.Fixed.fromDouble(value));
},
}
output.arrangeViews();
}

View file

@ -38,6 +38,7 @@
const std = @import("std"); const std = @import("std");
const mem = std.mem; const mem = std.mem;
const math = std.math;
const assert = std.debug.assert; const assert = std.debug.assert;
const wayland = @import("wayland"); const wayland = @import("wayland");
@ -51,9 +52,7 @@ const Location = enum {
left, left,
}; };
const default_main_location: Location = .left; // TODO: expose these as command line options
const default_main_count = 1;
const default_main_factor = 0.6;
const default_view_padding = 6; const default_view_padding = 6;
const default_outer_padding = 6; const default_outer_padding = 6;
@ -62,7 +61,7 @@ const gpa = std.heap.c_allocator;
const Context = struct { const Context = struct {
initialized: bool = false, initialized: bool = false,
layout_manager: ?*river.LayoutManagerV1 = null, layout_manager: ?*river.LayoutManagerV2 = null,
outputs: std.TailQueue(Output) = .{}, outputs: std.TailQueue(Output) = .{},
fn addOutput(context: *Context, registry: *wl.Registry, name: u32) !void { fn addOutput(context: *Context, registry: *wl.Registry, name: u32) !void {
@ -79,7 +78,11 @@ const Output = struct {
wl_output: *wl.Output, wl_output: *wl.Output,
name: u32, name: u32,
layout: *river.LayoutV1 = undefined, main_location: Location = .left,
main_count: u32 = 1,
main_factor: f64 = 0.6,
layout: *river.LayoutV2 = undefined,
fn init(output: *Output, context: *Context, wl_output: *wl.Output, name: u32) !void { fn init(output: *Output, context: *Context, wl_output: *wl.Output, name: u32) !void {
output.* = .{ .wl_output = wl_output, .name = name }; output.* = .{ .wl_output = wl_output, .name = name };
@ -97,21 +100,53 @@ const Output = struct {
output.layout.destroy(); output.layout.destroy();
} }
fn layoutListener(layout: *river.LayoutV1, event: river.LayoutV1.Event, output: *Output) void { fn layoutListener(layout: *river.LayoutV2, event: river.LayoutV2.Event, output: *Output) void {
switch (event) { switch (event) {
.namespace_in_use => fatal("namespace 'rivertile' already in use.", .{}), .namespace_in_use => fatal("namespace 'rivertile' already in use.", .{}),
.set_int_value => |ev| {
if (mem.eql(u8, mem.span(ev.name), "main_count")) {
if (ev.value > 0) output.main_count = @intCast(u32, ev.value);
}
},
.mod_int_value => |ev| {
if (mem.eql(u8, mem.span(ev.name), "main_count")) {
const result = @as(i33, output.main_count) + ev.delta;
if (result > 0) output.main_count = @intCast(u32, result);
}
},
.set_fixed_value => |ev| {
if (mem.eql(u8, mem.span(ev.name), "main_factor")) {
output.main_factor = math.clamp(ev.value.toDouble(), 0.1, 0.9);
}
},
.mod_fixed_value => |ev| {
if (mem.eql(u8, mem.span(ev.name), "main_factor")) {
const new_value = ev.delta.toDouble() + output.main_factor;
output.main_factor = math.clamp(new_value, 0.1, 0.9);
}
},
.set_string_value => |ev| {
if (mem.eql(u8, mem.span(ev.name), "main_location")) {
if (std.meta.stringToEnum(Location, mem.span(ev.value))) |new_location| {
output.main_location = new_location;
}
}
},
.layout_demand => |ev| { .layout_demand => |ev| {
const secondary_count = if (ev.view_count > default_main_count) const secondary_count = if (ev.view_count > output.main_count)
ev.view_count - default_main_count ev.view_count - output.main_count
else else
0; 0;
const usable_width = switch (default_main_location) { const usable_width = switch (output.main_location) {
.left, .right => ev.usable_width - 2 * default_outer_padding, .left, .right => ev.usable_width - 2 * default_outer_padding,
.top, .bottom => ev.usable_height - 2 * default_outer_padding, .top, .bottom => ev.usable_height - 2 * default_outer_padding,
}; };
const usable_height = switch (default_main_location) { const usable_height = switch (output.main_location) {
.left, .right => ev.usable_height - 2 * default_outer_padding, .left, .right => ev.usable_height - 2 * default_outer_padding,
.top, .bottom => ev.usable_width - 2 * default_outer_padding, .top, .bottom => ev.usable_width - 2 * default_outer_padding,
}; };
@ -126,18 +161,18 @@ const Output = struct {
var secondary_height: u32 = undefined; var secondary_height: u32 = undefined;
var secondary_height_rem: u32 = undefined; var secondary_height_rem: u32 = undefined;
if (default_main_count > 0 and secondary_count > 0) { if (output.main_count > 0 and secondary_count > 0) {
main_width = @floatToInt(u32, default_main_factor * @intToFloat(f64, usable_width)); main_width = @floatToInt(u32, output.main_factor * @intToFloat(f64, usable_width));
main_height = usable_height / default_main_count; main_height = usable_height / output.main_count;
main_height_rem = usable_height % default_main_count; main_height_rem = usable_height % output.main_count;
secondary_width = usable_width - main_width; secondary_width = usable_width - main_width;
secondary_height = usable_height / secondary_count; secondary_height = usable_height / secondary_count;
secondary_height_rem = usable_height % secondary_count; secondary_height_rem = usable_height % secondary_count;
} else if (default_main_count > 0) { } else if (output.main_count > 0) {
main_width = usable_width; main_width = usable_width;
main_height = usable_height / default_main_count; main_height = usable_height / output.main_count;
main_height_rem = usable_height % default_main_count; main_height_rem = usable_height % output.main_count;
} else if (secondary_width > 0) { } else if (secondary_width > 0) {
main_width = 0; main_width = 0;
secondary_width = usable_width; secondary_width = usable_width;
@ -152,17 +187,17 @@ const Output = struct {
var width: u32 = undefined; var width: u32 = undefined;
var height: u32 = undefined; var height: u32 = undefined;
if (i < default_main_count) { if (i < output.main_count) {
x = 0; x = 0;
y = @intCast(i32, (i * main_height) + if (i > 0) main_height_rem else 0); y = @intCast(i32, (i * main_height) + if (i > 0) main_height_rem else 0);
width = main_width; width = main_width;
height = main_height + if (i == 0) main_height_rem else 0; height = main_height + if (i == 0) main_height_rem else 0;
} else { } else {
x = @intCast(i32, main_width); x = @intCast(i32, main_width);
y = @intCast(i32, (i - default_main_count) * secondary_height + y = @intCast(i32, (i - output.main_count) * secondary_height +
if (i > default_main_count) secondary_height_rem else 0); if (i > output.main_count) secondary_height_rem else 0);
width = secondary_width; width = secondary_width;
height = secondary_height + if (i == default_main_count) secondary_height_rem else 0; height = secondary_height + if (i == output.main_count) secondary_height_rem else 0;
} }
x += @intCast(i32, default_view_padding); x += @intCast(i32, default_view_padding);
@ -170,7 +205,7 @@ const Output = struct {
width -= 2 * default_view_padding; width -= 2 * default_view_padding;
height -= 2 * default_view_padding; height -= 2 * default_view_padding;
switch (default_main_location) { switch (output.main_location) {
.left => layout.pushViewDimensions( .left => layout.pushViewDimensions(
ev.serial, ev.serial,
x + @intCast(i32, default_outer_padding), x + @intCast(i32, default_outer_padding),
@ -242,8 +277,8 @@ pub fn main() !void {
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void { fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void {
switch (event) { switch (event) {
.global => |global| { .global => |global| {
if (std.cstr.cmp(global.interface, river.LayoutManagerV1.getInterface().name) == 0) { if (std.cstr.cmp(global.interface, river.LayoutManagerV2.getInterface().name) == 0) {
context.layout_manager = registry.bind(global.name, river.LayoutManagerV1, 1) catch return; context.layout_manager = registry.bind(global.name, river.LayoutManagerV2, 1) catch return;
} else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) { } else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) {
context.addOutput(registry, global.name) catch |err| fatal("failed to bind output: {}", .{err}); context.addOutput(registry, global.name) catch |err| fatal("failed to bind output: {}", .{err});
} }