Files
Panama/config/dot/quickshell/services/ShellState.qml
T
Gabriel Brown 1f40f8e136 Add a snapshot manager, and start covering home
The machine already had snapper running hourly on btrfs, so the tool was
never missing. What was missing is that snapper's only configuration
covered / -- and /home is a separate subvolume with no configuration at
all. Six hundred and forty-three snapshots existed and not one of them
contained a document. Anyone reaching for file history would have found
their system and none of their files.

/home now has a configuration on the same hourly timeline, with
deliberately conservative retention: Steam's 1.2 TB lives on that
subvolume and churns on every game update, so keeping five hourly and
seven daily bounds what those updates can pin.

Per volume, because on this machine "one is covered and the important
one is not" was the news, and a timeline opening on system snapshots
would have buried it. Inside a volume the timeline is the familiar view:
points in time, newest first, each openable as a folder tree to take a
file out of.

Restoring sets the current version aside as .before-restore-N rather
than overwriting it. A restore that destroys the thing you were about to
compare against is how someone loses the work they were trying to save.

Rollback is deliberately absent. snapper's rollback changes the btrfs
default subvolume, and this system's fstab pins subvol= explicitly,
which overrides it -- so a rollback would report success and change
nothing after a reboot. A recovery feature that silently does nothing is
worse than not having one, and making it work means editing fstab and
the bootloader, whose failure cannot be repaired from inside the
desktop.

Per-snapshot size is reported as not measured, because measuring it
needs btrfs quotas that cost performance on every write. Free space is
shown instead, which is the number that decides anything.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 16:11:50 -04:00

117 lines
4.7 KiB
QML

pragma Singleton
// ─────────────────────────────────────────────────────────────────────────────
// Shared UI state.
//
// Every overlay in the shell is mutually exclusive with the others -- opening
// the overview should close the quick settings, and so on. Centralising that
// here means no module needs a reference to any other module, and the IPC
// handlers in shell.qml have exactly one thing to talk to.
// ─────────────────────────────────────────────────────────────────────────────
import Quickshell
import QtQuick
import qs.config
Singleton {
id: root
// Exactly one of these may be non-empty at a time.
// "" | "overview" | "quicksettings" | "notifications" | "clipboard" | "capture" | "activity" | "powermenu"
property string activeOverlay: ""
readonly property bool overviewOpen: activeOverlay === "overview"
readonly property bool quickSettingsOpen: activeOverlay === "quicksettings"
readonly property bool notificationsOpen: activeOverlay === "notifications"
readonly property bool clipboardOpen: activeOverlay === "clipboard"
readonly property bool captureOpen: activeOverlay === "capture"
readonly property bool activityOpen: activeOverlay === "activity"
readonly property bool powerMenuOpen: activeOverlay === "powermenu"
// Settings is a normal application window rather than a transient overlay.
// It can stay open while Quick Settings or the notification center appears.
property bool settingsOpen: false
property string settingsPage: "home"
readonly property bool anyOverlayOpen: activeOverlay !== ""
// The date menu keeps calendar context fixed while its right pane switches
// between the schedule, persistent activity and message history.
property string dateMenuPage: "agenda"
// 0 means "use the currently focused workspace". A positive value lets a
// contextual surface, such as Focus, open Mission Control at its target.
property int overviewWorkspaceId: 0
property string overviewQuery: ""
function toggle(name: string): void {
root.activeOverlay = (root.activeOverlay === name) ? "" : name;
}
function open(name: string): void {
root.activeOverlay = name;
}
function openDateMenu(page: string): void {
root.dateMenuPage = root.normalizedDateMenuPage(page);
root.activeOverlay = "notifications";
}
function toggleDateMenu(page: string): void {
const target = root.normalizedDateMenuPage(page);
if (root.activeOverlay === "notifications" && root.dateMenuPage === target) {
root.activeOverlay = "";
return;
}
root.dateMenuPage = target;
root.activeOverlay = "notifications";
}
function normalizedDateMenuPage(page: string): string {
return ["agenda", "ongoing", "notifications"].indexOf(page) >= 0 ? page : "agenda";
}
function openOverview(workspaceId: int): void {
root.overviewWorkspaceId = Math.max(0, workspaceId);
root.overviewQuery = "";
root.activeOverlay = "overview";
}
function searchOverview(query: string): void {
root.overviewWorkspaceId = 0;
root.overviewQuery = query;
root.activeOverlay = "overview";
}
function close(): void {
if (root.activeOverlay === "overview") {
root.overviewWorkspaceId = 0;
root.overviewQuery = "";
}
root.activeOverlay = "";
}
function openSettings(page: string): void {
const allowed = ["home", "appearance", "displays", "connectivity", "home-phone", "desktop", "sound", "notifications", "screen-intelligence", "shortcuts", "mouse", "privacy", "region", "accounts", "accessibility", "power", "datetime", "applications", "storage", "snapshots", "users", "sharing", "printers", "services", "about"];
root.settingsPage = allowed.indexOf(page) >= 0 ? page : "home";
DesktopPreferences.set("lastPage", root.settingsPage);
root.settingsOpen = true;
}
function toggleSettings(): void {
if (root.settingsOpen) {
root.closeSettings();
return;
}
root.openSettings(DesktopPreferences.get("lastPage") || "home");
}
function closeSettings(): void {
root.settingsOpen = false;
}
// Set by Dock.qml so the bar can avoid fighting it for pointer grabs, and
// read by the capture overlay so the dock isn't in the screenshot.
property bool dockRevealed: false
}