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}); }