Nothing showed what was using the drive, and removable media was handled by a tray helper with no surface in Settings at all. One scroll rather than tabs: space above, the device below. Every other settings page is a scrolling card stack, and a tab would not be deep-linkable from the launcher command or from search. Three things the page has to get right, each now pinned by a contract, because each is a way it could quietly lie. / and /home are one btrfs filesystem sharing one pool of free space, and a page that copies df shows double the free space that exists. zram is a block device and is not storage; counting it as a drive overstates this machine by 8 GB. Unmount and eject refuse anything not on a removable drive, because the UI is what asks and a UI can be wrong. The cheap read -- layout, usage, health -- runs when the page opens, at around 90ms. Measuring what is filling the drive means walking every file, so it happens on request and says so rather than showing an empty list that reads as "nothing here". Partitioning and formatting are deliberately absent. A settings pane is the wrong place to put erasing a disk two clicks deep; the page opens GNOME Disks for that. Adding the page found a fourth hard-coded page list in ShellState. A page missing from it does not error -- openSettings() falls back to "home", so the launcher opens the wrong page and logs nothing. A registry contract now holds the three lists together. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
117 lines
4.7 KiB
QML
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", "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
|
|
}
|