river-options: remove protocol

This protocol involves far too much accidental complexity. The original
motivating use-case was to provide a convenient way to send arbitrary
data to layout clients at runtime in order to avoid layout clients
needing to implement their own IPC and do this over a side-channel.
Instead of implementing a quite complex but still rigid options protocol
and storing this state in the compositor, instead we will simply add
events to the layout protocol to support this use case.

Consider the status quo event sequence:

1. send get_option_handle request (riverctl)
2. roundtrip waiting for first event (riverctl)
3. send set_foo_value request (riverctl)
4. receive set_foo_value request (river)
5. send foo_value event to all current handles (river)
6. receive foo_value event (rivertile)
7. send parameters_changed request (rivertile)
8. receive parameters_changed request (river)
9. send layout_demand (river)

And compare with the event sequence after the proposed change:

1. send set_foo_value request (riverctl)
2. receive set_foo_value request (river)
3. send set_foo_value event (river)
4. send layout_demand (river)

This requires *much* less back and forth between the server and clients
and is clearly much simpler.
This commit is contained in:
Isaac Freund 2021-04-26 21:03:04 +02:00
parent a6f908d7eb
commit 871fc7c8de
17 changed files with 65 additions and 1449 deletions

View File

@ -64,7 +64,6 @@ pub fn build(b: *zbs.Builder) !void {
scanner.addSystemProtocol("unstable/xdg-output/xdg-output-unstable-v1.xml"); scanner.addSystemProtocol("unstable/xdg-output/xdg-output-unstable-v1.xml");
scanner.addSystemProtocol("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"); scanner.addSystemProtocol("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml");
scanner.addProtocolPath("protocol/river-control-unstable-v1.xml"); scanner.addProtocolPath("protocol/river-control-unstable-v1.xml");
scanner.addProtocolPath("protocol/river-options-v2.xml");
scanner.addProtocolPath("protocol/river-status-unstable-v1.xml"); scanner.addProtocolPath("protocol/river-status-unstable-v1.xml");
scanner.addProtocolPath("protocol/river-layout-v1.xml"); scanner.addProtocolPath("protocol/river-layout-v1.xml");
scanner.addProtocolPath("protocol/wlr-layer-shell-unstable-v1.xml"); scanner.addProtocolPath("protocol/wlr-layer-shell-unstable-v1.xml");
@ -139,7 +138,7 @@ pub fn build(b: *zbs.Builder) !void {
} }
if (examples) { if (examples) {
inline for (.{ "status", "options" }) |example_name| { inline for (.{"status"}) |example_name| {
const example = b.addExecutable(example_name, "example/" ++ example_name ++ ".zig"); const example = b.addExecutable(example_name, "example/" ++ example_name ++ ".zig");
example.setTarget(target); example.setTarget(target);
example.setBuildMode(mode); example.setBuildMode(mode);

View File

@ -17,6 +17,8 @@ function __riverctl_completion ()
toggle-float \ toggle-float \
toggle-fullscreen \ toggle-fullscreen \
zoom \ zoom \
default-layout \
output-layout \
set-focused-tags \ set-focused-tags \
set-view-tags \ set-view-tags \
toggle-focused-tags \ toggle-focused-tags \
@ -36,12 +38,7 @@ function __riverctl_completion ()
focus-follow-cursor \ focus-follow-cursor \
opacity \ opacity \
set-repeat \ set-repeat \
xcursor-theme \ xcursor-theme
declare-option \
get-option \
set-option \
unset-option \
mod-option"
COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[1]}")) COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[1]}"))
elif [ "${COMP_CWORD}" -eq 2 ] elif [ "${COMP_CWORD}" -eq 2 ]
then then
@ -52,7 +49,6 @@ function __riverctl_completion ()
"map"|"unmap") OPTS="-release" ;; "map"|"unmap") OPTS="-release" ;;
"attach-mode") OPTS="top bottom" ;; "attach-mode") OPTS="top bottom" ;;
"focus-follows-cursor") OPTS="disabled normal strict" ;; "focus-follows-cursor") OPTS="disabled normal strict" ;;
"get-option"|"set-option"|"unset-option"|"mod-option") OPTS="-output -focused-output" ;;
*) return ;; *) return ;;
esac esac
COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[2]}")) COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[2]}"))

View File

@ -1,6 +1,6 @@
function __fish_riverctl_complete_no_subcommand function __fish_riverctl_complete_no_subcommand
for i in (commandline -opc) for i in (commandline -opc)
if contains -- $i close csd-filter-add exit float-filter-add focus-output focus-view move resize snap send-to-output spawn swap toggle-float toggle-fullscreen zoom set-focused-tags set-view-tags toggle-focused-tags toggle-view-tags spawn-tagmask declare-mode enter-mode map map-pointer unmap unmap-pointer attach-mode background-color border-color-focused border-color-unfocused border-width focus-follows-cursor opacity set-repeat xcursor-theme declare-option get-option set-option unset-option mod-option output_title if contains -- $i close csd-filter-add exit float-filter-add focus-output focus-view move resize snap send-to-output spawn swap toggle-float toggle-fullscreen zoom default-layout output-layout set-focused-tags set-view-tags toggle-focused-tags toggle-view-tags spawn-tagmask declare-mode enter-mode map map-pointer unmap unmap-pointer attach-mode background-color border-color-focused border-color-unfocused border-width focus-follows-cursor opacity set-repeat xcursor-theme
return 1 return 1
end end
end end
@ -23,6 +23,8 @@ complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a swap
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a toggle-float -d 'Toggle the floating state of the focused view' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a toggle-float -d 'Toggle the floating state of the focused view'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a toggle-fullscreen -d 'Toggle the fullscreen state of the focused view' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a toggle-fullscreen -d 'Toggle the fullscreen state of the focused view'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a zoom -d 'Bump the focused view to the top of the layout stack' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a zoom -d 'Bump the focused view to the top of the layout stack'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a default-layout -d 'Set the layout namespace to be used by all outputs by default.'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a output-layout -d 'Set the layout namespace of currently focused output.'
# Tag managements # Tag managements
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a set-focused-tags -d 'Show views with tags corresponding to the set bits of tags' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a set-focused-tags -d 'Show views with tags corresponding to the set bits of tags'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a set-view-tags -d 'Assign the currently focused view the tags corresponding to the set bits of tags' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a set-view-tags -d 'Assign the currently focused view the tags corresponding to the set bits of tags'
@ -46,13 +48,6 @@ complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a focus-fol
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a opacity -d 'Configure server-side opacity of views' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a opacity -d 'Configure server-side opacity of views'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a set-repeat -d 'Set the keyboard repeat rate and repeat delay' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a set-repeat -d 'Set the keyboard repeat rate and repeat delay'
complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a xcursor-theme -d 'Set the xcursor theme' complete -c riverctl -x -n '__fish_riverctl_complete_no_subcommand' -a xcursor-theme -d 'Set the xcursor theme'
# Options
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'
# Subcommands # Subcommands
complete -c riverctl -x -n '__fish_seen_subcommand_from focus-output' -a 'next previous' complete -c riverctl -x -n '__fish_seen_subcommand_from focus-output' -a 'next previous'

View File

@ -29,6 +29,8 @@ _riverctl() {
'toggle-float:Toggle the floating state of the focused view' 'toggle-float:Toggle the floating state of the focused view'
'toggle-fullscreen:Toggle the fullscreen state of the focused view' 'toggle-fullscreen:Toggle the fullscreen state of the focused view'
'zoom:Bump the focused view to the top of the layout stack' 'zoom:Bump the focused view to the top of the layout stack'
'default-layout:Set the layout namespace to be used by all outputs by default.'
'output-layout:Set the layout namespace of currently focused output.'
# Tag management # Tag management
'set-focused-tags:Show views with tags corresponding to the set bits of tags' 'set-focused-tags:Show views with tags corresponding to the set bits of tags'
'set-view-tags:Assign the currently focused view the tags corresponding to the set bits of tags' 'set-view-tags:Assign the currently focused view the tags corresponding to the set bits of tags'
@ -52,13 +54,6 @@ _riverctl() {
'opacity:Configure server-side opacity of views' 'opacity:Configure server-side opacity of views'
'set-repeat:Set the keyboard repeat rate and repeat delay' 'set-repeat:Set the keyboard repeat rate and repeat delay'
'xcursor-theme:Set the xcursor theme' 'xcursor-theme:Set the xcursor theme'
# Options
'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'
) )
local -A opt_args local -A opt_args

View File

@ -258,38 +258,6 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_
and is made available through the _XCURSOR_THEME_ and _XCURSOR_SIZE_ and is made available through the _XCURSOR_THEME_ and _XCURSOR_SIZE_
environment variables. environment variables.
## OPTIONS
River has various options that are saved in a typed key-value store. It also
allows users to store arbitrary custom options in the store. Options are
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* _name_ _type_ _value_
Declare a new option with the given _type_ and initial _value_. If
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.
*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.
# EXAMPLES # EXAMPLES
Bind bemenu-run to Super+P in normal mode: Bind bemenu-run to Super+P in normal mode:

View File

@ -13,29 +13,6 @@ rivertile - Tiled layout generator for river
*rivertile* is a layout client for river. It provides a simple tiled layout *rivertile* is a layout client for river. It provides a simple tiled layout
split main/secondary stacks. split main/secondary stacks.
# 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 # AUTHORS
Maintained by Isaac Freund <ifreund@ifreund.xyz> who is assisted by open Maintained by Isaac Freund <ifreund@ifreund.xyz> who is assisted by open

View File

@ -42,13 +42,13 @@ riverctl map normal $mod Return zoom
# Mod+H and Mod+L to decrease/increase the main_factor option by 0.05 # 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. # 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 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 #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. # 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. # 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 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 #riverctl map normal $mod+Shift L spawn riverctl mod-option -focused-output main_count -1
# Mod+Alt+{H,J,K,L} to move views # Mod+Alt+{H,J,K,L} to move views
riverctl map normal $mod+Mod1 H move left 100 riverctl map normal $mod+Mod1 H move left 100
@ -104,10 +104,10 @@ riverctl map normal $mod Space toggle-float
riverctl map normal $mod F toggle-fullscreen riverctl map normal $mod F toggle-fullscreen
# Mod+{Up,Right,Down,Left} to change layout orientation # Mod+{Up,Right,Down,Left} to change layout orientation
riverctl map normal $mod Up spawn riverctl set-option -focused-output main_location top #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 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 Down spawn riverctl set-option -focused-output main_location bottom
riverctl map normal $mod Left spawn riverctl set-option -focused-output main_location left #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 # 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 # normal mode. This makes it useful for testing a nested wayland compositor
@ -155,7 +155,7 @@ riverctl csd-filter-add "gedit"
# Set opacity and fade effect # Set opacity and fade effect
# riverctl opacity 1.0 0.75 0.0 0.1 20 # riverctl opacity 1.0 0.75 0.0 0.1 20
# Exec into the default layout generator, rivertile. # Set and exec into the default layout generator, rivertile.
# River will send the process group of the init executable SIGTERM on exit. # River will send the process group of the init executable SIGTERM on exit.
riverctl set-option layout rivertile riverctl default-layout rivertile
exec rivertile exec rivertile

View File

@ -1,209 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="river_options_v2">
<copyright>
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.
</copyright>
<description summary="set and retrieve options">
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.
</description>
<interface name="river_options_manager_v2" version="1">
<description summary="declare options and get handles">
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.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the river_options_manager_v2 object">
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.
</description>
</request>
<request name="declare_int_option">
<description summary="declare a new option">
The option is created in the global scope and is initialized with the
provided value. This request is ignored if the option already exists.
</description>
<arg name="key" type="string"/>
<arg name="value" type="int"/>
</request>
<request name="declare_uint_option">
<description summary="declare a new option">
The option is created in the global scope and is initialized with the
provided value. This request is ignored if the option already exists.
</description>
<arg name="key" type="string"/>
<arg name="value" type="uint"/>
</request>
<request name="declare_string_option">
<description summary="declare a new option">
The option is created in the global scope and is initialized with the
provided value. This request is ignored if the option already exists.
</description>
<arg name="key" type="string"/>
<arg name="value" type="string" allow-null="true"/>
</request>
<request name="declare_fixed_option">
<description summary="declare a new option">
The option is created in the global scope and is initialized with the
provided value. This request is ignored if the option already exists.
</description>
<arg name="key" type="string"/>
<arg name="value" type="fixed"/>
</request>
<request name="get_option_handle">
<description summary="get an option handle for the given key">
If the output argument is non-null, the option is local to the given
output. Otherwise it is considered global.
</description>
<arg name="key" type="string"/>
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
<arg name="handle" type="new_id" interface="river_option_handle_v2"/>
</request>
<request name="unset_option">
<description summary="unset an output-local value if any">
This causes the value of the option for the given output to fall
back to the global value.
</description>
<arg name="key" type="string"/>
<arg name="output" type="object" interface="wl_output"/>
</request>
</interface>
<interface name="river_option_handle_v2" version="1">
<description summary="handle to an option">
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.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the handle">
This request indicates that the client will not use the
river_option_handle_v2 any more and that it may be safely destroyed.
</description>
</request>
<enum name="error">
<entry name="request_while_undeclared" value="0" summary="a request other
than destroy was made after receiving the undeclared event"/>
<entry name="type_mismatch" value="1"
summary="a set request of the wrong type was made"/>
</enum>
<event name="undeclared">
<description summary="the option has never been declared">
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.
</description>
</event>
<event name="int_value">
<description summary="the current value of the int option">
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.
</description>
<arg name="value" type="int"/>
</event>
<event name="uint_value">
<description summary="the current value of the uint option">
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.
</description>
<arg name="value" type="uint"/>
</event>
<event name="string_value">
<description summary="the current value of the string option">
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.
</description>
<arg name="value" type="string" allow-null="true"/>
</event>
<event name="fixed_value">
<description summary="the current value of the fixed option">
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.
</description>
<arg name="value" type="fixed"/>
</event>
<request name="set_int_value">
<description summary="set the value of the option">
If the option is of type int, set the value of the option.
Otherwise, this request is a protocol error.
</description>
<arg name="value" type="int"/>
</request>
<request name="set_uint_value">
<description summary="set the value of the option">
If the option is of type uint, set the value of the option.
Otherwise, this request is a protocol error.
</description>
<arg name="value" type="uint"/>
</request>
<request name="set_string_value">
<description summary="set the value of the option">
If the option is of type string, set the value of the option.
Otherwise, this request is a protocol error.
</description>
<arg name="value" type="string" allow-null="true"/>
</request>
<request name="set_fixed_value">
<description summary="set the value of the option">
If the option is of type fixed, set the value of the option.
Otherwise, this request is a protocol error.
</description>
<arg name="value" type="fixed"/>
</request>
</interface>
</protocol>

View File

@ -1,184 +0,0 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
// 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const Self = @This();
const std = @import("std");
const 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 OutputOption = @import("OutputOption.zig");
const log = std.log.scoped(.river_options);
pub const Value = union(enum) {
int: i32,
uint: u32,
fixed: wl.Fixed,
string: ?[*:0]const u8,
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,
};
}
pub fn deinit(value: *Value) void {
if (value.* == .string) if (value.string) |s| util.gpa.free(mem.span(s));
}
};
options_manager: *OptionsManager,
link: wl.list.Link = undefined,
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(*Value),
} = 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, key: [*:0]const u8, value: Value) !void {
const self = try util.gpa.create(Self);
errdefer util.gpa.destroy(self);
var owned_value = try value.dupe();
errdefer owned_value.deinit();
self.* = .{
.options_manager = options_manager,
.key = try util.gpa.dupeZ(u8, mem.span(key)),
.value = owned_value,
};
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);
}
pub fn destroy(self: *Self) void {
{
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);
}
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 {
if (meta.activeTag(value) != meta.activeTag(self.value)) return error.TypeMismatch;
self.value.deinit();
self.value = try value.dupe();
{
var it = self.handles.iterator(.forward);
while (it.next()) |handle| self.sendValue(handle);
}
{
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);
}
pub fn sendValue(self: Self, handle: *river.OptionHandleV2) void {
switch (self.value) {
.int => |v| handle.sendIntValue(v),
.uint => |v| handle.sendUintValue(v),
.fixed => |v| handle.sendFixedValue(v),
.string => |v| handle.sendStringValue(v),
}
}
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: *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();
}

View File

@ -1,188 +0,0 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
// 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const Self = @This();
const std = @import("std");
const mem = std.mem;
const wayland = @import("wayland");
const wl = wayland.server.wl;
const river = wayland.server.river;
const wlr = @import("wlroots");
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),
options: wl.list.Head(Option, "link") = undefined,
pub fn init(self: *Self, server: *Server) !void {
self.* = .{
.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 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.getOutputOption(output)) |output_option| output_option.destroy();
}
}
fn handleServerDestroy(listener: *wl.Listener(*wl.Server), wl_server: *wl.Server) void {
const self = @fieldParentPtr(Self, "server_destroy", listener);
self.global.destroy();
var it = self.options.safeIterator(.forward);
while (it.next()) |option| option.destroy();
}
fn bind(client: *wl.Client, self: *Self, version: u32, id: u32) callconv(.C) void {
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: *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
const wlr_output = wlr.Output.fromWlOutput(wl_output) orelse return;
break :blk @intToPtr(*Output, wlr_output.data);
} else null;
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 = river.OptionHandleV2.create(
options_manager.getClient(),
options_manager.getVersion(),
req.handle,
) catch {
options_manager.getClient().postNoMemory();
return;
};
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",
);
},
}
}

View File

@ -38,8 +38,6 @@ const View = @import("View.zig");
const ViewStack = @import("view_stack.zig").ViewStack; const ViewStack = @import("view_stack.zig").ViewStack;
const AttachMode = @import("view_stack.zig").AttachMode; const AttachMode = @import("view_stack.zig").AttachMode;
const OutputStatus = @import("OutputStatus.zig"); const OutputStatus = @import("OutputStatus.zig");
const Option = @import("Option.zig");
const OutputOption = @import("OutputOption.zig");
const State = struct { const State = struct {
/// A bit field of focused tags /// A bit field of focused tags
@ -150,10 +148,6 @@ pub fn init(self: *Self, root: *Root, wlr_output: *wlr.Output) !void {
.height = effective_resolution.height, .height = effective_resolution.height,
}; };
const options_manager = &root.server.options_manager;
try options_manager.createOutputOptions(self);
errdefer options_manager.destroyOutputOptions(self);
self.setTitle(); self.setTitle();
} }
} }
@ -428,8 +422,6 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) v
std.log.scoped(.server).debug("output '{}' destroyed", .{self.wlr_output.name}); std.log.scoped(.server).debug("output '{}' destroyed", .{self.wlr_output.name});
root.server.options_manager.destroyOutputOptions(self);
// Remove the destroyed output from root if it wasn't already removed // Remove the destroyed output from root if it wasn't already removed
root.removeOutput(self); root.removeOutput(self);

View File

@ -1,145 +0,0 @@
// 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 <https://www.gnu.org/licenses/>.
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();
}

View File

@ -26,7 +26,6 @@ const c = @import("c.zig");
const util = @import("util.zig"); const util = @import("util.zig");
const Config = @import("Config.zig"); const Config = @import("Config.zig");
const OptionsManager = @import("OptionsManager.zig");
const Control = @import("Control.zig"); const Control = @import("Control.zig");
const DecorationManager = @import("DecorationManager.zig"); const DecorationManager = @import("DecorationManager.zig");
const InputManager = @import("InputManager.zig"); const InputManager = @import("InputManager.zig");
@ -66,7 +65,6 @@ root: Root,
config: Config, config: Config,
control: Control, control: Control,
status_manager: StatusManager, status_manager: StatusManager,
options_manager: OptionsManager,
layout_manager: LayoutManager, layout_manager: LayoutManager,
pub fn init(self: *Self) !void { pub fn init(self: *Self) !void {
@ -117,7 +115,6 @@ pub fn init(self: *Self) !void {
try self.decoration_manager.init(self); try self.decoration_manager.init(self);
try self.root.init(self); try self.root.init(self);
// Must be called after root is initialized // Must be called after root is initialized
try self.options_manager.init(self);
try self.input_manager.init(self); try self.input_manager.init(self);
try self.control.init(self); try self.control.init(self);
try self.status_manager.init(self); try self.status_manager.init(self);

View File

@ -1,119 +0,0 @@
// 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 <https://www.gnu.org/licenses/>.
const std = @import("std");
const mem = std.mem;
const cstr = std.cstr;
const root = @import("root");
pub const FlagDef = struct {
name: [*:0]const u8,
kind: enum { boolean, arg },
};
pub fn Args(comptime num_positionals: comptime_int, comptime flag_defs: []const FlagDef) type {
return struct {
const Self = @This();
positionals: [num_positionals][*:0]const u8,
flags: [flag_defs.len]struct {
name: [*:0]const u8,
value: union {
boolean: bool,
arg: ?[*:0]const u8,
},
},
pub fn parse(argv: [][*:0]const u8) Self {
var ret: Self = undefined;
// Init all flags in the flags array to false/null
inline for (flag_defs) |flag_def, flag_idx| {
switch (flag_def.kind) {
.boolean => ret.flags[flag_idx] = .{
.name = flag_def.name,
.value = .{ .boolean = false },
},
.arg => ret.flags[flag_idx] = .{
.name = flag_def.name,
.value = .{ .arg = null },
},
}
}
// Parse the argv in to the positionals and flags arrays
var arg_idx: usize = 0;
var positional_idx: usize = 0;
outer: while (arg_idx < argv.len) : (arg_idx += 1) {
var should_continue = false;
inline for (flag_defs) |flag_def, flag_idx| {
if (cstr.cmp(flag_def.name, argv[arg_idx]) == 0) {
switch (flag_def.kind) {
.boolean => ret.flags[flag_idx].value.boolean = true,
.arg => {
arg_idx += 1;
ret.flags[flag_idx].value.arg = if (arg_idx < argv.len)
argv[arg_idx]
else
root.printErrorExit("flag '" ++ flag_def.name ++
"' requires an argument but none was provided!", .{});
},
}
// TODO: this variable exists as a workaround for the fact that
// using continue :outer here crashes the stage1 compiler.
should_continue = true;
}
}
if (should_continue) continue;
if (positional_idx == num_positionals) {
root.printErrorExit(
"{} positional arguments expected but more were provided!",
.{num_positionals},
);
}
ret.positionals[positional_idx] = argv[arg_idx];
positional_idx += 1;
}
if (positional_idx < num_positionals) {
root.printErrorExit(
"{} positional arguments expected but only {} were provided!",
.{ num_positionals, positional_idx },
);
}
return ret;
}
pub fn boolFlag(self: Self, flag_name: [*:0]const u8) bool {
for (self.flags) |flag| {
if (cstr.cmp(flag.name, flag_name) == 0) return flag.value.boolean;
}
unreachable;
}
pub fn argFlag(self: Self, flag_name: [*:0]const u8) ?[*:0]const u8 {
for (self.flags) |flag| {
if (cstr.cmp(flag.name, flag_name) == 0) return flag.value.arg;
}
unreachable;
}
};
}

View File

@ -22,26 +22,13 @@ const assert = std.debug.assert;
const wayland = @import("wayland"); const wayland = @import("wayland");
const wl = wayland.client.wl; const wl = wayland.client.wl;
const river = wayland.client.river;
const zriver = wayland.client.zriver; const zriver = wayland.client.zriver;
const zxdg = wayland.client.zxdg;
const gpa = std.heap.c_allocator; const gpa = std.heap.c_allocator;
const options = @import("options.zig");
pub const Output = struct {
wl_output: *wl.Output,
name: []const u8,
};
pub const Globals = struct { pub const Globals = struct {
control: ?*zriver.ControlV1 = null, control: ?*zriver.ControlV1 = null,
options_manager: ?*river.OptionsManagerV2 = null,
status_manager: ?*zriver.StatusManagerV1 = null,
seat: ?*wl.Seat = null, seat: ?*wl.Seat = null,
output_manager: ?*zxdg.OutputManagerV1 = null,
outputs: std.ArrayList(Output) = std.ArrayList(Output).init(gpa),
}; };
pub fn main() !void { pub fn main() !void {
@ -54,20 +41,9 @@ pub fn main() !void {
\\The Wayland server does not support river-control-unstable-v1. \\The Wayland server does not support river-control-unstable-v1.
\\Do your versions of river and riverctl match? \\Do your versions of river and riverctl match?
, .{}), , .{}),
error.RiverStatusManagerNotAdvertised => printErrorExit(
\\The Wayland server does not support river-status-unstable-v1.
\\Do your versions of river and riverctl match?
, .{}),
error.RiverOptionsManagerNotAdvertised => printErrorExit(
\\The Wayland server does not support river-options-unstable-v1.
\\Do your versions of river and riverctl match?
, .{}),
error.SeatNotAdverstised => printErrorExit( error.SeatNotAdverstised => printErrorExit(
\\The Wayland server did not advertise any seat. \\The Wayland server did not advertise any seat.
, .{}), , .{}),
error.XdgOutputNotAdvertised => printErrorExit(
\\The Wayland server does not support xdg-output-unstable-v1.
, .{}),
else => return err, else => return err,
} }
}; };
@ -82,17 +58,6 @@ fn _main() !void {
registry.setListener(*Globals, registryListener, &globals) catch unreachable; registry.setListener(*Globals, registryListener, &globals) catch unreachable;
_ = try display.roundtrip(); _ = try display.roundtrip();
if (os.argv.len > 2 and mem.eql(u8, "declare-option", mem.span(os.argv[1]))) {
try options.declareOption(display, &globals);
} else if (os.argv.len > 2 and mem.eql(u8, "get-option", mem.span(os.argv[1]))) {
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 {
const control = globals.control orelse return error.RiverControlNotAdvertised; const control = globals.control orelse return error.RiverControlNotAdvertised;
const seat = globals.seat orelse return error.SeatNotAdverstised; const seat = globals.seat orelse return error.SeatNotAdverstised;
@ -107,7 +72,6 @@ fn _main() !void {
// Loop until our callback is called and we exit. // Loop until our callback is called and we exit.
while (true) _ = try display.dispatch(); while (true) _ = try display.dispatch();
}
} }
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals) void { fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals) void {
@ -118,15 +82,6 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *
globals.seat = registry.bind(global.name, wl.Seat, 1) catch @panic("out of memory"); 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) { } 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"); globals.control = registry.bind(global.name, zriver.ControlV1, 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) {
globals.output_manager = registry.bind(global.name, zxdg.OutputManagerV1, 2) catch @panic("out of memory");
} else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) {
const output = registry.bind(global.name, wl.Output, 1) catch @panic("out of memory");
globals.outputs.append(.{ .wl_output = output, .name = undefined }) catch @panic("out of memory");
} }
}, },
.global_remove => {}, .global_remove => {},
@ -142,10 +97,7 @@ fn callbackListener(callback: *zriver.CommandCallbackV1, event: zriver.CommandCa
} }
os.exit(0); os.exit(0);
}, },
.failure => |failure| { .failure => |failure| printErrorExit("Error: {}\n", .{failure.failure_message}),
std.debug.print("Error: {}\n", .{failure.failure_message});
os.exit(1);
},
} }
} }

View File

@ -1,320 +0,0 @@
// 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 <https://www.gnu.org/licenses/>.
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;
const root = @import("root");
const Args = @import("args.zig").Args;
const FlagDef = @import("args.zig").FlagDef;
const Globals = @import("main.zig").Globals;
const Output = @import("main.zig").Output;
const ValueType = enum {
int,
uint,
fixed,
string,
};
const Context = struct {
display: *wl.Display,
key: [*:0]const u8,
raw_value: [*:0]const u8,
output: ?*Output,
};
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{}).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 raw_value = args.positionals[2];
const options_manager = globals.options_manager orelse return error.RiverOptionsManagerNotAdvertised;
switch (value_type) {
.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),
}
_ = try display.flush();
}
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 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 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 {
// 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
null;
const ctx = Context{
.display = display,
.key = args.positionals[0],
.raw_value = undefined,
.output = output,
};
const options_manager = globals.options_manager orelse return error.RiverOptionsManagerNotAdvertised;
const handle = try options_manager.getOptionHandle(ctx.key, if (ctx.output) |o| o.wl_output else null);
handle.setListener(*const Context, getOptionListener, &ctx) catch unreachable;
// We always exit when our listener is called
while (true) _ = try display.dispatch();
}
pub fn setOption(display: *wl.Display, globals: *Globals) !void {
// https://github.com/ziglang/zig/issues/7807
const argv: [][*:0]const u8 = os.argv;
const args = Args(2, &[_]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
null;
const ctx = Context{
.display = display,
.key = args.positionals[0],
.raw_value = args.positionals[1],
.output = output,
};
const options_manager = globals.options_manager orelse return error.RiverOptionsManagerNotAdvertised;
const handle = try options_manager.getOptionHandle(ctx.key, if (ctx.output) |o| o.wl_output else null);
handle.setListener(*const Context, setOptionListener, &ctx) catch unreachable;
// We always exit when our listener is called
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;
const args = Args(2, &[_]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
null;
const ctx = Context{
.display = display,
.key = args.positionals[0],
.raw_value = args.positionals[1],
.output = output,
};
const options_manager = globals.options_manager orelse return error.RiverOptionsManagerNotAdvertised;
const handle = try options_manager.getOptionHandle(ctx.key, if (ctx.output) |o| o.wl_output else null);
handle.setListener(*const Context, modOptionListener, &ctx) catch unreachable;
// We always exit when our listener is called
while (true) _ = try display.dispatch();
}
fn parseOutputName(display: *wl.Display, globals: *Globals, output_name: [*:0]const u8) !*Output {
const output_manager = globals.output_manager orelse return error.XdgOutputNotAdvertised;
for (globals.outputs.items) |*output| {
const xdg_output = try output_manager.getXdgOutput(output.wl_output);
xdg_output.setListener(*Output, xdgOutputListener, output) catch unreachable;
}
_ = try display.roundtrip();
for (globals.outputs.items) |*output| {
if (mem.eql(u8, output.name, mem.span(output_name))) return output;
}
root.printErrorExit("unknown output '{}'", .{output_name});
}
fn xdgOutputListener(xdg_output: *zxdg.OutputV1, event: zxdg.OutputV1.Event, output: *Output) void {
switch (event) {
.name => |ev| output.name = std.heap.c_allocator.dupe(u8, mem.span(ev.name)) catch @panic("out of memory"),
else => {},
}
}
fn getFocusedOutput(display: *wl.Display, globals: *Globals) !*Output {
const status_manager = globals.status_manager orelse return error.RiverStatusManagerNotAdvertised;
const seat = globals.seat orelse return error.SeatNotAdverstised;
const seat_status = try status_manager.getRiverSeatStatus(seat);
var result: ?*wl.Output = null;
seat_status.setListener(*?*wl.Output, seatStatusListener, &result) catch unreachable;
_ = try display.roundtrip();
const wl_output = if (result) |output| output else return error.NoOutputFocused;
for (globals.outputs.items) |*output| {
if (output.wl_output == wl_output) return output;
} else unreachable;
}
fn seatStatusListener(seat_status: *zriver.SeatStatusV1, event: zriver.SeatStatusV1.Event, result: *?*wl.Output) void {
switch (event) {
.focused_output => |ev| result.* = ev.output,
.unfocused_output, .focused_view => {},
}
}
fn getOptionListener(
handle: *river.OptionHandleV2,
event: river.OptionHandleV2.Event,
ctx: *const Context,
) void {
switch (event) {
.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()}),
.string_value => |ev| if (ev.value) |s| printOutputExit("{}", .{s}) else os.exit(0),
}
}
fn printOutputExit(comptime format: []const u8, args: anytype) noreturn {
const stdout = std.io.getStdOut().writer();
stdout.print(format ++ "\n", args) catch os.exit(1);
os.exit(0);
}
fn setOptionListener(
handle: *river.OptionHandleV2,
event: river.OptionHandleV2.Event,
ctx: *const Context,
) void {
switch (event) {
.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);
os.exit(0);
}
fn modOptionListener(
handle: *river.OptionHandleV2,
event: river.OptionHandleV2.Event,
ctx: *const Context,
) void {
switch (event) {
.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),
.string_value => root.printErrorExit("can not modify string options, use set-option to overwrite them", .{}),
}
_ = 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));
}

View File

@ -63,7 +63,6 @@ const gpa = std.heap.c_allocator;
const Context = struct { const Context = struct {
initialized: bool = false, initialized: bool = false,
layout_manager: ?*river.LayoutManagerV1 = null, layout_manager: ?*river.LayoutManagerV1 = null,
options_manager: ?*river.OptionsManagerV2 = null,
outputs: std.TailQueue(Output) = .{}, outputs: std.TailQueue(Output) = .{},
fn addOutput(context: *Context, registry: *wl.Registry, name: u32) !void { fn addOutput(context: *Context, registry: *wl.Registry, name: u32) !void {
@ -76,102 +75,25 @@ const Context = struct {
} }
}; };
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,
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;
}
fn deinit(option: *Self) void {
option.handle.destroy();
option.* = undefined;
}
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,
}
if (option.value != prev_value) option.output.layout.parametersChanged();
}
};
}
const Output = struct { const Output = struct {
wl_output: *wl.Output, wl_output: *wl.Output,
name: u32, name: u32,
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,
layout: *river.LayoutV1 = undefined, layout: *river.LayoutV1 = undefined,
fn init(output: *Output, context: *Context, wl_output: *wl.Output, name: u32) !void { fn init(output: *Output, context: *Context, wl_output: *wl.Output, name: u32) !void {
output.* = .{ .wl_output = wl_output, .name = name }; output.* = .{ .wl_output = wl_output, .name = name };
if (context.initialized) try output.initOptionsAndLayout(context); if (context.initialized) try output.getLayout(context);
} }
fn initOptionsAndLayout(output: *Output, context: *Context) !void { fn getLayout(output: *Output, context: *Context) !void {
assert(context.initialized); 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();
output.layout = try context.layout_manager.?.getLayout(output.wl_output, "rivertile"); output.layout = try context.layout_manager.?.getLayout(output.wl_output, "rivertile");
output.layout.setListener(*Output, layoutListener, output) catch unreachable; output.layout.setListener(*Output, layoutListener, output) catch unreachable;
} }
fn deinit(output: *Output) void { fn deinit(output: *Output) void {
output.wl_output.release(); output.wl_output.release();
output.main_count.deinit();
output.main_factor.deinit();
output.view_padding.deinit();
output.outer_padding.deinit();
output.layout.destroy(); output.layout.destroy();
} }
@ -180,18 +102,18 @@ const Output = struct {
.namespace_in_use => fatal("namespace 'rivertile' already in use.", .{}), .namespace_in_use => fatal("namespace 'rivertile' already in use.", .{}),
.layout_demand => |ev| { .layout_demand => |ev| {
const secondary_count = if (ev.view_count > output.main_count.value) const secondary_count = if (ev.view_count > default_main_count)
ev.view_count - output.main_count.value ev.view_count - default_main_count
else else
0; 0;
const usable_width = switch (output.main_location.value) { const usable_width = switch (default_main_location) {
.left, .right => ev.usable_width - (2 * output.outer_padding.value), .left, .right => ev.usable_width - 2 * default_outer_padding,
.top, .bottom => ev.usable_height - (2 * output.outer_padding.value), .top, .bottom => ev.usable_height - 2 * default_outer_padding,
}; };
const usable_height = switch (output.main_location.value) { const usable_height = switch (default_main_location) {
.left, .right => ev.usable_height - (2 * output.outer_padding.value), .left, .right => ev.usable_height - 2 * default_outer_padding,
.top, .bottom => ev.usable_width - (2 * output.outer_padding.value), .top, .bottom => ev.usable_width - 2 * default_outer_padding,
}; };
// to make things pixel-perfect, we make the first main and first secondary // to make things pixel-perfect, we make the first main and first secondary
@ -204,18 +126,18 @@ const Output = struct {
var secondary_height: u32 = undefined; var secondary_height: u32 = undefined;
var secondary_height_rem: u32 = undefined; var secondary_height_rem: u32 = undefined;
if (output.main_count.value > 0 and secondary_count > 0) { if (default_main_count > 0 and secondary_count > 0) {
main_width = @floatToInt(u32, output.main_factor.value * @intToFloat(f64, usable_width)); main_width = @floatToInt(u32, default_main_factor * @intToFloat(f64, usable_width));
main_height = usable_height / output.main_count.value; main_height = usable_height / default_main_count;
main_height_rem = usable_height % output.main_count.value; main_height_rem = usable_height % default_main_count;
secondary_width = usable_width - main_width; secondary_width = usable_width - main_width;
secondary_height = usable_height / secondary_count; secondary_height = usable_height / secondary_count;
secondary_height_rem = usable_height % secondary_count; secondary_height_rem = usable_height % secondary_count;
} else if (output.main_count.value > 0) { } else if (default_main_count > 0) {
main_width = usable_width; main_width = usable_width;
main_height = usable_height / output.main_count.value; main_height = usable_height / default_main_count;
main_height_rem = usable_height % output.main_count.value; main_height_rem = usable_height % default_main_count;
} else if (secondary_width > 0) { } else if (secondary_width > 0) {
main_width = 0; main_width = 0;
secondary_width = usable_width; secondary_width = usable_width;
@ -230,50 +152,50 @@ const Output = struct {
var width: u32 = undefined; var width: u32 = undefined;
var height: u32 = undefined; var height: u32 = undefined;
if (i < output.main_count.value) { if (i < default_main_count) {
x = 0; x = 0;
y = @intCast(i32, (i * main_height) + if (i > 0) main_height_rem else 0); y = @intCast(i32, (i * main_height) + if (i > 0) main_height_rem else 0);
width = main_width; width = main_width;
height = main_height + if (i == 0) main_height_rem else 0; height = main_height + if (i == 0) main_height_rem else 0;
} else { } else {
x = @intCast(i32, main_width); x = @intCast(i32, main_width);
y = @intCast(i32, (i - output.main_count.value) * secondary_height + y = @intCast(i32, (i - default_main_count) * secondary_height +
if (i > output.main_count.value) secondary_height_rem else 0); if (i > default_main_count) secondary_height_rem else 0);
width = secondary_width; width = secondary_width;
height = secondary_height + if (i == output.main_count.value) secondary_height_rem else 0; height = secondary_height + if (i == default_main_count) secondary_height_rem else 0;
} }
x += @intCast(i32, output.view_padding.value); x += @intCast(i32, default_view_padding);
y += @intCast(i32, output.view_padding.value); y += @intCast(i32, default_view_padding);
width -= 2 * output.view_padding.value; width -= 2 * default_view_padding;
height -= 2 * output.view_padding.value; height -= 2 * default_view_padding;
switch (output.main_location.value) { switch (default_main_location) {
.left => layout.pushViewDimensions( .left => layout.pushViewDimensions(
ev.serial, ev.serial,
x + @intCast(i32, output.outer_padding.value), x + @intCast(i32, default_outer_padding),
y + @intCast(i32, output.outer_padding.value), y + @intCast(i32, default_outer_padding),
width, width,
height, height,
), ),
.right => layout.pushViewDimensions( .right => layout.pushViewDimensions(
ev.serial, ev.serial,
@intCast(i32, usable_width - width) - x + @intCast(i32, output.outer_padding.value), @intCast(i32, usable_width - width) - x + @intCast(i32, default_outer_padding),
y + @intCast(i32, output.outer_padding.value), y + @intCast(i32, default_outer_padding),
width, width,
height, height,
), ),
.top => layout.pushViewDimensions( .top => layout.pushViewDimensions(
ev.serial, ev.serial,
y + @intCast(i32, output.outer_padding.value), y + @intCast(i32, default_outer_padding),
x + @intCast(i32, output.outer_padding.value), x + @intCast(i32, default_outer_padding),
height, height,
width, width,
), ),
.bottom => layout.pushViewDimensions( .bottom => layout.pushViewDimensions(
ev.serial, ev.serial,
y + @intCast(i32, output.outer_padding.value), y + @intCast(i32, default_outer_padding),
@intCast(i32, usable_width - width) - x + @intCast(i32, output.outer_padding.value), @intCast(i32, usable_width - width) - x + @intCast(i32, default_outer_padding),
height, height,
width, width,
), ),
@ -305,23 +227,13 @@ pub fn main() !void {
if (context.layout_manager == null) { if (context.layout_manager == null) {
fatal("wayland compositor does not support river_layout_v1.\n", .{}); fatal("wayland compositor does not support river_layout_v1.\n", .{});
} }
if (context.options_manager == null) {
fatal("wayland compositor does not support river_options_v2.\n", .{});
}
// 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);
context.initialized = true; context.initialized = true;
var it = context.outputs.first; var it = context.outputs.first;
while (it) |node| : (it = node.next) { while (it) |node| : (it = node.next) {
const output = &node.data; const output = &node.data;
try output.initOptionsAndLayout(&context); try output.getLayout(&context);
} }
while (true) _ = try display.dispatch(); while (true) _ = try display.dispatch();
@ -332,8 +244,6 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *
.global => |global| { .global => |global| {
if (std.cstr.cmp(global.interface, river.LayoutManagerV1.getInterface().name) == 0) { if (std.cstr.cmp(global.interface, river.LayoutManagerV1.getInterface().name) == 0) {
context.layout_manager = registry.bind(global.name, river.LayoutManagerV1, 1) catch return; context.layout_manager = registry.bind(global.name, river.LayoutManagerV1, 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) { } else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) {
context.addOutput(registry, global.name) catch |err| fatal("failed to bind output: {}", .{err}); context.addOutput(registry, global.name) catch |err| fatal("failed to bind output: {}", .{err});
} }