164 lines
5.2 KiB
Rust
164 lines
5.2 KiB
Rust
use anyhow::{Context, Result};
|
|
use directories::ProjectDirs;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::path::PathBuf;
|
|
|
|
/// Top-level application configuration.
|
|
/// Serialized to/from ~/.config/rs-pictures/config.toml
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Config {
|
|
/// Directory where screenshots are saved.
|
|
pub save_directory: PathBuf,
|
|
|
|
/// File format for saved screenshots: "png" or "jpeg".
|
|
pub save_format: String,
|
|
|
|
/// Filename template. Supports strftime-style tokens via chrono.
|
|
/// Example: "screenshot_%Y-%m-%d_%H-%M-%S"
|
|
pub filename_template: String,
|
|
|
|
/// When true, automatically save to disk after capture without opening
|
|
/// the review window.
|
|
#[serde(default)]
|
|
pub auto_save: bool,
|
|
|
|
/// When true, automatically copy to the clipboard after capture without
|
|
/// opening the review window.
|
|
#[serde(default)]
|
|
pub auto_copy: bool,
|
|
|
|
/// Milliseconds to wait after launch before capturing the desktop snapshot.
|
|
/// Increase this if the overlay background still shows the terminal/launcher
|
|
/// that started rs-pictures. Default: 200.
|
|
#[serde(default = "default_capture_delay_ms")]
|
|
pub capture_delay_ms: u64,
|
|
|
|
/// If true, the selection overlay will be transparent (live preview) instead of
|
|
/// a frozen screenshot. The final capture happens after selection.
|
|
#[serde(default)]
|
|
pub live_mode: bool,
|
|
|
|
/// Visual effects applied after capture.
|
|
pub effects: EffectsConfig,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct EffectsConfig {
|
|
/// Apply rounded corners to the screenshot.
|
|
pub rounded_corners: bool,
|
|
|
|
/// Radius in pixels for rounded corners.
|
|
pub corner_radius: f32,
|
|
|
|
/// Apply a drop shadow beneath the screenshot.
|
|
pub drop_shadow: bool,
|
|
|
|
/// Blur radius for the drop shadow (higher = softer shadow).
|
|
pub shadow_blur_radius: f32,
|
|
|
|
/// Horizontal offset of the shadow in pixels.
|
|
pub shadow_offset_x: f32,
|
|
|
|
/// Vertical offset of the shadow in pixels.
|
|
pub shadow_offset_y: f32,
|
|
|
|
/// Shadow color as [R, G, B, A] in 0..=255.
|
|
pub shadow_color: [u8; 4],
|
|
}
|
|
|
|
impl Default for EffectsConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
rounded_corners: false,
|
|
corner_radius: 12.0,
|
|
drop_shadow: false,
|
|
shadow_blur_radius: 20.0,
|
|
shadow_offset_x: 5.0,
|
|
shadow_offset_y: 8.0,
|
|
shadow_color: [0, 0, 0, 160],
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Config {
|
|
fn default() -> Self {
|
|
let save_directory = dirs_default_pictures().unwrap_or_else(|| PathBuf::from("."));
|
|
Self {
|
|
save_directory,
|
|
save_format: "png".into(),
|
|
filename_template: "screenshot_%Y-%m-%d_%H-%M-%S".into(),
|
|
auto_save: false,
|
|
auto_copy: false,
|
|
capture_delay_ms: default_capture_delay_ms(),
|
|
live_mode: false,
|
|
effects: EffectsConfig::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Config {
|
|
/// Returns the path to the config file, creating parent directories if needed.
|
|
pub fn config_path() -> Option<PathBuf> {
|
|
ProjectDirs::from("", "", "rs-pictures")
|
|
.map(|pd| pd.config_dir().join("config.toml"))
|
|
}
|
|
|
|
/// Load config from disk, or return the default config if the file doesn't exist.
|
|
pub fn load() -> Result<Self> {
|
|
let path = match Self::config_path() {
|
|
Some(p) => p,
|
|
None => return Ok(Self::default()),
|
|
};
|
|
|
|
if !path.exists() {
|
|
let config = Self::default();
|
|
config.save()?;
|
|
return Ok(config);
|
|
}
|
|
|
|
let raw = std::fs::read_to_string(&path)
|
|
.with_context(|| format!("Failed to read config at {}", path.display()))?;
|
|
|
|
toml::from_str(&raw)
|
|
.with_context(|| format!("Failed to parse config at {}", path.display()))
|
|
}
|
|
|
|
/// Persist the current config to disk.
|
|
pub fn save(&self) -> Result<()> {
|
|
let path = Self::config_path()
|
|
.context("Could not determine config directory")?;
|
|
|
|
if let Some(parent) = path.parent() {
|
|
std::fs::create_dir_all(parent)
|
|
.with_context(|| format!("Failed to create config dir {}", parent.display()))?;
|
|
}
|
|
|
|
let serialized = toml::to_string_pretty(self)
|
|
.context("Failed to serialize config")?;
|
|
|
|
std::fs::write(&path, serialized)
|
|
.with_context(|| format!("Failed to write config to {}", path.display()))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Build the full output path for a new screenshot using chrono formatting.
|
|
pub fn output_path(&self) -> PathBuf {
|
|
let now = chrono::Local::now();
|
|
let filename = now.format(&self.filename_template).to_string();
|
|
let ext = &self.save_format;
|
|
self.save_directory.join(format!("{filename}.{ext}"))
|
|
}
|
|
}
|
|
|
|
fn default_capture_delay_ms() -> u64 { 200 }
|
|
|
|
fn dirs_default_pictures() -> Option<PathBuf> {
|
|
// Use XDG_PICTURES_DIR if available, otherwise ~/Pictures
|
|
if let Ok(val) = std::env::var("XDG_PICTURES_DIR") {
|
|
return Some(PathBuf::from(val));
|
|
}
|
|
directories::UserDirs::new()
|
|
.and_then(|ud| ud.picture_dir().map(|p| p.to_path_buf()))
|
|
}
|