mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
Correctly handle parsing of Binds and DefaultColumnWidth (#234)
* add dev dependencies to flake * parse only one default-column-width * require exactly one action per bind, and unique keys for binds * use proper filename for config errors if possible * fix duplicate keybinds after invalid action, lose some sanity
This commit is contained in:
@@ -51,6 +51,10 @@
|
|||||||
pkg-config
|
pkg-config
|
||||||
autoPatchelfHook
|
autoPatchelfHook
|
||||||
clang
|
clang
|
||||||
|
gdk-pixbuf
|
||||||
|
graphene
|
||||||
|
gtk4
|
||||||
|
libadwaita
|
||||||
];
|
];
|
||||||
|
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
|
|||||||
+209
-31
@@ -1,10 +1,13 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate tracing;
|
extern crate tracing;
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
use knuffel::errors::DecodeError;
|
||||||
use miette::{miette, Context, IntoDiagnostic, NarratableReportHandler};
|
use miette::{miette, Context, IntoDiagnostic, NarratableReportHandler};
|
||||||
use niri_ipc::{LayoutSwitchTarget, SizeChange};
|
use niri_ipc::{LayoutSwitchTarget, SizeChange};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@@ -474,8 +477,8 @@ pub enum PresetWidth {
|
|||||||
Fixed(#[knuffel(argument)] i32),
|
Fixed(#[knuffel(argument)] i32),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct DefaultColumnWidth(#[knuffel(children)] pub Vec<PresetWidth>);
|
pub struct DefaultColumnWidth(pub Option<PresetWidth>);
|
||||||
|
|
||||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct Struts {
|
pub struct Struts {
|
||||||
@@ -621,25 +624,23 @@ impl PartialEq for Match {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub struct Binds(#[knuffel(children)] pub Vec<Bind>);
|
pub struct Binds(pub Vec<Bind>);
|
||||||
|
|
||||||
#[derive(knuffel::Decode, Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Bind {
|
pub struct Bind {
|
||||||
#[knuffel(node_name)]
|
|
||||||
pub key: Key,
|
pub key: Key,
|
||||||
#[knuffel(children)]
|
pub action: Action,
|
||||||
pub actions: Vec<Action>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
pub struct Key {
|
pub struct Key {
|
||||||
pub keysym: Keysym,
|
pub keysym: Keysym,
|
||||||
pub modifiers: Modifiers,
|
pub modifiers: Modifiers,
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct Modifiers : u8 {
|
pub struct Modifiers : u8 {
|
||||||
const CTRL = 1;
|
const CTRL = 1;
|
||||||
const SHIFT = 2;
|
const SHIFT = 2;
|
||||||
@@ -825,7 +826,13 @@ impl Config {
|
|||||||
.into_diagnostic()
|
.into_diagnostic()
|
||||||
.with_context(|| format!("error reading {path:?}"))?;
|
.with_context(|| format!("error reading {path:?}"))?;
|
||||||
|
|
||||||
let config = Self::parse("config.kdl", &contents).context("error parsing")?;
|
let config = Self::parse(
|
||||||
|
path.file_name()
|
||||||
|
.and_then(OsStr::to_str)
|
||||||
|
.unwrap_or("config.kdl"),
|
||||||
|
&contents,
|
||||||
|
)
|
||||||
|
.context("error parsing")?;
|
||||||
debug!("loaded config from {path:?}");
|
debug!("loaded config from {path:?}");
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
@@ -882,10 +889,10 @@ where
|
|||||||
fn decode_node(
|
fn decode_node(
|
||||||
node: &knuffel::ast::SpannedNode<S>,
|
node: &knuffel::ast::SpannedNode<S>,
|
||||||
ctx: &mut knuffel::decode::Context<S>,
|
ctx: &mut knuffel::decode::Context<S>,
|
||||||
) -> Result<Self, knuffel::errors::DecodeError<S>> {
|
) -> Result<Self, DecodeError<S>> {
|
||||||
// Check for unexpected type name.
|
// Check for unexpected type name.
|
||||||
if let Some(type_name) = &node.type_name {
|
if let Some(type_name) = &node.type_name {
|
||||||
ctx.emit_error(knuffel::errors::DecodeError::unexpected(
|
ctx.emit_error(DecodeError::unexpected(
|
||||||
type_name,
|
type_name,
|
||||||
"type name",
|
"type name",
|
||||||
"no type name expected for this node",
|
"no type name expected for this node",
|
||||||
@@ -894,13 +901,13 @@ where
|
|||||||
|
|
||||||
// Get the first argument.
|
// Get the first argument.
|
||||||
let mut iter_args = node.arguments.iter();
|
let mut iter_args = node.arguments.iter();
|
||||||
let val = iter_args.next().ok_or_else(|| {
|
let val = iter_args
|
||||||
knuffel::errors::DecodeError::missing(node, "additional argument is required")
|
.next()
|
||||||
})?;
|
.ok_or_else(|| DecodeError::missing(node, "additional argument is required"))?;
|
||||||
|
|
||||||
// Check for unexpected type name.
|
// Check for unexpected type name.
|
||||||
if let Some(typ) = &val.type_name {
|
if let Some(typ) = &val.type_name {
|
||||||
ctx.emit_error(knuffel::errors::DecodeError::TypeName {
|
ctx.emit_error(DecodeError::TypeName {
|
||||||
span: typ.span().clone(),
|
span: typ.span().clone(),
|
||||||
found: Some((**typ).clone()),
|
found: Some((**typ).clone()),
|
||||||
expected: knuffel::errors::ExpectedType::no_type(),
|
expected: knuffel::errors::ExpectedType::no_type(),
|
||||||
@@ -911,15 +918,16 @@ where
|
|||||||
// Check the argument type.
|
// Check the argument type.
|
||||||
let rv = match *val.literal {
|
let rv = match *val.literal {
|
||||||
// If it's a string, use FromStr.
|
// If it's a string, use FromStr.
|
||||||
knuffel::ast::Literal::String(ref s) => Color::from_str(s)
|
knuffel::ast::Literal::String(ref s) => {
|
||||||
.map_err(|e| knuffel::errors::DecodeError::conversion(&val.literal, e)),
|
Color::from_str(s).map_err(|e| DecodeError::conversion(&val.literal, e))
|
||||||
|
}
|
||||||
// Otherwise, fall back to the 4-argument RGBA form.
|
// Otherwise, fall back to the 4-argument RGBA form.
|
||||||
_ => return ColorRgba::decode_node(node, ctx).map(Color::from),
|
_ => return ColorRgba::decode_node(node, ctx).map(Color::from),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
// Check for unexpected following arguments.
|
// Check for unexpected following arguments.
|
||||||
if let Some(val) = iter_args.next() {
|
if let Some(val) = iter_args.next() {
|
||||||
ctx.emit_error(knuffel::errors::DecodeError::unexpected(
|
ctx.emit_error(DecodeError::unexpected(
|
||||||
&val.literal,
|
&val.literal,
|
||||||
"argument",
|
"argument",
|
||||||
"unexpected argument",
|
"unexpected argument",
|
||||||
@@ -928,14 +936,14 @@ where
|
|||||||
|
|
||||||
// Check for unexpected properties and children.
|
// Check for unexpected properties and children.
|
||||||
for name in node.properties.keys() {
|
for name in node.properties.keys() {
|
||||||
ctx.emit_error(knuffel::errors::DecodeError::unexpected(
|
ctx.emit_error(DecodeError::unexpected(
|
||||||
name,
|
name,
|
||||||
"property",
|
"property",
|
||||||
format!("unexpected property `{}`", name.escape_default()),
|
format!("unexpected property `{}`", name.escape_default()),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
for child in node.children.as_ref().map(|lst| &lst[..]).unwrap_or(&[]) {
|
for child in node.children.as_ref().map(|lst| &lst[..]).unwrap_or(&[]) {
|
||||||
ctx.emit_error(knuffel::errors::DecodeError::unexpected(
|
ctx.emit_error(DecodeError::unexpected(
|
||||||
child,
|
child,
|
||||||
"node",
|
"node",
|
||||||
format!("unexpected node `{}`", child.node_name.escape_default()),
|
format!("unexpected node `{}`", child.node_name.escape_default()),
|
||||||
@@ -946,6 +954,176 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn expect_only_children<S>(
|
||||||
|
node: &knuffel::ast::SpannedNode<S>,
|
||||||
|
ctx: &mut knuffel::decode::Context<S>,
|
||||||
|
) where
|
||||||
|
S: knuffel::traits::ErrorSpan,
|
||||||
|
{
|
||||||
|
if let Some(type_name) = &node.type_name {
|
||||||
|
ctx.emit_error(DecodeError::unexpected(
|
||||||
|
type_name,
|
||||||
|
"type name",
|
||||||
|
"no type name expected for this node",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
for val in node.arguments.iter() {
|
||||||
|
ctx.emit_error(DecodeError::unexpected(
|
||||||
|
&val.literal,
|
||||||
|
"argument",
|
||||||
|
"no arguments expected for this node",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
for name in node.properties.keys() {
|
||||||
|
ctx.emit_error(DecodeError::unexpected(
|
||||||
|
name,
|
||||||
|
"property",
|
||||||
|
"no properties expected for this node",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> knuffel::Decode<S> for DefaultColumnWidth
|
||||||
|
where
|
||||||
|
S: knuffel::traits::ErrorSpan,
|
||||||
|
{
|
||||||
|
fn decode_node(
|
||||||
|
node: &knuffel::ast::SpannedNode<S>,
|
||||||
|
ctx: &mut knuffel::decode::Context<S>,
|
||||||
|
) -> Result<Self, DecodeError<S>> {
|
||||||
|
expect_only_children(node, ctx);
|
||||||
|
|
||||||
|
let mut children = node.children();
|
||||||
|
|
||||||
|
if let Some(child) = children.next() {
|
||||||
|
if let Some(unwanted_child) = children.next() {
|
||||||
|
ctx.emit_error(DecodeError::unexpected(
|
||||||
|
unwanted_child,
|
||||||
|
"node",
|
||||||
|
"expected no more than one child",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
PresetWidth::decode_node(child, ctx).map(Some).map(Self)
|
||||||
|
} else {
|
||||||
|
Ok(Self(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> knuffel::Decode<S> for Binds
|
||||||
|
where
|
||||||
|
S: knuffel::traits::ErrorSpan,
|
||||||
|
{
|
||||||
|
fn decode_node(
|
||||||
|
node: &knuffel::ast::SpannedNode<S>,
|
||||||
|
ctx: &mut knuffel::decode::Context<S>,
|
||||||
|
) -> Result<Self, DecodeError<S>> {
|
||||||
|
expect_only_children(node, ctx);
|
||||||
|
|
||||||
|
let mut seen_keys = HashSet::new();
|
||||||
|
|
||||||
|
let mut binds = Vec::new();
|
||||||
|
|
||||||
|
for child in node.children() {
|
||||||
|
match Bind::decode_node(child, ctx) {
|
||||||
|
Err(e) => {
|
||||||
|
ctx.emit_error(e);
|
||||||
|
}
|
||||||
|
Ok(bind) => {
|
||||||
|
if seen_keys.insert(bind.key) {
|
||||||
|
binds.push(bind);
|
||||||
|
} else {
|
||||||
|
// ideally, this error should point to the previous instance of this keybind
|
||||||
|
//
|
||||||
|
// i (sodiboo) have tried to implement this in various ways:
|
||||||
|
// miette!(), #[derive(Diagnostic)]
|
||||||
|
// DecodeError::Custom, DecodeError::Conversion
|
||||||
|
// nothing seems to work, and i suspect it's not possible.
|
||||||
|
//
|
||||||
|
// DecodeError is fairly restrictive.
|
||||||
|
// even DecodeError::Custom just wraps a std::error::Error
|
||||||
|
// and this erases all rich information from miette. (why???)
|
||||||
|
//
|
||||||
|
// why does knuffel do this?
|
||||||
|
// from what i can tell, it doesn't even use DecodeError for much.
|
||||||
|
// it only ever converts them to a Report anyways!
|
||||||
|
// https://github.com/tailhook/knuffel/blob/c44c6b0c0f31ea6d1174d5d2ed41064922ea44ca/src/wrappers.rs#L55-L58
|
||||||
|
//
|
||||||
|
// besides like, allowing downstream users (such as us!)
|
||||||
|
// to match on parse failure, i don't understand why
|
||||||
|
// it doesn't just use a generic error type
|
||||||
|
//
|
||||||
|
// even the matching isn't consistent,
|
||||||
|
// because errors can also be omitted as ctx.emit_error.
|
||||||
|
// why does *that one* especially, require a DecodeError?
|
||||||
|
//
|
||||||
|
// anyways if you can make it format nicely, definitely do fix this
|
||||||
|
ctx.emit_error(DecodeError::unexpected(
|
||||||
|
&child.node_name,
|
||||||
|
"keybind",
|
||||||
|
"duplicate keybind",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self(binds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> knuffel::Decode<S> for Bind
|
||||||
|
where
|
||||||
|
S: knuffel::traits::ErrorSpan,
|
||||||
|
{
|
||||||
|
fn decode_node(
|
||||||
|
node: &knuffel::ast::SpannedNode<S>,
|
||||||
|
ctx: &mut knuffel::decode::Context<S>,
|
||||||
|
) -> Result<Self, DecodeError<S>> {
|
||||||
|
expect_only_children(node, ctx);
|
||||||
|
|
||||||
|
let key = node
|
||||||
|
.node_name
|
||||||
|
.parse::<Key>()
|
||||||
|
.map_err(|e| DecodeError::conversion(&node.node_name, e.wrap_err("invalid keybind")))?;
|
||||||
|
|
||||||
|
let mut children = node.children();
|
||||||
|
|
||||||
|
// If the action is invalid but the key is fine, we still want to return something.
|
||||||
|
// That way, the parent can handle the existence of duplicate keybinds,
|
||||||
|
// even if their contents are not valid.
|
||||||
|
let dummy = Self {
|
||||||
|
key,
|
||||||
|
action: Action::Spawn(vec![]),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(child) = children.next() {
|
||||||
|
for unwanted_child in children {
|
||||||
|
ctx.emit_error(DecodeError::unexpected(
|
||||||
|
unwanted_child,
|
||||||
|
"node",
|
||||||
|
"only one action is allowed per keybind",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
match Action::decode_node(child, ctx) {
|
||||||
|
Ok(action) => Ok(Self { key, action }),
|
||||||
|
Err(e) => {
|
||||||
|
ctx.emit_error(e);
|
||||||
|
Ok(dummy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.emit_error(DecodeError::missing(
|
||||||
|
node,
|
||||||
|
"expected an action for this keybind",
|
||||||
|
));
|
||||||
|
Ok(dummy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromStr for Mode {
|
impl FromStr for Mode {
|
||||||
type Err = miette::Error;
|
type Err = miette::Error;
|
||||||
|
|
||||||
@@ -1302,9 +1480,9 @@ mod tests {
|
|||||||
PresetWidth::Fixed(960),
|
PresetWidth::Fixed(960),
|
||||||
PresetWidth::Fixed(1280),
|
PresetWidth::Fixed(1280),
|
||||||
],
|
],
|
||||||
default_column_width: Some(DefaultColumnWidth(vec![PresetWidth::Proportion(
|
default_column_width: Some(DefaultColumnWidth(Some(PresetWidth::Proportion(
|
||||||
0.25,
|
0.25,
|
||||||
)])),
|
)))),
|
||||||
gaps: 8,
|
gaps: 8,
|
||||||
struts: Struts {
|
struts: Struts {
|
||||||
left: 1,
|
left: 1,
|
||||||
@@ -1369,49 +1547,49 @@ mod tests {
|
|||||||
keysym: Keysym::t,
|
keysym: Keysym::t,
|
||||||
modifiers: Modifiers::COMPOSITOR,
|
modifiers: Modifiers::COMPOSITOR,
|
||||||
},
|
},
|
||||||
actions: vec![Action::Spawn(vec!["alacritty".to_owned()])],
|
action: Action::Spawn(vec!["alacritty".to_owned()]),
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
keysym: Keysym::q,
|
keysym: Keysym::q,
|
||||||
modifiers: Modifiers::COMPOSITOR,
|
modifiers: Modifiers::COMPOSITOR,
|
||||||
},
|
},
|
||||||
actions: vec![Action::CloseWindow],
|
action: Action::CloseWindow,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
keysym: Keysym::h,
|
keysym: Keysym::h,
|
||||||
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
|
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
|
||||||
},
|
},
|
||||||
actions: vec![Action::FocusMonitorLeft],
|
action: Action::FocusMonitorLeft,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
keysym: Keysym::l,
|
keysym: Keysym::l,
|
||||||
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT | Modifiers::CTRL,
|
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT | Modifiers::CTRL,
|
||||||
},
|
},
|
||||||
actions: vec![Action::MoveWindowToMonitorRight],
|
action: Action::MoveWindowToMonitorRight,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
keysym: Keysym::comma,
|
keysym: Keysym::comma,
|
||||||
modifiers: Modifiers::COMPOSITOR,
|
modifiers: Modifiers::COMPOSITOR,
|
||||||
},
|
},
|
||||||
actions: vec![Action::ConsumeWindowIntoColumn],
|
action: Action::ConsumeWindowIntoColumn,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
keysym: Keysym::_1,
|
keysym: Keysym::_1,
|
||||||
modifiers: Modifiers::COMPOSITOR,
|
modifiers: Modifiers::COMPOSITOR,
|
||||||
},
|
},
|
||||||
actions: vec![Action::FocusWorkspace(1)],
|
action: Action::FocusWorkspace(1),
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
keysym: Keysym::e,
|
keysym: Keysym::e,
|
||||||
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
|
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
|
||||||
},
|
},
|
||||||
actions: vec![Action::Quit(true)],
|
action: Action::Quit(true),
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
debug: DebugConfig {
|
debug: DebugConfig {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ pub fn resolve_window_rules(
|
|||||||
if let Some(x) = rule
|
if let Some(x) = rule
|
||||||
.default_column_width
|
.default_column_width
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|d| d.0.first().copied().map(ColumnWidth::from))
|
.map(|d| d.0.map(ColumnWidth::from))
|
||||||
{
|
{
|
||||||
resolved.default_width = Some(x);
|
resolved.default_width = Some(x);
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-7
@@ -1650,7 +1650,7 @@ fn bound_action(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if bind_modifiers == modifiers {
|
if bind_modifiers == modifiers {
|
||||||
return bind.actions.first().cloned();
|
return Some(bind.action.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1815,7 +1815,7 @@ mod tests {
|
|||||||
keysym: close_keysym,
|
keysym: close_keysym,
|
||||||
modifiers: Modifiers::COMPOSITOR | Modifiers::CTRL,
|
modifiers: Modifiers::COMPOSITOR | Modifiers::CTRL,
|
||||||
},
|
},
|
||||||
actions: vec![Action::CloseWindow],
|
action: Action::CloseWindow,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
let comp_mod = CompositorMod::Super;
|
let comp_mod = CompositorMod::Super;
|
||||||
@@ -1937,35 +1937,35 @@ mod tests {
|
|||||||
keysym: Keysym::q,
|
keysym: Keysym::q,
|
||||||
modifiers: Modifiers::COMPOSITOR,
|
modifiers: Modifiers::COMPOSITOR,
|
||||||
},
|
},
|
||||||
actions: vec![Action::CloseWindow],
|
action: Action::CloseWindow,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
keysym: Keysym::h,
|
keysym: Keysym::h,
|
||||||
modifiers: Modifiers::SUPER,
|
modifiers: Modifiers::SUPER,
|
||||||
},
|
},
|
||||||
actions: vec![Action::FocusColumnLeft],
|
action: Action::FocusColumnLeft,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
keysym: Keysym::j,
|
keysym: Keysym::j,
|
||||||
modifiers: Modifiers::empty(),
|
modifiers: Modifiers::empty(),
|
||||||
},
|
},
|
||||||
actions: vec![Action::FocusWindowDown],
|
action: Action::FocusWindowDown,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
keysym: Keysym::k,
|
keysym: Keysym::k,
|
||||||
modifiers: Modifiers::COMPOSITOR | Modifiers::SUPER,
|
modifiers: Modifiers::COMPOSITOR | Modifiers::SUPER,
|
||||||
},
|
},
|
||||||
actions: vec![Action::FocusWindowUp],
|
action: Action::FocusWindowUp,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
keysym: Keysym::l,
|
keysym: Keysym::l,
|
||||||
modifiers: Modifiers::SUPER | Modifiers::ALT,
|
modifiers: Modifiers::SUPER | Modifiers::ALT,
|
||||||
},
|
},
|
||||||
actions: vec![Action::FocusColumnRight],
|
action: Action::FocusColumnRight,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -200,7 +200,7 @@ impl Options {
|
|||||||
let default_width = layout
|
let default_width = layout
|
||||||
.default_column_width
|
.default_column_width
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|w| w.0.first().copied().map(ColumnWidth::from))
|
.map(|w| w.0.map(ColumnWidth::from))
|
||||||
.unwrap_or(Some(ColumnWidth::Proportion(0.5)));
|
.unwrap_or(Some(ColumnWidth::Proportion(0.5)));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
+10
-19
@@ -159,15 +159,9 @@ fn render(config: &Config, comp_mod: CompositorMod, scale: i32) -> anyhow::Resul
|
|||||||
|
|
||||||
// Prefer Quit(false) if found, otherwise try Quit(true), and if there's neither, fall back to
|
// Prefer Quit(false) if found, otherwise try Quit(true), and if there's neither, fall back to
|
||||||
// Quit(false).
|
// Quit(false).
|
||||||
if binds
|
if binds.iter().any(|bind| bind.action == Action::Quit(false)) {
|
||||||
.iter()
|
|
||||||
.any(|bind| bind.actions.first() == Some(&Action::Quit(false)))
|
|
||||||
{
|
|
||||||
actions.push(&Action::Quit(false));
|
actions.push(&Action::Quit(false));
|
||||||
} else if binds
|
} else if binds.iter().any(|bind| bind.action == Action::Quit(true)) {
|
||||||
.iter()
|
|
||||||
.any(|bind| bind.actions.first() == Some(&Action::Quit(true)))
|
|
||||||
{
|
|
||||||
actions.push(&Action::Quit(true));
|
actions.push(&Action::Quit(true));
|
||||||
} else {
|
} else {
|
||||||
actions.push(&Action::Quit(false));
|
actions.push(&Action::Quit(false));
|
||||||
@@ -186,12 +180,12 @@ fn render(config: &Config, comp_mod: CompositorMod, scale: i32) -> anyhow::Resul
|
|||||||
// Prefer move-column-to-workspace-down, but fall back to move-window-to-workspace-down.
|
// Prefer move-column-to-workspace-down, but fall back to move-window-to-workspace-down.
|
||||||
if binds
|
if binds
|
||||||
.iter()
|
.iter()
|
||||||
.any(|bind| bind.actions.first() == Some(&Action::MoveColumnToWorkspaceDown))
|
.any(|bind| bind.action == Action::MoveColumnToWorkspaceDown)
|
||||||
{
|
{
|
||||||
actions.push(&Action::MoveColumnToWorkspaceDown);
|
actions.push(&Action::MoveColumnToWorkspaceDown);
|
||||||
} else if binds
|
} else if binds
|
||||||
.iter()
|
.iter()
|
||||||
.any(|bind| bind.actions.first() == Some(&Action::MoveWindowToWorkspaceDown))
|
.any(|bind| bind.action == Action::MoveWindowToWorkspaceDown)
|
||||||
{
|
{
|
||||||
actions.push(&Action::MoveWindowToWorkspaceDown);
|
actions.push(&Action::MoveWindowToWorkspaceDown);
|
||||||
} else {
|
} else {
|
||||||
@@ -201,12 +195,12 @@ fn render(config: &Config, comp_mod: CompositorMod, scale: i32) -> anyhow::Resul
|
|||||||
// Same for -up.
|
// Same for -up.
|
||||||
if binds
|
if binds
|
||||||
.iter()
|
.iter()
|
||||||
.any(|bind| bind.actions.first() == Some(&Action::MoveColumnToWorkspaceUp))
|
.any(|bind| bind.action == Action::MoveColumnToWorkspaceUp)
|
||||||
{
|
{
|
||||||
actions.push(&Action::MoveColumnToWorkspaceUp);
|
actions.push(&Action::MoveColumnToWorkspaceUp);
|
||||||
} else if binds
|
} else if binds
|
||||||
.iter()
|
.iter()
|
||||||
.any(|bind| bind.actions.first() == Some(&Action::MoveWindowToWorkspaceUp))
|
.any(|bind| bind.action == Action::MoveWindowToWorkspaceUp)
|
||||||
{
|
{
|
||||||
actions.push(&Action::MoveWindowToWorkspaceUp);
|
actions.push(&Action::MoveWindowToWorkspaceUp);
|
||||||
} else {
|
} else {
|
||||||
@@ -221,22 +215,19 @@ fn render(config: &Config, comp_mod: CompositorMod, scale: i32) -> anyhow::Resul
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Screenshot is not as important, can omit if not bound.
|
// Screenshot is not as important, can omit if not bound.
|
||||||
if binds
|
if binds.iter().any(|bind| bind.action == Action::Screenshot) {
|
||||||
.iter()
|
|
||||||
.any(|bind| bind.actions.first() == Some(&Action::Screenshot))
|
|
||||||
{
|
|
||||||
actions.push(&Action::Screenshot);
|
actions.push(&Action::Screenshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the spawn actions.
|
// Add the spawn actions.
|
||||||
let mut spawn_actions = Vec::new();
|
let mut spawn_actions = Vec::new();
|
||||||
for bind in binds.iter().filter(|bind| {
|
for bind in binds.iter().filter(|bind| {
|
||||||
matches!(bind.actions.first(), Some(Action::Spawn(_)))
|
matches!(bind.action, Action::Spawn(_))
|
||||||
// Only show binds with Mod or Super to filter out stuff like volume up/down.
|
// Only show binds with Mod or Super to filter out stuff like volume up/down.
|
||||||
&& (bind.key.modifiers.contains(Modifiers::COMPOSITOR)
|
&& (bind.key.modifiers.contains(Modifiers::COMPOSITOR)
|
||||||
|| bind.key.modifiers.contains(Modifiers::SUPER))
|
|| bind.key.modifiers.contains(Modifiers::SUPER))
|
||||||
}) {
|
}) {
|
||||||
let action = bind.actions.first().unwrap();
|
let action = &bind.action;
|
||||||
|
|
||||||
// We only show one bind for each action, so we need to deduplicate the Spawn actions.
|
// We only show one bind for each action, so we need to deduplicate the Spawn actions.
|
||||||
if !spawn_actions.contains(&action) {
|
if !spawn_actions.contains(&action) {
|
||||||
@@ -252,7 +243,7 @@ fn render(config: &Config, comp_mod: CompositorMod, scale: i32) -> anyhow::Resul
|
|||||||
.binds
|
.binds
|
||||||
.0
|
.0
|
||||||
.iter()
|
.iter()
|
||||||
.find(|bind| bind.actions.first() == Some(action))
|
.find(|bind| bind.action == *action)
|
||||||
.map(|bind| key_name(comp_mod, &bind.key))
|
.map(|bind| key_name(comp_mod, &bind.key))
|
||||||
.unwrap_or_else(|| String::from("(not bound)"));
|
.unwrap_or_else(|| String::from("(not bound)"));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user