gardesk/garfield / a467a52

Browse files

ui: add free disk space display to status bar

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
a467a5292cc2801d71240ceb6fc0089f4d715458
Parents
2cc95b4
Tree
524f546

5 changed files

StatusFile+-
M Cargo.lock 1 0
M Cargo.toml 3 0
M garfield/Cargo.toml 1 0
M garfield/src/app.rs 3 2
M garfield/src/ui/status_bar.rs 39 3
Cargo.lockmodified
@@ -331,6 +331,7 @@ dependencies = [
331331
  "gartk-core",
332332
  "gartk-render",
333333
  "gartk-x11",
334
+ "libc",
334335
  "serde",
335336
  "serde_json",
336337
  "thiserror",
Cargo.tomlmodified
@@ -22,6 +22,9 @@ gartk-render = { path = "../gartk/gartk-render" }
2222
 # X11
2323
 x11rb = { version = "0.13", features = ["allow-unsafe-code"] }
2424
 
25
+# System
26
+libc = "0.2"
27
+
2528
 # Logging
2629
 tracing = "0.1"
2730
 tracing-subscriber = { version = "0.3", features = ["env-filter"] }
garfield/Cargo.tomlmodified
@@ -33,6 +33,7 @@ serde_json.workspace = true
3333
 # File system
3434
 dirs.workspace = true
3535
 chrono.workspace = true
36
+libc.workspace = true
3637
 
3738
 # IPC
3839
 garfield-ipc.workspace = true
garfield/src/app.rsmodified
@@ -1103,11 +1103,12 @@ impl App {
11031103
     fn update_status_bar(&mut self) {
11041104
         let stats = self.focused_pane()
11051105
             .and_then(|pane| pane.active_tab())
1106
-            .map(|tab| (tab.visible_count(), tab.selection_count(), tab.selected_size(), tab.view_mode().name()));
1106
+            .map(|tab| (tab.visible_count(), tab.selection_count(), tab.selected_size(), tab.view_mode().name(), tab.current_path().to_path_buf()));
11071107
 
1108
-        if let Some((visible_count, selected_count, selected_size, view_mode)) = stats {
1108
+        if let Some((visible_count, selected_count, selected_size, view_mode, path)) = stats {
11091109
             self.status_bar.update(visible_count, selected_count, selected_size);
11101110
             self.status_bar.set_view_mode(view_mode);
1111
+            self.status_bar.update_free_space(&path);
11111112
         }
11121113
     }
11131114
 
garfield/src/ui/status_bar.rsmodified
@@ -2,6 +2,8 @@
22
 
33
 use gartk_core::Rect;
44
 use gartk_render::{Renderer, TextStyle};
5
+use std::ffi::CString;
6
+use std::path::Path;
57
 
68
 /// Height of the status bar.
79
 pub const STATUS_BAR_HEIGHT: u32 = 24;
@@ -18,6 +20,8 @@ pub struct StatusBar {
1820
     selected_size: u64,
1921
     /// Current view mode name.
2022
     view_mode: String,
23
+    /// Free disk space in bytes.
24
+    free_space: Option<u64>,
2125
 }
2226
 
2327
 impl StatusBar {
@@ -29,6 +33,7 @@ impl StatusBar {
2933
             selected_count: 0,
3034
             selected_size: 0,
3135
             view_mode: "List".to_string(),
36
+            free_space: None,
3237
         }
3338
     }
3439
 
@@ -49,6 +54,11 @@ impl StatusBar {
4954
         self.view_mode = mode.to_string();
5055
     }
5156
 
57
+    /// Update free disk space for the given path.
58
+    pub fn update_free_space(&mut self, path: &Path) {
59
+        self.free_space = get_free_space(path);
60
+    }
61
+
5262
     /// Get status bar height.
5363
     pub fn height(&self) -> u32 {
5464
         STATUS_BAR_HEIGHT
@@ -104,10 +114,21 @@ impl StatusBar {
104114
             &text_style,
105115
         )?;
106116
 
107
-        // Right side: view mode
117
+        // Right side: free space and view mode
118
+        let mut right_x = self.bounds.x + self.bounds.width as i32 - padding;
119
+
120
+        // View mode
108121
         let mode_width = renderer.measure_text(&self.view_mode, &text_style)?.width;
109
-        let mode_x = self.bounds.x + self.bounds.width as i32 - mode_width as i32 - padding;
110
-        renderer.text(&self.view_mode, mode_x as f64, text_y as f64, &text_style)?;
122
+        right_x -= mode_width as i32;
123
+        renderer.text(&self.view_mode, right_x as f64, text_y as f64, &text_style)?;
124
+
125
+        // Free space (if available)
126
+        if let Some(free) = self.free_space {
127
+            let free_text = format!("{} free  |  ", format_bytes(free));
128
+            let free_width = renderer.measure_text(&free_text, &text_style)?.width;
129
+            right_x -= free_width as i32;
130
+            renderer.text(&free_text, right_x as f64, text_y as f64, &text_style)?;
131
+        }
111132
 
112133
         Ok(())
113134
     }
@@ -132,3 +153,18 @@ fn format_bytes(bytes: u64) -> String {
132153
         format!("{} B", bytes)
133154
     }
134155
 }
156
+
157
+/// Get free disk space for the filesystem containing the given path.
158
+fn get_free_space(path: &Path) -> Option<u64> {
159
+    let c_path = CString::new(path.to_string_lossy().as_bytes()).ok()?;
160
+
161
+    unsafe {
162
+        let mut stat: libc::statvfs = std::mem::zeroed();
163
+        if libc::statvfs(c_path.as_ptr(), &mut stat) == 0 {
164
+            // Available blocks * block size = free space for non-privileged users
165
+            Some(stat.f_bavail as u64 * stat.f_bsize as u64)
166
+        } else {
167
+            None
168
+        }
169
+    }
170
+}