use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
#[serde(default)]
pub account: String,
#[serde(default = "default_signal_cli_path")]
pub signal_cli_path: String,
#[serde(default = "default_download_dir")]
pub download_dir: PathBuf,
#[serde(default = "default_true")]
pub notify_direct: bool,
#[serde(default = "default_true")]
pub notify_group: bool,
#[serde(default)]
pub desktop_notifications: bool,
#[serde(default = "default_notification_preview")]
pub notification_preview: String,
#[serde(default = "default_clipboard_clear_seconds")]
pub clipboard_clear_seconds: u64,
#[serde(default = "default_true")]
pub inline_images: bool,
#[serde(default = "default_true")]
pub show_link_previews: bool,
#[serde(default)]
pub native_images: bool,
#[serde(default = "default_true")]
pub show_receipts: bool,
#[serde(default = "default_true")]
pub color_receipts: bool,
#[serde(default)]
pub nerd_fonts: bool,
#[serde(default)]
pub reaction_verbose: bool,
#[serde(default = "default_true")]
pub send_read_receipts: bool,
#[serde(default = "default_true")]
pub mouse_enabled: bool,
#[serde(default)]
pub sidebar_on_right: bool,
#[serde(default = "default_theme")]
pub theme: String,
#[serde(default = "default_keybinding_profile")]
pub keybinding_profile: String,
#[serde(default)]
pub proxy: String,
}
fn default_true() -> bool {
true
}
fn default_theme() -> String {
"Default".to_string()
}
fn default_keybinding_profile() -> String {
"Default".to_string()
}
fn default_notification_preview() -> String {
"full".to_string()
}
fn default_clipboard_clear_seconds() -> u64 {
30
}
fn default_signal_cli_path() -> String {
"signal-cli".to_string()
}
fn default_download_dir() -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("signal-downloads")
}
impl Default for Config {
fn default() -> Self {
Self {
account: String::new(),
signal_cli_path: default_signal_cli_path(),
download_dir: default_download_dir(),
notify_direct: true,
notify_group: true,
desktop_notifications: false,
notification_preview: default_notification_preview(),
clipboard_clear_seconds: default_clipboard_clear_seconds(),
inline_images: true,
show_link_previews: true,
native_images: false,
show_receipts: true,
color_receipts: true,
nerd_fonts: false,
reaction_verbose: false,
send_read_receipts: true,
mouse_enabled: true,
sidebar_on_right: false,
theme: default_theme(),
keybinding_profile: default_keybinding_profile(),
proxy: String::new(),
}
}
}
impl Config {
pub fn load(path: Option<&str>) -> Result<Self> {
let config_path = match path {
Some(p) => PathBuf::from(p),
None => {
let new_path = Self::default_config_path();
if !new_path.exists() {
let old_path = dirs::config_dir()
.unwrap_or_else(|| PathBuf::from(".config"))
.join("signal-tui")
.join("config.toml");
if old_path.exists() {
if let Some(parent) = new_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let _ = std::fs::rename(old_path.parent().unwrap(), new_path.parent().unwrap());
}
}
new_path
}
};
if config_path.exists() {
let contents = std::fs::read_to_string(&config_path)
.with_context(|| format!("Failed to read config from {}", config_path.display()))?;
let config: Config = toml::from_str(&contents)
.with_context(|| format!("Failed to parse config from {}", config_path.display()))?;
Ok(config)
} else {
Ok(Config::default())
}
}
pub fn save(&self) -> Result<()> {
let config_path = Self::default_config_path();
if let Some(parent) = config_path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("Failed to create config directory {}", parent.display()))?;
Self::set_dir_permissions(parent);
}
let contents = toml::to_string_pretty(self)
.context("Failed to serialize config")?;
std::fs::write(&config_path, contents)
.with_context(|| format!("Failed to write config to {}", config_path.display()))?;
Self::set_file_permissions(&config_path);
Ok(())
}
#[cfg(unix)]
fn set_file_permissions(path: &std::path::Path) {
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600));
}
#[cfg(not(unix))]
fn set_file_permissions(_path: &std::path::Path) {}
#[cfg(unix)]
fn set_dir_permissions(path: &std::path::Path) {
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700));
}
#[cfg(not(unix))]
fn set_dir_permissions(_path: &std::path::Path) {}
pub fn needs_setup(&self) -> bool {
self.account.is_empty()
}
pub fn default_config_path() -> PathBuf {
dirs::config_dir()
.unwrap_or_else(|| PathBuf::from(".config"))
.join("siggy")
.join("config.toml")
}
}