hlctl/src/hlwm/setting.rs

501 lines
21 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::{borrow::BorrowMut, str::FromStr};
use log::trace;
use serde::{de::Expected, Deserialize, Serialize};
use crate::{
hlwm::{command::CommandParseError, parser::ArgParser},
split,
};
use strum::IntoEnumIterator;
use super::{
color::Color,
hlwmbool::ToggleBool,
parser::{FromCommandArgs, ParseError},
ToCommandString,
};
#[derive(Debug, Clone, strum::Display, strum::EnumIter, PartialEq, strum::EnumDiscriminants)]
#[strum_discriminants(
name(SettingName),
derive(strum::Display, strum::EnumIter),
strum(serialize_all = "snake_case")
)]
#[strum(serialize_all = "snake_case")]
pub enum Setting {
/// If set, detect_monitors is automatically executed every time a monitor is connected, disconnected or resized.
AutoDetectMonitors(ToggleBool),
/// If set, EWMH panels are automatically detected and reserve space at the side of the monitors
/// they are on (via pad attributes of each monitor).
/// This setting is activated per default.
AutoDetectPanels(ToggleBool),
/// This setting controls the behaviour of focus and shift if no -e or -i argument is given.
/// If set, then focus and shift changes the focused frame even if there are other clients
/// in this frame in the specified direction.
/// Else, a client within current frame is selected if it is in the specified direction.
DefaultDirectionExternalOnly(ToggleBool),
/// Name of the layout algorithm, which is used if a new frame is created (on a new tag or by a non-trivial split).
DefaultFrameLayout(FrameLayout),
/// String to append when window or tab titles are shortened to fit in the available space
Ellipsis(String),
/// If set, commands [HlwmCommand::Focus] and [HlwmCommand::Shift] cross monitor boundaries.
/// If there is no client in the direction given to focus, then the monitor in the specified
/// direction is focused.
/// Similarly, if shift cannot move a window within a tag, the window is moved to the
/// neighbour monitor in the desired direction.
FocusCrossesMonitorBoundaries(ToggleBool),
/// If set and a window is focused by mouse cursor, this window is focused
/// (this feature is also known as sloppy focus).
/// If unset, you need to click to change the window focus by mouse.
///
/// If another window is hidden by the focus change
/// (e.g. when having pseudotiled windows in the max layout)
/// then an extra click is required to change the focus.
FocusFollowsMouse(ToggleBool),
/// If set, only pagers and taskbars are allowed to change the focus. If unset, all applications can request a
/// focus change
FocusStealingPrevention(ToggleBool),
/// Focused frame opacity in percent. Requires a running compositing manager to take actual effect.
FrameActiveOpacity(u8),
/// The fill color of a focused frame
FrameBgActiveColor(Color),
/// The fill color of an unfocused frame
/// It is only visible if non-focused frames are configured to be visible, see [ShowFrameDecoration].
FrameBgNormalColor(Color),
/// If set, the background of frames are transparent.
/// That means a rectangle is cut out from the inner such that only the frame border
/// and a stripe of width [Setting::FrameTransparentWidth] can be seen.
/// Use [Setting::FrameActiveOpacity] and [Setting::FrameNormalOpacity] for real transparency.
FrameBgTransparent(ToggleBool),
/// The border color of a focused frame
FrameBorderActiveColor(Color),
/// The color of the inner border of a frame
FrameBorderInnerColor(Color),
/// The width of the inner border of a frame.
/// Must be less than [Setting::FrameBorderWidth], since it does not add to the frame border width but is a
/// part of it.
FrameBorderInnerWidth(u32),
/// The border color of an unfocused frame
FrameBorderNormalColor(Color),
/// Border width of a frame
FrameBorderWidth(u32),
/// The gap between frames in the tiling mode
FrameGap(u32),
/// Unfocused frame opacity in percent.
/// Requires a running compositing manager to take actual effect
FrameNormalOpacity(u8),
/// The padding within a frame in the tiling mode
/// i.e. the space between the border of a frame and the windows within it
FramePadding(u32),
/// Specifies the width of the remaining frame colored with [Setting::FrameBgActiveColor]
/// if [Setting::FrameBgTransparent] is set
FrameTransparentWidth(u32),
/// This setting affects the size of the last client in a frame that is arranged by grid layout.
/// If set, then the last client always fills the gap within this frame.
/// If unset, then the last client has the same size as all other clients in this frame
GaplessGrid(ToggleBool),
/// If activated, windows are explicitly hidden when they are covered by another window in a frame with max layout.
/// This only has a visible effect if a compositor is used.
/// If activated, shadows do not stack up and transparent windows show the wallpaper behind them instead of
/// the other clients in the max layout.
HideCoveredWindows(ToggleBool),
/// If greater than 0, then the clients on all monitors arent moved or resized anymore.
/// If it is set to 0, then the arranging of monitors is enabled again, and all monitors are rearranged
/// if their content has changed in the meantime.
/// You should not change this setting manually due to concurrency issues;
/// use the commands [HlwmCommand::Lock] and [HlwmCommand::Unlock] instead.
MonitorsLocked(u8),
/// Specifies the gap around a monitor.
/// If the monitor is selected and the mouse position would be restored into this gap,
/// it is set to the center of the monitor.
/// This is useful, when the monitor was left via mouse movement, but is reselected by keyboard.
/// If the gap is 0 (default), the mouse is never recentered
MouseRecenterGap(u32),
/// If greater than 0, it specifies the least distance between a centered pseudotile window
/// and the border of the frame or tile it is assigned to.
/// If this distance is lower than pseudotile_center_threshold, it is aligned to the top left
/// of the clients tile.
PseudotileCenterThreshold(u32),
/// If set, a window is raised if it is clicked. The value of this setting is only noticed in floating mode
RaiseOnClick(ToggleBool),
/// If set, a window is raised if it is focused. The value of this setting is only used in floating mode
RaiseOnFocus(ToggleBool),
/// If set, a window is raised temporarily if it is focused on its tag.
/// Temporarily in this case means that the window will return to its previous stacking position
/// if another window is focused
RaiseOnFocusTemporarily(ToggleBool),
/// This controls, which frame decorations are shown (or none at all)
ShowFrameDecorations(ShowFrameDecoration),
/// See the docs for members of [SmartFrameSurroundings]
SmartFrameSurroundings(SmartFrameSurroundings),
/// If set, window borders and gaps will be removed and minimal when theres no
/// ambiguity regarding the focused window.
/// This minimal window decoration can be configured by the `theme.minimal` object.
SmartWindowSurroundings(ToggleBool),
/// If a client is dragged in floating mode, then it snaps to neighbour clients
/// if the distance between them is smaller than SnapDistance
SnapDistance(u32),
/// Specifies the remaining gap if a dragged client snaps to an edge in floating mode.
/// If SnapGap is set to 0, no gap will remain
SnapGap(u32),
/// If set: If you want to view a tag, that already is viewed on another monitor,
/// then the monitor contents will be swapped and you see the wanted tag on the focused monitor.
/// If not set, the other monitor is focused if it shows the desired tag.
SwapMonitorsToGetTag(ToggleBool),
/// If activated, multiple windows in a frame with the max layout algorithm are drawn as tabs
TabbedMax(ToggleBool),
/// It contains the chars that are used to print a nice ascii tree.
/// It must contain at least 8 characters. e.g. `X|:#+*-.` produces a tree like:
/// ```
/// X-.
/// #-. child 0
/// | #-* child 00
/// | +-* child 01
/// +-. child 1
/// : #-* child 10
/// : +-* child 11
/// ```
/// Useful values for tree_style are: `╾│ ├└╼─┐` or `-| |'--.` or `╾│ ├╰╼─╮.`
TreeStyle(String),
/// If set, a client's window content is resized immediately during resizing it with the mouse.
/// If unset, the client's content is resized after the mouse button is released.
UpdateDraggedClients(ToggleBool),
/// If set, verbose output is logged to herbstluftwms stderr.
/// The default value is controlled by the `--verbose` command line flag
Verbose(ToggleBool),
/// Border color of a focused window
/// **Warning:**
/// This only exists for compatibility reasons; it is only an alias for the attribute `theme.active.color`
WindowBorderActiveColor(Color),
/// Color of the inner border of a window.
/// **Warning:**
/// This only exists for compatibility reasons; it is only an alias for the attribute `theme.inner_color`
WindowBorderInnerColor(Color),
/// The width of the inner border of a window.
/// Must be less than window_border_width, since it does not add to the window border width but is a part of it.
/// **Warning:**
/// This only exists for compatibility reasons; it is only an alias for the attribute `theme.inner_width`
WindowBorderInnerWidth(u32),
/// Border color of an unfocused window
/// **Warning:**
/// This only exists for compatibility reasons; it is only an alias for the attribute `theme.normal.color`
WindowBorderNormalColor(Color),
/// Border color of an unfocused but urgent window.
/// **Warning:**
/// This only exists for compatibility reasons; it is only an alias for the attribute `theme.urgent.color`
WindowBorderUrgentColor(Color),
/// Border width of a window
/// **Warning:**
/// This only exists for compatibility reasons; it is only an alias for the attribute `theme.border_width`
WindowBorderWidth(u32),
/// The gap between windows within one frame in the tiling mode.
WindowGap(u32),
/// It controls the value of the `_NET_WM_NAME` property on the root window,
/// which specifies the name of the running window manager.
/// The value of this setting is not updated if the actual `_NET_WM_NAME` property
/// on the root window is changed externally. Example usage:
///
/// ```
/// cycle_value wmname herbstluftwm LG3D
/// ```
Wmname(String),
}
impl Default for SettingName {
fn default() -> Self {
SettingName::AutoDetectMonitors
}
}
impl FromStr for SettingName {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::iter()
.find(|i| i.to_string() == s)
.ok_or(ParseError::InvalidCommand(s.to_string()))
}
}
impl Serialize for Setting {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_command_string().replace("\t", " = "))
}
}
impl<'de> Deserialize<'de> for Setting {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
pub enum Expect {
Command(String),
}
impl Expected for Expect {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Expect::Command(err) => {
write!(f, "a valid [command][space/tab][value] string: {err}")
}
}
}
}
let str_val: String = Deserialize::deserialize(deserializer)?;
let ((command, arg), _) = split::on_first_match(&str_val, &['='])
.ok_or(CommandParseError::InvalidArgumentCount(0, "setting".into()))
.map_err(|err| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(&str_val), &err)
})?;
Ok(
Self::from_command_args(&command.trim(), [arg.trim()].into_iter()).map_err(|err| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Str(&str_val),
&Expect::Command(err.to_string()),
)
})?,
)
}
}
impl FromStr for Setting {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
trace!("[from_str] beginning to parse [{s}] as setting");
let ((command, arg), _) =
split::on_first_match(s, &['\t', ' ']).ok_or(ParseError::InvalidValue {
value: s.to_string(),
expected: "[setting] [value]",
})?;
Self::from_command_args(&command.trim(), [arg].into_iter())
}
}
impl FromCommandArgs for Setting {
fn from_command_args<S: Into<String>, I: Iterator<Item = S>>(
cmd: &str,
args: I,
) -> Result<Self, ParseError> {
let mut command = Self::iter()
.find(|s| s.to_string() == cmd)
.ok_or(ParseError::InvalidCommand(cmd.to_string()))?;
let mut parser = ArgParser::from_strings(args.map(|i| i.into()));
match command.borrow_mut() {
Setting::Verbose(arg)
| Setting::TabbedMax(arg)
| Setting::GaplessGrid(arg)
| Setting::RaiseOnClick(arg)
| Setting::RaiseOnFocus(arg)
| Setting::AutoDetectPanels(arg)
| Setting::FocusFollowsMouse(arg)
| Setting::HideCoveredWindows(arg)
| Setting::AutoDetectMonitors(arg)
| Setting::FrameBgTransparent(arg)
| Setting::SwapMonitorsToGetTag(arg)
| Setting::UpdateDraggedClients(arg)
| Setting::FocusStealingPrevention(arg)
| Setting::RaiseOnFocusTemporarily(arg)
| Setting::SmartWindowSurroundings(arg)
| Setting::DefaultDirectionExternalOnly(arg)
| Setting::FocusCrossesMonitorBoundaries(arg) => *arg = parser.next_from_str(cmd)?,
Setting::FrameBgActiveColor(arg)
| Setting::FrameBgNormalColor(arg)
| Setting::FrameBorderInnerColor(arg)
| Setting::FrameBorderActiveColor(arg)
| Setting::FrameBorderNormalColor(arg)
| Setting::WindowBorderInnerColor(arg)
| Setting::WindowBorderActiveColor(arg)
| Setting::WindowBorderNormalColor(arg)
| Setting::WindowBorderUrgentColor(arg) => *arg = parser.next_from_str(cmd)?,
Setting::DefaultFrameLayout(arg) => *arg = parser.next_from_str(cmd)?,
Setting::ShowFrameDecorations(arg) => *arg = parser.next_from_str(cmd)?,
Setting::SmartFrameSurroundings(arg) => *arg = parser.next_from_str(cmd)?,
Setting::MonitorsLocked(arg)
| Setting::FrameActiveOpacity(arg)
| Setting::FrameNormalOpacity(arg) => *arg = parser.next_from_str(cmd)?,
Setting::SnapGap(arg)
| Setting::FrameGap(arg)
| Setting::WindowGap(arg)
| Setting::SnapDistance(arg)
| Setting::FramePadding(arg)
| Setting::MouseRecenterGap(arg)
| Setting::FrameBorderWidth(arg)
| Setting::WindowBorderWidth(arg)
| Setting::FrameTransparentWidth(arg)
| Setting::FrameBorderInnerWidth(arg)
| Setting::WindowBorderInnerWidth(arg)
| Setting::PseudotileCenterThreshold(arg) => *arg = parser.next_from_str(cmd)?,
Setting::Wmname(arg) | Setting::Ellipsis(arg) | Setting::TreeStyle(arg) => {
*arg = parser.must_string(cmd)?
}
}
Ok(command)
}
}
impl Default for Setting {
fn default() -> Self {
Self::AutoDetectMonitors(ToggleBool::Bool(true))
}
}
impl ToCommandString for Setting {
fn to_command_string(&self) -> String {
match self {
Setting::AutoDetectMonitors(value)
| Setting::AutoDetectPanels(value)
| Setting::DefaultDirectionExternalOnly(value)
| Setting::FocusCrossesMonitorBoundaries(value)
| Setting::FocusFollowsMouse(value)
| Setting::FocusStealingPrevention(value)
| Setting::FrameBgTransparent(value)
| Setting::GaplessGrid(value)
| Setting::HideCoveredWindows(value)
| Setting::RaiseOnClick(value)
| Setting::RaiseOnFocus(value)
| Setting::RaiseOnFocusTemporarily(value)
| Setting::SmartWindowSurroundings(value)
| Setting::TabbedMax(value)
| Setting::UpdateDraggedClients(value)
| Setting::Verbose(value)
| Setting::SwapMonitorsToGetTag(value) => vec![self.to_string(), value.to_string()],
Setting::TreeStyle(value) | Setting::Wmname(value) | Setting::Ellipsis(value) => {
vec![self.to_string(), value.to_string()]
}
Setting::FrameActiveOpacity(value)
| Setting::FrameNormalOpacity(value)
| Setting::MonitorsLocked(value) => vec![self.to_string(), value.to_string()],
Setting::FrameBorderInnerWidth(value)
| Setting::FrameBorderWidth(value)
| Setting::FrameGap(value)
| Setting::FramePadding(value)
| Setting::FrameTransparentWidth(value)
| Setting::MouseRecenterGap(value)
| Setting::PseudotileCenterThreshold(value)
| Setting::SnapDistance(value)
| Setting::SnapGap(value)
| Setting::WindowBorderInnerWidth(value)
| Setting::WindowBorderWidth(value)
| Setting::WindowGap(value) => vec![self.to_string(), value.to_string()],
Setting::FrameBgActiveColor(value)
| Setting::FrameBgNormalColor(value)
| Setting::FrameBorderActiveColor(value)
| Setting::FrameBorderInnerColor(value)
| Setting::FrameBorderNormalColor(value)
| Setting::WindowBorderActiveColor(value)
| Setting::WindowBorderInnerColor(value)
| Setting::WindowBorderNormalColor(value)
| Setting::WindowBorderUrgentColor(value) => vec![self.to_string(), value.to_string()],
Setting::DefaultFrameLayout(value) => vec![self.to_string(), value.to_string()],
Setting::ShowFrameDecorations(value) => vec![self.to_string(), value.to_string()],
Setting::SmartFrameSurroundings(value) => vec![self.to_string(), value.to_string()],
}
.join("\t")
}
}
#[derive(Debug, Clone, Serialize, Deserialize, strum::Display, strum::EnumString, PartialEq)]
#[strum(serialize_all = "snake_case")]
pub enum ShowFrameDecoration {
/// Show no frame decorations at all
None,
/// Show decorations of frames that have client windows,
Nonempty,
/// Show decorations on the tags with at least two frames,
IfMultiple,
/// Show decorations of frames that have no client windows,
IfEmpty,
/// Show the decoration of focused and nonempty frames,
Focused,
/// Show decorations of focused and non-empty frames on tags with at least two frames.
FocusedIfMultiple,
/// Show all frame decorations.
All,
}
impl Default for ShowFrameDecoration {
fn default() -> Self {
Self::FocusedIfMultiple
}
}
#[derive(Debug, Clone, Serialize, Deserialize, strum::Display, strum::EnumString, PartialEq)]
#[strum(serialize_all = "snake_case")]
pub enum SmartFrameSurroundings {
/// Frame borders and gaps will be removed when there is no ambiguity regarding the focused frame
HideAll,
/// Only frame gaps will be removed when there is no ambiguity regarding the focused frame
HideGaps,
/// Always show frame borders and gaps
Off,
}
impl Default for SmartFrameSurroundings {
fn default() -> Self {
Self::Off
}
}
#[derive(Debug, Clone, Serialize, Deserialize, strum::Display, strum::EnumString, PartialEq)]
#[strum(serialize_all = "snake_case")]
pub enum FrameLayout {
/// clients are placed below each other
Vertical,
/// clients are placed next to each other
Horizontal,
/// all clients are maximized in this frame
Max,
/// clients are arranged in an almost quadratic grid
Grid,
}
impl Default for FrameLayout {
fn default() -> Self {
Self::Vertical
}
}
#[cfg(test)]
mod test {
use serde::{Deserialize, Serialize};
use crate::hlwm::{setting::SettingName, ToggleBool};
use super::Setting;
#[derive(Debug, Serialize, Deserialize)]
pub struct SettingWrapper {
setting: Setting,
}
#[test]
fn setting_serialize_deserialize() {
let setting = SettingWrapper {
setting: Setting::AutoDetectMonitors(ToggleBool::Toggle),
};
let serialized = toml::to_string_pretty(&setting).expect("serializing");
let deserialized: SettingWrapper = toml::from_str(&serialized).expect("deserializing");
assert_eq!(setting.setting, deserialized.setting);
}
#[test]
fn setting_name() {
assert_eq!(
"auto_detect_monitors",
SettingName::AutoDetectMonitors.to_string()
)
}
}