2022-05-04 22:37:28 +00:00
|
|
|
use anyhow::anyhow;
|
2022-05-05 08:14:53 +00:00
|
|
|
use std::{fmt, fs::{self, File}, io::{Read, Write}, path::PathBuf, time::Duration};
|
2022-05-04 22:37:28 +00:00
|
|
|
use toml::Value;
|
|
|
|
|
2022-05-16 03:50:45 +00:00
|
|
|
use crate::desktop::NewLoginCommand;
|
|
|
|
|
2022-05-05 08:14:53 +00:00
|
|
|
const CONFIG_FILE_RELATIVE_PATH: &str = "bscreensaver/bscreensaver.toml";
|
|
|
|
|
|
|
|
const LOCK_TIMEOUT: &str = "lock-timeout";
|
|
|
|
const BLANK_BEFORE_LOCKING: &str = "blank-before-locking";
|
|
|
|
const DIALOG_BACKEND: &str = "dialog-backend";
|
|
|
|
const NEW_LOGIN_COMMAND: &str = "new-login-command";
|
2022-05-24 03:27:59 +00:00
|
|
|
const HANDLE_BRIGHTNESS_KEYS: &str = "handle-brightness-keys";
|
2022-05-05 08:14:53 +00:00
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
2022-05-04 22:37:28 +00:00
|
|
|
pub enum DialogBackend {
|
|
|
|
Gtk3,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DialogBackend {
|
|
|
|
pub fn binary_name(&self) -> &str {
|
|
|
|
match self {
|
|
|
|
Self::Gtk3 => "bscreensaver-dialog-gtk3",
|
|
|
|
}
|
|
|
|
}
|
2022-05-05 08:14:53 +00:00
|
|
|
|
|
|
|
pub fn display_name(&self) -> &str {
|
|
|
|
match self {
|
|
|
|
Self::Gtk3 => "GTK3",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for DialogBackend {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
Self::Gtk3 => write!(f, "gtk3"),
|
|
|
|
}
|
|
|
|
}
|
2022-05-04 22:37:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<&str> for DialogBackend {
|
|
|
|
type Error = anyhow::Error;
|
|
|
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
|
|
|
match value {
|
|
|
|
"gtk3" => Ok(Self::Gtk3),
|
|
|
|
other => Err(anyhow!("'{}' is not a valid dialog backend (valid: 'gtk3')", other)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-05 08:14:53 +00:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2022-05-04 22:37:28 +00:00
|
|
|
pub struct Configuration {
|
|
|
|
pub lock_timeout: Duration,
|
|
|
|
pub blank_before_locking: Duration,
|
|
|
|
pub dialog_backend: DialogBackend,
|
2022-05-16 03:50:45 +00:00
|
|
|
pub new_login_command: NewLoginCommand,
|
2022-05-24 03:27:59 +00:00
|
|
|
pub handle_brightness_keys: bool,
|
2022-05-04 22:37:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Configuration {
|
|
|
|
pub fn load() -> anyhow::Result<Configuration> {
|
|
|
|
use humantime::parse_duration;
|
|
|
|
|
|
|
|
let config_tomls = xdg::BaseDirectories::new()?
|
2022-05-05 08:14:53 +00:00
|
|
|
.find_config_files(CONFIG_FILE_RELATIVE_PATH)
|
2022-05-04 22:37:28 +00:00
|
|
|
.map(|config_path| parse_config_toml(&config_path))
|
|
|
|
.collect::<Result<Vec<Value>, _>>()?;
|
|
|
|
|
|
|
|
let mut config = Configuration::default();
|
|
|
|
|
|
|
|
for config_toml in config_tomls {
|
2022-05-24 03:33:14 +00:00
|
|
|
config.lock_timeout = match config_toml.get(LOCK_TIMEOUT) {
|
2022-05-04 22:37:28 +00:00
|
|
|
None => config.lock_timeout,
|
2022-05-24 03:33:14 +00:00
|
|
|
Some(val) => parse_duration(val.as_str().ok_or(anyhow!("'{}' must be a duration string like '10m' or '90s'", LOCK_TIMEOUT))?)?,
|
2022-05-04 22:37:28 +00:00
|
|
|
};
|
2022-05-24 03:33:14 +00:00
|
|
|
config.blank_before_locking = match config_toml.get(BLANK_BEFORE_LOCKING) {
|
2022-05-04 22:37:28 +00:00
|
|
|
None => config.blank_before_locking,
|
2022-05-24 03:33:14 +00:00
|
|
|
Some(val) => parse_duration(val.as_str().ok_or(anyhow!("'{}' must be a duration string like '10m' or '90s'", BLANK_BEFORE_LOCKING))?)?,
|
2022-05-04 22:37:28 +00:00
|
|
|
};
|
2022-05-24 03:33:14 +00:00
|
|
|
config.dialog_backend = match config_toml.get(DIALOG_BACKEND) {
|
2022-05-04 22:37:28 +00:00
|
|
|
None => config.dialog_backend,
|
2022-05-24 03:33:14 +00:00
|
|
|
Some(val) => DialogBackend::try_from(val.as_str().ok_or(anyhow!("'{}' must be a string", DIALOG_BACKEND))?)?,
|
2022-05-04 22:37:28 +00:00
|
|
|
};
|
2022-05-24 03:33:14 +00:00
|
|
|
config.new_login_command = match config_toml.get(NEW_LOGIN_COMMAND) {
|
2022-05-04 22:37:28 +00:00
|
|
|
None => config.new_login_command,
|
2022-05-16 03:50:45 +00:00
|
|
|
Some(val) => val.try_into().unwrap_or(NewLoginCommand::Auto),
|
2022-05-04 22:37:28 +00:00
|
|
|
};
|
2022-05-24 03:27:59 +00:00
|
|
|
config.handle_brightness_keys = match config_toml.get(HANDLE_BRIGHTNESS_KEYS) {
|
|
|
|
None => config.handle_brightness_keys,
|
2022-05-24 03:33:14 +00:00
|
|
|
Some(val) => val.as_bool().ok_or(anyhow!("'{}' must be a boolean", HANDLE_BRIGHTNESS_KEYS))?,
|
2022-05-24 03:27:59 +00:00
|
|
|
}
|
2022-05-04 22:37:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if config.blank_before_locking >= config.lock_timeout {
|
|
|
|
Err(anyhow!("'blank-before-locking' cannot be greater than 'lock-timeout'"))
|
|
|
|
} else {
|
|
|
|
Ok(config)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-05 08:14:53 +00:00
|
|
|
pub fn save(&self) -> anyhow::Result<()> {
|
|
|
|
use humantime::format_duration;
|
|
|
|
use toml::map::Map;
|
2022-05-04 22:37:28 +00:00
|
|
|
|
2022-05-05 08:14:53 +00:00
|
|
|
let mut config_map = Map::new();
|
|
|
|
config_map.insert(LOCK_TIMEOUT.to_string(), Value::String(format_duration(self.lock_timeout).to_string()));
|
|
|
|
config_map.insert(BLANK_BEFORE_LOCKING.to_string(), Value::String(format_duration(self.blank_before_locking).to_string()));
|
|
|
|
config_map.insert(DIALOG_BACKEND.to_string(), Value::String(self.dialog_backend.to_string()));
|
2022-05-16 03:50:45 +00:00
|
|
|
config_map.insert(NEW_LOGIN_COMMAND.to_string(), self.new_login_command.clone().try_into()?);
|
2022-05-24 03:27:59 +00:00
|
|
|
config_map.insert(HANDLE_BRIGHTNESS_KEYS.to_string(), Value::Boolean(self.handle_brightness_keys));
|
2022-05-05 08:14:53 +00:00
|
|
|
|
|
|
|
let config_path = xdg::BaseDirectories::new()?.place_config_file(CONFIG_FILE_RELATIVE_PATH)?;
|
|
|
|
let mut tmp_filename = config_path.file_name().unwrap().to_os_string();
|
|
|
|
tmp_filename.push(".new");
|
|
|
|
let tmp_path = config_path.with_file_name(tmp_filename);
|
|
|
|
let mut f = File::create(&tmp_path)?;
|
|
|
|
f.write_all(Value::Table(config_map).to_string().as_bytes())?;
|
|
|
|
drop(f);
|
|
|
|
fs::rename(tmp_path, config_path)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2022-05-04 22:37:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Configuration {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
lock_timeout: Duration::from_secs(60 * 10),
|
|
|
|
blank_before_locking: Duration::ZERO,
|
|
|
|
dialog_backend: DialogBackend::Gtk3,
|
2022-05-16 03:50:45 +00:00
|
|
|
new_login_command: NewLoginCommand::Auto,
|
2022-05-24 03:27:59 +00:00
|
|
|
handle_brightness_keys: false,
|
2022-05-04 22:37:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-05 08:14:53 +00:00
|
|
|
fn parse_config_toml(config_path: &PathBuf) -> anyhow::Result<Value> {
|
|
|
|
let mut f = File::open(config_path)?;
|
|
|
|
let mut config = String::new();
|
|
|
|
f.read_to_string(&mut config)?;
|
|
|
|
drop(f);
|
|
|
|
|
|
|
|
let config_toml = config.parse::<Value>()?;
|
|
|
|
Ok(config_toml)
|
|
|
|
}
|