use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum LockscreenAuthMode {
OsAuth, #[default]
Pin, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
#[serde(default = "default_auto_tiling_on_startup")]
pub auto_tiling_on_startup: bool,
#[serde(default = "default_tiling_gaps")]
pub tiling_gaps: bool,
#[serde(default = "default_show_date_in_clock")]
pub show_date_in_clock: bool,
#[serde(default = "default_theme")]
pub theme: String,
#[serde(default = "default_background_char_index")]
pub background_char_index: usize,
#[serde(default = "default_tint_terminal")]
pub tint_terminal: bool,
#[serde(default = "default_auto_save")]
pub auto_save: bool,
#[serde(default = "default_persist_enabled")]
pub persist_enabled: bool,
#[serde(default = "default_lockscreen_enabled")]
pub lockscreen_enabled: bool,
#[serde(default)]
pub lockscreen_auth_mode: LockscreenAuthMode,
#[serde(default)]
pub lockscreen_pin_hash: Option<String>,
#[serde(default)]
pub lockscreen_salt: Option<String>,
#[serde(default)]
pub network_widget_enabled: bool,
#[serde(default)]
pub network_interface: String,
#[serde(default = "default_keybinding_profile")]
pub keybinding_profile: String,
}
fn default_keybinding_profile() -> String {
"term39".to_string()
}
fn default_auto_tiling_on_startup() -> bool {
false }
fn default_tiling_gaps() -> bool {
true }
fn default_show_date_in_clock() -> bool {
true }
fn default_theme() -> String {
"classic".to_string()
}
fn default_background_char_index() -> usize {
0 }
fn default_tint_terminal() -> bool {
false }
fn default_auto_save() -> bool {
true }
fn default_persist_enabled() -> bool {
true }
fn default_lockscreen_enabled() -> bool {
true }
impl Default for AppConfig {
fn default() -> Self {
Self {
auto_tiling_on_startup: false,
tiling_gaps: true,
show_date_in_clock: true,
theme: default_theme(),
background_char_index: default_background_char_index(),
tint_terminal: default_tint_terminal(),
auto_save: default_auto_save(),
persist_enabled: default_persist_enabled(),
lockscreen_enabled: default_lockscreen_enabled(),
lockscreen_auth_mode: LockscreenAuthMode::default(),
lockscreen_pin_hash: None,
lockscreen_salt: None,
network_widget_enabled: false,
network_interface: String::new(),
keybinding_profile: default_keybinding_profile(),
}
}
}
impl AppConfig {
fn config_path() -> Option<PathBuf> {
let config_dir = dirs::config_dir()?;
let app_config_dir = config_dir.join("term39");
Some(app_config_dir.join("config.toml"))
}
pub fn load() -> Self {
let path = match Self::config_path() {
Some(p) => p,
None => return Self::default(),
};
if !path.exists() {
let default_config = Self::default();
let _ = default_config.save();
return default_config;
}
match fs::read_to_string(&path) {
Ok(contents) => toml::from_str(&contents).unwrap_or_default(),
Err(_) => Self::default(),
}
}
pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> {
let path = Self::config_path().ok_or("Could not determine config path")?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let toml_string = toml::to_string_pretty(self)?;
fs::write(path, toml_string)?;
Ok(())
}
pub fn toggle_auto_tiling_on_startup(&mut self) {
self.auto_tiling_on_startup = !self.auto_tiling_on_startup;
let _ = self.save();
}
pub fn toggle_tiling_gaps(&mut self) {
self.tiling_gaps = !self.tiling_gaps;
let _ = self.save();
}
pub fn toggle_show_date_in_clock(&mut self) {
self.show_date_in_clock = !self.show_date_in_clock;
let _ = self.save();
}
#[allow(dead_code)]
pub fn set_theme(&mut self, theme_name: String) {
self.theme = theme_name;
let _ = self.save();
}
pub const BACKGROUND_CHARS: [char; 5] = [
'â–‘', ' ', 'â–’', 'â–“', 'â–ˆ', ];
pub const BACKGROUND_CHAR_NAMES: [&'static str; 5] = [
"Light Shade",
"Empty",
"Medium Shade",
"Dark Shade",
"Full Block",
];
pub fn get_background_char(&self) -> char {
Self::BACKGROUND_CHARS
.get(self.background_char_index)
.copied()
.unwrap_or(Self::BACKGROUND_CHARS[0])
}
pub fn get_background_char_name(&self) -> &'static str {
Self::BACKGROUND_CHAR_NAMES
.get(self.background_char_index)
.unwrap_or(&Self::BACKGROUND_CHAR_NAMES[0])
}
pub fn cycle_background_char(&mut self) {
self.background_char_index =
(self.background_char_index + 1) % Self::BACKGROUND_CHARS.len();
let _ = self.save();
}
pub fn cycle_background_char_backward(&mut self) {
if self.background_char_index == 0 {
self.background_char_index = Self::BACKGROUND_CHARS.len() - 1;
} else {
self.background_char_index -= 1;
}
let _ = self.save();
}
pub fn toggle_tint_terminal(&mut self) {
self.tint_terminal = !self.tint_terminal;
let _ = self.save();
}
pub fn toggle_auto_save(&mut self) {
self.auto_save = !self.auto_save;
let _ = self.save();
}
pub fn toggle_persist_enabled(&mut self) {
self.persist_enabled = !self.persist_enabled;
let _ = self.save();
}
pub fn toggle_lockscreen_enabled(&mut self) {
self.lockscreen_enabled = !self.lockscreen_enabled;
if !self.lockscreen_enabled {
self.lockscreen_pin_hash = None;
self.lockscreen_salt = None;
}
let _ = self.save();
}
pub fn cycle_lockscreen_auth_mode(&mut self, os_auth_available: bool) {
self.lockscreen_auth_mode = match self.lockscreen_auth_mode {
LockscreenAuthMode::OsAuth => LockscreenAuthMode::Pin,
LockscreenAuthMode::Pin => {
if os_auth_available {
LockscreenAuthMode::OsAuth
} else {
LockscreenAuthMode::Pin }
}
};
let _ = self.save();
}
pub fn has_pin_configured(&self) -> bool {
self.lockscreen_pin_hash.is_some() && self.lockscreen_salt.is_some()
}
pub fn set_pin(&mut self, hash: String, salt: String) {
self.lockscreen_pin_hash = Some(hash);
self.lockscreen_salt = Some(salt);
let _ = self.save();
}
#[allow(dead_code)]
pub fn clear_pin(&mut self) {
self.lockscreen_pin_hash = None;
self.lockscreen_salt = None;
let _ = self.save();
}
pub fn get_or_create_salt(&mut self) -> String {
if let Some(ref salt) = self.lockscreen_salt {
return salt.clone();
}
#[cfg(target_os = "linux")]
if let Ok(machine_id) = std::fs::read_to_string("/etc/machine-id") {
let salt = machine_id.trim().to_string();
self.lockscreen_salt = Some(salt.clone());
let _ = self.save();
return salt;
}
#[cfg(any(target_os = "freebsd", target_os = "netbsd"))]
{
if let Ok(hostid) = std::fs::read_to_string("/etc/hostid") {
let salt = hostid.trim().to_string();
if !salt.is_empty() {
self.lockscreen_salt = Some(salt.clone());
let _ = self.save();
return salt;
}
}
#[cfg(target_os = "freebsd")]
{
use std::process::Command;
if let Ok(output) = Command::new("sysctl")
.args(["-n", "kern.hostuuid"])
.output()
{
if output.status.success() {
let uuid = String::from_utf8_lossy(&output.stdout);
let salt = uuid.trim().to_string();
if !salt.is_empty() {
self.lockscreen_salt = Some(salt.clone());
let _ = self.save();
return salt;
}
}
}
}
}
#[cfg(target_os = "macos")]
{
use std::process::Command;
if let Ok(output) = Command::new("ioreg")
.args(["-rd1", "-c", "IOPlatformExpertDevice"])
.output()
{
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if line.contains("IOPlatformUUID") {
if let Some(uuid) = line.split('"').nth(3) {
self.lockscreen_salt = Some(uuid.to_string());
let _ = self.save();
return uuid.to_string();
}
}
}
}
}
let random_salt = Self::generate_random_salt();
self.lockscreen_salt = Some(random_salt.clone());
let _ = self.save();
random_salt
}
fn generate_random_salt() -> String {
use sha2::{Digest, Sha256};
use std::time::{SystemTime, UNIX_EPOCH};
let mut hasher = Sha256::new();
if let Ok(duration) = SystemTime::now().duration_since(UNIX_EPOCH) {
hasher.update(duration.as_nanos().to_le_bytes());
}
hasher.update(std::process::id().to_le_bytes());
if let Ok(home) = std::env::var("HOME") {
hasher.update(home.as_bytes());
}
if let Ok(user) = std::env::var("USER") {
hasher.update(user.as_bytes());
}
let result = hasher.finalize();
result.iter().map(|b| format!("{:02x}", b)).collect()
}
pub fn cycle_keybinding_profile(&mut self) {
use crate::input::keybinding_profile::KeybindingProfile;
self.keybinding_profile =
KeybindingProfile::next_name(&self.keybinding_profile).to_string();
let _ = self.save();
}
pub fn cycle_keybinding_profile_backward(&mut self) {
use crate::input::keybinding_profile::KeybindingProfile;
self.keybinding_profile =
KeybindingProfile::prev_name(&self.keybinding_profile).to_string();
let _ = self.save();
}
pub fn toggle_network_widget(&mut self) {
self.network_widget_enabled = !self.network_widget_enabled;
let _ = self.save();
}
#[allow(dead_code)]
pub fn set_network_interface(&mut self, interface: String) {
self.network_interface = interface;
let _ = self.save();
}
}