Files
Panama/config/dot/quickshell/services/ShellState.qml
T
Gabriel Brown 79b3d5cb85 Close the sweep's last blind spot, and stop shortcuts silently colliding
gapsIn and gapsOut were the only two compositor settings the write sweep had
never verified: Hyprland answers for them in CSS shorthand, "5 5 5 5", and the
sweep had no way to compare that. The preference behind each is a single int
that Hyprland expands to four sides, so a uniform reading compares exactly. A
non-uniform one is not something the preference can express, and is skipped
rather than collapsed to a number it never wrote. 63 of 63 verified live now,
none skipped.

Wallpaper thumbnails are cached. The report that five of them sat at "Loading…"
was a screenshot taken 1.1 seconds after the page opened -- decoding one of
these at tile size takes between 1.2 and 2.6 seconds and about ten start at
once, which the code already said. Measuring it did turn up something real
though: without a cache, scrolling back up pays that decode again for every
tile. The tradeoff is a wallpaper replaced in place showing a stale thumbnail
until restart, which is worth it for a directory of files that are added rather
than edited.

A chord already in use is now named rather than taken: "Super+Q is already
Terminal". Two actions on one chord means whichever Hyprland reads last wins,
which is not a thing to find out later by pressing it. Rebinding a shortcut to
the chord it already holds is correctly not a conflict.

Also: Open Appearance lands on the Windows tab now that the page has tabs,
Storage points at reclaimable container space, and a dock row shows its desktop
id only when two pinned applications share a name -- it is developer text, and
repeating it under fifteen recognisable names made the list harder to scan.

Written down because it cost the shell: QML has no default parameter values, and
`function openSettings(page: string, section: string = "")` fails the entire
configuration rather than the one function -- so the bar and dock went with it,
and 43 contracts failed at once pointing at the same line. qmllint --bare passes
that, which is why the usual check before touching the running shell did not
catch it. openSettingsSection exists as a separate function for that reason.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-20 09:56:02 -04:00

141 lines
5.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 = "";
}
// A section within the page, for pages that have tabs. Consumed once by the
// page and cleared, rather than bound to -- a binding would pin the tab and
// stop anyone changing it by hand once they arrived.
property string settingsSection: ""
function takeSettingsSection(): string {
const section = root.settingsSection;
root.settingsSection = "";
return section;
}
function openSettings(page: string): void {
root.settingsSection = "";
root.showSettings(page);
}
// Opening straight to a tab within a page. A separate function rather than a
// default argument: QML has no default parameter values, and writing one
// fails the whole configuration -- which takes the shell down with it.
function openSettingsSection(page: string, section: string): void {
root.settingsSection = section;
root.showSettings(page);
}
function showSettings(page: string): void {
const allowed = ["home", "appearance", "displays", "connectivity", "home-phone", "desktop", "sound", "gaming", "notifications", "screen-intelligence", "shortcuts", "mouse", "privacy", "region", "accounts", "accessibility", "power", "datetime", "applications", "updates", "storage", "snapshots", "users", "sharing", "firewall", "printers", "containers", "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
}