feat: implement declarative scratchpads
This commit is contained in:
parent
d756c8a6a2
commit
b6502e1d8a
17 changed files with 549 additions and 78 deletions
|
|
@ -66,6 +66,7 @@ pub enum SimpleCommand {
|
|||
SetFullscreen(bool),
|
||||
SendToScratchpad,
|
||||
ToggleScratchpad,
|
||||
CycleScratchpad,
|
||||
Forward(bool),
|
||||
EnableWindowManagement(bool),
|
||||
SetFloatAboveFullscreen(bool),
|
||||
|
|
@ -138,6 +139,9 @@ pub enum Action {
|
|||
ToggleScratchpad {
|
||||
name: String,
|
||||
},
|
||||
CycleScratchpad {
|
||||
name: String,
|
||||
},
|
||||
Multi {
|
||||
actions: Vec<Action>,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ pub mod modified_keysym;
|
|||
mod output;
|
||||
mod output_match;
|
||||
mod repeat_rate;
|
||||
mod scratchpad;
|
||||
pub mod shortcuts;
|
||||
mod simple_im;
|
||||
mod status;
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ impl ActionParser<'_> {
|
|||
"exit-fullscreen" => SetFullscreen(false),
|
||||
"send-to-scratchpad" => SendToScratchpad,
|
||||
"toggle-scratchpad" => ToggleScratchpad,
|
||||
"cycle-scratchpad" => CycleScratchpad,
|
||||
"focus-parent" => FocusParent,
|
||||
"close" => Close,
|
||||
"disable-pointer-constraint" => DisablePointerConstraint,
|
||||
|
|
@ -242,6 +243,15 @@ impl ActionParser<'_> {
|
|||
Ok(Action::ToggleScratchpad { name })
|
||||
}
|
||||
|
||||
fn parse_cycle_scratchpad(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let name = ext
|
||||
.extract(opt(str("name")))?
|
||||
.map(|name| name.value)
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
Ok(Action::CycleScratchpad { name })
|
||||
}
|
||||
|
||||
fn parse_configure_connector(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let con = ext
|
||||
.extract(val("connector"))?
|
||||
|
|
@ -573,6 +583,7 @@ impl Parser for ActionParser<'_> {
|
|||
"move-to-workspace" => self.parse_move_to_workspace(&mut ext),
|
||||
"send-to-scratchpad" => self.parse_send_to_scratchpad(&mut ext),
|
||||
"toggle-scratchpad" => self.parse_toggle_scratchpad(&mut ext),
|
||||
"cycle-scratchpad" => self.parse_cycle_scratchpad(&mut ext),
|
||||
"configure-connector" => self.parse_configure_connector(&mut ext),
|
||||
"configure-input" => self.parse_configure_input(&mut ext),
|
||||
"configure-output" => self.parse_configure_output(&mut ext),
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use {
|
|||
log_level::LogLevelParser,
|
||||
output::OutputsParser,
|
||||
repeat_rate::RepeatRateParser,
|
||||
scratchpad::ScratchpadsParser,
|
||||
shortcuts::{
|
||||
ComplexShortcutsParser, ShortcutsParser, ShortcutsParserError,
|
||||
parse_modified_keysym_str,
|
||||
|
|
@ -570,6 +571,13 @@ impl Parser for ConfigParser<'_> {
|
|||
}
|
||||
}
|
||||
}
|
||||
let mut scratchpads = vec![];
|
||||
if let Some(value) = scratchpads_val {
|
||||
match value.parse(&mut ScratchpadsParser(self.0)) {
|
||||
Ok(v) => scratchpads = v,
|
||||
Err(e) => log::warn!("Could not parse the scratchpads: {}", self.0.error(e)),
|
||||
}
|
||||
}
|
||||
Ok(Config {
|
||||
keymap,
|
||||
repeat_rate,
|
||||
|
|
|
|||
87
toml-config/src/config/parsers/scratchpad.rs
Normal file
87
toml-config/src/config/parsers/scratchpad.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
Scratchpad,
|
||||
context::Context,
|
||||
extractor::{Extractor, ExtractorError, opt, str, val},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::exec::{ExecParser, ExecParserError},
|
||||
},
|
||||
toml::{
|
||||
toml_span::{Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ScratchpadParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
#[error(transparent)]
|
||||
Exec(#[from] ExecParserError),
|
||||
}
|
||||
|
||||
pub struct ScratchpadParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for ScratchpadParser<'_> {
|
||||
type Value = Scratchpad;
|
||||
type Error = ScratchpadParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (name, exec_val) = ext.extract((str("name"), opt(val("exec"))))?;
|
||||
let exec = match exec_val {
|
||||
None => None,
|
||||
Some(e) => Some(e.parse_map(&mut ExecParser(self.0))?),
|
||||
};
|
||||
Ok(Scratchpad {
|
||||
name: name.value.to_string(),
|
||||
exec,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScratchpadsParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for ScratchpadsParser<'_> {
|
||||
type Value = Vec<Scratchpad>;
|
||||
type Error = ScratchpadParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||
|
||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut res = vec![];
|
||||
for el in array {
|
||||
match el.parse(&mut ScratchpadParser(self.0)) {
|
||||
Ok(o) => res.push(o),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse scratchpad: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
log::warn!(
|
||||
"`scratchpads` value should be an array: {}",
|
||||
self.0.error3(span)
|
||||
);
|
||||
ScratchpadParser(self.0)
|
||||
.parse_table(span, table)
|
||||
.map(|v| vec![v])
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ use {
|
|||
config::{
|
||||
Action, AnimationCurveConfig, ClientRule, Config, ConfigConnector, ConfigDrmDevice,
|
||||
ConfigKeymap, ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output,
|
||||
OutputMatch, SimpleCommand, Status, Theme, WindowRule, parse_config,
|
||||
OutputMatch, SimpleCommand, Status, Theme, WindowMatch, WindowRule, parse_config,
|
||||
},
|
||||
rules::{MatcherTemp, RuleMapper},
|
||||
shortcuts::ModeState,
|
||||
|
|
@ -175,6 +175,7 @@ impl Action {
|
|||
SimpleCommand::SetFullscreen(b) => window_or_seat!(s, s.set_fullscreen(b)),
|
||||
SimpleCommand::SendToScratchpad => window_or_seat!(s, s.send_to_scratchpad("")),
|
||||
SimpleCommand::ToggleScratchpad => b.new(move || s.toggle_scratchpad("")),
|
||||
SimpleCommand::CycleScratchpad => b.new(move || s.cycle_scratchpad("")),
|
||||
SimpleCommand::FocusParent => b.new(move || s.focus_parent()),
|
||||
SimpleCommand::Close => window_or_seat!(s, s.close()),
|
||||
SimpleCommand::DisablePointerConstraint => {
|
||||
|
|
@ -310,6 +311,7 @@ impl Action {
|
|||
}
|
||||
Action::SendToScratchpad { name } => window_or_seat!(s, s.send_to_scratchpad(&name)),
|
||||
Action::ToggleScratchpad { name } => b.new(move || s.toggle_scratchpad(&name)),
|
||||
Action::CycleScratchpad { name } => b.new(move || s.cycle_scratchpad(&name)),
|
||||
Action::ConfigureConnector { con } => b.new(move || {
|
||||
for c in connectors() {
|
||||
if con.match_.matches(c) {
|
||||
|
|
@ -1461,6 +1463,43 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
|
|||
window: Default::default(),
|
||||
});
|
||||
state.clear_modes_after_reload();
|
||||
// Desugar `[[scratchpads]]` into spawn-on-graphics-init plus an internal
|
||||
// window rule that parks the spawned window. Each spawned process gets a
|
||||
// unique tag so only its own windows are captured, never other windows of
|
||||
// the same application.
|
||||
if !config.scratchpads.is_empty() {
|
||||
let mut spawn_actions = vec![];
|
||||
for (i, sp) in config.scratchpads.drain(..).enumerate() {
|
||||
let Some(mut exec) = sp.exec else {
|
||||
continue;
|
||||
};
|
||||
let tag = exec
|
||||
.tag
|
||||
.clone()
|
||||
.unwrap_or_else(|| format!("__scratchpad.{i}.{}", sp.name));
|
||||
exec.tag = Some(tag.clone());
|
||||
spawn_actions.push(Action::Exec { exec });
|
||||
config.window_rules.push(WindowRule {
|
||||
name: None,
|
||||
match_: WindowMatch {
|
||||
tag: Some(tag),
|
||||
..Default::default()
|
||||
},
|
||||
action: Some(Action::SendToScratchpad { name: sp.name }),
|
||||
latch: None,
|
||||
auto_focus: None,
|
||||
initial_tile_state: None,
|
||||
});
|
||||
}
|
||||
if !spawn_actions.is_empty() {
|
||||
let mut actions = Vec::with_capacity(spawn_actions.len() + 1);
|
||||
if let Some(existing) = config.on_graphics_initialized.take() {
|
||||
actions.push(existing);
|
||||
}
|
||||
actions.extend(spawn_actions);
|
||||
config.on_graphics_initialized = Some(Action::Multi { actions });
|
||||
}
|
||||
}
|
||||
let (client_rules, client_rule_mapper) = state.create_rules(&config.client_rules);
|
||||
persistent.client_rules.set(client_rules);
|
||||
*state.persistent.client_rule_mapper.borrow_mut() = Some(client_rule_mapper);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue