diff --git a/contrib/Settings-sample.toml b/contrib/Settings-sample.toml index 61141b5..84f8ae5 100644 --- a/contrib/Settings-sample.toml +++ b/contrib/Settings-sample.toml @@ -193,6 +193,11 @@ amplitude = 4.0 # min-speed = 0.0 # max-speed = 3000.0 +[screenshot] +# The path to save screenshots. +# Relative paths are relative to "/mnt/onboard". Also accepts full path. +save-path = "Screenshots" + [calculator] # The default font size and margin width, for the Calculator application. # The units are the same as in the `[reader]` section. diff --git a/crates/core/src/settings/mod.rs b/crates/core/src/settings/mod.rs index 2ff92aa..2a270ab 100644 --- a/crates/core/src/settings/mod.rs +++ b/crates/core/src/settings/mod.rs @@ -122,6 +122,7 @@ pub struct Settings { pub import: ImportSettings, pub dictionary: DictionarySettings, pub sketch: SketchSettings, + pub screenshot: ScreenshotSettings, pub calculator: CalculatorSettings, pub battery: BatterySettings, pub frontlight_levels: LightLevels, @@ -257,6 +258,20 @@ impl Default for CalculatorSettings { } } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(default, rename_all = "kebab-case")] +pub struct ScreenshotSettings { + pub save_path: PathBuf, +} + +impl Default for ScreenshotSettings { + fn default() -> Self { + ScreenshotSettings { + save_path: PathBuf::from("Screenshots"), + } + } +} + #[derive(Debug, Copy, Clone, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct Columns { @@ -555,6 +570,7 @@ impl Default for Settings { import: ImportSettings::default(), dictionary: DictionarySettings::default(), sketch: SketchSettings::default(), + screenshot: ScreenshotSettings::default(), calculator: CalculatorSettings::default(), battery: BatterySettings::default(), frontlight_levels: LightLevels::default(), diff --git a/crates/emulator/src/main.rs b/crates/emulator/src/main.rs index e94f91f..5c6152a 100644 --- a/crates/emulator/src/main.rs +++ b/crates/emulator/src/main.rs @@ -1,9 +1,9 @@ use std::mem; use std::thread; -use std::fs::File; +use std::fs::{self, File}; use std::sync::mpsc; use std::collections::VecDeque; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::time::Duration; use plato_core::anyhow::{Error, Context as ResultExt}; use plato_core::chrono::Local; @@ -28,6 +28,7 @@ use plato_core::view::menu::{Menu, MenuKind}; use plato_core::view::intermission::Intermission; use plato_core::view::dictionary::Dictionary; use plato_core::view::calculator::Calculator; +use plato_core::settings::INTERNAL_CARD_ROOT; use plato_core::view::sketch::Sketch; use plato_core::view::touch_events::TouchEvents; use plato_core::view::rotation_values::RotationValues; @@ -558,10 +559,20 @@ fn main() -> Result<(), Error> { } rq.add(RenderData::expose(context.fb.rect(), UpdateMode::Full)); process_render_queue(view.as_ref(), &mut rq, &mut context, &mut updating); + let save_path = if context.settings.screenshot.save_path.is_absolute() { + context.settings.screenshot.save_path.clone() + } else { + PathBuf::from(INTERNAL_CARD_ROOT).join(&context.settings.screenshot.save_path) + }; let name = Local::now().format("screenshot-%Y%m%d_%H%M%S.png"); - let msg = match context.fb.save(&name.to_string()) { - Err(e) => format!("Couldn't take screenshot: {}).", e), - Ok(_) => format!("Saved {}.", name), + let path = save_path.join(name.to_string()); + let msg = if let Err(e) = fs::create_dir_all(&save_path) { + format!("Can't create screenshot directory: {}).", e) + } else { + match context.fb.save(&path.to_string_lossy()) { + Err(e) => format!("Couldn't take screenshot: {}).", e), + Ok(_) => format!("Saved {}.", path.display()), + } }; let notif = Notification::new(msg, &tx, &mut rq, &mut context); view.children_mut().push(Box::new(notif) as Box); diff --git a/crates/plato/src/app.rs b/crates/plato/src/app.rs index fe7f65f..0c776e1 100644 --- a/crates/plato/src/app.rs +++ b/crates/plato/src/app.rs @@ -1,8 +1,8 @@ -use std::fs::File; +use std::fs::{self, File}; use std::env; use std::thread; use std::process::Command; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::mpsc::{self, Receiver, Sender}; use std::collections::VecDeque; use std::time::{Duration, Instant}; @@ -25,7 +25,7 @@ use plato_core::input::{DeviceEvent, PowerSource, ButtonCode, ButtonStatus, VAL_ use plato_core::input::{raw_events, device_events, usb_events, display_rotate_event, button_scheme_event}; use plato_core::gesture::{GestureEvent, gesture_events}; use plato_core::helpers::{load_toml, save_toml}; -use plato_core::settings::{ButtonScheme, Settings, SETTINGS_PATH, RotationLock, IntermKind}; +use plato_core::settings::{ButtonScheme, Settings, SETTINGS_PATH, RotationLock, IntermKind, INTERNAL_CARD_ROOT}; use plato_core::frontlight::{Frontlight, StandardFrontlight, NaturalFrontlight, PremixedFrontlight}; use plato_core::lightsensor::{LightSensor, KoboLightSensor}; use plato_core::battery::{Battery, KoboBattery}; @@ -935,10 +935,20 @@ pub fn run() -> Result<(), Error> { } rq.add(RenderData::expose(context.fb.rect(), UpdateMode::Full)); process_render_queue(view.as_ref(), &mut rq, &mut context, &mut updating); + let save_path = if context.settings.screenshot.save_path.is_absolute() { + context.settings.screenshot.save_path.clone() + } else { + PathBuf::from(INTERNAL_CARD_ROOT).join(&context.settings.screenshot.save_path) + }; let name = Local::now().format("screenshot-%Y%m%d_%H%M%S.png"); - let msg = match context.fb.save(&name.to_string()) { - Err(e) => format!("{}", e), - Ok(_) => format!("Saved {}.", name), + let path = save_path.join(name.to_string()); + let msg = if let Err(e) = fs::create_dir_all(&save_path) { + format!("Can't create screenshot directory: {}).", e) + } else { + match context.fb.save(&path.to_string_lossy()) { + Err(e) => format!("{}", e), + Ok(_) => format!("Saved {}.", path.display()), + } }; let notif = Notification::new(msg, &tx, &mut rq, &mut context); view.children_mut().push(Box::new(notif) as Box);