window management library
git clone https://git.lucas.co/cce-window-manager.git
src/state.rs (4.5K)
1 // Persisted session state, saved to $XDG_STATE_HOME/cce/state.json on shutdown
2 // and restored on startup. Pure data — serialization and matching logic only;
3 // the save/load I/O lives in `window_manager.rs`.
4
5 use super::tiling::TilingMode;
6
7 /// The desktop-grid geometry the geometries in this file were measured
8 /// under. Saved so a LATER session under a different grid can re-tile a
9 /// Tiled entry onto the same block of squares (`cells::remap_block`) instead
10 /// of re-deriving its span from stale pixels — a box saved under one grid
11 /// lands misaligned on another, touches extra cells, and the tiled snap
12 /// then grows the window by a cell. Absent in files written before this
13 /// field existed; the loader then has nothing to remap from and applies the
14 /// geometry as-is.
15 #[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq)]
16 pub struct SavedGrid {
17 pub cell_w: f64,
18 pub cell_h: f64,
19 pub gap_width: f64,
20 pub cell_inset: f64,
21 }
22
23 impl SavedGrid {
24 pub fn from_params(p: &crate::snap::SnapParams) -> Self {
25 Self { cell_w: p.cell_w, cell_h: p.cell_h, gap_width: p.gap_width, cell_inset: p.cell_inset }
26 }
27
28 /// The saved grid as snap params, borrowing everything non-geometric
29 /// (threshold) from `current` — remapping needs geometry only.
30 pub fn to_params(&self, current: &crate::snap::SnapParams) -> crate::snap::SnapParams {
31 crate::snap::SnapParams {
32 cell_w: self.cell_w,
33 cell_h: self.cell_h,
34 gap_width: self.gap_width,
35 cell_inset: self.cell_inset,
36 threshold: current.threshold,
37 }
38 }
39
40 pub fn matches(&self, p: &crate::snap::SnapParams) -> bool {
41 self.cell_w == p.cell_w
42 && self.cell_h == p.cell_h
43 && self.gap_width == p.gap_width
44 && self.cell_inset == p.cell_inset
45 }
46 }
47
48 #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
49 pub struct SavedWindowState {
50 pub app_id: String,
51 pub title: String,
52 pub tiling_mode: TilingMode,
53 pub minimized: bool,
54 pub virtual_x: f64,
55 pub virtual_y: f64,
56 pub scale: f64,
57 pub width: u32,
58 pub height: u32,
59 pub cmdline: String,
60 #[serde(default)]
61 pub focused: bool,
62 }
63
64 #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
65 pub struct SavedState {
66 pub desk_pan_x: f64,
67 pub desk_pan_y: f64,
68 pub desk_zoom: f64,
69 pub windows: Vec<SavedWindowState>,
70 #[serde(default)]
71 pub last_window_states: Vec<SavedWindowState>,
72 /// See [`SavedGrid`]. `None` in pre-field files.
73 #[serde(default)]
74 pub grid: Option<SavedGrid>,
75 }
76
77 #[cfg(test)]
78 mod tests {
79 use super::*;
80
81 /// A state file from before the grid field must still load — and one
82 /// with it must round-trip.
83 #[test]
84 fn legacy_state_without_grid_still_loads() {
85 let legacy = r#"{
86 "desk_pan_x": 0.0, "desk_pan_y": 0.0, "desk_zoom": 1.0,
87 "windows": [{
88 "app_id": "cce-terminal", "title": "t", "tiling_mode": "Tiled",
89 "minimized": false, "virtual_x": 4.0, "virtual_y": 4.0,
90 "scale": 1.0, "width": 1560, "height": 504, "cmdline": "cce-terminal"
91 }]
92 }"#;
93 let s: SavedState = serde_json::from_str(legacy).unwrap();
94 assert!(s.grid.is_none());
95 assert_eq!(s.windows.len(), 1);
96
97 let with_grid = SavedState {
98 grid: Some(SavedGrid { cell_w: 512.0, cell_h: 512.0, gap_width: 16.0, cell_inset: 4.0 }),
99 ..s
100 };
101 let json = serde_json::to_string(&with_grid).unwrap();
102 let back: SavedState = serde_json::from_str(&json).unwrap();
103 assert_eq!(back.grid, with_grid.grid);
104 }
105
106 /// The saved-grid remap keeps a Tiled entry on ITS SQUARES across a gap
107 /// change: 3 columns at gap 16 stays 3 columns at gap 8, where a raw
108 /// pixel restore would misalign and span 4.
109 #[test]
110 fn saved_grid_remap_keeps_the_block() {
111 let old = SavedGrid { cell_w: 512.0, cell_h: 512.0, gap_width: 16.0, cell_inset: 4.0 };
112 let new = crate::snap::SnapParams {
113 cell_w: 512.0, cell_h: 512.0, gap_width: 8.0, cell_inset: 4.0, threshold: 24.0,
114 };
115 // Column -5, 3 cells wide, 1 tall, under the old grid.
116 let (x, y, w, h) = (-5.0 * 528.0 + 4.0, 4.0, 2.0 * 528.0 + 512.0 - 8.0, 504.0);
117 let (nx, ny, nw, nh) =
118 crate::cells::remap_block(x, y, w, h, &old.to_params(&new), &new);
119 assert_eq!((nx, ny), (-5.0 * 520.0 + 4.0, 4.0));
120 assert_eq!((nw, nh), (2.0 * 520.0 + 512.0 - 8.0, 504.0));
121 }
122 }