feat: combo shutter sound
This commit is contained in:
1 parent
7068b7d28b
commit
2e839398b2
8 files changed
+181
-7
No files matched your search
+6
-1
@@ -38,7 +38,12 @@
|
|||||||
# Clicking a preview opens Satty if configured. --suffix keeps a user's own Satty first.
|
# Clicking a preview opens Satty if configured. --suffix keeps a user's own Satty first.
|
||||||
# The editor embeds DejaVu Sans Bold; its licence wants the notice shipped.
|
# The editor embeds DejaVu Sans Bold; its licence wants the notice shipped.
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
wrapProgram $out/bin/valw --suffix PATH : ${lib.makeBinPath [ pkgs.satty ]}
|
wrapProgram $out/bin/valw --suffix PATH : ${
|
||||||
|
lib.makeBinPath [
|
||||||
|
pkgs.satty
|
||||||
|
pkgs.pipewire
|
||||||
|
]
|
||||||
|
}
|
||||||
install -Dm644 assets/fonts/LICENSE-DejaVu $out/share/licenses/valw/LICENSE-DejaVu
|
install -Dm644 assets/fonts/LICENSE-DejaVu $out/share/licenses/valw/LICENSE-DejaVu
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub struct Config {
|
|||||||
pub preview: Preview,
|
pub preview: Preview,
|
||||||
pub zoom: Zoom,
|
pub zoom: Zoom,
|
||||||
pub editor: Editor,
|
pub editor: Editor,
|
||||||
|
pub sound: Sound,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
@@ -69,6 +70,26 @@ pub enum Backend {
|
|||||||
Satty,
|
Satty,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct Sound {
|
||||||
|
pub enabled: bool,
|
||||||
|
/// 0.0–1.0.
|
||||||
|
pub volume: f32,
|
||||||
|
/// A pause this long starts the combo over.
|
||||||
|
pub combo_reset_secs: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Sound {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: true,
|
||||||
|
volume: 0.6,
|
||||||
|
combo_reset_secs: 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Preview {
|
impl Default for Preview {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -139,6 +160,12 @@ pub fn parse(text: &str) -> Result<(Config, Vec<String>)> {
|
|||||||
if !(20.0..=2000.0).contains(&config.zoom.flashlight_radius) {
|
if !(20.0..=2000.0).contains(&config.zoom.flashlight_radius) {
|
||||||
bail!("zoom.flashlight_radius must be between 20 and 2000");
|
bail!("zoom.flashlight_radius must be between 20 and 2000");
|
||||||
}
|
}
|
||||||
|
if !(0.0..=1.0).contains(&config.sound.volume) {
|
||||||
|
bail!("sound.volume must be between 0 and 1");
|
||||||
|
}
|
||||||
|
if !(1..=60).contains(&config.sound.combo_reset_secs) {
|
||||||
|
bail!("sound.combo_reset_secs must be between 1 and 60");
|
||||||
|
}
|
||||||
Ok((config, unknown))
|
Ok((config, unknown))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,6 +274,26 @@ mod tests {
|
|||||||
assert!(parse("[editor]\nbackend = \"gimp\"\n").is_err());
|
assert!(parse("[editor]\nbackend = \"gimp\"\n").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sound_defaults_and_bounds() {
|
||||||
|
let s = Config::default().sound;
|
||||||
|
assert!(s.enabled);
|
||||||
|
assert_eq!((s.volume, s.combo_reset_secs), (0.6, 5));
|
||||||
|
for bad in ["volume = 1.5", "volume = nan", "volume = -0.1"] {
|
||||||
|
let err = parse(&format!("[sound]\n{bad}\n")).unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
err.to_string(),
|
||||||
|
"sound.volume must be between 0 and 1",
|
||||||
|
"{bad}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let err = parse("[sound]\ncombo_reset_secs = 0\n").unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
err.to_string(),
|
||||||
|
"sound.combo_reset_secs must be between 1 and 60"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_file_gives_defaults() {
|
fn missing_file_gives_defaults() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
|||||||
+22
-2
@@ -16,8 +16,6 @@ mod region;
|
|||||||
mod render;
|
mod render;
|
||||||
mod selection;
|
mod selection;
|
||||||
mod shadow;
|
mod shadow;
|
||||||
// The sound lands in two tasks; Task 5 of the polish plan removes this.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
mod sound;
|
mod sound;
|
||||||
mod stack;
|
mod stack;
|
||||||
mod thumbnail;
|
mod thumbnail;
|
||||||
@@ -86,6 +84,9 @@ enum Command {
|
|||||||
/// Internal: the process that shows preview thumbnails.
|
/// Internal: the process that shows preview thumbnails.
|
||||||
#[command(name = "__preview-host", hide = true)]
|
#[command(name = "__preview-host", hide = true)]
|
||||||
PreviewHost,
|
PreviewHost,
|
||||||
|
/// Internal: play the whole combo, for tuning the sounds by ear.
|
||||||
|
#[command(name = "__combo-demo", hide = true)]
|
||||||
|
ComboDemo,
|
||||||
/// Internal: serve a PNG from stdin as the clipboard (the editor's copy).
|
/// Internal: serve a PNG from stdin as the clipboard (the editor's copy).
|
||||||
#[command(name = "__clipboard", hide = true)]
|
#[command(name = "__clipboard", hide = true)]
|
||||||
Clipboard,
|
Clipboard,
|
||||||
@@ -114,6 +115,8 @@ struct Common {
|
|||||||
toolbar_cursor: Option<bool>,
|
toolbar_cursor: Option<bool>,
|
||||||
#[arg(skip)]
|
#[arg(skip)]
|
||||||
toolbar_preview: Option<bool>,
|
toolbar_preview: Option<bool>,
|
||||||
|
#[arg(skip)]
|
||||||
|
toolbar_sound: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the cursor goes into the shot.
|
/// Whether the cursor goes into the shot.
|
||||||
@@ -123,6 +126,11 @@ fn wants_cursor(common: &Common, config: &Config) -> bool {
|
|||||||
.unwrap_or(common.cursor || config.capture.show_cursor)
|
.unwrap_or(common.cursor || config.capture.show_cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the shutter sound plays.
|
||||||
|
fn wants_sound(common: &Common, config: &Config) -> bool {
|
||||||
|
common.toolbar_sound.unwrap_or(config.sound.enabled)
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the preview thumbnail is shown.
|
/// Whether the preview thumbnail is shown.
|
||||||
fn wants_preview(common: &Common, config: &Config) -> bool {
|
fn wants_preview(common: &Common, config: &Config) -> bool {
|
||||||
common
|
common
|
||||||
@@ -180,6 +188,7 @@ fn run(cli: Cli) -> Result<()> {
|
|||||||
Command::Window { common } => capture(Mode::Window, common),
|
Command::Window { common } => capture(Mode::Window, common),
|
||||||
Command::Zoom { common } => capture(Mode::Zoom, common),
|
Command::Zoom { common } => capture(Mode::Zoom, common),
|
||||||
Command::Edit { file } => editor::run(&file),
|
Command::Edit { file } => editor::run(&file),
|
||||||
|
Command::ComboDemo => sound::demo(&config::load(&config::default_path())?.sound),
|
||||||
Command::Toolbar => {
|
Command::Toolbar => {
|
||||||
let config = config::load(&config::default_path())?;
|
let config = config::load(&config::default_path())?;
|
||||||
let (mode, common) = from_toolbar(toolbar::run(&config)?);
|
let (mode, common) = from_toolbar(toolbar::run(&config)?);
|
||||||
@@ -202,6 +211,7 @@ fn capture(mode: Mode, common: Common) -> Result<()> {
|
|||||||
}
|
}
|
||||||
let cursor = wants_cursor(&common, &config);
|
let cursor = wants_cursor(&common, &config);
|
||||||
let preview = wants_preview(&common, &config);
|
let preview = wants_preview(&common, &config);
|
||||||
|
let sound = wants_sound(&common, &config);
|
||||||
let mut wl = Wayland::connect()?;
|
let mut wl = Wayland::connect()?;
|
||||||
if let Ok(version) = niri::version() {
|
if let Ok(version) = niri::version() {
|
||||||
tracing::info!("niri {version}");
|
tracing::info!("niri {version}");
|
||||||
@@ -256,6 +266,7 @@ fn capture(mode: Mode, common: Common) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
sound::play(&config.sound, sound);
|
||||||
drop(wl);
|
drop(wl);
|
||||||
|
|
||||||
let target = Target::from_args(common.output, common.clipboard_only);
|
let target = Target::from_args(common.output, common.clipboard_only);
|
||||||
@@ -305,6 +316,7 @@ fn from_toolbar(pick: toolbar::Picked) -> (Mode, Common) {
|
|||||||
no_preview: false,
|
no_preview: false,
|
||||||
toolbar_cursor: Some(pick.cursor),
|
toolbar_cursor: Some(pick.cursor),
|
||||||
toolbar_preview: Some(pick.preview),
|
toolbar_preview: Some(pick.preview),
|
||||||
|
toolbar_sound: Some(pick.sound),
|
||||||
};
|
};
|
||||||
(mode, common)
|
(mode, common)
|
||||||
}
|
}
|
||||||
@@ -406,6 +418,7 @@ mod tests {
|
|||||||
assert!(!parses(&["edit"]));
|
assert!(!parses(&["edit"]));
|
||||||
assert!(parses(&["__clipboard"]));
|
assert!(parses(&["__clipboard"]));
|
||||||
assert!(parses(&["toolbar"]));
|
assert!(parses(&["toolbar"]));
|
||||||
|
assert!(parses(&["__combo-demo"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -415,6 +428,7 @@ mod tests {
|
|||||||
mode,
|
mode,
|
||||||
cursor,
|
cursor,
|
||||||
preview,
|
preview,
|
||||||
|
sound: true,
|
||||||
};
|
};
|
||||||
let (mode, common) = from_toolbar(pick(T::Screen, true, false));
|
let (mode, common) = from_toolbar(pick(T::Screen, true, false));
|
||||||
assert!(matches!(mode, Mode::Screen { all: false }));
|
assert!(matches!(mode, Mode::Screen { all: false }));
|
||||||
@@ -453,9 +467,14 @@ mod tests {
|
|||||||
mode: T::Screen,
|
mode: T::Screen,
|
||||||
cursor,
|
cursor,
|
||||||
preview,
|
preview,
|
||||||
|
sound: preview,
|
||||||
})
|
})
|
||||||
.1
|
.1
|
||||||
};
|
};
|
||||||
|
on.sound.enabled = true;
|
||||||
|
off.sound.enabled = false;
|
||||||
|
assert!(!wants_sound(&pick(false, false), &on));
|
||||||
|
assert!(wants_sound(&pick(true, true), &off));
|
||||||
assert!(
|
assert!(
|
||||||
!wants_cursor(&pick(false, false), &on),
|
!wants_cursor(&pick(false, false), &on),
|
||||||
"unticked beats the config"
|
"unticked beats the config"
|
||||||
@@ -500,6 +519,7 @@ mod tests {
|
|||||||
let help = Cli::command().render_help().to_string();
|
let help = Cli::command().render_help().to_string();
|
||||||
assert!(!help.contains("preview-host"), "{help}");
|
assert!(!help.contains("preview-host"), "{help}");
|
||||||
assert!(!help.contains("__clipboard"), "{help}");
|
assert!(!help.contains("__clipboard"), "{help}");
|
||||||
|
assert!(!help.contains("combo-demo"), "{help}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+88
-1
@@ -1,4 +1,91 @@
|
|||||||
//! The combo shutter sound.
|
//! The combo shutter sound, played with PipeWire's `pw-play`.
|
||||||
|
|
||||||
pub mod combo;
|
pub mod combo;
|
||||||
pub mod synth;
|
pub mod synth;
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
|
use crate::config;
|
||||||
|
|
||||||
|
fn runtime_dir() -> PathBuf {
|
||||||
|
crate::lock::default_path()
|
||||||
|
.parent()
|
||||||
|
.map(Path::to_path_buf)
|
||||||
|
.unwrap_or_else(std::env::temp_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The WAV for shot `n`, written into `dir` the first time.
|
||||||
|
fn cached(dir: &Path, n: u8) -> Result<PathBuf> {
|
||||||
|
let path = dir.join(format!("{n}.wav"));
|
||||||
|
if !path.exists() {
|
||||||
|
std::fs::create_dir_all(dir)
|
||||||
|
.with_context(|| format!("could not create {}", dir.display()))?;
|
||||||
|
crate::output::write_atomic(&path, &synth::wav(&synth::sound(n)))?;
|
||||||
|
}
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Plays the next sound of the combo, unless sounds are off. Never fails the
|
||||||
|
/// capture: problems are logged.
|
||||||
|
pub fn play(config: &config::Sound, enabled: bool) {
|
||||||
|
if !enabled {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let now = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.map(|d| d.as_millis() as u64)
|
||||||
|
.unwrap_or(0);
|
||||||
|
let state = runtime_dir().join("valw-combo");
|
||||||
|
let n = combo::next(combo::load(&state), now, config.combo_reset_secs * 1000);
|
||||||
|
if let Err(e) = combo::save(&state, n, now) {
|
||||||
|
tracing::warn!("could not remember the combo: {e:#}");
|
||||||
|
}
|
||||||
|
if let Err(e) = spawn(n, config.volume) {
|
||||||
|
tracing::warn!("no shutter sound: {e:#}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn(n: u8, volume: f32) -> Result<()> {
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
|
let path = cached(&runtime_dir().join("valw-sound"), n)?;
|
||||||
|
let mut command = std::process::Command::new("pw-play");
|
||||||
|
command
|
||||||
|
.arg(format!("--volume={volume}"))
|
||||||
|
.arg(&path)
|
||||||
|
.stdin(std::process::Stdio::null())
|
||||||
|
.stdout(std::process::Stdio::null())
|
||||||
|
.stderr(std::process::Stdio::null());
|
||||||
|
// SAFETY: setsid is async-signal-safe.
|
||||||
|
unsafe {
|
||||||
|
command.pre_exec(|| rustix::process::setsid().map(drop).map_err(Into::into));
|
||||||
|
}
|
||||||
|
command.spawn().context("could not start pw-play")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `valw __combo-demo`: the whole combo, for tuning by ear.
|
||||||
|
pub fn demo(config: &config::Sound) -> Result<()> {
|
||||||
|
for n in [1, 2, 3, 4, 5, 6, 7, 6, 7] {
|
||||||
|
spawn(n, config.volume)?;
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_cache_holds_seven_wavs() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
for n in 1..=7 {
|
||||||
|
let path = cached(dir.path(), n).unwrap();
|
||||||
|
assert!(path.ends_with(format!("{n}.wav")));
|
||||||
|
assert_eq!(&std::fs::read(&path).unwrap()[0..4], b"RIFF");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -113,6 +113,7 @@ pub fn bar(
|
|||||||
MenuItem::Timer(t) => state.timer == *t,
|
MenuItem::Timer(t) => state.timer == *t,
|
||||||
MenuItem::Cursor => state.cursor,
|
MenuItem::Cursor => state.cursor,
|
||||||
MenuItem::Preview => state.preview,
|
MenuItem::Preview => state.preview,
|
||||||
|
MenuItem::Sound => state.sound,
|
||||||
};
|
};
|
||||||
if on {
|
if on {
|
||||||
label(&mut p, *r, crate::toolbar::layout::BUTTON_PAD, "✓", scale);
|
label(&mut p, *r, crate::toolbar::layout::BUTTON_PAD, "✓", scale);
|
||||||
@@ -162,6 +163,7 @@ mod tests {
|
|||||||
timer: 0,
|
timer: 0,
|
||||||
cursor: false,
|
cursor: false,
|
||||||
preview: true,
|
preview: true,
|
||||||
|
sound: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn alpha(p: &Pixmap, x: f32, y: f32, scale: f32) -> u8 {
|
fn alpha(p: &Pixmap, x: f32, y: f32, scale: f32) -> u8 {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ pub enum MenuItem {
|
|||||||
Timer(u32),
|
Timer(u32),
|
||||||
Cursor,
|
Cursor,
|
||||||
Preview,
|
Preview,
|
||||||
|
Sound,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -61,12 +62,13 @@ const BAR: [Option<(Target, &str)>; 7] = [
|
|||||||
Some((Target::Options, "Options ▾")),
|
Some((Target::Options, "Options ▾")),
|
||||||
];
|
];
|
||||||
|
|
||||||
const MENU: [(MenuItem, &str); 5] = [
|
const MENU: [(MenuItem, &str); 6] = [
|
||||||
(MenuItem::Timer(0), "No timer"),
|
(MenuItem::Timer(0), "No timer"),
|
||||||
(MenuItem::Timer(5), "5 second timer"),
|
(MenuItem::Timer(5), "5 second timer"),
|
||||||
(MenuItem::Timer(10), "10 second timer"),
|
(MenuItem::Timer(10), "10 second timer"),
|
||||||
(MenuItem::Cursor, "Show cursor"),
|
(MenuItem::Cursor, "Show cursor"),
|
||||||
(MenuItem::Preview, "Show preview"),
|
(MenuItem::Preview, "Show preview"),
|
||||||
|
(MenuItem::Sound, "Play sound"),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Lays the toolbar out on an output of `output` logical px; `measure`
|
/// Lays the toolbar out on an output of `output` logical px; `measure`
|
||||||
@@ -240,7 +242,8 @@ mod tests {
|
|||||||
MenuItem::Timer(5),
|
MenuItem::Timer(5),
|
||||||
MenuItem::Timer(10),
|
MenuItem::Timer(10),
|
||||||
MenuItem::Cursor,
|
MenuItem::Cursor,
|
||||||
MenuItem::Preview
|
MenuItem::Preview,
|
||||||
|
MenuItem::Sound
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
let cursor = centre(l.rows[3].1);
|
let cursor = centre(l.rows[3].1);
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ pub struct Picked {
|
|||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
pub cursor: bool,
|
pub cursor: bool,
|
||||||
pub preview: bool,
|
pub preview: bool,
|
||||||
|
pub sound: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows the bar, waits for a pick, saves it, runs the countdown.
|
/// Shows the bar, waits for a pick, saves it, runs the countdown.
|
||||||
@@ -62,6 +63,7 @@ pub fn run(config: &Config) -> Result<Picked> {
|
|||||||
mode: picked.1,
|
mode: picked.1,
|
||||||
cursor: s.cursor,
|
cursor: s.cursor,
|
||||||
preview: s.preview,
|
preview: s.preview,
|
||||||
|
sound: s.sound,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,6 +229,7 @@ impl Toolbar {
|
|||||||
MenuItem::Timer(t) => self.state.timer = t,
|
MenuItem::Timer(t) => self.state.timer = t,
|
||||||
MenuItem::Cursor => self.state.cursor = !self.state.cursor,
|
MenuItem::Cursor => self.state.cursor = !self.state.cursor,
|
||||||
MenuItem::Preview => self.state.preview = !self.state.preview,
|
MenuItem::Preview => self.state.preview = !self.state.preview,
|
||||||
|
MenuItem::Sound => self.state.sound = !self.state.sound,
|
||||||
}
|
}
|
||||||
self.draw();
|
self.draw();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ pub struct ToolbarState {
|
|||||||
pub timer: u32,
|
pub timer: u32,
|
||||||
pub cursor: bool,
|
pub cursor: bool,
|
||||||
pub preview: bool,
|
pub preview: bool,
|
||||||
|
pub sound: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The file, every key optional.
|
/// The file, every key optional.
|
||||||
@@ -33,6 +34,7 @@ struct File {
|
|||||||
timer: Option<u32>,
|
timer: Option<u32>,
|
||||||
cursor: Option<bool>,
|
cursor: Option<bool>,
|
||||||
preview: Option<bool>,
|
preview: Option<bool>,
|
||||||
|
sound: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const TIMERS: [u32; 3] = [0, 5, 10];
|
pub const TIMERS: [u32; 3] = [0, 5, 10];
|
||||||
@@ -51,6 +53,7 @@ pub fn defaults(config: &Config) -> ToolbarState {
|
|||||||
timer: 0,
|
timer: 0,
|
||||||
cursor: config.capture.show_cursor,
|
cursor: config.capture.show_cursor,
|
||||||
preview: config.preview.enabled,
|
preview: config.preview.enabled,
|
||||||
|
sound: config.sound.enabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +88,7 @@ fn parse(text: &str, fallback: ToolbarState) -> Result<ToolbarState> {
|
|||||||
timer,
|
timer,
|
||||||
cursor: file.cursor.unwrap_or(fallback.cursor),
|
cursor: file.cursor.unwrap_or(fallback.cursor),
|
||||||
preview: file.preview.unwrap_or(fallback.preview),
|
preview: file.preview.unwrap_or(fallback.preview),
|
||||||
|
sound: file.sound.unwrap_or(fallback.sound),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,13 +110,15 @@ mod tests {
|
|||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
config.capture.show_cursor = true;
|
config.capture.show_cursor = true;
|
||||||
config.preview.enabled = false;
|
config.preview.enabled = false;
|
||||||
|
config.sound.enabled = false;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
defaults(&config),
|
defaults(&config),
|
||||||
ToolbarState {
|
ToolbarState {
|
||||||
mode: Mode::Region,
|
mode: Mode::Region,
|
||||||
timer: 0,
|
timer: 0,
|
||||||
cursor: true,
|
cursor: true,
|
||||||
preview: false
|
preview: false,
|
||||||
|
sound: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -126,6 +132,7 @@ mod tests {
|
|||||||
timer: 10,
|
timer: 10,
|
||||||
cursor: true,
|
cursor: true,
|
||||||
preview: true,
|
preview: true,
|
||||||
|
sound: false,
|
||||||
};
|
};
|
||||||
save(&path, &state).unwrap();
|
save(&path, &state).unwrap();
|
||||||
assert_eq!(load(&path, &Config::default()), state);
|
assert_eq!(load(&path, &Config::default()), state);
|
||||||
|
|||||||
Reference in new issue
Block a user