toml-config: add named actions
This commit is contained in:
parent
8552c5f1eb
commit
3100773ae0
15 changed files with 587 additions and 4 deletions
|
|
@ -18,6 +18,7 @@ phf = { version = "0.11.2", features = ["macros"] }
|
|||
indexmap = "2.2.5"
|
||||
bstr = { version = "1.9.1", default-features = false }
|
||||
ahash = "0.8.11"
|
||||
run-on-drop = "1.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
simplelog = { version = "0.12.2", features = ["test"] }
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ use {
|
|||
std::{
|
||||
error::Error,
|
||||
fmt::{Display, Formatter},
|
||||
rc::Rc,
|
||||
time::Duration,
|
||||
},
|
||||
thiserror::Error,
|
||||
|
|
@ -64,6 +65,7 @@ pub enum SimpleCommand {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[expect(clippy::enum_variant_names)]
|
||||
pub enum Action {
|
||||
ConfigureConnector {
|
||||
con: ConfigConnector,
|
||||
|
|
@ -133,6 +135,16 @@ pub enum Action {
|
|||
SetRepeatRate {
|
||||
rate: RepeatRate,
|
||||
},
|
||||
DefineAction {
|
||||
name: String,
|
||||
action: Box<Action>,
|
||||
},
|
||||
UndefineAction {
|
||||
name: String,
|
||||
},
|
||||
NamedAction {
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
|
@ -338,6 +350,12 @@ pub struct Shortcut {
|
|||
pub latch: Option<Action>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NamedAction {
|
||||
pub name: Rc<String>,
|
||||
pub action: Action,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
pub keymap: Option<ConfigKeymap>,
|
||||
|
|
@ -371,6 +389,8 @@ pub struct Config {
|
|||
pub xwayland: Option<Xwayland>,
|
||||
pub color_management: Option<ColorManagement>,
|
||||
pub float: Option<Float>,
|
||||
pub named_actions: Vec<NamedAction>,
|
||||
pub max_action_depth: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use {
|
|||
};
|
||||
|
||||
pub mod action;
|
||||
mod actions;
|
||||
mod color;
|
||||
pub mod color_management;
|
||||
pub mod config;
|
||||
|
|
|
|||
|
|
@ -87,6 +87,11 @@ pub struct ActionParser<'a>(pub &'a Context<'a>);
|
|||
impl ActionParser<'_> {
|
||||
fn parse_simple_cmd(&self, span: Span, string: &str) -> ParseResult<Self> {
|
||||
use {crate::config::SimpleCommand::*, jay_config::Direction::*};
|
||||
if let Some(name) = string.strip_prefix("$") {
|
||||
return Ok(Action::NamedAction {
|
||||
name: name.to_string(),
|
||||
});
|
||||
}
|
||||
let cmd = match string {
|
||||
"focus-left" => Focus(Left),
|
||||
"focus-down" => Focus(Down),
|
||||
|
|
@ -323,6 +328,28 @@ impl ActionParser<'_> {
|
|||
.map_spanned_err(ActionParserError::RepeatRate)?;
|
||||
Ok(Action::SetRepeatRate { rate })
|
||||
}
|
||||
|
||||
fn parse_undefine_action(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let (name,) = ext.extract((str("name"),))?;
|
||||
Ok(Action::UndefineAction {
|
||||
name: name.value.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_define_action(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let (name, action) = ext.extract((str("name"), val("action")))?;
|
||||
Ok(Action::DefineAction {
|
||||
name: name.value.to_string(),
|
||||
action: Box::new(action.parse(&mut ActionParser(self.0))?),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_named_action(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let (name,) = ext.extract((str("name"),))?;
|
||||
Ok(Action::NamedAction {
|
||||
name: name.value.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser for ActionParser<'_> {
|
||||
|
|
@ -374,6 +401,9 @@ impl Parser for ActionParser<'_> {
|
|||
"configure-idle" => self.parse_configure_idle(&mut ext),
|
||||
"move-to-output" => self.parse_move_to_output(&mut ext),
|
||||
"set-repeat-rate" => self.parse_set_repeat_rate(&mut ext),
|
||||
"define-action" => self.parse_define_action(&mut ext),
|
||||
"undefine-action" => self.parse_undefine_action(&mut ext),
|
||||
"named" => self.parse_named_action(&mut ext),
|
||||
v => {
|
||||
ext.ignore_unused();
|
||||
return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span));
|
||||
|
|
|
|||
78
toml-config/src/config/parsers/actions.rs
Normal file
78
toml-config/src/config/parsers/actions.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
Action, NamedAction,
|
||||
context::Context,
|
||||
extractor::ExtractorError,
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::action::ActionParser,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
std::{collections::HashSet, rc::Rc},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ActionsParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
ExtractorError(#[from] ExtractorError),
|
||||
}
|
||||
|
||||
pub struct ActionsParser<'a, 'b> {
|
||||
pub cx: &'a Context<'a>,
|
||||
pub used_names: HashSet<Spanned<Rc<String>>>,
|
||||
pub actions: &'b mut Vec<NamedAction>,
|
||||
}
|
||||
|
||||
impl Parser for ActionsParser<'_, '_> {
|
||||
type Value = ();
|
||||
type Error = ActionsParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
_span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
for (name, value) in table.iter() {
|
||||
let Some(action) = parse_action(self.cx, &name.value, value) else {
|
||||
continue;
|
||||
};
|
||||
let name = Rc::new(name.value.clone()).spanned(name.span);
|
||||
log_used(self.cx, &mut self.used_names, name.clone());
|
||||
self.actions.push(NamedAction {
|
||||
name: name.value,
|
||||
action,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_action(cx: &Context<'_>, name: &str, value: &Spanned<Value>) -> Option<Action> {
|
||||
match value.parse(&mut ActionParser(cx)) {
|
||||
Ok(a) => Some(a),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse action for name {name}: {}", cx.error(e));
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn log_used(cx: &Context<'_>, used: &mut HashSet<Spanned<Rc<String>>>, key: Spanned<Rc<String>>) {
|
||||
if let Some(prev) = used.get(&key) {
|
||||
log::warn!(
|
||||
"Duplicate actions overrides previous definition: {}",
|
||||
cx.error3(key.span)
|
||||
);
|
||||
log::info!("Previous definition here: {}", cx.error3(prev.span));
|
||||
}
|
||||
used.insert(key);
|
||||
}
|
||||
|
|
@ -3,10 +3,11 @@ use {
|
|||
config::{
|
||||
Action, Config, Libei, Theme, UiDrag,
|
||||
context::Context,
|
||||
extractor::{Extractor, ExtractorError, arr, bol, opt, recover, str, val},
|
||||
extractor::{Extractor, ExtractorError, arr, bol, int, opt, recover, str, val},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::{
|
||||
action::ActionParser,
|
||||
actions::ActionsParser,
|
||||
color_management::ColorManagementParser,
|
||||
connector::ConnectorsParser,
|
||||
drm_device::DrmDevicesParser,
|
||||
|
|
@ -119,7 +120,7 @@ impl Parser for ConfigParser<'_> {
|
|||
ui_drag_val,
|
||||
xwayland_val,
|
||||
),
|
||||
(color_management_val, float_val),
|
||||
(color_management_val, float_val, actions_val, max_action_depth_val),
|
||||
) = ext.extract((
|
||||
(
|
||||
opt(val("keymap")),
|
||||
|
|
@ -157,7 +158,12 @@ impl Parser for ConfigParser<'_> {
|
|||
opt(val("ui-drag")),
|
||||
opt(val("xwayland")),
|
||||
),
|
||||
(opt(val("color-management")), opt(val("float"))),
|
||||
(
|
||||
opt(val("color-management")),
|
||||
opt(val("float")),
|
||||
opt(val("actions")),
|
||||
recover(opt(int("max-action-depth"))),
|
||||
),
|
||||
))?;
|
||||
let mut keymap = None;
|
||||
if let Some(value) = keymap_val {
|
||||
|
|
@ -391,6 +397,28 @@ impl Parser for ConfigParser<'_> {
|
|||
}
|
||||
}
|
||||
}
|
||||
let mut named_actions = vec![];
|
||||
if let Some(value) = actions_val {
|
||||
let mut parser = ActionsParser {
|
||||
cx: self.0,
|
||||
used_names: Default::default(),
|
||||
actions: &mut named_actions,
|
||||
};
|
||||
if let Err(e) = value.parse(&mut parser) {
|
||||
log::warn!("Could not parse named actions: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
let mut max_action_depth = 16;
|
||||
if let Some(mut value) = max_action_depth_val {
|
||||
if value.value < 0 {
|
||||
log::warn!(
|
||||
"Max action depth should not be negative: {}",
|
||||
self.0.error3(value.span)
|
||||
);
|
||||
value.value = 0;
|
||||
}
|
||||
max_action_depth = value.value as _;
|
||||
}
|
||||
Ok(Config {
|
||||
keymap,
|
||||
repeat_rate,
|
||||
|
|
@ -423,6 +451,8 @@ impl Parser for ConfigParser<'_> {
|
|||
xwayland,
|
||||
color_management,
|
||||
float,
|
||||
named_actions,
|
||||
max_action_depth,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,14 @@ use {
|
|||
},
|
||||
xwayland::set_x_scaling_mode,
|
||||
},
|
||||
std::{cell::RefCell, io::ErrorKind, path::PathBuf, rc::Rc, time::Duration},
|
||||
run_on_drop::on_drop,
|
||||
std::{
|
||||
cell::{Cell, RefCell},
|
||||
io::ErrorKind,
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
time::Duration,
|
||||
},
|
||||
};
|
||||
|
||||
fn default_seat() -> Seat {
|
||||
|
|
@ -225,6 +232,40 @@ impl Action {
|
|||
Action::SetRepeatRate { rate } => {
|
||||
B::new(move || s.set_repeat_rate(rate.rate, rate.delay))
|
||||
}
|
||||
Action::DefineAction { name, action } => {
|
||||
let state = state.clone();
|
||||
let action = action.into_rc_fn(&state);
|
||||
let name = Rc::new(name);
|
||||
B::new(move || {
|
||||
state
|
||||
.actions
|
||||
.borrow_mut()
|
||||
.insert(name.clone(), action.clone());
|
||||
})
|
||||
}
|
||||
Action::UndefineAction { name } => {
|
||||
let state = state.clone();
|
||||
B::new(move || {
|
||||
state.actions.borrow_mut().remove(&name);
|
||||
})
|
||||
}
|
||||
Action::NamedAction { name } => {
|
||||
let state = state.clone();
|
||||
B::new(move || {
|
||||
let depth = state.action_depth.get();
|
||||
if depth >= state.action_depth_max {
|
||||
log::error!("Maximum action depth reached");
|
||||
return;
|
||||
}
|
||||
state.action_depth.set(depth + 1);
|
||||
let _reset = on_drop(|| state.action_depth.set(depth));
|
||||
let Some(action) = state.actions.borrow().get(&name).cloned() else {
|
||||
log::error!("There is no action named {name}");
|
||||
return;
|
||||
};
|
||||
action();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -607,6 +648,7 @@ impl Output {
|
|||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::type_complexity)]
|
||||
struct State {
|
||||
outputs: AHashMap<String, OutputMatch>,
|
||||
drm_devices: AHashMap<String, DrmDeviceMatch>,
|
||||
|
|
@ -617,6 +659,10 @@ struct State {
|
|||
io_maps: Vec<(InputMatch, OutputMatch)>,
|
||||
io_inputs: RefCell<AHashMap<InputDevice, Vec<bool>>>,
|
||||
io_outputs: RefCell<AHashMap<Connector, Vec<bool>>>,
|
||||
|
||||
action_depth_max: u64,
|
||||
action_depth: Cell<u64>,
|
||||
actions: RefCell<AHashMap<Rc<String>, Rc<dyn Fn()>>>,
|
||||
}
|
||||
|
||||
impl Drop for State {
|
||||
|
|
@ -914,8 +960,15 @@ fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
|||
io_maps,
|
||||
io_inputs: Default::default(),
|
||||
io_outputs: Default::default(),
|
||||
action_depth_max: config.max_action_depth,
|
||||
action_depth: Cell::new(0),
|
||||
actions: Default::default(),
|
||||
});
|
||||
state.set_status(&config.status);
|
||||
for a in config.named_actions {
|
||||
let action = a.action.into_rc_fn(&state);
|
||||
state.actions.borrow_mut().insert(a.name, action);
|
||||
}
|
||||
let mut switch_actions = vec![];
|
||||
for input in &mut config.inputs {
|
||||
let mut actions = AHashMap::new();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue