diff --git a/build.zig b/build.zig index d2f43ee..389dff5 100644 --- a/build.zig +++ b/build.zig @@ -55,7 +55,7 @@ pub fn build(b: *zbs.Builder) !void { scanner.addSystemProtocol("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"); scanner.addProtocolPath("protocol/river-control-unstable-v1.xml"); scanner.addProtocolPath("protocol/river-status-unstable-v1.xml"); - scanner.addProtocolPath("protocol/river-layout-v2.xml"); + scanner.addProtocolPath("protocol/river-layout-v3.xml"); scanner.addProtocolPath("protocol/wlr-layer-shell-unstable-v1.xml"); scanner.addProtocolPath("protocol/wlr-output-power-management-unstable-v1.xml"); diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd index a485a73..994c78d 100644 --- a/doc/riverctl.1.scd +++ b/doc/riverctl.1.scd @@ -90,15 +90,11 @@ over the Wayland protocol. Set the layout namespace of currently focused output, overriding 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. +*send-layout-cmd* _namespace_ _command_ + Send _command_ to the layout client on the currently focused output + with the given _namespace_, if any. What commands a layout client + understands depends on the layout client. For rivertile, see the + documentation in the *rivertile*(1) man page. ## TAG MANAGEMENT diff --git a/doc/rivertile.1.scd b/doc/rivertile.1.scd index c0658c1..d12a4ce 100644 --- a/doc/rivertile.1.scd +++ b/doc/rivertile.1.scd @@ -32,22 +32,33 @@ modified while rivertile is running with the help of *riverctl*(1). Set the initial number of views in the main area of the layout. (Default: 1) -*-main-factor* _ratio_ - Set the initial ratio of main area to total layout area. (Default: 0.6) +*-main-ratio* _ratio_ + Set the initial ratio of main area to total layout area. The _ratio_ + must be between 0.1 and 0.9, inclusive. (Default: 0.6) -# VALUES +# COMMANDS -These values may be modified while rivertile is running with the help of +These commands may be sent to rivertile at runtime with the help of *riverctl*(1). -_main_location_ (string: top, bottom, left, or right) - The location of the main area in the layout. +*set-main-location* [*top*|*bottom*|*left*|*right*] + Set the location of the main area in the layout. -_main_count_ (int) - The number of views in the main area of the layout. +*set-main-count* _count_ + Set 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. +*mod-main-count* _delta_ + Modify the number of views in the main area of the layout by a + positive or negative _delta_. + +*set-main-ratio* _ratio_ + Set the ratio of main area to total layout area. The _ratio_ must + be between 0.1 and 0.9, inclusive. + +*mod-main-ratio* _delta_ + Modify the ratio of main area to total layout area by a positive or + negative _delta_. The resulting ratio will be clamped to be between + 0.1 and 0.9, inclusive. # EXAMPLES @@ -55,9 +66,9 @@ Start *rivertile* with 4 pixels outer padding and 2 main views: rivertile -outer-padding 4 -main-count 2 -Set the main location of rivertile to top at runtime: +Set the main location of rivertile to *top* at runtime: - riverctl set-layout-value rivertile string main_location top + riverctl send-layout-cmd rivertile "set-main-location top" # AUTHORS diff --git a/example/init b/example/init index 07e0347..886beae 100755 --- a/example/init +++ b/example/init @@ -40,13 +40,13 @@ riverctl map normal $mod+Shift Comma send-to-output previous # Mod+Return to bump the focused view to the top of the layout stack riverctl map normal $mod Return zoom -# Mod+H and Mod+L to decrease/increase the main_factor value of rivertile by 0.05 -riverctl map normal $mod H mod-layout-value rivertile fixed main_factor -0.05 -riverctl map normal $mod L mod-layout-value rivertile fixed main_factor +0.05 +# Mod+H and Mod+L to decrease/increase the main ratio of rivertile(1) +riverctl map normal $mod H send-layout-cmd rivertile "mod-main-ratio -0.05" +riverctl map normal $mod L send-layout-cmd rivertile "mod-main-ratio +0.05" -# Mod+Shift+H and Mod+Shift+L to increment/decrement the main_count value of rivertile. -riverctl map normal $mod+Shift H mod-layout-value rivertile int main_count +1 -riverctl map normal $mod+Shift L mod-layout-value rivertile int main_count -1 +# Mod+Shift+H and Mod+Shift+L to increment/decrement the main count of rivertile(1) +riverctl map normal $mod+Shift H send-layout-cmd rivertile "mod-main-count +1" +riverctl map normal $mod+Shift L send-layout-cmd rivertile "mod-main-count -1" # Mod+Alt+{H,J,K,L} to move views riverctl map normal $mod+Mod1 H move left 100 @@ -102,10 +102,10 @@ riverctl map normal $mod Space toggle-float riverctl map normal $mod F toggle-fullscreen # Mod+{Up,Right,Down,Left} to change layout orientation -riverctl map normal $mod Up set-layout-value rivertile string main_location top -riverctl map normal $mod Right set-layout-value rivertile string main_location right -riverctl map normal $mod Down set-layout-value rivertile string main_location bottom -riverctl map normal $mod Left set-layout-value rivertile string main_location left +riverctl map normal $mod Up send-layout-cmd rivertile "set-main-location top" +riverctl map normal $mod Right send-layout-cmd rivertile "set-main-location right" +riverctl map normal $mod Down send-layout-cmd rivertile "set-main-location bottom" +riverctl map normal $mod Left send-layout-cmd rivertile "set-main-location left" # Declare a passthrough mode. This mode has only a single mapping to return to # normal mode. This makes it useful for testing a nested wayland compositor diff --git a/protocol/river-layout-v2.xml b/protocol/river-layout-v2.xml deleted file mode 100644 index f0c82e0..0000000 --- a/protocol/river-layout-v2.xml +++ /dev/null @@ -1,256 +0,0 @@ - - - - Copyright 2020-2021 The River Developers - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - - 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 - the river_layout_v2 object. - - This set of views is logically structured as a simple list. Views - in this list cannot be individually addressed, instead the order of - requests/events is significant. - - The entire set of proposed positions and dimensions for the views in the - list are called a layout. Due to their list heritage, layouts are also - logically strictly linear; Any complex underlying data structure a client - may use when generating the layout is lost in transmission. This is an - intentional limitation. - - Note that the client may need to handle multiple layout demands per - river_layout_v2 object simultaneously. - - Warning! The protocol described in this file is currently in the testing - phase. Backward compatible changes may be added together with the - corresponding interface version bump. Backward incompatible changes can - only be done by creating a new major version of the extension. - - - - - A global factory for river_layout_v2 objects. - - - - - This request indicates that the client will not use the - river_layout_manager object any more. Objects that have been created - through this instance are not affected. - - - - - - This creates a new river_layout_v2 object for the given wl_output. - - All layout related communication is done through this interface. - - The namespace is used by the compositor to decide which river_layout_v2 - object will receive layout demands for the output. - - The namespace is required to be be unique per-output. Furthermore, - two separate clients may not share a namespace on separate outputs. If - these conditions are not upheld, the the namespace_in_use event will - be sent directly after creation of the river_layout_v2 object. - - - - - - - - - - This interface allows clients to receive layout demands from the - compositor for a specific output and subsequently propose positions and - dimensions of individual views. - - - - - - - - - - This request indicates that the client will not use the river_layout_v2 - object any more. - - - - - - 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 - a different namespace they must create a new river_layout_v2 object. - - - - - - The compositor sends this event to inform the client that it requires a - layout for a set of views. - - The usable width and height height indicate the space in which the - client can safely position views without interfering with desktop - widgets such as panels. - - The serial of this event is used to identify subsequent events and - request as belonging to this layout demand. Beware that the client - might need to handle multiple layout demands at the same time. - - The server will ignore responses to all but the most recent - layout demand. Thus, clients are only required to respond to the most - recent layout_demand received. If a newer layout_demand is received - before the client has finished responding to an old demand, the client - may abort work on the old demand as any further work would be wasted. - - - - - - - - - - - This event is sent by the server as part of the layout demand with - matching serial. It provides additional information about one of - the views to be arranged. - - Every view part of the layout demand is advertised exactly once, - in the order of the view list. - - - - - - - - - This event is sent by the server as the last event of the layout - demand with matching serial, after all advertise_view events. - - - - - - - This request proposes a size and position of a view in the layout demand - with matching serial. - - Pushed view dimensions apply to the views in the same order they were - advertised. That is, the first push_view_dimensions request applies - to the first view advertised, the second to the second, and so on. - - A client must propose position and dimensions for the entire set of - views. Proposing too many or too few view dimensions is a protocol error. - - This request may be sent before the corresponding view has been - advertised. - - The x and y coordinates are relative to the usable area of the output, - with (0,0) as the top left corner. - - - - - - - - - - - This request indicates that the client is done pushing dimensions - and the compositor may apply the layout. This completes the layout - demand with matching serial, any other requests sent with the serial - are a protocol error. - - The compositor is free to use this proposed layout however it chooses, - including ignoring it. - - - - - - - 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) - - - - - - - - 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) - - - - - - - - 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) - - - - - - - - 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) - - - - - - - - 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) - - - - - - diff --git a/protocol/river-layout-v3.xml b/protocol/river-layout-v3.xml new file mode 100644 index 0000000..3363355 --- /dev/null +++ b/protocol/river-layout-v3.xml @@ -0,0 +1,181 @@ + + + + Copyright 2020-2021 The River Developers + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + + 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 the river_layout_v3 object. + + Layouts are a strictly linear list of views, the position and dimensions + of which are supplied by the client. Any complex underlying data structure + a client may use when generating the layout is lost in transmission. This + is an intentional limitation. + + Additonally, this protocol allows the compositor to deliver arbitrary + user-provided commands associated with a layout to clients. A client + may use these commands to implement runtime configuration/control, or + may ignore them entirely. How the user provides these commands to the + compositor is not specified by this protocol and left to compositor policy. + + Warning! The protocol described in this file is currently in the + testing phase. Backward compatible changes may be added together with + the corresponding interface version bump. Backward incompatible changes + can only be done by creating a new major version of the extension. + + + + + A global factory for river_layout_v3 objects. + + + + + This request indicates that the client will not use the + river_layout_manager object any more. Objects that have been created + through this instance are not affected. + + + + + + This creates a new river_layout_v3 object for the given wl_output. + + All layout related communication is done through this interface. + + The namespace is used by the compositor to decide which river_layout_v3 + object will receive layout demands for the output. + + The namespace is required to be be unique per-output. Furthermore, + two separate clients may not share a namespace on separate outputs. If + these conditions are not upheld, the the namespace_in_use event will + be sent directly after creation of the river_layout_v3 object. + + + + + + + + + + This interface allows clients to receive layout demands from the + compositor for a specific output and subsequently propose positions and + dimensions of individual views. + + + + + + + + + + This request indicates that the client will not use the river_layout_v3 + object any more. + + + + + + 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 + a different namespace they must create a new river_layout_v3 object. + + + + + + The compositor sends this event to inform the client that it requires a + layout for a set of views. + + The usable width and height height indicate the space in which the + client can safely position views without interfering with desktop + widgets such as panels. + + The serial of this event is used to identify subsequent requests as + belonging to this layout demand. Beware that the client might need + to handle multiple layout demands at the same time. + + The server will ignore responses to all but the most recent layout + demand. Thus, clients are only required to respond to the most recent + layout_demand received. If a newer layout_demand is received before + the client has finished responding to an old demand, the client should + abort work on the old demand as any further work would be wasted. + + + + + + + + + + + This request proposes a size and position for a view in the layout demand + with matching serial. + + A client must send this request for every view that is part of the + layout demand. The number of views in the layout is given by the + view_count argument of the layout_demand event. Pushing too many or + too few view dimensions is a protocol error. + + The x and y coordinates are relative to the usable area of the output, + with (0,0) as the top left corner. + + + + + + + + + + + This request indicates that the client is done pushing dimensions + and the compositor may apply the layout. This completes the layout + demand with matching serial, any other requests sent with the serial + are a protocol error. + + The layout_name argument is a user-facing name or short description + of the layout that is being committed. The compositor may for example + display this on a status bar, though what exactly is done with it is + left to the compositor's discretion. + + The compositor is free to use this proposed layout however it chooses, + including ignoring it. + + + + + + + + This event informs the client of a command sent to it by the user. + + The semantic meaning of the command is left for the client to + decide. It is also free to ignore it entirely if it so chooses. + + A layout_demand will be sent after this event if the compositor is + currently using this layout object to arrange the output. + + + + + diff --git a/river/Layout.zig b/river/Layout.zig index eca3d7b..9fe6109 100644 --- a/river/Layout.zig +++ b/river/Layout.zig @@ -36,12 +36,12 @@ const LayoutDemand = @import("LayoutDemand.zig"); const log = std.log.scoped(.layout); -layout: *river.LayoutV2, +layout: *river.LayoutV3, namespace: []const u8, output: *Output, pub fn create(client: *wl.Client, version: u32, id: u32, output: *Output, namespace: []const u8) !void { - const layout = try river.LayoutV2.create(client, version, id); + const layout = try river.LayoutV3.create(client, version, id); if (namespaceInUse(namespace, output, client)) { layout.sendNamespaceInUse(); @@ -92,7 +92,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 /// namespace already being in use) until the client destroys them. -fn handleRequestInert(layout: *river.LayoutV2, request: river.LayoutV2.Request, _: ?*c_void) void { +fn handleRequestInert(layout: *river.LayoutV3, request: river.LayoutV3.Request, _: ?*c_void) void { if (request == .destroy) layout.destroy(); } @@ -108,28 +108,19 @@ pub fn startLayoutDemand(self: *Self, views: u32) void { log.err("failed starting layout demand", .{}); return; }; - const serial = self.output.layout_demand.?.serial; - // Then we let the client know that we require a layout self.layout.sendLayoutDemand( views, self.output.usable_box.width, self.output.usable_box.height, self.output.pending.tags, - serial, + self.output.layout_demand.?.serial, ); - // And finally we advertise all visible views - var it = ViewStack(View).iter(self.output.views.first, .forward, self.output.pending.tags, Output.arrangeFilter); - while (it.next()) |view| { - self.layout.sendAdvertiseView(view.pending.tags, view.getAppId(), serial); - } - self.layout.sendAdvertiseDone(serial); - server.root.trackLayoutDemands(); } -fn handleRequest(layout: *river.LayoutV2, request: river.LayoutV2.Request, self: *Self) void { +fn handleRequest(layout: *river.LayoutV3, request: river.LayoutV3.Request, self: *Self) void { switch (request) { .destroy => layout.destroy(), @@ -168,7 +159,7 @@ fn handleRequest(layout: *river.LayoutV2, request: river.LayoutV2.Request, self: } } -fn handleDestroy(layout: *river.LayoutV2, self: *Self) void { +fn handleDestroy(layout: *river.LayoutV3, self: *Self) void { self.destroy(); } diff --git a/river/LayoutManager.zig b/river/LayoutManager.zig index 98df1c7..168a0e5 100644 --- a/river/LayoutManager.zig +++ b/river/LayoutManager.zig @@ -38,7 +38,7 @@ server_destroy: wl.Listener(*wl.Server) = wl.Listener(*wl.Server).init(handleSer pub fn init(self: *Self) !void { self.* = .{ - .global = try wl.Global.create(server.wl_server, river.LayoutManagerV2, 1, *Self, self, bind), + .global = try wl.Global.create(server.wl_server, river.LayoutManagerV3, 1, *Self, self, bind), }; server.wl_server.addDestroyListener(&self.server_destroy); @@ -50,7 +50,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 { - const layout_manager = river.LayoutManagerV2.create(client, 1, id) catch { + const layout_manager = river.LayoutManagerV3.create(client, 1, id) catch { client.postNoMemory(); log.crit("out of memory", .{}); return; @@ -58,7 +58,7 @@ fn bind(client: *wl.Client, self: *Self, version: u32, id: u32) callconv(.C) voi layout_manager.setHandler(*Self, handleRequest, null, self); } -fn handleRequest(layout_manager: *river.LayoutManagerV2, request: river.LayoutManagerV2.Request, self: *Self) void { +fn handleRequest(layout_manager: *river.LayoutManagerV3, request: river.LayoutManagerV3.Request, self: *Self) void { switch (request) { .destroy => layout_manager.destroy(), diff --git a/river/command.zig b/river/command.zig index b3fc953..47413ee 100644 --- a/river/command.zig +++ b/river/command.zig @@ -64,14 +64,13 @@ const str_to_impl_fn = [_]struct { .{ .name = "list-inputs", .impl = @import("command/input.zig").listInputs }, .{ .name = "map", .impl = @import("command/map.zig").map }, .{ .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 = "output-layout", .impl = @import("command/layout.zig").outputLayout }, .{ .name = "resize", .impl = @import("command/move.zig").resize }, + .{ .name = "send-layout-cmd", .impl = @import("command/layout.zig").sendLayoutCmd }, .{ .name = "send-to-output", .impl = @import("command/output.zig").sendToOutput }, .{ .name = "set-cursor-warp", .impl = @import("command/config.zig").setCursorWarp }, .{ .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-view-tags", .impl = @import("command/tags.zig").setViewTags }, .{ .name = "snap", .impl = @import("command/move.zig").snap }, @@ -99,7 +98,6 @@ pub const Error = error{ InvalidButton, InvalidCharacter, InvalidDirection, - InvalidType, InvalidPhysicalDirection, InvalidOrientation, InvalidRgba, @@ -144,7 +142,6 @@ pub fn errToMsg(err: Error) [:0]const u8 { Error.InvalidButton => "invalid button", Error.InvalidCharacter => "invalid character in argument", Error.InvalidDirection => "invalid direction. Must be 'next' or 'previous'", - Error.InvalidType => "invalid type", Error.InvalidPhysicalDirection => "invalid direction. Must be 'up', 'down', 'left' or 'right'", Error.InvalidOrientation => "invalid orientation. Must be 'horizontal', or 'vertical'", Error.InvalidRgba => "invalid color format, must be #RRGGBB or #RRGGBBAA", diff --git a/river/command/layout.zig b/river/command/layout.zig index 5fcf432..da67e82 100644 --- a/river/command/layout.zig +++ b/river/command/layout.zig @@ -56,28 +56,20 @@ pub fn defaultLayout( } } -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( +/// riverctl send-layout-cmd rivertile "mod-main-count 1" +/// riverctl send-layout-cmd rivertile "mod-main-factor -0.1" +/// riverctl send-layout-cmd rivertile "main-location top" +pub fn sendLayoutCmd( allocator: *std.mem.Allocator, seat: *Seat, args: []const [:0]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; + if (args.len < 3) return Error.NotEnoughArguments; + if (args.len > 3) return Error.TooManyArguments; const output = seat.focused_output; + const target_namespace = args[1]; var it = output.layouts.first; const layout = while (it) |node| : (it = node.next) { @@ -85,68 +77,7 @@ pub fn setLayoutValue( 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 [:0]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)); - }, - } + layout.layout.sendUserCommand(args[2]); output.arrangeViews(); } diff --git a/rivertile/main.zig b/rivertile/main.zig index d2c88ac..6600563 100644 --- a/rivertile/main.zig +++ b/rivertile/main.zig @@ -59,11 +59,19 @@ const usage = \\ layout. (Default left) \\ -main-count Set the initial number of views in the main area of the \\ layout. (Default 1) - \\ -main-factor Set the initial ratio of main area to total layout + \\ -main-ratio Set the initial ratio of main area to total layout \\ area. (Default: 0.6) \\ ; +const Command = enum { + @"set-main-location", + @"set-main-count", + @"mod-main-count", + @"set-main-ratio", + @"mod-main-ratio", +}; + const Location = enum { top, right, @@ -76,14 +84,14 @@ var view_padding: u32 = 6; var outer_padding: u32 = 6; var default_main_location: Location = .left; var default_main_count: u32 = 1; -var default_main_factor: f64 = 0.6; +var default_main_ratio: f64 = 0.6; /// We don't free resources on exit, only when output globals are removed. const gpa = std.heap.c_allocator; const Context = struct { initialized: bool = false, - layout_manager: ?*river.LayoutManagerV2 = null, + layout_manager: ?*river.LayoutManagerV3 = null, outputs: std.TailQueue(Output) = .{}, fn addOutput(context: *Context, registry: *wl.Registry, name: u32) !void { @@ -102,9 +110,9 @@ const Output = struct { main_location: Location, main_count: u32, - main_factor: f64, + main_ratio: f64, - layout: *river.LayoutV2 = undefined, + layout: *river.LayoutV3 = undefined, fn init(output: *Output, context: *Context, wl_output: *wl.Output, name: u32) !void { output.* = .{ @@ -112,7 +120,7 @@ const Output = struct { .name = name, .main_location = default_main_location, .main_count = default_main_count, - .main_factor = default_main_factor, + .main_ratio = default_main_ratio, }; if (context.initialized) try output.getLayout(context); } @@ -128,39 +136,63 @@ const Output = struct { output.layout.destroy(); } - fn layoutListener(layout: *river.LayoutV2, event: river.LayoutV2.Event, output: *Output) void { + fn layoutListener(layout: *river.LayoutV3, event: river.LayoutV3.Event, output: *Output) void { switch (event) { .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); + .user_command => |ev| { + var it = mem.tokenize(mem.span(ev.command), " "); + const raw_cmd = it.next() orelse { + std.log.err("not enough arguments", .{}); + return; + }; + const raw_arg = it.next() orelse { + std.log.err("not enough arguments", .{}); + return; + }; + if (it.next() != null) { + std.log.err("too many arguments", .{}); + return; } - }, - .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; - } + const cmd = std.meta.stringToEnum(Command, raw_cmd) orelse { + std.log.err("unknown command: {s}", .{raw_cmd}); + return; + }; + switch (cmd) { + .@"set-main-location" => { + output.main_location = std.meta.stringToEnum(Location, raw_arg) orelse { + std.log.err("unknown location: {s}", .{raw_arg}); + return; + }; + }, + .@"set-main-count" => { + output.main_count = std.fmt.parseInt(u32, raw_arg, 10) catch |err| { + std.log.err("failed to parse argument: {}", .{err}); + return; + }; + }, + .@"mod-main-count" => { + const arg = std.fmt.parseInt(i32, raw_arg, 10) catch |err| { + std.log.err("failed to parse argument: {}", .{err}); + return; + }; + const result = math.add(i33, output.main_count, arg) catch math.maxInt(u32); + if (result > 0) output.main_count = @intCast(u32, result); + }, + .@"set-main-ratio" => { + const arg = std.fmt.parseFloat(f64, raw_arg) catch |err| { + std.log.err("failed to parse argument: {}", .{err}); + return; + }; + output.main_ratio = math.clamp(arg, 0.1, 0.9); + }, + .@"mod-main-ratio" => { + const arg = std.fmt.parseFloat(f64, raw_arg) catch |err| { + std.log.err("failed to parse argument: {}", .{err}); + return; + }; + output.main_ratio = math.clamp(output.main_ratio + arg, 0.1, 0.9); + }, } }, @@ -191,7 +223,7 @@ const Output = struct { var secondary_height_rem: u32 = undefined; if (main_count > 0 and secondary_count > 0) { - main_width = @floatToInt(u32, output.main_factor * @intToFloat(f64, usable_width)); + main_width = @floatToInt(u32, output.main_ratio * @intToFloat(f64, usable_width)); main_height = usable_height / main_count; main_height_rem = usable_height % main_count; @@ -236,41 +268,43 @@ const Output = struct { switch (output.main_location) { .left => layout.pushViewDimensions( - ev.serial, x + @intCast(i32, outer_padding), y + @intCast(i32, outer_padding), width, height, + ev.serial, ), .right => layout.pushViewDimensions( - ev.serial, @intCast(i32, usable_width - width) - x + @intCast(i32, outer_padding), y + @intCast(i32, outer_padding), width, height, + ev.serial, ), .top => layout.pushViewDimensions( - ev.serial, y + @intCast(i32, outer_padding), x + @intCast(i32, outer_padding), height, width, + ev.serial, ), .bottom => layout.pushViewDimensions( - ev.serial, y + @intCast(i32, outer_padding), @intCast(i32, usable_width - width) - x + @intCast(i32, outer_padding), height, width, + ev.serial, ), } } - layout.commit(ev.serial); + switch (output.main_location) { + .left => layout.commit("rivertile - left", ev.serial), + .right => layout.commit("rivertile - right", ev.serial), + .top => layout.commit("rivertile - top", ev.serial), + .bottom => layout.commit("rivertile - bottom", ev.serial), + } }, - - .advertise_view => {}, - .advertise_done => {}, } } }; @@ -285,7 +319,7 @@ pub fn main() !void { .{ .name = "-outer-padding", .kind = .arg }, .{ .name = "-main-location", .kind = .arg }, .{ .name = "-main-count", .kind = .arg }, - .{ .name = "-main-factor", .kind = .arg }, + .{ .name = "-main-ratio", .kind = .arg }, }).parse(argv[1..]); if (args.boolFlag("-h") or args.boolFlag("--help")) { @@ -308,9 +342,9 @@ pub fn main() !void { default_main_count = std.fmt.parseUnsigned(u32, mem.span(raw), 10) catch fatal("invalid value '{s}' provided to -main-count", .{raw}); } - if (args.argFlag("-main-factor")) |raw| { - default_main_factor = std.fmt.parseFloat(f64, mem.span(raw)) catch - fatal("invalid value '{s}' provided to -main-factor", .{raw}); + if (args.argFlag("-main-ratio")) |raw| { + default_main_ratio = std.fmt.parseFloat(f64, mem.span(raw)) catch + fatal("invalid value '{s}' provided to -main-ratio", .{raw}); } const display = wl.Display.connect(null) catch { @@ -343,8 +377,8 @@ pub fn main() !void { fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void { switch (event) { .global => |global| { - if (std.cstr.cmp(global.interface, river.LayoutManagerV2.getInterface().name) == 0) { - context.layout_manager = registry.bind(global.name, river.LayoutManagerV2, 1) catch return; + if (std.cstr.cmp(global.interface, river.LayoutManagerV3.getInterface().name) == 0) { + context.layout_manager = registry.bind(global.name, river.LayoutManagerV3, 1) catch return; } 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}); }