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