diff --git a/build.zig b/build.zig
index ec4c502..dc09c77 100644
--- a/build.zig
+++ b/build.zig
@@ -64,7 +64,7 @@ pub fn build(b: *zbs.Builder) !void {
scanner.addSystemProtocol("unstable/xdg-output/xdg-output-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-options-unstable-v1.xml");
+ scanner.addProtocolPath("protocol/river-options-v2.xml");
scanner.addProtocolPath("protocol/river-status-unstable-v1.xml");
scanner.addProtocolPath("protocol/river-layout-v1.xml");
scanner.addProtocolPath("protocol/wlr-layer-shell-unstable-v1.xml");
diff --git a/completions/bash/riverctl b/completions/bash/riverctl
index e744cd1..4a34c03 100644
--- a/completions/bash/riverctl
+++ b/completions/bash/riverctl
@@ -45,6 +45,7 @@ function __riverctl_completion ()
declare-option \
get-option \
set-option \
+ unset-option \
mod-option"
COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[1]}"))
elif [ "${COMP_CWORD}" -eq 2 ]
@@ -56,7 +57,7 @@ function __riverctl_completion ()
"map"|"unmap") OPTS="-release" ;;
"attach-mode") OPTS="top bottom" ;;
"focus-follows-cursor") OPTS="disabled normal strict" ;;
- "declare-option"|"get-option"|"set-option"|"mod-option") OPTS="-output -focused-output" ;;
+ "get-option"|"set-option"|"unset-option"|"mod-option") OPTS="-output -focused-output" ;;
*) return ;;
esac
COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[2]}"))
@@ -66,4 +67,3 @@ function __riverctl_completion ()
}
complete -F __riverctl_completion riverctl
-
diff --git a/completions/fish/riverctl.fish b/completions/fish/riverctl.fish
index 1bdfe61..281805a 100644
--- a/completions/fish/riverctl.fish
+++ b/completions/fish/riverctl.fish
@@ -1,6 +1,6 @@
function __fish_riverctl_complete_no_subcommand
for i in (commandline -opc)
- if contains -- $i close csd-filter-add exit float-filter-add focus-output focus-view layout mod-main-count mod-main-factor move resize snap send-to-output spawn swap toggle-float toggle-fullscreen zoom 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 outer-padding set-repeat view-padding xcursor-theme declare-option get-option set-option mod-option output_title
+ if contains -- $i close csd-filter-add exit float-filter-add focus-output focus-view layout mod-main-count mod-main-factor move resize snap send-to-output spawn swap toggle-float toggle-fullscreen zoom 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 outer-padding set-repeat view-padding xcursor-theme declare-option get-option set-option unset-option mod-option output_title
return 1
end
end
@@ -55,6 +55,7 @@ complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a xcursor-t
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a declare-option -d 'Declare a new option with the given type and initial value'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a get-option -d 'Print the current value of the given option to stdout'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a set-option -d 'Set the value of the specified option'
+complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a unset-option -d 'Unset the value of the specified option for the given output'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a mod-option -d 'Add value to the value of the specified option'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a output_title -d 'Changing this option changes the title of the Wayland and X11 backend outputs'
@@ -71,8 +72,7 @@ complete -c riverctl -x -n '__fish_seen_subcommand_from map' -a
complete -c riverctl -x -n '__fish_seen_subcommand_from unmap' -a '-release'
complete -c riverctl -x -n '__fish_seen_subcommand_from attach-mode' -a 'top bottom'
complete -c riverctl -x -n '__fish_seen_subcommand_from focus-follows-cursor' -a 'disabled normal strict'
-complete -c riverctl -x -n '__fish_seen_subcommand_from declare-option' -a '-output -focused-output'
complete -c riverctl -x -n '__fish_seen_subcommand_from get-option' -a '-output -focused-output'
complete -c riverctl -x -n '__fish_seen_subcommand_from set-option' -a '-output -focused-output'
+complete -c riverctl -x -n '__fish_seen_subcommand_from unset-option' -a '-output -focused-output'
complete -c riverctl -x -n '__fish_seen_subcommand_from mod-option' -a '-output -focused-output'
-
diff --git a/completions/zsh/_riverctl b/completions/zsh/_riverctl
index 76918f5..23a706b 100644
--- a/completions/zsh/_riverctl
+++ b/completions/zsh/_riverctl
@@ -61,6 +61,7 @@ _riverctl() {
'declare-option:Declare a new option with the given type and initial value'
'get-option:Print the current value of the given option to stdout'
'set-option:Set the value of the specified option'
+ 'unset-option:Unset the value of the specified option for the given output'
'mod-option:Add value to the value of the specified option'
'output_title:Changing this option changes the title of the Wayland and X11 backend outputs'
)
@@ -87,13 +88,12 @@ _riverctl() {
unmap) _alternative 'arguments:optional:(-release)' ;;
attach-mode) _attach ;;
focus-follows-cursor) _focus_cursor ;;
- declare-option) _river_opts ;;
get-option) _river_opts ;;
set-option) _river_opts ;;
+ unset-option) _river_opts ;;
mod-option) _river_opts ;;
*) return 0 ;;
esac
return 1
}
-
diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd
index 0d17e44..a5c444d 100644
--- a/doc/riverctl.1.scd
+++ b/doc/riverctl.1.scd
@@ -259,11 +259,15 @@ scoped either globally or per-output if the *-output* flag is passed with the
name of the output as obtained from the xdg-output protocol. Alternatively,
the currently focused output may be targeted with the *-focused-output* flag.
-*declare-option* [*-output* _output_name_|*-focused-output*] _name_ _type_ _value_
+*declare-option* _name_ _type_ _value_
Declare a new option with the given _type_ and initial _value_. If
- the option already exists with the given _type_, it is still set
- to _value_. If the option already exists with a different type,
- nothing happens.
+ the option already exists, this command does nothing. The following
+ types are available:
+
+ - _int_: a signed 32-bit integer
+ - _uint_: an unsigned 32-bit integer
+ - _fixed_: a signed 24.8 bit fixed point number
+ - _string_: a string of bytes, may be null
*get-option* [*-output* _output_name_|*-focused-output*] _name_
Print the current value of the given option to stdout.
@@ -271,36 +275,24 @@ the currently focused output may be targeted with the *-focused-output* flag.
*set-option* [*-output* _output_name_|*-focused-output*] _name_ _value_
Set the value of the specified option to _value_.
+*unset-option* (*-output* _output_name_|*-focused-output*) _name_
+ Unset the value of the specified option for the given output and
+ cause it to fall back to the global value. Either the *-output* or
+ *-focused-output* flag is required.
+
*mod-option* [*-output* _output_name_|*-focused-output*] _name_ _value_
Add _value_ to the value of the specified option. _value_ can be negative.
-River declares certain default options for all outputs.
-
-*output_title* (string)
- Changing this option changes the title of the wayland and X11 backend
- outputs.
+River declares certain default options which will always be available:
*layout* (string)
The layout namespace used to determine which layout should arrange this
output. If set to null or no layout with this namespace exists for this
output, the output will enter floating mode. Defaults to null.
-*main_amount* (uint, optional hint for layouts)
- An arbitrary positive integer indicating the amount of main views. Defaults
- to 1.
-
-*main_factor* (float, optional hint for layouts)
- A floating point numger indicating the relative size of the area reserved
- for main views. Note that layouts commonly expect values between 0.1 and 0.9.
- Defaults to 0.6.
-
-*view_padding* (uint, optional hint for layouts)
- A positive integer indicating the padding in of pixels between / around
- views. Defaults to 10.
-
-*outer_padding* (uint, optional hint for layouts)
- A positive integer indicating the padding in of pixels around the layut.
- Defaults to 10.
+*output_title* (string)
+ Changing this option changes the title of the wayland and X11 backend
+ outputs.
# EXAMPLES
diff --git a/doc/rivertile.1.scd b/doc/rivertile.1.scd
index cbf8f32..b099a21 100644
--- a/doc/rivertile.1.scd
+++ b/doc/rivertile.1.scd
@@ -10,15 +10,31 @@ rivertile - Tiled layout generator for river
# DESCRIPTION
-*rivertile* is a layout client for river. It provides four tiled layouts per
-output with split main/secondary stacks with the main area in different
-positions.
+*rivertile* is a layout client for river. It provides a simple tiled layout
+split main/secondary stacks.
-The namespaces of the four layouts are "tile-top", "tile-right", "tile-bottom"
-and "tile-left", corresponding to the position of the main area.
+# OPTIONS
-*rivertile* uses the *main_amount*, *main_factor*, *view_padding* and
-*outer_padding* options.
+These options may be set using *riverctl*(1) or another river-options
+wayland client. *rivertile* declares these options on startup, so setting
+these options before starting rivertile requires them to be declared manually.
+
+*main_location* (string, default "top")
+ The location of the main area. Vaild locations are "top", "bottom",
+ "left", and "right".
+
+*main_count* (uint, default 1)
+ The number of main views.
+
+*main_factor* (fixed, default 0.6)
+ The percentage of the layout area reserved for main views. *rivertle*
+ clamps this to the range `[0.1, 0.9]`.
+
+*view_padding* (uint, default 6)
+ Padding around every view in pixels.
+
+*outer_padding* (uint, default 6)
+ Padding around the edge of the layout area in pixels.
# AUTHORS
@@ -29,4 +45,3 @@ source contributors. For more information about river's development, see
# SEE ALSO
*river*(1), *riverctl*(1)
-
diff --git a/example/init b/example/init
index 4cfca78..0a43af2 100755
--- a/example/init
+++ b/example/init
@@ -1,11 +1,12 @@
#!/bin/sh
-# This is the example configuration file for river(1).
+# This is the example configuration file for river.
#
# If you wish to edit this, you will probably want to copy it to
# $XDG_CONFIG_HOME/river/init or $HOME/.config/river/init first.
#
-# See the riverctl(1) man page for complete documentation
+# See the river(1), riverctl(1), and river(1) man pages for complete
+# documentation.
# Use the "logo" key as the primary modifier
mod="Mod4"
@@ -39,6 +40,16 @@ 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 option by 0.05
+# rivertile(1) uses this option to determine the width of the main stack.
+riverctl map normal $mod H spawn riverctl mod-option -focused-output main_factor -0.05
+riverctl map normal $mod L spawn riverctl mod-option -focused-output main_factor +0.05
+
+# Mod+Shift+H and Mod+Shift+L to increment/decrement the main_count option.
+# rivertile(1) uses this option to determine the number of "main" views in the layout.
+riverctl map normal $mod+Shift H spawn riverctl mod-option -focused-output main_count +1
+riverctl map normal $mod+Shift L spawn riverctl mod-option -focused-output main_count -1
+
# Mod+Alt+{H,J,K,L} to move views
riverctl map normal $mod+Mod1 H move left 100
riverctl map normal $mod+Mod1 J move down 100
@@ -93,10 +104,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 spawn riverctl set-option -focused-output layout tile-up
-riverctl map normal $mod Right spawn riverctl set-option -focused-output layout tile-right
-riverctl map normal $mod Down spawn riverctl set-option -focused-output layout tile-down
-riverctl map normal $mod Left spawn riverctl set-option -focused-output layout tile-left
+riverctl map normal $mod Up spawn riverctl set-option -focused-output main_location top
+riverctl map normal $mod Right spawn riverctl set-option -focused-output main_location right
+riverctl map normal $mod Down spawn riverctl set-option -focused-output main_location bottom
+riverctl map normal $mod Left spawn riverctl set-option -focused-output 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
@@ -134,18 +145,6 @@ done
# Set repeat rate
riverctl set-repeat 50 300
-# Set the layout on startup
-riverctl spawn rivertile
-riverctl set-option -focused-output layout tile-left
-
-# Mod+Alt+{1..9} to set main amount
-# Mod+Alt+Ctrl+{1..9} to set main factor
-#for i in $(seq 1 9)
-#do
-# riverctl map normal $mod+mod1 spawn riverctl set-option -focused-output main_amount "${i}"
-# riverctl map normal $mod+Control+mod1 spawn riverctl set-option -focused-output main_factor "0.${i}"
-#done
-
# Set app-ids of views which should float
riverctl float-filter-add "float"
riverctl float-filter-add "popup"
@@ -155,3 +154,8 @@ riverctl csd-filter-add "gedit"
# Set opacity and fade effect
# riverctl opacity 1.0 0.75 0.0 0.1 20
+
+# Exec into the default layout generator, rivertile.
+# River will send the process group of the init executable SIGTERM on exit.
+riverctl set-option layout rivertile
+exec rivertile
diff --git a/example/options.zig b/example/options.zig
deleted file mode 100644
index 52ede7a..0000000
--- a/example/options.zig
+++ /dev/null
@@ -1,102 +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 os = std.os;
-const mem = std.mem;
-const fmt = std.fmt;
-
-const wayland = @import("wayland");
-const wl = wayland.client.wl;
-const zriver = wayland.client.zriver;
-
-const SetupContext = struct {
- options_manager: ?*zriver.OptionsManagerV1 = null,
- outputs: std.ArrayList(*wl.Output) = std.ArrayList(*wl.Output).init(std.heap.c_allocator),
-};
-
-const ValueType = enum {
- int,
- uint,
- fixed,
- string,
-};
-
-/// Disclaimer, the output handling implemented here is by no means robust. A
-/// proper client should likely use xdg-output to identify outputs by name.
-///
-/// Usage: ./options output_num|NULL [ ]
-/// Examples:
-/// ./options foo
-/// ./options foo NULL uint 42
-/// ./options foo 1 string ziggy
-pub fn main() !void {
- const display = try wl.Display.connect(null);
- const registry = try display.getRegistry();
-
- var context = SetupContext{};
-
- registry.setListener(*SetupContext, registryListener, &context) catch unreachable;
- _ = try display.roundtrip();
-
- const options_manager = context.options_manager orelse return error.RiverOptionsManagerNotAdvertised;
-
- const key = os.argv[1];
- const output = if (mem.eql(u8, "NULL", mem.span(os.argv[2])))
- null
- else
- context.outputs.items[fmt.parseInt(u32, mem.span(os.argv[2]), 10) catch return error.InvalidOutput];
- const handle = try options_manager.getOptionHandle(key, output);
- handle.setListener([*:0]u8, optionListener, key) catch unreachable;
-
- if (os.argv.len > 3) {
- const value_type = std.meta.stringToEnum(ValueType, mem.span(os.argv[3])) orelse return error.InvalidType;
- switch (value_type) {
- .int => handle.setIntValue(fmt.parseInt(i32, mem.span(os.argv[4]), 10) catch return error.InvalidInt),
- .uint => handle.setUintValue(fmt.parseInt(u32, mem.span(os.argv[4]), 10) catch return error.InvalidUint),
- .fixed => handle.setFixedValue(wl.Fixed.fromDouble(fmt.parseFloat(f64, mem.span(os.argv[4])) catch return error.InvalidFixed)),
- .string => handle.setStringValue(os.argv[4]),
- }
- }
-
- // Loop forever, listening for new events.
- while (true) _ = try display.dispatch();
-}
-
-fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *SetupContext) void {
- switch (event) {
- .global => |global| {
- if (std.cstr.cmp(global.interface, zriver.OptionsManagerV1.getInterface().name) == 0) {
- context.options_manager = registry.bind(global.name, zriver.OptionsManagerV1, 1) catch return;
- } else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) {
- const output = registry.bind(global.name, wl.Output, 1) catch return;
- context.outputs.append(output) catch @panic("out of memory");
- }
- },
- .global_remove => {},
- }
-}
-
-fn optionListener(handle: *zriver.OptionHandleV1, event: zriver.OptionHandleV1.Event, key: [*:0]const u8) void {
- switch (event) {
- .unset => std.debug.print("option '{}' unset\n", .{key}),
- .int_value => |ev| std.debug.print("option '{}' of type int has value {}\n", .{ key, ev.value }),
- .uint_value => |ev| std.debug.print("option '{}' of type uint has value {}\n", .{ key, ev.value }),
- .fixed_value => |ev| std.debug.print("option '{}' of type fixed has value {}\n", .{ key, ev.value.toDouble() }),
- .string_value => |ev| std.debug.print("option '{}' of type string has value {}\n", .{ key, ev.value }),
- }
-}
diff --git a/protocol/river-options-unstable-v1.xml b/protocol/river-options-unstable-v1.xml
deleted file mode 100644
index 9e393ba..0000000
--- a/protocol/river-options-unstable-v1.xml
+++ /dev/null
@@ -1,162 +0,0 @@
-
-
-
- Copyright 2020 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 allows clients to access a typed key-value store of
- options. These options are identified by string keys and are scoped
- either globally or per-output. This protocol does not define any
- semantic meaning of the options, that is left up to compositors.
-
- Compositors are free to set options themselves at any time, though
- the type of any given option is immutable once set.
-
- Options may never be unset once set.
-
-
-
-
- This request indicates that the client will not use the manager object
- any more. Objects that have been created through this instance are
- not affected.
-
-
-
-
-
- If the output argument is non-null, the option is local to the given
- output. Otherwise it is considered global.
-
-
-
-
-
-
-
-
-
- On binding this object, one of the events will immediately be sent by
- the server to inform the client of the current state of the option. New
- events will be sent as the state changes.
-
-
-
-
- This request indicates that the client will not use the
- zriver_option_handle_v1 any more and that it may be safely destroyed.
-
-
-
-
-
- The option with this key has never been set, so the first set_*_value
- request received from any client will determine its type.
-
- This can only ever be sent as the first event after binding this
- interface as options cannot be unset once set.
-
-
-
-
-
- This indicates to the client that the option is of type int as well
- as the current value of the option. Once set the type of the option
- can never change.
-
-
-
-
-
-
- This indicates to the client that the option is of type uint as well
- as the current value of the option. Once set the type of the option
- can never change.
-
-
-
-
-
-
- This indicates to the client that the option is of type fixed as
- well as the current value of the option. Once set the type of the option
- can never change.
-
-
-
-
-
-
- This indicates to the client that the option is of type string as
- well as the current value of the option. Once set the type of the option
- can never change.
-
-
-
-
-
-
- If the option is either unset or set to a value of type int, this
- request asks the compositor to set the value of the option as well
- as the type if previously unset. The compositor is not required to
- honor this request.
-
- If the option is already set and is not of type int, this request does nothing.
-
-
-
-
-
-
- If the option is either unset or set to a value of type uint, this
- request asks the compositor to set the value of the option as well
- as the type if previously unset. The compositor is not required to
- honor this request.
-
- If the option is already set and is not of type uint, this request
- does nothing.
-
-
-
-
-
-
- If the option is either unset or set to a value of type fixed, this
- request asks the compositor to set the value of the option as well
- as the type if previously unset. The compositor is not required to
- honor this request.
-
- If the option is already set and is not of type fixed, this request
- does nothing.
-
-
-
-
-
-
- If the option is either unset or set to a value of type string,
- this request asks the compositor to set the value of the option as
- well as the type if previously unset. The compositor is not required
- to honor this request.
-
- If the option is already set and is not of type string, this request
- does nothing.
-
-
-
-
-
diff --git a/protocol/river-options-v2.xml b/protocol/river-options-v2.xml
new file mode 100644
index 0000000..cb9f1d0
--- /dev/null
+++ b/protocol/river-options-v2.xml
@@ -0,0 +1,209 @@
+
+
+
+ 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 allows clients to access a typed key-value store of
+ options. These options are global but may be overridden using a handle
+ scoped to a wl_output. If no output scoped value has been set, then the
+ global value is provided to this handle.
+
+ This protocol does not define any semantic meaning of the options,
+ that is left up to compositors.
+
+ Compositors are free to set or declare options themselves at any time,
+ though the type of any given option is immutable once set.
+
+ Options are never removed once declared.
+
+ 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.
+
+
+
+
+ This interface allows clients to declare new options and create
+ river_option_v2 handle objects in order to retrieve the current
+ value or set a new one.
+
+
+
+
+ This request indicates that the client will not use the manager object
+ any more. Objects that have been created through this instance are
+ not affected.
+
+
+
+
+
+ The option is created in the global scope and is initialized with the
+ provided value. This request is ignored if the option already exists.
+
+
+
+
+
+
+
+ The option is created in the global scope and is initialized with the
+ provided value. This request is ignored if the option already exists.
+
+
+
+
+
+
+
+ The option is created in the global scope and is initialized with the
+ provided value. This request is ignored if the option already exists.
+
+
+
+
+
+
+
+ The option is created in the global scope and is initialized with the
+ provided value. This request is ignored if the option already exists.
+
+
+
+
+
+
+
+ If the output argument is non-null, the option is local to the given
+ output. Otherwise it is considered global.
+
+
+
+
+
+
+
+
+ This causes the value of the option for the given output to fall
+ back to the global value.
+
+
+
+
+
+
+
+
+ On binding this object, one of the events will immediately be sent by
+ the server to inform the client of the current state of the option,
+ including its type. Making one of the 4 set requests before receiving
+ this first event would be a bug as the client would not yet know the
+ type of the option. New events will be sent as the state changes.
+
+
+
+
+ This request indicates that the client will not use the
+ river_option_handle_v2 any more and that it may be safely destroyed.
+
+
+
+
+
+
+
+
+
+
+ No option with the the given name has ever been declared. All requests
+ on this object aside from the destroy request are a protocol error and
+ no further events will be sent.
+
+
+
+
+
+ This indicates to the client that the option is of type int as well
+ as the current value of the option. Once set the type of the option
+ can never change.
+
+
+
+
+
+
+ This indicates to the client that the option is of type uint as well
+ as the current value of the option. Once set the type of the option
+ can never change.
+
+
+
+
+
+
+ This indicates to the client that the option is of type string as well
+ as the current value of the option. Once set the type of the option
+ can never change.
+
+
+
+
+
+
+ This indicates to the client that the option is of type fixed as well
+ as the current value of the option. Once set the type of the option
+ can never change.
+
+
+
+
+
+
+ If the option is of type int, set the value of the option.
+ Otherwise, this request is a protocol error.
+
+
+
+
+
+
+ If the option is of type uint, set the value of the option.
+ Otherwise, this request is a protocol error.
+
+
+
+
+
+
+ If the option is of type string, set the value of the option.
+ Otherwise, this request is a protocol error.
+
+
+
+
+
+
+ If the option is of type fixed, set the value of the option.
+ Otherwise, this request is a protocol error.
+
+
+
+
+
diff --git a/river/Layout.zig b/river/Layout.zig
index 887f630..407667f 100644
--- a/river/Layout.zig
+++ b/river/Layout.zig
@@ -61,7 +61,7 @@ pub fn create(client: *wl.Client, version: u32, id: u32, output: *Output, namesp
// If the namespace matches that of the output, set the layout as
// the active one of the output and arrange it.
- if (output.layout_option.value.string) |current_layout| {
+ if (output.layout_option.get().string) |current_layout| {
if (mem.eql(u8, namespace, mem.span(current_layout))) {
output.pending.layout = &node.data;
output.arrangeViews();
diff --git a/river/Option.zig b/river/Option.zig
index a7e0d37..4265afa 100644
--- a/river/Option.zig
+++ b/river/Option.zig
@@ -1,6 +1,6 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
-// Copyright 2020 The River Developers
+// Copyright 2020-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
@@ -23,28 +23,30 @@ const meta = std.meta;
const wayland = @import("wayland");
const wl = wayland.server.wl;
-const zriver = wayland.server.zriver;
+const river = wayland.server.river;
const util = @import("util.zig");
const Output = @import("Output.zig");
const OptionsManager = @import("OptionsManager.zig");
+const OutputOption = @import("OutputOption.zig");
+
+const log = std.log.scoped(.river_options);
pub const Value = union(enum) {
- unset: void,
int: i32,
uint: u32,
fixed: wl.Fixed,
string: ?[*:0]const u8,
- fn dupe(value: Value) !Value {
+ pub fn dupe(value: Value) !Value {
return switch (value) {
.string => |v| Value{ .string = if (v) |s| try util.gpa.dupeZ(u8, mem.span(s)) else null },
else => value,
};
}
- fn deinit(value: *Value) void {
+ pub fn deinit(value: *Value) void {
if (value.* == .string) if (value.string) |s| util.gpa.free(mem.span(s));
}
};
@@ -52,19 +54,20 @@ pub const Value = union(enum) {
options_manager: *OptionsManager,
link: wl.list.Link = undefined,
-output: ?*Output,
-key: [*:0]const u8,
+key: [:0]const u8,
value: Value,
+output_options: wl.list.Head(OutputOption, "link") = undefined,
+
event: struct {
/// Emitted whenever the value of the option changes.
- update: wl.Signal(*Self),
+ update: wl.Signal(*Value),
} = undefined,
-handles: wl.list.Head(zriver.OptionHandleV1, null) = undefined,
+handles: wl.list.Head(river.OptionHandleV2, null) = undefined,
/// Allocate a new option, duping the provided key and value
-pub fn create(options_manager: *OptionsManager, output: ?*Output, key: [*:0]const u8, value: Value) !*Self {
+pub fn create(options_manager: *OptionsManager, key: [*:0]const u8, value: Value) !void {
const self = try util.gpa.create(Self);
errdefer util.gpa.destroy(self);
@@ -73,56 +76,70 @@ pub fn create(options_manager: *OptionsManager, output: ?*Output, key: [*:0]cons
self.* = .{
.options_manager = options_manager,
- .output = output,
.key = try util.gpa.dupeZ(u8, mem.span(key)),
.value = owned_value,
};
- self.handles.init();
+ errdefer util.gpa.free(self.key);
+
+ self.output_options.init();
+ errdefer {
+ var it = self.output_options.safeIterator(.forward);
+ while (it.next()) |output_option| output_option.destroy();
+ }
+ var it = options_manager.server.root.all_outputs.first;
+ while (it) |node| : (it = node.next) try OutputOption.create(self, node.data);
+
self.event.update.init();
+ self.handles.init();
options_manager.options.append(self);
-
- return self;
}
pub fn destroy(self: *Self) void {
- var it = self.handles.safeIterator(.forward);
- while (it.next()) |handle| handle.destroy();
- if (self.value == .string) if (self.value.string) |s| util.gpa.free(mem.span(s));
+ {
+ var it = self.handles.safeIterator(.forward);
+ while (it.next()) |handle| handle.destroy();
+ }
+ {
+ var it = self.output_options.safeIterator(.forward);
+ while (it.next()) |output_option| output_option.destroy();
+ }
+ self.value.deinit();
self.link.remove();
util.gpa.destroy(self);
}
-/// Asserts that the new value is not .unset.
-/// Ignores the new value if the value is currently set and the type does not match.
+pub fn getOutputOption(self: *Self, output: *Output) ?*OutputOption {
+ var it = self.output_options.iterator(.forward);
+ while (it.next()) |output_option| {
+ if (output_option.output == output) return output_option;
+ } else return null;
+}
+
/// If the value is a string, the string is cloned.
/// If the value is changed, send the proper event to all clients
pub fn set(self: *Self, value: Value) !void {
- std.debug.assert(value != .unset);
- if (self.value != .unset and meta.activeTag(value) != meta.activeTag(self.value)) return;
+ if (meta.activeTag(value) != meta.activeTag(self.value)) return error.TypeMismatch;
- if (switch (self.value) {
- .unset => true,
- // TODO: std.mem needs a good way to compare optional sentinel pointers
- .string => ((self.value.string == null) != (value.string == null)) or
- (self.value.string != null and value.string != null and
- std.cstr.cmp(self.value.string.?, value.string.?) != 0),
- else => !std.meta.eql(self.value, value),
- }) {
- self.value.deinit();
- self.value = try value.dupe();
+ self.value.deinit();
+ self.value = try value.dupe();
+ {
var it = self.handles.iterator(.forward);
while (it.next()) |handle| self.sendValue(handle);
-
- // Call listeners, if any.
- self.event.update.emit(self);
}
+ {
+ var it = self.output_options.iterator(.forward);
+ while (it.next()) |output_option| {
+ if (output_option.value == null) output_option.notifyChanged();
+ }
+ }
+
+ self.event.update.emit(&self.value);
}
-fn sendValue(self: Self, handle: *zriver.OptionHandleV1) void {
+pub fn sendValue(self: Self, handle: *river.OptionHandleV2) void {
switch (self.value) {
- .unset => handle.sendUnset(),
.int => |v| handle.sendIntValue(v),
.uint => |v| handle.sendUintValue(v),
.fixed => |v| handle.sendFixedValue(v),
@@ -130,24 +147,38 @@ fn sendValue(self: Self, handle: *zriver.OptionHandleV1) void {
}
}
-pub fn addHandle(self: *Self, handle: *zriver.OptionHandleV1) void {
- self.handles.append(handle);
- self.sendValue(handle);
- handle.setHandler(*Self, handleRequest, handleDestroy, self);
+pub fn addHandle(self: *Self, output: ?*Output, handle: *river.OptionHandleV2) void {
+ if (output) |o| {
+ self.getOutputOption(o).?.addHandle(handle);
+ } else {
+ self.handles.append(handle);
+ self.sendValue(handle);
+ handle.setHandler(*Self, handleRequest, handleDestroy, self);
+ }
}
-fn handleRequest(handle: *zriver.OptionHandleV1, request: zriver.OptionHandleV1.Request, self: *Self) void {
+fn handleRequest(handle: *river.OptionHandleV2, request: river.OptionHandleV2.Request, self: *Self) void {
switch (request) {
.destroy => handle.destroy(),
- .set_int_value => |req| self.set(.{ .int = req.value }) catch unreachable,
- .set_uint_value => |req| self.set(.{ .uint = req.value }) catch unreachable,
- .set_fixed_value => |req| self.set(.{ .fixed = req.value }) catch unreachable,
- .set_string_value => |req| self.set(.{ .string = req.value }) catch {
- handle.getClient().postNoMemory();
+ .set_int_value => |req| self.set(.{ .int = req.value }) catch |err| switch (err) {
+ error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type int"),
+ error.OutOfMemory => unreachable,
+ },
+ .set_uint_value => |req| self.set(.{ .uint = req.value }) catch |err| switch (err) {
+ error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type uint"),
+ error.OutOfMemory => unreachable,
+ },
+ .set_fixed_value => |req| self.set(.{ .fixed = req.value }) catch |err| switch (err) {
+ error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type fixed"),
+ error.OutOfMemory => unreachable,
+ },
+ .set_string_value => |req| self.set(.{ .string = req.value }) catch |err| switch (err) {
+ error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type string"),
+ error.OutOfMemory => handle.getClient().postNoMemory(),
},
}
}
-fn handleDestroy(handle: *zriver.OptionHandleV1, self: *Self) void {
+fn handleDestroy(handle: *river.OptionHandleV2, self: *Self) void {
handle.getLink().remove();
}
diff --git a/river/OptionsManager.zig b/river/OptionsManager.zig
index 063f6e1..a42f449 100644
--- a/river/OptionsManager.zig
+++ b/river/OptionsManager.zig
@@ -1,6 +1,6 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
-// Copyright 2020 The River Developers
+// Copyright 2020-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
@@ -18,10 +18,11 @@
const Self = @This();
const std = @import("std");
+const mem = std.mem;
const wayland = @import("wayland");
const wl = wayland.server.wl;
-const zriver = wayland.server.zriver;
+const river = wayland.server.river;
const wlr = @import("wlroots");
@@ -29,8 +30,12 @@ const util = @import("util.zig");
const Option = @import("Option.zig");
const Output = @import("Output.zig");
+const OutputOption = @import("OutputOption.zig");
const Server = @import("Server.zig");
+const log = std.log.scoped(.river_options);
+
+server: *Server,
global: *wl.Global,
server_destroy: wl.Listener(*wl.Server) = wl.Listener(*wl.Server).init(handleServerDestroy),
@@ -38,16 +43,25 @@ options: wl.list.Head(Option, "link") = undefined,
pub fn init(self: *Self, server: *Server) !void {
self.* = .{
- .global = try wl.Global.create(server.wl_server, zriver.OptionsManagerV1, 1, *Self, self, bind),
+ .server = server,
+ .global = try wl.Global.create(server.wl_server, river.OptionsManagerV2, 1, *Self, self, bind),
};
self.options.init();
server.wl_server.addDestroyListener(&self.server_destroy);
+
+ try Option.create(self, "layout", .{ .string = null });
+ try Option.create(self, "output_title", .{ .string = null });
}
-pub fn handleOutputDestroy(self: *Self, output: *Output) void {
- var it = self.options.safeIterator(.forward);
+pub fn createOutputOptions(self: *Self, output: *Output) !void {
+ var it = self.options.iterator(.forward);
+ while (it.next()) |option| try OutputOption.create(option, output);
+}
+
+pub fn destroyOutputOptions(self: *Self, output: *Output) void {
+ var it = self.options.iterator(.forward);
while (it.next()) |option| {
- if (option.output == output) option.destroy();
+ if (option.getOutputOption(output)) |output_option| output_option.destroy();
}
}
@@ -59,20 +73,53 @@ 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 options_manager = zriver.OptionsManagerV1.create(client, 1, id) catch {
+ const options_manager = river.OptionsManagerV2.create(client, version, id) catch {
client.postNoMemory();
return;
};
options_manager.setHandler(*Self, handleRequest, null, self);
}
+pub fn getOption(self: *Self, key: [:0]const u8) ?*Option {
+ var it = self.options.iterator(.forward);
+ while (it.next()) |option| {
+ if (mem.eql(u8, option.key, key)) return option;
+ } else return null;
+}
+
fn handleRequest(
- options_manager: *zriver.OptionsManagerV1,
- request: zriver.OptionsManagerV1.Request,
+ options_manager: *river.OptionsManagerV2,
+ request: river.OptionsManagerV2.Request,
self: *Self,
) void {
switch (request) {
.destroy => options_manager.destroy(),
+
+ .declare_int_option => |req| if (self.getOption(mem.span(req.key)) == null) {
+ Option.create(self, req.key, .{ .int = req.value }) catch {
+ options_manager.getClient().postNoMemory();
+ return;
+ };
+ },
+ .declare_uint_option => |req| if (self.getOption(mem.span(req.key)) == null) {
+ Option.create(self, req.key, .{ .uint = req.value }) catch {
+ options_manager.getClient().postNoMemory();
+ return;
+ };
+ },
+ .declare_string_option => |req| if (self.getOption(mem.span(req.key)) == null) {
+ Option.create(self, req.key, .{ .string = req.value }) catch {
+ options_manager.getClient().postNoMemory();
+ return;
+ };
+ },
+ .declare_fixed_option => |req| if (self.getOption(mem.span(req.key)) == null) {
+ Option.create(self, req.key, .{ .fixed = req.value }) catch {
+ options_manager.getClient().postNoMemory();
+ return;
+ };
+ },
+
.get_option_handle => |req| {
const output = if (req.output) |wl_output| blk: {
// Ignore if the wl_output is inert
@@ -80,19 +127,24 @@ fn handleRequest(
break :blk @intToPtr(*Output, wlr_output.data);
} else null;
- // Look for an existing Option, if not found create a new one
- var it = self.options.iterator(.forward);
- const option = while (it.next()) |option| {
- if (option.output == output and std.cstr.cmp(option.key, req.key) == 0) {
- break option;
- }
- } else
- Option.create(self, output, req.key, .unset) catch {
+ const option = self.getOption(mem.span(req.key)) orelse {
+ // There is no option with the requested key. In this case
+ // all we do is send an undeclared event and wait for the
+ // client to destroy the resource.
+ const handle = river.OptionHandleV2.create(
+ options_manager.getClient(),
+ options_manager.getVersion(),
+ req.handle,
+ ) catch {
options_manager.getClient().postNoMemory();
return;
};
+ handle.sendUndeclared();
+ handle.setHandler(*Self, undeclaredHandleRequest, null, self);
+ return;
+ };
- const handle = zriver.OptionHandleV1.create(
+ const handle = river.OptionHandleV2.create(
options_manager.getClient(),
options_manager.getVersion(),
req.handle,
@@ -101,7 +153,36 @@ fn handleRequest(
return;
};
- option.addHandle(handle);
+ option.addHandle(output, handle);
+ },
+
+ .unset_option => |req| {
+ // Ignore if the wl_output is inert
+ const wlr_output = wlr.Output.fromWlOutput(req.output) orelse return;
+ const output = @intToPtr(*Output, wlr_output.data);
+
+ const option = self.getOption(mem.span(req.key)) orelse return;
+ option.getOutputOption(output).?.unset();
+ },
+ }
+}
+
+fn undeclaredHandleRequest(
+ handle: *river.OptionHandleV2,
+ request: river.OptionHandleV2.Request,
+ self: *Self,
+) void {
+ switch (request) {
+ .destroy => handle.destroy(),
+ .set_int_value,
+ .set_uint_value,
+ .set_fixed_value,
+ .set_string_value,
+ => {
+ handle.postError(
+ .request_while_undeclared,
+ "a request other than destroy was made on a handle to an undeclared option",
+ );
},
}
}
diff --git a/river/Output.zig b/river/Output.zig
index bf894be..d11879d 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -39,6 +39,7 @@ const ViewStack = @import("view_stack.zig").ViewStack;
const AttachMode = @import("view_stack.zig").AttachMode;
const OutputStatus = @import("OutputStatus.zig");
const Option = @import("Option.zig");
+const OutputOption = @import("OutputOption.zig");
const State = struct {
/// A bit field of focused tags
@@ -94,11 +95,10 @@ enable: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleEnable),
frame: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleFrame),
mode: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleMode),
-layout_option: *Option,
+layout_option: *OutputOption,
-/// Listeners for options
-output_title: wl.Listener(*Option) = wl.Listener(*Option).init(handleTitleChange),
-layout_change: wl.Listener(*Option) = wl.Listener(*Option).init(handleLayoutChange),
+output_title: wl.Listener(*Option.Value) = wl.Listener(*Option.Value).init(handleTitleChange),
+layout_change: wl.Listener(*Option.Value) = wl.Listener(*Option.Value).init(handleLayoutChange),
pub fn init(self: *Self, root: *Root, wlr_output: *wlr.Output) !void {
// Some backends don't have modes. DRM+KMS does, and we need to set a mode
@@ -150,24 +150,29 @@ pub fn init(self: *Self, root: *Root, wlr_output: *wlr.Output) !void {
.width = effective_resolution.width,
.height = effective_resolution.height,
};
+
+ const options_manager = &root.server.options_manager;
+ try options_manager.createOutputOptions(self);
+ errdefer options_manager.destroyOutputOptions(self);
+
+ // Set the default title of this output
+ var buf: ["river - ".len + wlr_output.name.len + 1]u8 = undefined;
+ const default_title = fmt.bufPrintZ(&buf, "river - {}", .{mem.spanZ(&wlr_output.name)}) catch unreachable;
+ self.setTitle(default_title);
+
+ const global_title_option = options_manager.getOption("output_title") orelse unreachable;
+ const title_option = global_title_option.getOutputOption(self).?;
+ title_option.set(.{ .string = default_title }) catch |err| switch (err) {
+ error.TypeMismatch => unreachable,
+ error.OutOfMemory => return err,
+ };
+
+ const global_layout_option = options_manager.getOption("layout") orelse unreachable;
+ self.layout_option = global_layout_option.getOutputOption(self).?;
+
+ self.layout_option.event.update.add(&self.layout_change);
+ title_option.event.update.add(&self.output_title);
}
-
- // Set the default title of this output
- var buf: ["river - ".len + wlr_output.name.len + 1]u8 = undefined;
- const default_title = fmt.bufPrintZ(&buf, "river - {}", .{mem.spanZ(&wlr_output.name)}) catch unreachable;
- self.setTitle(default_title);
-
- // Create all default output options
- const options_manager = &root.server.options_manager;
- self.layout_option = try Option.create(options_manager, self, "layout", .{ .string = null });
- const title_option = try Option.create(options_manager, self, "output_title", .{ .string = default_title.ptr });
- _ = try Option.create(options_manager, self, "main_amount", .{ .uint = 1 });
- _ = try Option.create(options_manager, self, "main_factor", .{ .fixed = wl.Fixed.fromDouble(0.6) });
- _ = try Option.create(options_manager, self, "view_padding", .{ .uint = 10 });
- _ = try Option.create(options_manager, self, "outer_padding", .{ .uint = 10 });
-
- self.layout_option.event.update.add(&self.layout_change);
- title_option.event.update.add(&self.output_title);
}
pub fn getLayer(self: *Self, layer: zwlr.LayerShellV1.Layer) *std.TailQueue(LayerSurface) {
@@ -440,7 +445,7 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) v
std.log.scoped(.server).debug("output '{}' destroyed", .{self.wlr_output.name});
- root.server.options_manager.handleOutputDestroy(self);
+ root.server.options_manager.destroyOutputOptions(self);
// Remove the destroyed output from root if it wasn't already removed
root.removeOutput(self);
@@ -509,20 +514,21 @@ pub fn setTitle(self: *Self, title: [*:0]const u8) void {
}
}
-fn handleTitleChange(listener: *wl.Listener(*Option), option: *Option) void {
- if (option.value.string) |title| option.output.?.setTitle(title);
+fn handleTitleChange(listener: *wl.Listener(*Option.Value), value: *Option.Value) void {
+ const self = @fieldParentPtr(Self, "output_title", listener);
+ if (value.string) |title| self.setTitle(title);
}
-fn handleLayoutChange(listener: *wl.Listener(*Option), option: *Option) void {
+fn handleLayoutChange(listener: *wl.Listener(*Option.Value), value: *Option.Value) void {
+ const self = @fieldParentPtr(Self, "layout_change", listener);
// The user changed the layout namespace of this output. Try to find a
// matching layout.
- const output = option.output.?;
- output.pending.layout = if (option.value.string) |namespace| blk: {
- var layout_it = output.layouts.first;
+ self.pending.layout = if (value.string) |namespace| blk: {
+ var layout_it = self.layouts.first;
break :blk while (layout_it) |node| : (layout_it = node.next) {
if (mem.eql(u8, mem.span(namespace), node.data.namespace)) break &node.data;
} else null;
} else null;
- output.arrangeViews();
- output.root.startTransaction();
+ self.arrangeViews();
+ self.root.startTransaction();
}
diff --git a/river/OutputOption.zig b/river/OutputOption.zig
new file mode 100644
index 0000000..db3a0b3
--- /dev/null
+++ b/river/OutputOption.zig
@@ -0,0 +1,145 @@
+// 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 Self = @This();
+
+const std = @import("std");
+const mem = std.mem;
+const meta = std.meta;
+
+const wayland = @import("wayland");
+const wl = wayland.server.wl;
+const river = wayland.server.river;
+
+const util = @import("util.zig");
+
+const Output = @import("Output.zig");
+const OptionsManager = @import("OptionsManager.zig");
+const Option = @import("Option.zig");
+
+const Value = Option.Value;
+
+option: *Option,
+
+link: wl.list.Link = undefined,
+
+output: *Output,
+value: ?Value = null,
+
+event: struct {
+ /// Emitted whenever the value of the option changes.
+ update: wl.Signal(*Value),
+} = undefined,
+
+handles: wl.list.Head(river.OptionHandleV2, null) = undefined,
+
+pub fn create(option: *Option, output: *Output) !void {
+ const self = try util.gpa.create(Self);
+ errdefer util.gpa.destroy(self);
+
+ self.* = .{ .option = option, .output = output };
+ self.event.update.init();
+ self.handles.init();
+
+ option.output_options.append(self);
+}
+
+pub fn destroy(self: *Self) void {
+ if (self.value) |*value| value.deinit();
+ self.link.remove();
+ util.gpa.destroy(self);
+}
+
+pub fn addHandle(self: *Self, handle: *river.OptionHandleV2) void {
+ self.handles.append(handle);
+ self.sendValue(handle);
+ handle.setHandler(*Self, handleRequest, handleDestroy, self);
+}
+
+pub fn unset(self: *Self) void {
+ if (self.value) |*value| value.deinit();
+ self.value = null;
+
+ // Unsetting the output-specific value causes us to fall back to the
+ // global value. Send this new value to all clients.
+ var it = self.handles.iterator(.forward);
+ while (it.next()) |handle| {
+ self.option.sendValue(handle);
+ }
+
+ self.event.update.emit(&self.option.value);
+}
+
+/// If the value is a string, the string is cloned.
+/// If the value is changed, send the proper event to all clients
+pub fn set(self: *Self, value: Value) !void {
+ if (meta.activeTag(value) != meta.activeTag(self.option.value)) return error.TypeMismatch;
+
+ if (self.value) |*v| v.deinit();
+ self.value = try value.dupe();
+
+ self.notifyChanged();
+}
+
+pub fn notifyChanged(self: *Self) void {
+ var it = self.handles.iterator(.forward);
+ while (it.next()) |handle| self.sendValue(handle);
+ self.event.update.emit(self.get());
+}
+
+pub fn get(self: *Self) *Value {
+ return if (self.value) |*value| value else &self.option.value;
+}
+
+fn sendValue(self: Self, handle: *river.OptionHandleV2) void {
+ if (self.value) |value| {
+ switch (value) {
+ .int => |v| handle.sendIntValue(v),
+ .uint => |v| handle.sendUintValue(v),
+ .fixed => |v| handle.sendFixedValue(v),
+ .string => |v| handle.sendStringValue(v),
+ }
+ } else {
+ self.option.sendValue(handle);
+ }
+}
+
+fn handleRequest(handle: *river.OptionHandleV2, request: river.OptionHandleV2.Request, self: *Self) void {
+ switch (request) {
+ .destroy => handle.destroy(),
+ .set_int_value => |req| self.set(.{ .int = req.value }) catch |err| switch (err) {
+ error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type int"),
+ error.OutOfMemory => unreachable,
+ },
+ .set_uint_value => |req| self.set(.{ .uint = req.value }) catch |err| switch (err) {
+ error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type uint"),
+ error.OutOfMemory => unreachable,
+ },
+ .set_fixed_value => |req| self.set(.{ .fixed = req.value }) catch |err| switch (err) {
+ error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type fixed"),
+ error.OutOfMemory => unreachable,
+ },
+ .set_string_value => |req| self.set(.{ .string = req.value }) catch |err| switch (err) {
+ error.TypeMismatch => handle.postError(.type_mismatch, "option is not of type string"),
+ error.OutOfMemory => handle.getClient().postNoMemory(),
+ },
+ }
+}
+
+fn handleDestroy(handle: *river.OptionHandleV2, self: *Self) void {
+ handle.getLink().remove();
+}
diff --git a/river/Server.zig b/river/Server.zig
index 7a7ce32..66553fe 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -115,9 +115,9 @@ pub fn init(self: *Self) !void {
self.config = try Config.init();
try self.decoration_manager.init(self);
- try self.options_manager.init(self);
try self.root.init(self);
// Must be called after root is initialized
+ try self.options_manager.init(self);
try self.input_manager.init(self);
try self.control.init(self);
try self.status_manager.init(self);
diff --git a/riverctl/main.zig b/riverctl/main.zig
index ebffb39..6fd6341 100644
--- a/riverctl/main.zig
+++ b/riverctl/main.zig
@@ -22,6 +22,7 @@ const assert = std.debug.assert;
const wayland = @import("wayland");
const wl = wayland.client.wl;
+const river = wayland.client.river;
const zriver = wayland.client.zriver;
const zxdg = wayland.client.zxdg;
@@ -36,7 +37,7 @@ pub const Output = struct {
pub const Globals = struct {
control: ?*zriver.ControlV1 = null,
- options_manager: ?*zriver.OptionsManagerV1 = null,
+ options_manager: ?*river.OptionsManagerV2 = null,
status_manager: ?*zriver.StatusManagerV1 = null,
seat: ?*wl.Seat = null,
output_manager: ?*zxdg.OutputManagerV1 = null,
@@ -87,6 +88,8 @@ fn _main() !void {
try options.getOption(display, &globals);
} else if (os.argv.len > 2 and mem.eql(u8, "set-option", mem.span(os.argv[1]))) {
try options.setOption(display, &globals);
+ } else if (os.argv.len > 2 and mem.eql(u8, "unset-option", mem.span(os.argv[1]))) {
+ try options.unsetOption(display, &globals);
} else if (os.argv.len > 2 and mem.eql(u8, "mod-option", mem.span(os.argv[1]))) {
try options.modOption(display, &globals);
} else {
@@ -115,8 +118,8 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *
globals.seat = registry.bind(global.name, wl.Seat, 1) catch @panic("out of memory");
} else if (std.cstr.cmp(global.interface, zriver.ControlV1.getInterface().name) == 0) {
globals.control = registry.bind(global.name, zriver.ControlV1, 1) catch @panic("out of memory");
- } else if (std.cstr.cmp(global.interface, zriver.OptionsManagerV1.getInterface().name) == 0) {
- globals.options_manager = registry.bind(global.name, zriver.OptionsManagerV1, 1) catch @panic("out of memory");
+ } else if (std.cstr.cmp(global.interface, river.OptionsManagerV2.getInterface().name) == 0) {
+ globals.options_manager = registry.bind(global.name, river.OptionsManagerV2, 1) catch @panic("out of memory");
} else if (std.cstr.cmp(global.interface, zriver.StatusManagerV1.getInterface().name) == 0) {
globals.status_manager = registry.bind(global.name, zriver.StatusManagerV1, 1) catch @panic("out of memory");
} else if (std.cstr.cmp(global.interface, zxdg.OutputManagerV1.getInterface().name) == 0 and global.version >= 2) {
diff --git a/riverctl/options.zig b/riverctl/options.zig
index aab9fa0..9501610 100644
--- a/riverctl/options.zig
+++ b/riverctl/options.zig
@@ -17,11 +17,13 @@
const std = @import("std");
const os = std.os;
+const math = std.math;
const mem = std.mem;
const fmt = std.fmt;
const wayland = @import("wayland");
const wl = wayland.client.wl;
+const river = wayland.client.river;
const zriver = wayland.client.zriver;
const zxdg = wayland.client.zxdg;
@@ -49,69 +51,42 @@ const Context = struct {
pub fn declareOption(display: *wl.Display, globals: *Globals) !void {
// https://github.com/ziglang/zig/issues/7807
const argv: [][*:0]const u8 = os.argv;
- const args = Args(3, &[_]FlagDef{
- .{ .name = "-output", .kind = .arg },
- .{ .name = "-focused-output", .kind = .boolean },
- }).parse(argv[2..]);
+ const args = Args(3, &[_]FlagDef{}).parse(argv[2..]);
const key = args.positionals[0];
- const value_type = std.meta.stringToEnum(ValueType, mem.span(args.positionals[1])) orelse
- root.printErrorExit("'{}' is not a valid type, must be int, uint, fixed, or string", .{args.positionals[1]});
+ const value_type = std.meta.stringToEnum(ValueType, mem.span(args.positionals[1])) orelse {
+ root.printErrorExit(
+ "'{}' is not a valid type, must be int, uint, fixed, or string",
+ .{args.positionals[1]},
+ );
+ };
const raw_value = args.positionals[2];
- const output = if (args.argFlag("-output")) |o|
- try parseOutputName(display, globals, o)
- else if (args.boolFlag("-focused-output"))
- try getFocusedOutput(display, globals)
- else
- null;
-
const options_manager = globals.options_manager orelse return error.RiverOptionsManagerNotAdvertised;
- const handle = try options_manager.getOptionHandle(key, if (output) |o| o.wl_output else null);
switch (value_type) {
- .int => setIntValueRaw(handle, raw_value),
- .uint => setUintValueRaw(handle, raw_value),
- .fixed => setFixedValueRaw(handle, raw_value),
- .string => handle.setStringValue(if (raw_value[0] == 0) null else raw_value),
+ .int => options_manager.declareIntOption(key, parseInt(raw_value)),
+ .uint => options_manager.declareUintOption(key, parseUint(raw_value)),
+ .fixed => options_manager.declareFixedOption(key, parseFixed(raw_value)),
+ .string => options_manager.declareStringOption(key, raw_value),
}
- _ = display.flush() catch os.exit(1);
+
+ _ = try display.flush();
}
-fn setIntValueRaw(handle: *zriver.OptionHandleV1, raw_value: [*:0]const u8) void {
- handle.setIntValue(fmt.parseInt(i32, mem.span(raw_value), 10) catch
- root.printErrorExit("{} is not a valid int", .{raw_value}));
+fn parseInt(raw_value: [*:0]const u8) i32 {
+ return fmt.parseInt(i32, mem.span(raw_value), 10) catch
+ root.printErrorExit("{} is not a valid int", .{raw_value});
}
-fn setUintValueRaw(handle: *zriver.OptionHandleV1, raw_value: [*:0]const u8) void {
- handle.setUintValue(fmt.parseInt(u32, mem.span(raw_value), 10) catch
- root.printErrorExit("{} is not a valid uint", .{raw_value}));
+fn parseUint(raw_value: [*:0]const u8) u32 {
+ return fmt.parseInt(u32, mem.span(raw_value), 10) catch
+ root.printErrorExit("{} is not a valid uint", .{raw_value});
}
-fn setFixedValueRaw(handle: *zriver.OptionHandleV1, raw_value: [*:0]const u8) void {
- handle.setFixedValue(wl.Fixed.fromDouble(fmt.parseFloat(f64, mem.span(raw_value)) catch
- root.printErrorExit("{} is not a valid fixed", .{raw_value})));
-}
-
-fn modIntValueRaw(handle: *zriver.OptionHandleV1, current: i32, raw_value: [*:0]const u8) void {
- const mod = fmt.parseInt(i32, mem.span(raw_value), 10) catch
- root.printErrorExit("{} is not a valid int modificator", .{raw_value});
- handle.setIntValue(current + mod);
-}
-
-fn modUintValueRaw(handle: *zriver.OptionHandleV1, current: u32, raw_value: [*:0]const u8) void {
- // We need to allow negative mod values, but the value of the option may
- // never be below zero.
- const mod = fmt.parseInt(i32, mem.span(raw_value), 10) catch
- root.printErrorExit("{} is not a valid uint modificator", .{raw_value});
- const new = @intCast(i32, current) + mod;
- handle.setUintValue(if (new < 0) 0 else @intCast(u32, new));
-}
-
-fn modFixedValueRaw(handle: *zriver.OptionHandleV1, current: wl.Fixed, raw_value: [*:0]const u8) void {
- const mod = fmt.parseFloat(f64, mem.span(raw_value)) catch
- root.printErrorExit("{} is not a valid fixed modificator", .{raw_value});
- handle.setFixedValue(wl.Fixed.fromDouble(current.toDouble() + mod));
+fn parseFixed(raw_value: [*:0]const u8) wl.Fixed {
+ return wl.Fixed.fromDouble(fmt.parseFloat(f64, mem.span(raw_value)) catch
+ root.printErrorExit("{} is not a valid fixed", .{raw_value}));
}
pub fn getOption(display: *wl.Display, globals: *Globals) !void {
@@ -174,6 +149,30 @@ pub fn setOption(display: *wl.Display, globals: *Globals) !void {
while (true) _ = try display.dispatch();
}
+pub fn unsetOption(display: *wl.Display, globals: *Globals) !void {
+ // https://github.com/ziglang/zig/issues/7807
+ const argv: [][*:0]const u8 = os.argv;
+ const args = Args(1, &[_]FlagDef{
+ .{ .name = "-output", .kind = .arg },
+ .{ .name = "-focused-output", .kind = .boolean },
+ }).parse(argv[2..]);
+
+ const output = if (args.argFlag("-output")) |o|
+ try parseOutputName(display, globals, o)
+ else if (args.boolFlag("-focused-output"))
+ try getFocusedOutput(display, globals)
+ else
+ root.printErrorExit("unset requires either -output or -focused-output", .{});
+
+ const key = args.positionals[0];
+
+ const options_manager = globals.options_manager orelse return error.RiverOptionsManagerNotAdvertised;
+
+ options_manager.unsetOption(key, output.wl_output);
+
+ _ = try display.flush();
+}
+
pub fn modOption(display: *wl.Display, globals: *Globals) !void {
// https://github.com/ziglang/zig/issues/7807
const argv: [][*:0]const u8 = os.argv;
@@ -246,16 +245,12 @@ fn seatStatusListener(seat_status: *zriver.SeatStatusV1, event: zriver.SeatStatu
}
fn getOptionListener(
- handle: *zriver.OptionHandleV1,
- event: zriver.OptionHandleV1.Event,
+ handle: *river.OptionHandleV2,
+ event: river.OptionHandleV2.Event,
ctx: *const Context,
) void {
switch (event) {
- .unset => if (ctx.output) |output| {
- root.printErrorExit("option '{}' has not been declared on output '{}'", .{ ctx.key, output.name });
- } else {
- root.printErrorExit("option '{}' has not been declared globally", .{ctx.key});
- },
+ .undeclared => root.printErrorExit("option '{}' has not been declared", .{ctx.key}),
.int_value => |ev| printOutputExit("{}", .{ev.value}),
.uint_value => |ev| printOutputExit("{}", .{ev.value}),
.fixed_value => |ev| printOutputExit("{d}", .{ev.value.toDouble()}),
@@ -270,19 +265,15 @@ fn printOutputExit(comptime format: []const u8, args: anytype) noreturn {
}
fn setOptionListener(
- handle: *zriver.OptionHandleV1,
- event: zriver.OptionHandleV1.Event,
+ handle: *river.OptionHandleV2,
+ event: river.OptionHandleV2.Event,
ctx: *const Context,
) void {
switch (event) {
- .unset => if (ctx.output) |output| {
- root.printErrorExit("option '{}' has not been declared on output '{}'", .{ ctx.key, output.name });
- } else {
- root.printErrorExit("option '{}' has not been declared globally", .{ctx.key});
- },
- .int_value => |ev| setIntValueRaw(handle, ctx.raw_value),
- .uint_value => |ev| setUintValueRaw(handle, ctx.raw_value),
- .fixed_value => |ev| setFixedValueRaw(handle, ctx.raw_value),
+ .undeclared => root.printErrorExit("option '{}' has not been declared", .{ctx.key}),
+ .int_value => |ev| handle.setIntValue(parseInt(ctx.raw_value)),
+ .uint_value => |ev| handle.setUintValue(parseUint(ctx.raw_value)),
+ .fixed_value => |ev| handle.setFixedValue(parseFixed(ctx.raw_value)),
.string_value => |ev| handle.setStringValue(if (ctx.raw_value[0] == 0) null else ctx.raw_value),
}
_ = ctx.display.flush() catch os.exit(1);
@@ -290,16 +281,12 @@ fn setOptionListener(
}
fn modOptionListener(
- handle: *zriver.OptionHandleV1,
- event: zriver.OptionHandleV1.Event,
+ handle: *river.OptionHandleV2,
+ event: river.OptionHandleV2.Event,
ctx: *const Context,
) void {
switch (event) {
- .unset => if (ctx.output) |output| {
- root.printErrorExit("option '{}' has not been declared on output '{}'", .{ ctx.key, output.name });
- } else {
- root.printErrorExit("option '{}' has not been declared globally", .{ctx.key});
- },
+ .undeclared => root.printErrorExit("option '{}' has not been declared", .{ctx.key}),
.int_value => |ev| modIntValueRaw(handle, ev.value, ctx.raw_value),
.uint_value => |ev| modUintValueRaw(handle, ev.value, ctx.raw_value),
.fixed_value => |ev| modFixedValueRaw(handle, ev.value, ctx.raw_value),
@@ -308,3 +295,26 @@ fn modOptionListener(
_ = ctx.display.flush() catch os.exit(1);
os.exit(0);
}
+
+fn modIntValueRaw(handle: *river.OptionHandleV2, current: i32, raw_value: [*:0]const u8) void {
+ const mod = fmt.parseInt(i32, mem.span(raw_value), 10) catch
+ root.printErrorExit("{} is not a valid int modifier", .{raw_value});
+ const new_value = math.add(i32, current, mod) catch
+ root.printErrorExit("provided value of {d} would overflow option if added", .{mod});
+ handle.setIntValue(new_value);
+}
+
+fn modUintValueRaw(handle: *river.OptionHandleV2, current: u32, raw_value: [*:0]const u8) void {
+ // We need to allow negative mod values, but the value of the option may
+ // never be below zero.
+ const mod = fmt.parseInt(i32, mem.span(raw_value), 10) catch
+ root.printErrorExit("{} is not a valid uint modifier", .{raw_value});
+ const new = @intCast(i32, current) + mod;
+ handle.setUintValue(if (new < 0) 0 else @intCast(u32, new));
+}
+
+fn modFixedValueRaw(handle: *river.OptionHandleV2, current: wl.Fixed, raw_value: [*:0]const u8) void {
+ const mod = fmt.parseFloat(f64, mem.span(raw_value)) catch
+ root.printErrorExit("{} is not a valid fixed modifier", .{raw_value});
+ handle.setFixedValue(wl.Fixed.fromDouble(current.toDouble() + mod));
+}
diff --git a/rivertile/main.zig b/rivertile/main.zig
index c020101..b33d9b0 100644
--- a/rivertile/main.zig
+++ b/rivertile/main.zig
@@ -1,6 +1,6 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
-// Copyright 2020 The River Developers
+// Copyright 2020-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
@@ -14,15 +14,13 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-//
-//
// This is an implementation of the default "tiled" layout of dwm and the
-// 3 other orientations thereof. This code is written with the left
-// orientation in mind and then the input/output values are adjusted to apply
-// the necessary transformations to derive the other 3.
+// 3 other orientations thereof. This code is written for the main stack
+// to the left and then the input/output values are adjusted to apply
+// the necessary transformations to derive the other orientations.
//
-// With 4 views and one main, the left layout looks something like this:
+// With 4 views and one main on the left, the layout looks something like this:
//
// +-----------------------+------------+
// | | |
@@ -37,229 +35,164 @@
// | | |
// | | |
// +-----------------------+------------+
-//
const std = @import("std");
+const mem = std.mem;
+const assert = std.debug.assert;
+
const wayland = @import("wayland");
const wl = wayland.client.wl;
-const zriver = wayland.client.zriver;
const river = wayland.client.river;
+const Location = enum {
+ top,
+ right,
+ bottom,
+ left,
+};
+
+const default_main_location: Location = .left;
+const default_main_count = 1;
+const default_main_factor = 0.6;
+const default_view_padding = 6;
+const default_outer_padding = 6;
+
+/// We don't free resources on exit, only when output globals are removed.
const gpa = std.heap.c_allocator;
const Context = struct {
- running: bool = true,
+ initialized: bool = false,
layout_manager: ?*river.LayoutManagerV1 = null,
- options_manager: ?*zriver.OptionsManagerV1 = null,
+ options_manager: ?*river.OptionsManagerV2 = null,
outputs: std.TailQueue(Output) = .{},
- pub fn addOutput(self: *Context, registry: *wl.Registry, name: u32) !void {
- const output = try registry.bind(name, wl.Output, 3);
+ fn addOutput(context: *Context, registry: *wl.Registry, name: u32) !void {
+ const wl_output = try registry.bind(name, wl.Output, 3);
+ errdefer wl_output.release();
const node = try gpa.create(std.TailQueue(Output).Node);
- node.data.init(self, output);
- self.outputs.append(node);
- }
-
- pub fn destroyAllOutputs(self: *Context) void {
- while (self.outputs.pop()) |node| {
- node.data.deinit();
- gpa.destroy(node);
- }
- }
-
- pub fn configureAllOutputs(self: *Context) void {
- var it = self.outputs.first;
- while (it) |node| : (it = node.next) {
- node.data.configure(self);
- }
+ errdefer gpa.destroy(node);
+ try node.data.init(context, wl_output, name);
+ context.outputs.append(node);
}
};
-const Option = struct {
- pub const Value = union(enum) {
- unset: void,
- double: f64,
- uint: u32,
- };
+fn Option(comptime key: [:0]const u8, comptime T: type, comptime default: T) type {
+ return struct {
+ const Self = @This();
+ output: *Output,
+ handle: *river.OptionHandleV2,
+ value: T = default,
- handle: ?*zriver.OptionHandleV1 = null,
- value: Value = .unset,
- output: *Output = undefined,
+ fn init(option: *Self, context: *Context, output: *Output) !void {
+ option.* = .{
+ .output = output,
+ .handle = try context.options_manager.?.getOptionHandle(key, output.wl_output),
+ };
+ option.handle.setListener(*Self, optionListener, option) catch unreachable;
+ }
- pub fn init(self: *Option, output: *Output, comptime key: [*:0]const u8, initial: Value) !void {
- self.* = .{
- .value = initial,
- .output = output,
- .handle = try output.context.options_manager.?.getOptionHandle(
- key,
- output.output,
- ),
- };
- self.handle.?.setListener(*Option, optionListener, self) catch |err| {
- self.handle.?.destroy();
- self.handle = null;
- return err;
- };
- }
+ fn deinit(option: *Self) void {
+ option.handle.destroy();
+ option.* = undefined;
+ }
- pub fn deinit(self: *Option) void {
- if (self.handle) |handle| handle.destroy();
- }
-
- fn optionListener(handle: *zriver.OptionHandleV1, event: zriver.OptionHandleV1.Event, self: *Option) void {
- switch (event) {
- .unset => switch (self.value) {
- .uint => handle.setUintValue(self.value.uint),
- .double => handle.setFixedValue(wl.Fixed.fromDouble(self.value.double)),
+ fn optionListener(handle: *river.OptionHandleV2, event: river.OptionHandleV2.Event, option: *Self) void {
+ const prev_value = option.value;
+ assert(event != .undeclared); // We declare all options used in main()
+ switch (T) {
+ u32 => switch (event) {
+ .uint_value => |ev| option.value = ev.value,
+ else => std.log.err("expected value of uint type for " ++ key ++
+ " option, falling back to default", .{}),
+ },
+ f64 => switch (event) {
+ .fixed_value => |ev| option.value = ev.value.toDouble(),
+ else => std.log.err("expected value of fixed type for " ++ key ++
+ " option, falling back to default", .{}),
+ },
+ Location => switch (event) {
+ .string_value => |ev| if (ev.value) |value| {
+ if (std.meta.stringToEnum(Location, mem.span(value))) |location| {
+ option.value = location;
+ } else {
+ std.log.err(
+ \\invalid main_location "{s}", must be "top", "bottom", "left", or "right"
+ , .{value});
+ }
+ },
+ else => std.log.err("expected value of string type for " ++ key ++
+ " option, falling back to default", .{}),
+ },
else => unreachable,
- },
- .int_value => {},
- .uint_value => |data| self.value = .{ .uint = data.value },
- .fixed_value => |data| self.value = .{ .double = data.value.toDouble() },
- .string_value => {},
+ }
+ if (option.value != prev_value) option.output.layout.parametersChanged();
}
- if (self.output.top.layout) |layout| layout.parametersChanged();
- if (self.output.right.layout) |layout| layout.parametersChanged();
- if (self.output.bottom.layout) |layout| layout.parametersChanged();
- if (self.output.left.layout) |layout| layout.parametersChanged();
- }
-
- pub fn getValueOrElse(self: *Option, comptime T: type, comptime otherwise: T) T {
- switch (T) {
- u32 => return if (self.value == .uint) self.value.uint else otherwise,
- f64 => return if (self.value == .double) self.value.double else otherwise,
- else => @compileError("Unsupported type for Option.getValueOrElse()"),
- }
- }
-};
+ };
+}
const Output = struct {
- context: *Context,
- output: *wl.Output,
+ wl_output: *wl.Output,
+ name: u32,
- top: Layout = undefined,
- right: Layout = undefined,
- bottom: Layout = undefined,
- left: Layout = undefined,
+ main_location: Option("main_location", Location, default_main_location) = undefined,
+ main_count: Option("main_count", u32, default_main_count) = undefined,
+ main_factor: Option("main_factor", f64, default_main_factor) = undefined,
+ view_padding: Option("view_padding", u32, default_view_padding) = undefined,
+ outer_padding: Option("outer_padding", u32, default_outer_padding) = undefined,
- main_amount: Option = .{},
- main_factor: Option = .{},
- view_padding: Option = .{},
- outer_padding: Option = .{},
+ layout: *river.LayoutV1 = undefined,
- configured: bool = false,
-
- pub fn init(self: *Output, context: *Context, wl_output: *wl.Output) void {
- self.* = .{
- .output = wl_output,
- .context = context,
- };
- self.configure(context);
+ fn init(output: *Output, context: *Context, wl_output: *wl.Output, name: u32) !void {
+ output.* = .{ .wl_output = wl_output, .name = name };
+ if (context.initialized) try output.initOptionsAndLayout(context);
}
- pub fn deinit(self: *Output) void {
- self.output.release();
+ fn initOptionsAndLayout(output: *Output, context: *Context) !void {
+ assert(context.initialized);
+ try output.main_location.init(context, output);
+ errdefer output.main_location.deinit();
+ try output.main_count.init(context, output);
+ errdefer output.main_count.deinit();
+ try output.main_factor.init(context, output);
+ errdefer output.main_factor.deinit();
+ try output.view_padding.init(context, output);
+ errdefer output.view_padding.deinit();
+ try output.outer_padding.init(context, output);
+ errdefer output.outer_padding.deinit();
- if (self.configured) {
- self.top.deinit();
- self.right.deinit();
- self.bottom.deinit();
- self.left.deinit();
-
- self.main_amount.deinit();
- self.main_factor.deinit();
- self.view_padding.deinit();
- self.outer_padding.deinit();
- }
+ output.layout = try context.layout_manager.?.getLayout(output.wl_output, "rivertile");
+ output.layout.setListener(*Output, layoutListener, output) catch unreachable;
}
- pub fn configure(self: *Output, context: *Context) void {
- if (self.configured) return;
- if (context.layout_manager == null) return;
- if (context.options_manager == null) return;
+ fn deinit(output: *Output) void {
+ output.wl_output.release();
- self.configured = true;
+ output.main_count.deinit();
+ output.main_factor.deinit();
+ output.view_padding.deinit();
+ output.outer_padding.deinit();
- self.main_amount.init(self, "main_amount", .{ .uint = 1 }) catch {};
- self.main_factor.init(self, "main_factor", .{ .double = 0.6 }) catch {};
- self.view_padding.init(self, "view_padding", .{ .uint = 10 }) catch {};
- self.outer_padding.init(self, "outer_padding", .{ .uint = 10 }) catch {};
-
- self.top.init(self, .top) catch {};
- self.right.init(self, .right) catch {};
- self.bottom.init(self, .bottom) catch {};
- self.left.init(self, .left) catch {};
- }
-};
-
-const Layout = struct {
- output: *Output,
- layout: ?*river.LayoutV1,
- orientation: Orientation,
-
- const Orientation = enum {
- top,
- right,
- bottom,
- left,
- };
-
- pub fn init(self: *Layout, output: *Output, orientation: Orientation) !void {
- self.output = output;
- self.orientation = orientation;
- self.layout = try output.context.layout_manager.?.getLayout(
- self.output.output,
- self.getNamespace(),
- );
- self.layout.?.setListener(*Layout, layoutListener, self) catch |err| {
- self.layout.?.destroy();
- self.layout = null;
- return err;
- };
+ output.layout.destroy();
}
- fn getNamespace(self: *Layout) [*:0]const u8 {
- return switch (self.orientation) {
- .top => "tile-top",
- .right => "tile-right",
- .bottom => "tile-bottom",
- .left => "tile-left",
- };
- }
-
- pub fn deinit(self: *Layout) void {
- if (self.layout) |layout| {
- layout.destroy();
- self.layout = null;
- }
- }
-
- fn layoutListener(layout: *river.LayoutV1, event: river.LayoutV1.Event, self: *Layout) void {
+ fn layoutListener(layout: *river.LayoutV1, event: river.LayoutV1.Event, output: *Output) void {
switch (event) {
- .namespace_in_use => {
- std.debug.warn("{}: Namespace already in use.\n", .{self.getNamespace()});
- self.deinit();
- },
+ .namespace_in_use => fatal("namespace 'rivertile' already in use.", .{}),
- .layout_demand => |data| {
- const main_amount = self.output.main_amount.getValueOrElse(u32, 1);
- const main_factor = std.math.clamp(self.output.main_factor.getValueOrElse(f64, 0.6), 0.1, 0.9);
- const view_padding = self.output.view_padding.getValueOrElse(u32, 0);
- const outer_padding = self.output.outer_padding.getValueOrElse(u32, 0);
-
- const secondary_count = if (data.view_count > main_amount)
- data.view_count - main_amount
+ .layout_demand => |ev| {
+ const secondary_count = if (ev.view_count > output.main_count.value)
+ ev.view_count - output.main_count.value
else
0;
- const usable_width = if (self.orientation == .left or self.orientation == .right)
- data.usable_width - (2 * outer_padding)
- else
- data.usable_height - (2 * outer_padding);
- const usable_height = if (self.orientation == .left or self.orientation == .right)
- data.usable_height - (2 * outer_padding)
- else
- data.usable_width - (2 * outer_padding);
+ const usable_width = switch (output.main_location.value) {
+ .left, .right => ev.usable_width - (2 * output.outer_padding.value),
+ .top, .bottom => ev.usable_height - (2 * output.outer_padding.value),
+ };
+ const usable_height = switch (output.main_location.value) {
+ .left, .right => ev.usable_height - (2 * output.outer_padding.value),
+ .top, .bottom => ev.usable_width - (2 * output.outer_padding.value),
+ };
// to make things pixel-perfect, we make the first main and first secondary
// view slightly larger if the height is not evenly divisible
@@ -271,18 +204,18 @@ const Layout = struct {
var secondary_height: u32 = undefined;
var secondary_height_rem: u32 = undefined;
- if (main_amount > 0 and secondary_count > 0) {
- main_width = @floatToInt(u32, main_factor * @intToFloat(f64, usable_width));
- main_height = usable_height / main_amount;
- main_height_rem = usable_height % main_amount;
+ if (output.main_count.value > 0 and secondary_count > 0) {
+ main_width = @floatToInt(u32, output.main_factor.value * @intToFloat(f64, usable_width));
+ main_height = usable_height / output.main_count.value;
+ main_height_rem = usable_height % output.main_count.value;
secondary_width = usable_width - main_width;
secondary_height = usable_height / secondary_count;
secondary_height_rem = usable_height % secondary_count;
- } else if (main_amount > 0) {
+ } else if (output.main_count.value > 0) {
main_width = usable_width;
- main_height = usable_height / main_amount;
- main_height_rem = usable_height % main_amount;
+ main_height = usable_height / output.main_count.value;
+ main_height_rem = usable_height % output.main_count.value;
} else if (secondary_width > 0) {
main_width = 0;
secondary_width = usable_width;
@@ -291,63 +224,63 @@ const Layout = struct {
}
var i: u32 = 0;
- while (i < data.view_count) : (i += 1) {
+ while (i < ev.view_count) : (i += 1) {
var x: i32 = undefined;
var y: i32 = undefined;
var width: u32 = undefined;
var height: u32 = undefined;
- if (i < main_amount) {
+ if (i < output.main_count.value) {
x = 0;
y = @intCast(i32, (i * main_height) + if (i > 0) main_height_rem else 0);
width = main_width;
height = main_height + if (i == 0) main_height_rem else 0;
} else {
x = @intCast(i32, main_width);
- y = @intCast(i32, (i - main_amount) * secondary_height +
- if (i > main_amount) secondary_height_rem else 0);
+ y = @intCast(i32, (i - output.main_count.value) * secondary_height +
+ if (i > output.main_count.value) secondary_height_rem else 0);
width = secondary_width;
- height = secondary_height + if (i == main_amount) secondary_height_rem else 0;
+ height = secondary_height + if (i == output.main_count.value) secondary_height_rem else 0;
}
- x += @intCast(i32, view_padding);
- y += @intCast(i32, view_padding);
- width -= 2 * view_padding;
- height -= 2 * view_padding;
+ x += @intCast(i32, output.view_padding.value);
+ y += @intCast(i32, output.view_padding.value);
+ width -= 2 * output.view_padding.value;
+ height -= 2 * output.view_padding.value;
- switch (self.orientation) {
+ switch (output.main_location.value) {
.left => layout.pushViewDimensions(
- data.serial,
- x + @intCast(i32, outer_padding),
- y + @intCast(i32, outer_padding),
+ ev.serial,
+ x + @intCast(i32, output.outer_padding.value),
+ y + @intCast(i32, output.outer_padding.value),
width,
height,
),
.right => layout.pushViewDimensions(
- data.serial,
- @intCast(i32, usable_width - width) - x + @intCast(i32, outer_padding),
- y + @intCast(i32, outer_padding),
+ ev.serial,
+ @intCast(i32, usable_width - width) - x + @intCast(i32, output.outer_padding.value),
+ y + @intCast(i32, output.outer_padding.value),
width,
height,
),
.top => layout.pushViewDimensions(
- data.serial,
- y + @intCast(i32, outer_padding),
- x + @intCast(i32, outer_padding),
+ ev.serial,
+ y + @intCast(i32, output.outer_padding.value),
+ x + @intCast(i32, output.outer_padding.value),
height,
width,
),
.bottom => layout.pushViewDimensions(
- data.serial,
- y + @intCast(i32, outer_padding),
- @intCast(i32, usable_width - width) - x + @intCast(i32, outer_padding),
+ ev.serial,
+ y + @intCast(i32, output.outer_padding.value),
+ @intCast(i32, usable_width - width) - x + @intCast(i32, output.outer_padding.value),
height,
width,
),
}
}
- layout.commit(data.serial);
+ layout.commit(ev.serial);
},
.advertise_view => {},
@@ -366,25 +299,32 @@ pub fn main() !void {
var context: Context = .{};
const registry = try display.getRegistry();
- try registry.setListener(*Context, registryListener, &context);
+ registry.setListener(*Context, registryListener, &context) catch unreachable;
_ = try display.roundtrip();
if (context.layout_manager == null) {
- std.debug.warn("Wayland server does not support river_layout_unstable_v1.\n", .{});
- std.os.exit(1);
+ fatal("wayland compositor does not support river_layout_v1.\n", .{});
}
-
if (context.options_manager == null) {
- std.debug.warn("Wayland server does not support river_options_unstable_v1.\n", .{});
- std.os.exit(1);
+ fatal("wayland compositor does not support river_options_v2.\n", .{});
}
- context.configureAllOutputs();
- defer context.destroyAllOutputs();
+ // TODO: should be @tagName(default_main_location), https://github.com/ziglang/zig/issues/3779
+ context.options_manager.?.declareStringOption("main_location", "left");
+ context.options_manager.?.declareUintOption("main_count", default_main_count);
+ context.options_manager.?.declareFixedOption("main_factor", wl.Fixed.fromDouble(default_main_factor));
+ context.options_manager.?.declareUintOption("view_padding", default_view_padding);
+ context.options_manager.?.declareUintOption("outer_padding", default_outer_padding);
- while (context.running) {
- _ = try display.dispatch();
+ context.initialized = true;
+
+ var it = context.outputs.first;
+ while (it) |node| : (it = node.next) {
+ const output = &node.data;
+ try output.initOptionsAndLayout(&context);
}
+
+ while (true) _ = try display.dispatch();
}
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void {
@@ -392,15 +332,28 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *
.global => |global| {
if (std.cstr.cmp(global.interface, river.LayoutManagerV1.getInterface().name) == 0) {
context.layout_manager = registry.bind(global.name, river.LayoutManagerV1, 1) catch return;
- } else if (std.cstr.cmp(global.interface, zriver.OptionsManagerV1.getInterface().name) == 0) {
- context.options_manager = registry.bind(global.name, zriver.OptionsManagerV1, 1) catch return;
+ } else if (std.cstr.cmp(global.interface, river.OptionsManagerV2.getInterface().name) == 0) {
+ context.options_manager = registry.bind(global.name, river.OptionsManagerV2, 1) catch return;
} else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) {
- context.addOutput(registry, global.name) catch {
- std.debug.warn("Failed to bind output.\n", .{});
- context.running = false;
- };
+ context.addOutput(registry, global.name) catch |err| fatal("failed to bind output: {}", .{err});
+ }
+ },
+ .global_remove => |ev| {
+ var it = context.outputs.first;
+ while (it) |node| : (it = node.next) {
+ const output = &node.data;
+ if (output.name == ev.name) {
+ context.outputs.remove(node);
+ output.deinit();
+ gpa.destroy(node);
+ break;
+ }
}
},
- .global_remove => |global| {},
}
}
+
+fn fatal(comptime format: []const u8, args: anytype) noreturn {
+ std.log.err(format, args);
+ std.os.exit(1);
+}