From 871fc7c8de172365bd18456c799ec8aacea9ee4a Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Mon, 26 Apr 2021 21:03:04 +0200 Subject: [PATCH] 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. --- build.zig | 3 +- completions/bash/riverctl | 10 +- completions/fish/riverctl.fish | 11 +- completions/zsh/_riverctl | 9 +- doc/riverctl.1.scd | 32 ---- doc/rivertile.1.scd | 23 --- example/init | 20 +-- protocol/river-options-v2.xml | 209 --------------------- river/Option.zig | 184 ------------------- river/OptionsManager.zig | 188 ------------------- river/Output.zig | 8 - river/OutputOption.zig | 145 --------------- river/Server.zig | 3 - riverctl/args.zig | 119 ------------ riverctl/main.zig | 70 ++------ riverctl/options.zig | 320 --------------------------------- rivertile/main.zig | 160 ++++------------- 17 files changed, 65 insertions(+), 1449 deletions(-) delete mode 100644 protocol/river-options-v2.xml delete mode 100644 river/Option.zig delete mode 100644 river/OptionsManager.zig delete mode 100644 river/OutputOption.zig delete mode 100644 riverctl/args.zig delete mode 100644 riverctl/options.zig diff --git a/build.zig b/build.zig index dc09c77..04ab4e9 100644 --- a/build.zig +++ b/build.zig @@ -64,7 +64,6 @@ 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-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"); @@ -139,7 +138,7 @@ pub fn build(b: *zbs.Builder) !void { } if (examples) { - inline for (.{ "status", "options" }) |example_name| { + inline for (.{"status"}) |example_name| { const example = b.addExecutable(example_name, "example/" ++ example_name ++ ".zig"); example.setTarget(target); example.setBuildMode(mode); diff --git a/completions/bash/riverctl b/completions/bash/riverctl index e747516..576ca75 100644 --- a/completions/bash/riverctl +++ b/completions/bash/riverctl @@ -17,6 +17,8 @@ function __riverctl_completion () toggle-float \ toggle-fullscreen \ zoom \ + default-layout \ + output-layout \ set-focused-tags \ set-view-tags \ toggle-focused-tags \ @@ -36,12 +38,7 @@ function __riverctl_completion () focus-follow-cursor \ opacity \ set-repeat \ - xcursor-theme \ - declare-option \ - get-option \ - set-option \ - unset-option \ - mod-option" + xcursor-theme COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[1]}")) elif [ "${COMP_CWORD}" -eq 2 ] then @@ -52,7 +49,6 @@ function __riverctl_completion () "map"|"unmap") OPTS="-release" ;; "attach-mode") OPTS="top bottom" ;; "focus-follows-cursor") OPTS="disabled normal strict" ;; - "get-option"|"set-option"|"unset-option"|"mod-option") OPTS="-output -focused-output" ;; *) return ;; esac COMPREPLY=($(compgen -W "${OPTS}" -- "${COMP_WORDS[2]}")) diff --git a/completions/fish/riverctl.fish b/completions/fish/riverctl.fish index e893ce4..9b9d09b 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 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 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-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 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 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' @@ -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 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' -# 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 complete -c riverctl -x -n '__fish_seen_subcommand_from focus-output' -a 'next previous' diff --git a/completions/zsh/_riverctl b/completions/zsh/_riverctl index dc60f39..feac372 100644 --- a/completions/zsh/_riverctl +++ b/completions/zsh/_riverctl @@ -29,6 +29,8 @@ _riverctl() { 'toggle-float:Toggle the floating 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' + '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 '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' @@ -52,13 +54,6 @@ _riverctl() { 'opacity:Configure server-side opacity of views' 'set-repeat:Set the keyboard repeat rate and repeat delay' '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 diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd index 8219af3..7c9c3d5 100644 --- a/doc/riverctl.1.scd +++ b/doc/riverctl.1.scd @@ -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_ 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 Bind bemenu-run to Super+P in normal mode: diff --git a/doc/rivertile.1.scd b/doc/rivertile.1.scd index b099a21..fdab055 100644 --- a/doc/rivertile.1.scd +++ b/doc/rivertile.1.scd @@ -13,29 +13,6 @@ rivertile - Tiled layout generator for river *rivertile* is a layout client for river. It provides a simple tiled layout 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 Maintained by Isaac Freund who is assisted by open diff --git a/example/init b/example/init index 0a43af2..7afc9bb 100755 --- a/example/init +++ b/example/init @@ -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 # 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 +#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 +#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 @@ -104,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 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 +#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 @@ -155,7 +155,7 @@ 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. +# Set and 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 +riverctl default-layout rivertile exec rivertile diff --git a/protocol/river-options-v2.xml b/protocol/river-options-v2.xml deleted file mode 100644 index cb9f1d0..0000000 --- a/protocol/river-options-v2.xml +++ /dev/null @@ -1,209 +0,0 @@ - - - - Copyright 2020-2021 The River Developers - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - - This protocol 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/Option.zig b/river/Option.zig deleted file mode 100644 index 4265afa..0000000 --- a/river/Option.zig +++ /dev/null @@ -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 . - -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(); -} diff --git a/river/OptionsManager.zig b/river/OptionsManager.zig deleted file mode 100644 index a42f449..0000000 --- a/river/OptionsManager.zig +++ /dev/null @@ -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 . - -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", - ); - }, - } -} diff --git a/river/Output.zig b/river/Output.zig index 6f981f6..252a409 100644 --- a/river/Output.zig +++ b/river/Output.zig @@ -38,8 +38,6 @@ const View = @import("View.zig"); 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 @@ -150,10 +148,6 @@ pub fn init(self: *Self, root: *Root, wlr_output: *wlr.Output) !void { .height = effective_resolution.height, }; - const options_manager = &root.server.options_manager; - try options_manager.createOutputOptions(self); - errdefer options_manager.destroyOutputOptions(self); - 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}); - root.server.options_manager.destroyOutputOptions(self); - // Remove the destroyed output from root if it wasn't already removed root.removeOutput(self); diff --git a/river/OutputOption.zig b/river/OutputOption.zig deleted file mode 100644 index db3a0b3..0000000 --- a/river/OutputOption.zig +++ /dev/null @@ -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 . - -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 66553fe..87b6922 100644 --- a/river/Server.zig +++ b/river/Server.zig @@ -26,7 +26,6 @@ const c = @import("c.zig"); const util = @import("util.zig"); const Config = @import("Config.zig"); -const OptionsManager = @import("OptionsManager.zig"); const Control = @import("Control.zig"); const DecorationManager = @import("DecorationManager.zig"); const InputManager = @import("InputManager.zig"); @@ -66,7 +65,6 @@ root: Root, config: Config, control: Control, status_manager: StatusManager, -options_manager: OptionsManager, layout_manager: LayoutManager, pub fn init(self: *Self) !void { @@ -117,7 +115,6 @@ pub fn init(self: *Self) !void { try self.decoration_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/args.zig b/riverctl/args.zig deleted file mode 100644 index 29ca0f9..0000000 --- a/riverctl/args.zig +++ /dev/null @@ -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 . - -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; - } - }; -} diff --git a/riverctl/main.zig b/riverctl/main.zig index 6fd6341..64434e2 100644 --- a/riverctl/main.zig +++ b/riverctl/main.zig @@ -22,26 +22,13 @@ 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; 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 { control: ?*zriver.ControlV1 = null, - options_manager: ?*river.OptionsManagerV2 = null, - status_manager: ?*zriver.StatusManagerV1 = null, seat: ?*wl.Seat = null, - output_manager: ?*zxdg.OutputManagerV1 = null, - outputs: std.ArrayList(Output) = std.ArrayList(Output).init(gpa), }; pub fn main() !void { @@ -54,20 +41,9 @@ pub fn main() !void { \\The Wayland server does not support river-control-unstable-v1. \\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( \\The Wayland server did not advertise any seat. , .{}), - error.XdgOutputNotAdvertised => printErrorExit( - \\The Wayland server does not support xdg-output-unstable-v1. - , .{}), else => return err, } }; @@ -82,32 +58,20 @@ fn _main() !void { registry.setListener(*Globals, registryListener, &globals) catch unreachable; _ = 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 seat = globals.seat orelse return error.SeatNotAdverstised; + const control = globals.control orelse return error.RiverControlNotAdvertised; + const seat = globals.seat orelse return error.SeatNotAdverstised; - // Skip our name, send all other args - // This next line is needed cause of https://github.com/ziglang/zig/issues/2622 - const args = os.argv; - for (args[1..]) |arg| control.addArgument(arg); + // Skip our name, send all other args + // This next line is needed cause of https://github.com/ziglang/zig/issues/2622 + const args = os.argv; + for (args[1..]) |arg| control.addArgument(arg); - const callback = try control.runCommand(seat); + const callback = try control.runCommand(seat); - callback.setListener(?*c_void, callbackListener, null) catch unreachable; + callback.setListener(?*c_void, callbackListener, null) catch unreachable; - // Loop until our callback is called and we exit. - while (true) _ = try display.dispatch(); - } + // Loop until our callback is called and we exit. + while (true) _ = try display.dispatch(); } 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"); } 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, 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 => {}, @@ -142,10 +97,7 @@ fn callbackListener(callback: *zriver.CommandCallbackV1, event: zriver.CommandCa } os.exit(0); }, - .failure => |failure| { - std.debug.print("Error: {}\n", .{failure.failure_message}); - os.exit(1); - }, + .failure => |failure| printErrorExit("Error: {}\n", .{failure.failure_message}), } } diff --git a/riverctl/options.zig b/riverctl/options.zig deleted file mode 100644 index 9501610..0000000 --- a/riverctl/options.zig +++ /dev/null @@ -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 . - -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)); -} diff --git a/rivertile/main.zig b/rivertile/main.zig index b33d9b0..4133a75 100644 --- a/rivertile/main.zig +++ b/rivertile/main.zig @@ -63,7 +63,6 @@ const gpa = std.heap.c_allocator; const Context = struct { initialized: bool = false, layout_manager: ?*river.LayoutManagerV1 = null, - options_manager: ?*river.OptionsManagerV2 = null, outputs: std.TailQueue(Output) = .{}, 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 { wl_output: *wl.Output, 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, 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); + if (context.initialized) try output.getLayout(context); } - fn initOptionsAndLayout(output: *Output, context: *Context) !void { + fn getLayout(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(); - output.layout = try context.layout_manager.?.getLayout(output.wl_output, "rivertile"); output.layout.setListener(*Output, layoutListener, output) catch unreachable; } fn deinit(output: *Output) void { output.wl_output.release(); - - output.main_count.deinit(); - output.main_factor.deinit(); - output.view_padding.deinit(); - output.outer_padding.deinit(); - output.layout.destroy(); } @@ -180,18 +102,18 @@ const Output = struct { .namespace_in_use => fatal("namespace 'rivertile' already in use.", .{}), .layout_demand => |ev| { - const secondary_count = if (ev.view_count > output.main_count.value) - ev.view_count - output.main_count.value + const secondary_count = if (ev.view_count > default_main_count) + ev.view_count - default_main_count else 0; - 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_width = switch (default_main_location) { + .left, .right => ev.usable_width - 2 * default_outer_padding, + .top, .bottom => ev.usable_height - 2 * default_outer_padding, }; - 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), + const usable_height = switch (default_main_location) { + .left, .right => ev.usable_height - 2 * default_outer_padding, + .top, .bottom => ev.usable_width - 2 * default_outer_padding, }; // 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_rem: u32 = undefined; - 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; + if (default_main_count > 0 and secondary_count > 0) { + main_width = @floatToInt(u32, default_main_factor * @intToFloat(f64, usable_width)); + main_height = usable_height / default_main_count; + main_height_rem = usable_height % default_main_count; secondary_width = usable_width - main_width; secondary_height = 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_height = usable_height / output.main_count.value; - main_height_rem = usable_height % output.main_count.value; + main_height = usable_height / default_main_count; + main_height_rem = usable_height % default_main_count; } else if (secondary_width > 0) { main_width = 0; secondary_width = usable_width; @@ -230,50 +152,50 @@ const Output = struct { var width: u32 = undefined; var height: u32 = undefined; - if (i < output.main_count.value) { + if (i < default_main_count) { 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 - output.main_count.value) * secondary_height + - if (i > output.main_count.value) secondary_height_rem else 0); + y = @intCast(i32, (i - default_main_count) * secondary_height + + if (i > default_main_count) secondary_height_rem else 0); 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); - y += @intCast(i32, output.view_padding.value); - width -= 2 * output.view_padding.value; - height -= 2 * output.view_padding.value; + x += @intCast(i32, default_view_padding); + y += @intCast(i32, default_view_padding); + width -= 2 * default_view_padding; + height -= 2 * default_view_padding; - switch (output.main_location.value) { + switch (default_main_location) { .left => layout.pushViewDimensions( ev.serial, - x + @intCast(i32, output.outer_padding.value), - y + @intCast(i32, output.outer_padding.value), + x + @intCast(i32, default_outer_padding), + y + @intCast(i32, default_outer_padding), width, height, ), .right => layout.pushViewDimensions( ev.serial, - @intCast(i32, usable_width - width) - x + @intCast(i32, output.outer_padding.value), - y + @intCast(i32, output.outer_padding.value), + @intCast(i32, usable_width - width) - x + @intCast(i32, default_outer_padding), + y + @intCast(i32, default_outer_padding), width, height, ), .top => layout.pushViewDimensions( ev.serial, - y + @intCast(i32, output.outer_padding.value), - x + @intCast(i32, output.outer_padding.value), + y + @intCast(i32, default_outer_padding), + x + @intCast(i32, default_outer_padding), height, width, ), .bottom => layout.pushViewDimensions( ev.serial, - y + @intCast(i32, output.outer_padding.value), - @intCast(i32, usable_width - width) - x + @intCast(i32, output.outer_padding.value), + y + @intCast(i32, default_outer_padding), + @intCast(i32, usable_width - width) - x + @intCast(i32, default_outer_padding), height, width, ), @@ -305,23 +227,13 @@ pub fn main() !void { if (context.layout_manager == null) { 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; var it = context.outputs.first; while (it) |node| : (it = node.next) { const output = &node.data; - try output.initOptionsAndLayout(&context); + try output.getLayout(&context); } while (true) _ = try display.dispatch(); @@ -332,8 +244,6 @@ 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, 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 |err| fatal("failed to bind output: {}", .{err}); }