Format Rust sources at 100 columns
This commit is contained in:
1 parent
4661f595f8
commit
610478f8c7
336 files changed
+23497
-7892
No files matched your search
@@ -6,21 +6,39 @@ impl Config {
|
||||
Ok(FocusEffectSettings {
|
||||
enabled: effect.enabled,
|
||||
blur: effect.blur,
|
||||
active_opacity: unit_theme_value(effect.active_opacity, "appearance.focus_effect.active_opacity")?,
|
||||
inactive_opacity: unit_theme_value(effect.inactive_opacity, "appearance.focus_effect.inactive_opacity")?,
|
||||
inactive_dim: unit_theme_value(effect.inactive_dim, "appearance.focus_effect.inactive_dim")?,
|
||||
duration_ms: nonnegative_theme_value(effect.duration_ms, "appearance.focus_effect.duration_ms")?,
|
||||
active_opacity: unit_theme_value(
|
||||
effect.active_opacity,
|
||||
"appearance.focus_effect.active_opacity",
|
||||
)?,
|
||||
inactive_opacity: unit_theme_value(
|
||||
effect.inactive_opacity,
|
||||
"appearance.focus_effect.inactive_opacity",
|
||||
)?,
|
||||
inactive_dim: unit_theme_value(
|
||||
effect.inactive_dim,
|
||||
"appearance.focus_effect.inactive_dim",
|
||||
)?,
|
||||
duration_ms: nonnegative_theme_value(
|
||||
effect.duration_ms,
|
||||
"appearance.focus_effect.duration_ms",
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn theme_settings(&self) -> Result<ThemeSettings, ConfigError> {
|
||||
let border_width = nonnegative_theme_value(self.theme.geometry.border_width, "geometry.border_width")?;
|
||||
let focus_ring_width =
|
||||
nonnegative_theme_value(self.theme.geometry.focus_ring_width, "geometry.focus_ring_width")?;
|
||||
let window_radius = nonnegative_theme_value(self.theme.geometry.window_radius, "geometry.window_radius")?;
|
||||
let shadow_offset_y = finite_theme_value(self.theme.shadow.soft.offset_y, "shadow.soft.offset_y")?;
|
||||
let border_width =
|
||||
nonnegative_theme_value(self.theme.geometry.border_width, "geometry.border_width")?;
|
||||
let focus_ring_width = nonnegative_theme_value(
|
||||
self.theme.geometry.focus_ring_width,
|
||||
"geometry.focus_ring_width",
|
||||
)?;
|
||||
let window_radius =
|
||||
nonnegative_theme_value(self.theme.geometry.window_radius, "geometry.window_radius")?;
|
||||
let shadow_offset_y =
|
||||
finite_theme_value(self.theme.shadow.soft.offset_y, "shadow.soft.offset_y")?;
|
||||
let shadow_blur = nonnegative_theme_value(self.theme.shadow.soft.blur, "shadow.soft.blur")?;
|
||||
let shadow_opacity = unit_theme_value(self.theme.shadow.soft.opacity, "shadow.soft.opacity")?;
|
||||
let shadow_opacity =
|
||||
unit_theme_value(self.theme.shadow.soft.opacity, "shadow.soft.opacity")?;
|
||||
|
||||
let material_radius = nonnegative_theme_value(
|
||||
self.theme.geometry.shell_radius.unwrap_or(14.0),
|
||||
@@ -30,13 +48,22 @@ impl Config {
|
||||
border_width,
|
||||
focus_ring_width,
|
||||
border_color: parse_color(&self.theme.colors.border, "colors.border")?,
|
||||
text_primary_color: parse_color(&self.theme.colors.text_primary, "colors.text_primary")?,
|
||||
text_primary_color: parse_color(
|
||||
&self.theme.colors.text_primary,
|
||||
"colors.text_primary",
|
||||
)?,
|
||||
accent_color: parse_color(&self.theme.colors.accent, "colors.accent")?,
|
||||
border_gradient: self.theme.border.settings("border")?,
|
||||
focus_ring_gradient: self.theme.focus_ring.settings("focus_ring")?,
|
||||
shadow_color: parse_color(&self.theme.colors.shadow, "colors.shadow")?,
|
||||
surface_base_color: parse_color(&self.theme.colors.surface_base, "colors.surface_base")?,
|
||||
surface_raised_color: parse_color(&self.theme.colors.surface_raised, "colors.surface_raised")?,
|
||||
surface_base_color: parse_color(
|
||||
&self.theme.colors.surface_base,
|
||||
"colors.surface_base",
|
||||
)?,
|
||||
surface_raised_color: parse_color(
|
||||
&self.theme.colors.surface_raised,
|
||||
"colors.surface_raised",
|
||||
)?,
|
||||
bar_background_color: parse_color(
|
||||
self.theme
|
||||
.surface
|
||||
@@ -52,8 +79,15 @@ impl Config {
|
||||
shadow_blur,
|
||||
shadow_opacity,
|
||||
material_style: self.theme.material.style,
|
||||
backdrop_blur: nonnegative_theme_value(self.theme.material.blur_radius, "material.blur_radius")?.min(32.0),
|
||||
material_tint_strength: unit_theme_value(self.theme.material.tint_strength, "material.tint_strength")?,
|
||||
backdrop_blur: nonnegative_theme_value(
|
||||
self.theme.material.blur_radius,
|
||||
"material.blur_radius",
|
||||
)?
|
||||
.min(32.0),
|
||||
material_tint_strength: unit_theme_value(
|
||||
self.theme.material.tint_strength,
|
||||
"material.tint_strength",
|
||||
)?,
|
||||
material_radius,
|
||||
panel_radius: material_radius,
|
||||
reduce_transparency: false,
|
||||
@@ -189,7 +223,11 @@ impl BorderPaintConfig {
|
||||
"focus_ring.gradient.angle",
|
||||
)
|
||||
} else {
|
||||
("border.gradient.from", "border.gradient.to", "border.gradient.angle")
|
||||
(
|
||||
"border.gradient.from",
|
||||
"border.gradient.to",
|
||||
"border.gradient.angle",
|
||||
)
|
||||
};
|
||||
let gradient = self
|
||||
.gradient
|
||||
|
||||
@@ -12,7 +12,10 @@ impl Config {
|
||||
fn binding_commands(&self) -> HashMap<String, Vec<String>> {
|
||||
let mut commands = HashMap::from([
|
||||
("terminal".to_owned(), vec!["foot".to_owned()]),
|
||||
("screenshot".to_owned(), vec!["ferese-screenshot".to_owned()]),
|
||||
(
|
||||
"screenshot".to_owned(),
|
||||
vec!["ferese-screenshot".to_owned()],
|
||||
),
|
||||
(
|
||||
"screenshot-full".to_owned(),
|
||||
vec!["ferese-screenshot".to_owned(), "--full".to_owned()],
|
||||
@@ -36,7 +39,10 @@ impl Config {
|
||||
for configured in &self.bindings {
|
||||
let identity = binding_identity(configured, &keymap)?;
|
||||
if !supplied.insert(identity.clone()) {
|
||||
return Err(ConfigError::Binding(format!("duplicate binding {:?}", configured.keys)));
|
||||
return Err(ConfigError::Binding(format!(
|
||||
"duplicate binding {:?}",
|
||||
configured.keys
|
||||
)));
|
||||
}
|
||||
|
||||
bindings.retain(|binding| binding_identity_for_runtime(binding) != identity);
|
||||
@@ -56,13 +62,19 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn validate_commands(commands: &HashMap<String, Vec<String>>) -> Result<(), ConfigError> {
|
||||
pub(super) fn validate_commands(
|
||||
commands: &HashMap<String, Vec<String>>,
|
||||
) -> Result<(), ConfigError> {
|
||||
for (name, argv) in commands {
|
||||
if name.trim().is_empty() {
|
||||
return Err(ConfigError::Binding("command names cannot be empty".to_owned()));
|
||||
return Err(ConfigError::Binding(
|
||||
"command names cannot be empty".to_owned(),
|
||||
));
|
||||
}
|
||||
if argv.is_empty() || argv[0].is_empty() {
|
||||
return Err(ConfigError::Binding(format!("command {name:?} must contain a program")));
|
||||
return Err(ConfigError::Binding(format!(
|
||||
"command {name:?} must contain a program"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,10 +110,9 @@ pub(super) fn parse_binding(
|
||||
}
|
||||
|
||||
let identity = binding_identity(configured, keymap)?;
|
||||
let action = configured
|
||||
.action
|
||||
.as_deref()
|
||||
.ok_or_else(|| ConfigError::Binding(format!("binding {:?} has no action", configured.keys)))?;
|
||||
let action = configured.action.as_deref().ok_or_else(|| {
|
||||
ConfigError::Binding(format!("binding {:?} has no action", configured.keys))
|
||||
})?;
|
||||
let action = parse_action(action, configured.argument.as_deref(), commands)?;
|
||||
let trigger = match identity.match_mode {
|
||||
BindingMatch::Keysym => BindingTrigger::Keysym(identity.key),
|
||||
@@ -130,7 +141,9 @@ pub(super) fn binding_identity(
|
||||
) -> Result<BindingIdentity, ConfigError> {
|
||||
let (modifiers, key) = parse_chord(&configured.keys)?;
|
||||
if let Some((fingers, direction)) = parse_swipe(&key) {
|
||||
if configured.match_mode == BindingMatch::Physical || modifiers != BindingModifiers::default() {
|
||||
if configured.match_mode == BindingMatch::Physical
|
||||
|| modifiers != BindingModifiers::default()
|
||||
{
|
||||
return Err(ConfigError::Binding(
|
||||
"swipes cannot use keyboard modifiers or physical key matching".into(),
|
||||
));
|
||||
@@ -170,7 +183,9 @@ pub(super) fn binding_identity_for_runtime(binding: &Binding) -> BindingIdentity
|
||||
let (match_mode, key) = match binding.trigger {
|
||||
BindingTrigger::Keysym(key) => (BindingMatch::Keysym, key),
|
||||
BindingTrigger::Physical(key) => (BindingMatch::Physical, key.raw()),
|
||||
BindingTrigger::Swipe { fingers, direction } => (BindingMatch::Swipe, fingers * 4 + direction as u32),
|
||||
BindingTrigger::Swipe { fingers, direction } => {
|
||||
(BindingMatch::Swipe, fingers * 4 + direction as u32)
|
||||
}
|
||||
};
|
||||
|
||||
BindingIdentity {
|
||||
@@ -198,7 +213,9 @@ pub(super) fn parse_chord(chord: &str) -> Result<(BindingModifiers, String), Con
|
||||
};
|
||||
if let Some(slot) = slot {
|
||||
if *slot {
|
||||
return Err(ConfigError::Binding(format!("duplicate modifier in {chord:?}")));
|
||||
return Err(ConfigError::Binding(format!(
|
||||
"duplicate modifier in {chord:?}"
|
||||
)));
|
||||
}
|
||||
*slot = true;
|
||||
} else if key.replace(component.to_owned()).is_some() {
|
||||
@@ -208,7 +225,9 @@ pub(super) fn parse_chord(chord: &str) -> Result<(BindingModifiers, String), Con
|
||||
}
|
||||
}
|
||||
|
||||
let key = key.ok_or_else(|| ConfigError::Binding(format!("key chord {chord:?} does not contain a key")))?;
|
||||
let key = key.ok_or_else(|| {
|
||||
ConfigError::Binding(format!("key chord {chord:?} does not contain a key"))
|
||||
})?;
|
||||
Ok((modifiers, key))
|
||||
}
|
||||
|
||||
@@ -264,8 +283,10 @@ pub(super) fn parse_action(
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
let required_argument =
|
||||
|| argument.ok_or_else(|| ConfigError::Binding(format!("action {action:?} requires an argument")));
|
||||
let required_argument = || {
|
||||
argument
|
||||
.ok_or_else(|| ConfigError::Binding(format!("action {action:?} requires an argument")))
|
||||
};
|
||||
|
||||
match action {
|
||||
"binding-mode" => Ok(BindingAction::BindingMode(required_argument()?.to_owned())),
|
||||
@@ -275,7 +296,9 @@ pub(super) fn parse_action(
|
||||
}
|
||||
"workspace-next" | "workspace-previous" => {
|
||||
no_argument()?;
|
||||
Ok(BindingAction::SwitchRelativeWorkspace(action == "workspace-next"))
|
||||
Ok(BindingAction::SwitchRelativeWorkspace(
|
||||
action == "workspace-next",
|
||||
))
|
||||
}
|
||||
"workspace-back-and-forth" => {
|
||||
no_argument()?;
|
||||
@@ -310,9 +333,15 @@ pub(super) fn parse_action(
|
||||
}
|
||||
"focus" => Ok(BindingAction::Focus(parse_direction(required_argument()?)?)),
|
||||
"move" => Ok(BindingAction::Move(parse_direction(required_argument()?)?)),
|
||||
"resize" => Ok(BindingAction::Resize(parse_direction(required_argument()?)?)),
|
||||
"workspace" => Ok(BindingAction::SwitchWorkspace(parse_workspace(required_argument()?)?)),
|
||||
"move-to-workspace" => Ok(BindingAction::MoveToWorkspace(parse_workspace(required_argument()?)?)),
|
||||
"resize" => Ok(BindingAction::Resize(
|
||||
parse_direction(required_argument()?)?,
|
||||
)),
|
||||
"workspace" => Ok(BindingAction::SwitchWorkspace(parse_workspace(
|
||||
required_argument()?,
|
||||
)?)),
|
||||
"move-to-workspace" => Ok(BindingAction::MoveToWorkspace(parse_workspace(
|
||||
required_argument()?,
|
||||
)?)),
|
||||
"toggle-maximized" => {
|
||||
no_argument()?;
|
||||
Ok(BindingAction::ToggleMaximized)
|
||||
@@ -337,7 +366,9 @@ pub(super) fn parse_action(
|
||||
no_argument()?;
|
||||
Ok(BindingAction::ToggleColumnTabbed)
|
||||
}
|
||||
"join-tab" => Ok(BindingAction::JoinTab(parse_direction(required_argument()?)?)),
|
||||
"join-tab" => Ok(BindingAction::JoinTab(parse_direction(
|
||||
required_argument()?
|
||||
)?)),
|
||||
"leave-tab" => {
|
||||
no_argument()?;
|
||||
Ok(BindingAction::LeaveTab)
|
||||
@@ -396,14 +427,18 @@ pub(super) fn parse_direction(argument: &str) -> Result<Direction, ConfigError>
|
||||
"right" => Ok(Direction::Right),
|
||||
"up" => Ok(Direction::Up),
|
||||
"down" => Ok(Direction::Down),
|
||||
_ => Err(ConfigError::Binding(format!("invalid direction {argument:?}"))),
|
||||
_ => Err(ConfigError::Binding(format!(
|
||||
"invalid direction {argument:?}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn parse_workspace(argument: &str) -> Result<u8, ConfigError> {
|
||||
match argument.parse() {
|
||||
Ok(workspace) if workspace > 0 => Ok(workspace),
|
||||
_ => Err(ConfigError::Binding(format!("invalid workspace {argument:?}"))),
|
||||
_ => Err(ConfigError::Binding(format!(
|
||||
"invalid workspace {argument:?}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,13 +481,25 @@ pub(super) fn default_bindings() -> Vec<BindingConfig> {
|
||||
|
||||
for (key, direction) in [("H", "left"), ("J", "down"), ("K", "up"), ("L", "right")] {
|
||||
bindings.push(binding("Super+".to_owned() + key, "focus", Some(direction)));
|
||||
bindings.push(binding("Super+Shift+".to_owned() + key, "move", Some(direction)));
|
||||
bindings.push(binding("Super+Ctrl+".to_owned() + key, "resize", Some(direction)));
|
||||
bindings.push(binding(
|
||||
"Super+Shift+".to_owned() + key,
|
||||
"move",
|
||||
Some(direction),
|
||||
));
|
||||
bindings.push(binding(
|
||||
"Super+Ctrl+".to_owned() + key,
|
||||
"resize",
|
||||
Some(direction),
|
||||
));
|
||||
}
|
||||
|
||||
for workspace in 1..=9 {
|
||||
let workspace = workspace.to_string();
|
||||
bindings.push(binding(format!("Super+{workspace}"), "workspace", Some(&workspace)));
|
||||
bindings.push(binding(
|
||||
format!("Super+{workspace}"),
|
||||
"workspace",
|
||||
Some(&workspace),
|
||||
));
|
||||
bindings.push(binding(
|
||||
format!("Super+Shift+{workspace}"),
|
||||
"move-to-workspace",
|
||||
@@ -463,7 +510,11 @@ pub(super) fn default_bindings() -> Vec<BindingConfig> {
|
||||
bindings
|
||||
}
|
||||
|
||||
pub(super) fn binding(keys: impl Into<String>, action: impl Into<String>, argument: Option<&str>) -> BindingConfig {
|
||||
pub(super) fn binding(
|
||||
keys: impl Into<String>,
|
||||
action: impl Into<String>,
|
||||
argument: Option<&str>,
|
||||
) -> BindingConfig {
|
||||
BindingConfig {
|
||||
keys: keys.into(),
|
||||
match_mode: BindingMatch::Keysym,
|
||||
@@ -557,7 +608,10 @@ impl Binding {
|
||||
let symbol = parse_keysym(&key)?;
|
||||
if (modifiers.ctrl
|
||||
&& modifiers.alt
|
||||
&& matches!(symbol, keysyms::KEY_Escape | keysyms::KEY_F1..=keysyms::KEY_F12))
|
||||
&& matches!(
|
||||
symbol,
|
||||
keysyms::KEY_Escape | keysyms::KEY_F1..=keysyms::KEY_F12
|
||||
))
|
||||
|| (keysyms::KEY_XF86Switch_VT_1..=keysyms::KEY_XF86Switch_VT_12).contains(&symbol)
|
||||
{
|
||||
return Err(ConfigError::Binding("Reserved system shortcut".into()));
|
||||
@@ -576,19 +630,34 @@ impl Binding {
|
||||
match (&self.trigger, &other.trigger) {
|
||||
(BindingTrigger::Keysym(left), BindingTrigger::Keysym(right)) => left == right,
|
||||
(BindingTrigger::Keysym(symbol), BindingTrigger::Physical(code))
|
||||
| (BindingTrigger::Physical(code), BindingTrigger::Keysym(symbol)) => (0..map.num_layouts_for_key(*code))
|
||||
.any(|layout| {
|
||||
| (BindingTrigger::Physical(code), BindingTrigger::Keysym(symbol)) => {
|
||||
(0..map.num_layouts_for_key(*code)).any(|layout| {
|
||||
map.key_get_syms_by_level(*code, layout, 0)
|
||||
.iter()
|
||||
.any(|key| key.raw() == *symbol)
|
||||
}),
|
||||
})
|
||||
}
|
||||
(BindingTrigger::Physical(left), BindingTrigger::Physical(right)) => left == right,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matches(&self, keycode: Keycode, keysyms: &[u32], logo: bool, ctrl: bool, alt: bool, shift: bool) -> bool {
|
||||
self.modifiers == BindingModifiers { logo, ctrl, alt, shift }
|
||||
pub fn matches(
|
||||
&self,
|
||||
keycode: Keycode,
|
||||
keysyms: &[u32],
|
||||
logo: bool,
|
||||
ctrl: bool,
|
||||
alt: bool,
|
||||
shift: bool,
|
||||
) -> bool {
|
||||
self.modifiers
|
||||
== BindingModifiers {
|
||||
logo,
|
||||
ctrl,
|
||||
alt,
|
||||
shift,
|
||||
}
|
||||
&& match self.trigger {
|
||||
BindingTrigger::Keysym(expected) => keysyms.contains(&expected),
|
||||
BindingTrigger::Physical(expected) => expected == keycode,
|
||||
@@ -600,7 +669,11 @@ impl Binding {
|
||||
let description = match &self.action {
|
||||
BindingAction::BindingMode(name) => format!("Switch to binding mode {name}"),
|
||||
BindingAction::None => return None,
|
||||
BindingAction::Spawn(argv) if argv.first().is_some_and(|program| program == "ferese-screenshot") => {
|
||||
BindingAction::Spawn(argv)
|
||||
if argv
|
||||
.first()
|
||||
.is_some_and(|program| program == "ferese-screenshot") =>
|
||||
{
|
||||
if argv.iter().any(|arg| arg == "--full") {
|
||||
"Capture whole screen"
|
||||
} else {
|
||||
@@ -619,7 +692,9 @@ impl Binding {
|
||||
BindingAction::Exit => "Log out (with confirmation)".into(),
|
||||
BindingAction::Focus(direction) => format!("Focus window {direction:?}"),
|
||||
BindingAction::FocusLastWindow => "Return to last focused window".into(),
|
||||
BindingAction::FocusFloating => "Switch focus between tiled and floating windows".into(),
|
||||
BindingAction::FocusFloating => {
|
||||
"Switch focus between tiled and floating windows".into()
|
||||
}
|
||||
BindingAction::FocusMru(reverse) => if *reverse {
|
||||
"Previous window in focus history"
|
||||
} else {
|
||||
@@ -629,11 +704,16 @@ impl Binding {
|
||||
BindingAction::Move(direction) => format!("Move window {direction:?}"),
|
||||
BindingAction::Resize(direction) => format!("Resize window {direction:?}"),
|
||||
BindingAction::SwitchWorkspace(index) => format!("Go to workspace {index}"),
|
||||
BindingAction::WorkspaceBackAndForth => "Return to last workspace on this monitor".into(),
|
||||
BindingAction::MoveToWorkspace(index) => format!("Move window to workspace {index}"),
|
||||
BindingAction::SwitchRelativeWorkspace(next) => {
|
||||
if *next { "Next workspace" } else { "Previous workspace" }.into()
|
||||
BindingAction::WorkspaceBackAndForth => {
|
||||
"Return to last workspace on this monitor".into()
|
||||
}
|
||||
BindingAction::MoveToWorkspace(index) => format!("Move window to workspace {index}"),
|
||||
BindingAction::SwitchRelativeWorkspace(next) => if *next {
|
||||
"Next workspace"
|
||||
} else {
|
||||
"Previous workspace"
|
||||
}
|
||||
.into(),
|
||||
BindingAction::ToggleFullscreen => "Toggle fullscreen".into(),
|
||||
BindingAction::ToggleMaximized => "Toggle full-width window".into(),
|
||||
BindingAction::ToggleLayout => "Switch workspace layout".into(),
|
||||
@@ -642,7 +722,12 @@ impl Binding {
|
||||
BindingAction::ToggleColumnTabbed => "Toggle stacked / tabbed column".into(),
|
||||
BindingAction::JoinTab(direction) => format!("Join tab group {direction:?}"),
|
||||
BindingAction::LeaveTab => "Leave tab group".into(),
|
||||
BindingAction::CycleTab(previous) => if *previous { "Previous tab" } else { "Next tab" }.into(),
|
||||
BindingAction::CycleTab(previous) => if *previous {
|
||||
"Previous tab"
|
||||
} else {
|
||||
"Next tab"
|
||||
}
|
||||
.into(),
|
||||
BindingAction::Consume => "Join window into column or tab group".into(),
|
||||
BindingAction::Expel => "Leave column or tab group".into(),
|
||||
BindingAction::ToggleFloating => "Toggle floating window".into(),
|
||||
@@ -694,7 +779,11 @@ impl Binding {
|
||||
matches!(self.trigger, BindingTrigger::Swipe { fingers, .. } if fingers == count)
|
||||
}
|
||||
|
||||
pub(crate) fn matches_swipe(&self, count: u32, target: crate::gestures::SwipeDirection) -> bool {
|
||||
pub(crate) fn matches_swipe(
|
||||
&self,
|
||||
count: u32,
|
||||
target: crate::gestures::SwipeDirection,
|
||||
) -> bool {
|
||||
matches!(self.trigger, BindingTrigger::Swipe { fingers, direction } if fingers == count && direction == target)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,12 @@ impl BindingSet {
|
||||
alt: bool,
|
||||
shift: bool,
|
||||
) -> Option<&Binding> {
|
||||
let modifiers = BindingModifiers { logo, ctrl, alt, shift };
|
||||
let modifiers = BindingModifiers {
|
||||
logo,
|
||||
ctrl,
|
||||
alt,
|
||||
shift,
|
||||
};
|
||||
let physical = self.keyboard.get(&BindingIdentity {
|
||||
modifiers,
|
||||
match_mode: BindingMatch::Physical,
|
||||
@@ -96,7 +101,10 @@ mod tests {
|
||||
let code = Keycode::new(38);
|
||||
let bindings = vec![
|
||||
binding_for(BindingTrigger::Keysym(keysyms::KEY_b), BindingAction::Close),
|
||||
binding_for(BindingTrigger::Physical(code), BindingAction::ToggleFloating),
|
||||
binding_for(
|
||||
BindingTrigger::Physical(code),
|
||||
BindingAction::ToggleFloating,
|
||||
),
|
||||
binding_for(BindingTrigger::Keysym(keysyms::KEY_a), BindingAction::Exit),
|
||||
binding_for(BindingTrigger::Keysym(keysyms::KEY_b), BindingAction::None),
|
||||
];
|
||||
@@ -125,7 +133,10 @@ mod tests {
|
||||
let config = Config::default();
|
||||
let mut bindings = config.bindings(&config.input_settings().unwrap()).unwrap();
|
||||
bindings.extend([
|
||||
binding_for(BindingTrigger::Physical(Keycode::new(38)), BindingAction::None),
|
||||
binding_for(
|
||||
BindingTrigger::Physical(Keycode::new(38)),
|
||||
BindingAction::None,
|
||||
),
|
||||
binding_for(BindingTrigger::Keysym(keysyms::KEY_a), BindingAction::Close),
|
||||
binding_for(
|
||||
BindingTrigger::Swipe {
|
||||
@@ -138,7 +149,8 @@ mod tests {
|
||||
let index = BindingSet::from(bindings);
|
||||
|
||||
for bits in 0..16 {
|
||||
let (logo, ctrl, alt, shift) = (bits & 1 != 0, bits & 2 != 0, bits & 4 != 0, bits & 8 != 0);
|
||||
let (logo, ctrl, alt, shift) =
|
||||
(bits & 1 != 0, bits & 2 != 0, bits & 4 != 0, bits & 8 != 0);
|
||||
|
||||
for code in 8..100 {
|
||||
for symbols in [
|
||||
@@ -164,7 +176,10 @@ mod tests {
|
||||
#[test]
|
||||
fn replacing_bindings_discards_old_index_entries() {
|
||||
let code = Keycode::new(38);
|
||||
let mut bindings = BindingSet::from(vec![binding_for(BindingTrigger::Physical(code), BindingAction::Close)]);
|
||||
let mut bindings = BindingSet::from(vec![binding_for(
|
||||
BindingTrigger::Physical(code),
|
||||
BindingAction::Close,
|
||||
)]);
|
||||
assert_eq!(
|
||||
bindings.action_for_key(code, &[], false, false, false, false),
|
||||
Some(&BindingAction::Close)
|
||||
@@ -173,7 +188,10 @@ mod tests {
|
||||
BindingTrigger::Keysym(keysyms::KEY_b),
|
||||
BindingAction::None,
|
||||
)]);
|
||||
assert_eq!(bindings.action_for_key(code, &[], false, false, false, false), None);
|
||||
assert_eq!(
|
||||
bindings.action_for_key(code, &[], false, false, false, false),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
bindings.action_for_key(code, &[keysyms::KEY_b], false, false, false, false),
|
||||
Some(&BindingAction::None)
|
||||
@@ -182,9 +200,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cloning_spawn_actions_shares_the_command_storage() {
|
||||
let action = BindingAction::Spawn(vec!["foot".into(), "--app-id".into(), "work".into()].into());
|
||||
let action =
|
||||
BindingAction::Spawn(vec!["foot".into(), "--app-id".into(), "work".into()].into());
|
||||
let cloned = action.clone();
|
||||
let (BindingAction::Spawn(original), BindingAction::Spawn(cloned)) = (&action, cloned) else {
|
||||
let (BindingAction::Spawn(original), BindingAction::Spawn(cloned)) = (&action, cloned)
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
assert!(Arc::ptr_eq(original, &cloned));
|
||||
|
||||
@@ -75,16 +75,24 @@ impl Config {
|
||||
if configured.disabled
|
||||
|| identity.match_mode == BindingMatch::Swipe
|
||||
|| !identities.insert(identity.clone())
|
||||
|| (identity.match_mode == BindingMatch::Keysym && identity.key == keysyms::KEY_Escape)
|
||||
|| (identity.match_mode == BindingMatch::Keysym
|
||||
&& identity.key == keysyms::KEY_Escape)
|
||||
|| (identity.match_mode == BindingMatch::Physical
|
||||
&& (0..keymap.num_layouts_for_key(Keycode::new(identity.key))).any(|layout| {
|
||||
(0..keymap.num_levels_for_key(Keycode::new(identity.key), layout)).any(|level| {
|
||||
keymap
|
||||
.key_get_syms_by_level(Keycode::new(identity.key), layout, level)
|
||||
.iter()
|
||||
.any(|symbol| symbol.raw() == keysyms::KEY_Escape)
|
||||
})
|
||||
}))
|
||||
&& (0..keymap.num_layouts_for_key(Keycode::new(identity.key))).any(
|
||||
|layout| {
|
||||
(0..keymap.num_levels_for_key(Keycode::new(identity.key), layout))
|
||||
.any(|level| {
|
||||
keymap
|
||||
.key_get_syms_by_level(
|
||||
Keycode::new(identity.key),
|
||||
layout,
|
||||
level,
|
||||
)
|
||||
.iter()
|
||||
.any(|symbol| symbol.raw() == keysyms::KEY_Escape)
|
||||
})
|
||||
},
|
||||
))
|
||||
{
|
||||
return Err(ConfigError::Binding(format!(
|
||||
"invalid, duplicate or reserved mode binding {:?}",
|
||||
@@ -95,12 +103,17 @@ impl Config {
|
||||
}
|
||||
modes.maps.insert(mode.name.clone(), bindings.into());
|
||||
}
|
||||
for binding in default.iter().chain(modes.maps.values().flat_map(|map| map.iter())) {
|
||||
for binding in default
|
||||
.iter()
|
||||
.chain(modes.maps.values().flat_map(|map| map.iter()))
|
||||
{
|
||||
if let BindingAction::BindingMode(name) = &binding.action
|
||||
&& name != "default"
|
||||
&& !modes.maps.contains_key(name)
|
||||
{
|
||||
return Err(ConfigError::Binding(format!("unknown binding mode {name:?}")));
|
||||
return Err(ConfigError::Binding(format!(
|
||||
"unknown binding mode {name:?}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(modes)
|
||||
@@ -131,7 +144,14 @@ mod tests {
|
||||
assert!(modes.set("resize"));
|
||||
let selected = modes.bindings(&default);
|
||||
assert_eq!(
|
||||
selected.action_for_key(Keycode::new(43), &[keysyms::KEY_h], false, false, false, false),
|
||||
selected.action_for_key(
|
||||
Keycode::new(43),
|
||||
&[keysyms::KEY_h],
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
),
|
||||
Some(&BindingAction::Resize(Direction::Left))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -139,11 +159,25 @@ mod tests {
|
||||
Some(&BindingAction::Resize(Direction::Up))
|
||||
);
|
||||
assert_eq!(
|
||||
selected.action_for_key(Keycode::new(43), &[keysyms::KEY_h], true, false, false, false),
|
||||
selected.action_for_key(
|
||||
Keycode::new(43),
|
||||
&[keysyms::KEY_h],
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
selected.action_for_key(Keycode::new(36), &[keysyms::KEY_Return], true, false, false, false),
|
||||
selected.action_for_key(
|
||||
Keycode::new(36),
|
||||
&[keysyms::KEY_Return],
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
),
|
||||
None
|
||||
);
|
||||
assert!(!modes.set("missing"));
|
||||
|
||||
@@ -57,11 +57,15 @@ impl Config {
|
||||
}
|
||||
|
||||
pub fn spring_config(&self) -> Result<SpringConfig, ConfigError> {
|
||||
self.animations.spring_config().map_err(ConfigError::Binding)
|
||||
self.animations
|
||||
.spring_config()
|
||||
.map_err(ConfigError::Binding)
|
||||
}
|
||||
|
||||
pub fn viewport_spring_config(&self) -> Result<SpringConfig, ConfigError> {
|
||||
self.animations.viewport_config().map_err(ConfigError::Binding)
|
||||
self.animations
|
||||
.viewport_config()
|
||||
.map_err(ConfigError::Binding)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +160,8 @@ mod tests {
|
||||
#[test]
|
||||
fn compositor_speed_uses_shared_motion_bounds() {
|
||||
for speed in [1e-100, 0.009, 101.0, 1e100] {
|
||||
let config = Config::parse_source(&format!("animations {{ speed {speed:e}; }}")).unwrap();
|
||||
let config =
|
||||
Config::parse_source(&format!("animations {{ speed {speed:e}; }}")).unwrap();
|
||||
assert!(config.animation_speed().is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,11 @@ impl Config {
|
||||
}
|
||||
|
||||
pub fn scrolling_focus_strategy(&self) -> ViewportFocusStrategy {
|
||||
match self.scrolling.focus_strategy.unwrap_or(FocusStrategyValue::Minimal) {
|
||||
match self
|
||||
.scrolling
|
||||
.focus_strategy
|
||||
.unwrap_or(FocusStrategyValue::Minimal)
|
||||
{
|
||||
FocusStrategyValue::Minimal => ViewportFocusStrategy::Minimal,
|
||||
FocusStrategyValue::CenterOnFocus => ViewportFocusStrategy::Center,
|
||||
FocusStrategyValue::Paged => ViewportFocusStrategy::Paged,
|
||||
@@ -66,7 +70,10 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn nonnegative_layout_value(value: f64, field: &'static str) -> Result<f64, ConfigError> {
|
||||
pub(super) fn nonnegative_layout_value(
|
||||
value: f64,
|
||||
field: &'static str,
|
||||
) -> Result<f64, ConfigError> {
|
||||
if value.is_finite() && value >= 0.0 {
|
||||
Ok(value)
|
||||
} else {
|
||||
@@ -74,10 +81,17 @@ pub(super) fn nonnegative_layout_value(value: f64, field: &'static str) -> Resul
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn parse_column_width(value: &ColumnWidthValue, field: &'static str) -> Result<ColumnWidth, ConfigError> {
|
||||
pub(super) fn parse_column_width(
|
||||
value: &ColumnWidthValue,
|
||||
field: &'static str,
|
||||
) -> Result<ColumnWidth, ConfigError> {
|
||||
match value {
|
||||
ColumnWidthValue::Proportion(value) if value.is_finite() && *value > 0.0 => Ok(ColumnWidth::Proportion(*value)),
|
||||
ColumnWidthValue::Named(value) if value.eq_ignore_ascii_case("full") => Ok(ColumnWidth::Full),
|
||||
ColumnWidthValue::Proportion(value) if value.is_finite() && *value > 0.0 => {
|
||||
Ok(ColumnWidth::Proportion(*value))
|
||||
}
|
||||
ColumnWidthValue::Named(value) if value.eq_ignore_ascii_case("full") => {
|
||||
Ok(ColumnWidth::Full)
|
||||
}
|
||||
value => Err(ConfigError::ColumnWidth {
|
||||
field,
|
||||
value: match value {
|
||||
|
||||
@@ -20,7 +20,10 @@ pub(crate) fn read_source(path: &Path) -> Result<String, String> {
|
||||
Ok(source)
|
||||
}
|
||||
|
||||
pub(crate) fn prepare(source: &str, directory: &Path) -> Result<(Config, RuntimeConfig, Candidate), String> {
|
||||
pub(crate) fn prepare(
|
||||
source: &str,
|
||||
directory: &Path,
|
||||
) -> Result<(Config, RuntimeConfig, Candidate), String> {
|
||||
let document = Document::parse(source).map_err(|e| e.to_string())?;
|
||||
prepare_document(&document, directory)
|
||||
}
|
||||
@@ -68,9 +71,11 @@ pub(crate) fn prepare_document(
|
||||
) -> Result<(Config, RuntimeConfig, Candidate), String> {
|
||||
let candidate = crate::theme::resolve_candidate(document, directory)?;
|
||||
let config: Config =
|
||||
serde_json::from_value(document.with_theme(&candidate.theme).value().clone()).map_err(|e| e.to_string())?;
|
||||
serde_json::from_value(document.with_theme(&candidate.theme).value().clone())
|
||||
.map_err(|e| e.to_string())?;
|
||||
let mut runtime = config.runtime_config().map_err(|e| e.to_string())?;
|
||||
runtime.theme_settings = Config::resolved_theme_settings(&candidate.theme).map_err(|e| e.to_string())?;
|
||||
runtime.theme_settings =
|
||||
Config::resolved_theme_settings(&candidate.theme).map_err(|e| e.to_string())?;
|
||||
Ok((config, runtime, candidate))
|
||||
}
|
||||
|
||||
@@ -119,7 +124,10 @@ mod tests {
|
||||
let valid = startup(Some(&path)).unwrap();
|
||||
assert!(valid.error.is_none());
|
||||
assert_eq!(valid.source.as_deref(), Some(source));
|
||||
assert_eq!(valid.runtime.panel_style, ferese_panel::PanelStyle::Floating);
|
||||
assert_eq!(
|
||||
valid.runtime.panel_style,
|
||||
ferese_panel::PanelStyle::Floating
|
||||
);
|
||||
assert_eq!(
|
||||
Document::parse(valid.source.as_deref().unwrap())
|
||||
.unwrap()
|
||||
|
||||
@@ -9,13 +9,19 @@ impl Config {
|
||||
.map(|profile| {
|
||||
let name = profile.name.trim();
|
||||
if name.is_empty() {
|
||||
return Err(ConfigError::OutputProfile("profile names cannot be empty".to_owned()));
|
||||
return Err(ConfigError::OutputProfile(
|
||||
"profile names cannot be empty".to_owned(),
|
||||
));
|
||||
}
|
||||
if !names.insert(name.to_owned()) {
|
||||
return Err(ConfigError::OutputProfile(format!("duplicate profile name {name:?}")));
|
||||
return Err(ConfigError::OutputProfile(format!(
|
||||
"duplicate profile name {name:?}"
|
||||
)));
|
||||
}
|
||||
if profile.outputs.is_empty() {
|
||||
return Err(ConfigError::OutputProfile(format!("profile {name:?} has no outputs")));
|
||||
return Err(ConfigError::OutputProfile(format!(
|
||||
"profile {name:?} has no outputs"
|
||||
)));
|
||||
}
|
||||
|
||||
let mut matchers = HashSet::new();
|
||||
@@ -70,13 +76,16 @@ impl Config {
|
||||
)));
|
||||
}
|
||||
if let Some(source) = &profile.mirror_source
|
||||
&& !outputs.iter().any(|output| &output.matcher == source && output.enabled)
|
||||
&& !outputs
|
||||
.iter()
|
||||
.any(|output| &output.matcher == source && output.enabled)
|
||||
{
|
||||
return Err(ConfigError::OutputProfile(format!(
|
||||
"profile {name:?}: mirror source must name an enabled output"
|
||||
)));
|
||||
}
|
||||
if profile.layout == OutputLayout::Mirror && outputs.iter().filter(|output| output.enabled).count() < 2
|
||||
if profile.layout == OutputLayout::Mirror
|
||||
&& outputs.iter().filter(|output| output.enabled).count() < 2
|
||||
{
|
||||
return Err(ConfigError::OutputProfile(format!(
|
||||
"profile {name:?}: mirror needs at least two enabled outputs"
|
||||
@@ -101,7 +110,9 @@ pub(super) fn parse_output_mode(value: &str) -> Result<OutputModeRequest, String
|
||||
let (size, refresh) = value
|
||||
.trim()
|
||||
.split_once('@')
|
||||
.map_or((value.trim(), None), |(size, refresh)| (size, Some(refresh)));
|
||||
.map_or((value.trim(), None), |(size, refresh)| {
|
||||
(size, Some(refresh))
|
||||
});
|
||||
let (width, height) = size
|
||||
.split_once('x')
|
||||
.ok_or_else(|| format!("mode {value:?} must use WIDTHxHEIGHT or WIDTHxHEIGHT@REFRESH"))?;
|
||||
|
||||
@@ -74,8 +74,12 @@ impl Config {
|
||||
Err(ConfigError::LayoutValue { field, value })
|
||||
}
|
||||
};
|
||||
let color =
|
||||
|value: &Option<String>, field| value.as_ref().map(|s| parse_color(s, field).map(|c| c.0)).transpose();
|
||||
let color = |value: &Option<String>, field| {
|
||||
value
|
||||
.as_ref()
|
||||
.map(|s| parse_color(s, field).map(|c| c.0))
|
||||
.transpose()
|
||||
};
|
||||
let width = range(c.width, 1.0, 16.0, "tab_indicator.width")?;
|
||||
let hit_width = range(c.hit_width, width, 64.0, "tab_indicator.hit_width")?;
|
||||
let font_size = range(c.font_size, 8.0, 32.0, "tab_indicator.font_size")?;
|
||||
@@ -85,11 +89,21 @@ impl Config {
|
||||
font_size,
|
||||
outline_width: range(c.outline_width, 0.0, 8.0, "tab_indicator.outline_width")?,
|
||||
outline_color: color(&c.outline_color, "tab_indicator.outline_color")?,
|
||||
segment_height: range(c.segment_height, width, 128.0, "tab_indicator.segment_height")?,
|
||||
segment_height: range(
|
||||
c.segment_height,
|
||||
width,
|
||||
128.0,
|
||||
"tab_indicator.segment_height",
|
||||
)?,
|
||||
segment_gap: range(c.segment_gap, 0.0, 64.0, "tab_indicator.segment_gap")?,
|
||||
gap_threshold: range(c.gap_threshold, width, 128.0, "tab_indicator.gap_threshold")?,
|
||||
list_width: range(c.list_width, 80.0, 640.0, "tab_indicator.list_width")?,
|
||||
row_height: range(c.row_height, font_size + 6.0, 80.0, "tab_indicator.row_height")?,
|
||||
row_height: range(
|
||||
c.row_height,
|
||||
font_size + 6.0,
|
||||
80.0,
|
||||
"tab_indicator.row_height",
|
||||
)?,
|
||||
padding: range(c.padding, 0.0, 24.0, "tab_indicator.padding")?,
|
||||
flash_duration: std::time::Duration::from_millis(range(
|
||||
c.flash_duration_ms as f64,
|
||||
@@ -111,9 +125,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn indicator_defaults_overrides_and_validation() {
|
||||
let c = Config::parse_source("").unwrap().tab_indicator_settings().unwrap();
|
||||
let c = Config::parse_source("")
|
||||
.unwrap()
|
||||
.tab_indicator_settings()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
(c.width, c.segment_height, c.segment_gap, c.hit_width, c.gap_threshold),
|
||||
(
|
||||
c.width,
|
||||
c.segment_height,
|
||||
c.segment_gap,
|
||||
c.hit_width,
|
||||
c.gap_threshold
|
||||
),
|
||||
(4., 24., 4., 12., 8.)
|
||||
);
|
||||
assert_eq!(c.flash_duration.as_millis(), 1000);
|
||||
@@ -123,7 +146,10 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
let c = config.tab_indicator_settings().unwrap();
|
||||
assert_eq!((c.width, c.hit_width, c.flash_duration.as_millis()), (3., 14., 750));
|
||||
assert_eq!(
|
||||
(c.width, c.hit_width, c.flash_duration.as_millis()),
|
||||
(3., 14., 750)
|
||||
);
|
||||
assert_eq!(c.active_color, Some([1., 0., 0., 1.]));
|
||||
for source in [
|
||||
"width 0",
|
||||
@@ -133,7 +159,9 @@ mod tests {
|
||||
"flash-duration-ms 60001",
|
||||
"active-color \"invalid\"",
|
||||
] {
|
||||
let config = Config::parse_source(&format!("scrolling {{ tab-indicator {{ {source}; }}; }}")).unwrap();
|
||||
let config =
|
||||
Config::parse_source(&format!("scrolling {{ tab-indicator {{ {source}; }}; }}"))
|
||||
.unwrap();
|
||||
assert!(config.tab_indicator_settings().is_err(), "{source}");
|
||||
}
|
||||
assert!(Config::parse_source("scrolling { tab-indicator { typo 1; }; }").is_err());
|
||||
|
||||
Reference in new issue
Block a user