use serde::{Deserialize, Serialize};
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct WindowState {
pub x: i32,
pub y: i32,
}
impl Default for WindowState {
fn default() -> Self {
Self { x: 50, y: 50 }
}
}
pub fn get_state_dir() -> PathBuf {
std::env::var("XDG_STATE_HOME").map_or_else(|_| {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
PathBuf::from(home).join(".local").join("state")
}, PathBuf::from)
.join("float-clock-wayland")
}
pub fn write_atomic(path: &Path, content: &str) -> std::io::Result<()> {
let parent = path.parent().unwrap_or_else(|| Path::new("."));
fs::create_dir_all(parent)?;
let pid = std::process::id();
let filename = path.file_name().unwrap_or_default().to_string_lossy();
let tmp_path = parent.join(format!(".tmp_{pid}_{filename}"));
let mut file = fs::File::create(&tmp_path)?;
file.write_all(content.as_bytes())?;
file.sync_all()?;
drop(file);
fs::rename(&tmp_path, path)
}
pub fn load_window_state() -> WindowState {
let path = get_state_dir().join("state.json");
if let Ok(contents) = fs::read_to_string(&path)
&& let Ok(state) = serde_json::from_str::<WindowState>(&contents) {
return state;
}
WindowState::default()
}
pub fn save_window_state(state: WindowState) {
let dir = get_state_dir();
if let Err(e) = fs::create_dir_all(&dir) {
log::warn!("Could not create state directory {dir:?}: {e}");
return;
}
let path = dir.join("state.json");
if let Ok(json_str) = serde_json::to_string_pretty(&state)
&& let Err(e) = write_atomic(&path, &json_str) {
log::warn!("Could not save window state to {path:?}: {e}");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_window_state_serialization() {
let state = WindowState { x: 140, y: 280 };
let json = serde_json::to_string(&state).unwrap();
let deserialized: WindowState = serde_json::from_str(&json).unwrap();
assert_eq!(state, deserialized);
}
}