toolbar: --state, --set and --run for the Noctalia plugin

This commit is contained in:
elwrcl committed 2026-10-04 05:22:38 +03:00
1 parent 3d7a4de10a
commit c8626dbe3e
2 files changed
+144 -10

No files matched your search

+42 -3
View File
@@ -84,7 +84,17 @@ enum Command {
file: PathBuf, file: PathBuf,
}, },
/// Pick a mode from a floating bar (Cmd+Shift+5). /// Pick a mode from a floating bar (Cmd+Shift+5).
Toolbar, Toolbar {
/// Print the remembered choices as one line of JSON (for the Noctalia plugin).
#[arg(long, conflicts_with_all = ["set", "run"])]
state: bool,
/// Change a remembered choice: mode, timer, cursor, preview or sound.
#[arg(long, value_name = "KEY=VALUE", conflicts_with = "run")]
set: Vec<String>,
/// Capture at once with the remembered choices, without the bar.
#[arg(long, value_name = "MODE", num_args = 0..=1)]
run: Option<Option<toolbar::state::Mode>>,
},
/// The paint shader behind niri's overview (keep it running; see the README). /// The paint shader behind niri's overview (keep it running; see the README).
Backdrop, Backdrop,
/// Report what the compositor and system support. /// Report what the compositor and system support.
@@ -218,9 +228,24 @@ fn run(cli: Cli) -> Result<()> {
// Handled in main, before logging starts. // Handled in main, before logging starts.
Command::CheckConfig { file } => config::check(&file), Command::CheckConfig { file } => config::check(&file),
Command::ComboDemo => sound::demo(&config::load(&config::default_path())?.sound), Command::ComboDemo => sound::demo(&config::load(&config::default_path())?.sound),
Command::Toolbar => { Command::Toolbar { state, set, run } => {
let config = config::load(&config::default_path())?; let config = config::load(&config::default_path())?;
let (mode, common) = from_toolbar(toolbar::run(&config)?); let path = toolbar::state::default_path();
if state {
println!(
"{}",
toolbar::state::to_json(&toolbar::state::load(&path, &config))
);
return Ok(());
}
if !set.is_empty() {
return toolbar::state::set(&path, &config, &set);
}
let picked = match run {
Some(mode) => toolbar::run_remembered(&config, mode)?,
None => toolbar::run(&config)?,
};
let (mode, common) = from_toolbar(picked);
capture(mode, common) capture(mode, common)
} }
Command::Clipboard => { Command::Clipboard => {
@@ -473,6 +498,20 @@ mod tests {
assert!(!parses(&["edit"])); assert!(!parses(&["edit"]));
assert!(parses(&["__clipboard"])); assert!(parses(&["__clipboard"]));
assert!(parses(&["toolbar"])); assert!(parses(&["toolbar"]));
assert!(parses(&["toolbar", "--state"]));
assert!(parses(&[
"toolbar",
"--set",
"timer=5",
"--set",
"sound=false"
]));
assert!(parses(&["toolbar", "--run"]));
assert!(parses(&["toolbar", "--run", "zoom"]));
assert!(!parses(&["toolbar", "--run", "video"]));
assert!(!parses(&["toolbar", "--state", "--run"]));
assert!(!parses(&["toolbar", "--state", "--set", "timer=5"]));
assert!(!parses(&["toolbar", "--set", "timer=5", "--run", "zoom"]));
assert!(parses(&["backdrop"])); assert!(parses(&["backdrop"]));
assert!(parses(&["window", "--pick"])); assert!(parses(&["window", "--pick"]));
assert!(parses(&["__combo-demo"])); assert!(parses(&["__combo-demo"]));
+102 -7
View File
@@ -5,6 +5,7 @@ pub mod draw;
pub mod layout; pub mod layout;
pub mod state; pub mod state;
use std::path::Path;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
@@ -44,6 +45,17 @@ pub struct Picked {
pub sound: bool, pub sound: bool,
} }
impl From<ToolbarState> for Picked {
fn from(s: ToolbarState) -> Self {
Picked {
mode: s.mode,
cursor: s.cursor,
preview: s.preview,
sound: s.sound,
}
}
}
/// Shows the bar, waits for a pick, saves it, runs the countdown. /// Shows the bar, waits for a pick, saves it, runs the countdown.
pub fn run(config: &Config) -> Result<Picked> { pub fn run(config: &Config) -> Result<Picked> {
let path = state::default_path(); let path = state::default_path();
@@ -58,13 +70,33 @@ pub fn run(config: &Config) -> Result<Picked> {
tracing::warn!("could not remember the toolbar choice: {e:#}"); tracing::warn!("could not remember the toolbar choice: {e:#}");
} }
countdown(&mut wl, &output, picked.0.timer)?; countdown(&mut wl, &output, picked.0.timer)?;
let s = picked.0; Ok(Picked::from(picked.0))
Ok(Picked { }
mode: picked.1,
cursor: s.cursor, /// The remembered state, with `mode` (if given) remembered as the new mode.
preview: s.preview, pub fn remember(path: &Path, config: &Config, mode: Option<Mode>) -> ToolbarState {
sound: s.sound, let mut s = state::load(path, config);
}) if let Some(mode) = mode {
s.mode = mode;
if let Err(e) = state::save(path, &s) {
tracing::warn!("could not remember the toolbar choice: {e:#}");
}
}
s
}
/// `valw toolbar --run`: the remembered options without the bar (for the
/// Noctalia plugin), then the countdown.
pub fn run_remembered(config: &Config, mode: Option<Mode>) -> Result<Picked> {
let s = remember(&state::default_path(), config, mode);
if s.timer > 0 {
let mut wl = Wayland::connect()?;
let outputs = wl.outputs();
anyhow::ensure!(!outputs.is_empty(), "the compositor reported no outputs");
let output = outputs[crate::focused_output(&outputs)].clone();
countdown(&mut wl, &output, s.timer)?;
}
Ok(Picked::from(s))
} }
pub struct Toolbar { pub struct Toolbar {
@@ -388,3 +420,66 @@ impl Pill {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_pick_carries_the_state_options() {
let state = ToolbarState {
mode: Mode::Window,
timer: 5,
cursor: true,
preview: false,
sound: true,
};
assert_eq!(
Picked::from(state),
Picked {
mode: Mode::Window,
cursor: true,
preview: false,
sound: true,
}
);
}
#[test]
fn run_remembers_a_given_mode() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("toolbar.toml");
let config = Config::default();
state::save(
&path,
&ToolbarState {
timer: 5,
..state::defaults(&config)
},
)
.unwrap();
let state = remember(&path, &config, Some(Mode::Zoom));
assert_eq!(
(state.mode, state.timer),
(Mode::Zoom, 5),
"the options stay"
);
assert_eq!(state::load(&path, &config).mode, Mode::Zoom, "saved");
assert_eq!(
remember(&path, &config, None).mode,
Mode::Zoom,
"no mode: the remembered one"
);
}
#[test]
fn run_without_a_mode_writes_nothing() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("toolbar.toml");
let config = Config::default();
assert_eq!(remember(&path, &config, None), state::defaults(&config));
assert!(!path.exists());
}
}