diff --git a/src/TeamsISO.App/MainWindow.xaml b/src/TeamsISO.App/MainWindow.xaml
index a787a96..cfe66b7 100644
--- a/src/TeamsISO.App/MainWindow.xaml
+++ b/src/TeamsISO.App/MainWindow.xaml
@@ -367,12 +367,33 @@
+ VerticalAlignment="Center"
+ Margin="0,0,8,0">
+
+
+
+
+
+
+
+
+
diff --git a/src/TeamsISO.App/ViewModels/MainViewModel.cs b/src/TeamsISO.App/ViewModels/MainViewModel.cs
index 223c2d5..37c79e9 100644
--- a/src/TeamsISO.App/ViewModels/MainViewModel.cs
+++ b/src/TeamsISO.App/ViewModels/MainViewModel.cs
@@ -221,6 +221,26 @@ public sealed class MainViewModel : ObservableObject, IDisposable
private string _recordingElapsed = string.Empty;
private DateTimeOffset? _recordingStartedAt;
+ ///
+ /// Free disk space on the recording drive, formatted as "245 GB" /
+ /// "1.2 TB" / "8.4 GB". Empty when recording isn't enabled or the
+ /// recording path is invalid. Polled at the existing 1Hz stats tick.
+ ///
+ public string RecordingFreeSpace
+ {
+ get => _recordingFreeSpace;
+ private set => SetField(ref _recordingFreeSpace, value);
+ }
+ private string _recordingFreeSpace = string.Empty;
+
+ /// True when free disk space drops below 10GB — UI cues coral.
+ public bool IsLowDiskSpace
+ {
+ get => _isLowDiskSpace;
+ private set => SetField(ref _isLowDiskSpace, value);
+ }
+ private bool _isLowDiskSpace;
+
/// True when the REST control surface (or OSC bridge, or both) is listening.
public bool IsControlSurfaceRunning
{
@@ -555,6 +575,23 @@ public sealed class MainViewModel : ObservableObject, IDisposable
Toast.Show($"Stopped {enabled.Length} ISO(s)");
}
+ ///
+ /// Format a byte count as a human-readable string with 0-1 decimal
+ /// places — e.g. "8.4 GB", "245 GB", "1.2 TB". Optimized for footer
+ /// readability over precision: nobody needs to know it's 245.34 GB.
+ ///
+ private static string FormatBytes(long bytes)
+ {
+ if (bytes < 0) return "—";
+ const long GB = 1024L * 1024 * 1024;
+ const long TB = GB * 1024;
+ if (bytes >= TB) return $"{bytes / (double)TB:0.0} TB";
+ if (bytes >= GB) return bytes >= 100 * GB
+ ? $"{bytes / GB} GB" // 100+ GB: no decimal — clutter
+ : $"{bytes / (double)GB:0.0} GB"; // < 100GB: one decimal for the low-warning case
+ return $"{bytes / 1024 / 1024} MB";
+ }
+
///
/// Pull the meaningful "meeting title" out of Teams' raw window title.
/// Teams uses formats like:
@@ -628,11 +665,38 @@ public sealed class MainViewModel : ObservableObject, IDisposable
RecordingElapsed = recElapsed.TotalHours >= 1
? $"{(int)recElapsed.TotalHours:D2}:{recElapsed.Minutes:D2}:{recElapsed.Seconds:D2}"
: $"{recElapsed.Minutes:D2}:{recElapsed.Seconds:D2}";
+
+ // Disk free on the recording drive. DriveInfo is cheap (a
+ // single GetDiskFreeSpaceEx call). We tolerate any failure
+ // path silently — disk-space display is a comfort feature, not
+ // a critical signal, and the existing DiskSpaceWatcher already
+ // handles the auto-disable-at-1GB safety net.
+ try
+ {
+ var dir = Settings.RecordingDirectory;
+ if (!string.IsNullOrEmpty(dir))
+ {
+ var root = System.IO.Path.GetPathRoot(dir);
+ if (!string.IsNullOrEmpty(root))
+ {
+ var drive = new System.IO.DriveInfo(root);
+ if (drive.IsReady)
+ {
+ var freeBytes = drive.AvailableFreeSpace;
+ RecordingFreeSpace = FormatBytes(freeBytes);
+ IsLowDiskSpace = freeBytes < 10L * 1024 * 1024 * 1024; // <10GB
+ }
+ }
+ }
+ }
+ catch { /* defensive — drive enumeration occasionally throws on network paths */ }
}
else
{
_recordingStartedAt = null;
RecordingElapsed = string.Empty;
+ RecordingFreeSpace = string.Empty;
+ IsLowDiskSpace = false;
}
// Session timer — start on first ISO going live, reset when none are