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
361 lines
14 KiB
QML
361 lines
14 KiB
QML
import QtQuick
|
|
import qs.config
|
|
import qs.services
|
|
|
|
Rectangle {
|
|
id: root
|
|
|
|
property string selectedPage: "home"
|
|
property string query: searchInput.text.trim().toLowerCase()
|
|
signal pageRequested(string page)
|
|
|
|
readonly property var results: SettingsSearch.search(root.query)
|
|
|
|
onQueryChanged: {
|
|
if (sidebarScroll)
|
|
sidebarScroll.contentY = 0;
|
|
}
|
|
|
|
function pageLabel(page: string): string {
|
|
const found = root.destinations.find(item => item.page === page);
|
|
return found ? found.label : "Settings";
|
|
}
|
|
|
|
readonly property var destinations: [
|
|
{ page: "home", label: "Home", icon: "\u{F02DC}" },
|
|
{ page: "appearance", label: "Appearance", icon: "\u{F0E0D}" },
|
|
{ page: "displays", label: "Displays", icon: "\u{F0379}" },
|
|
{ page: "connectivity", label: "Network & Devices", icon: "\u{F08D4}" },
|
|
{ page: "sharing", label: "Sharing", icon: "\u{F04E6}" },
|
|
{ page: "printers", label: "Printers", icon: "\u{F042A}" },
|
|
{ page: "home-phone", label: "Home & Phone", icon: "\u{F02DC}" },
|
|
{ page: "desktop", label: "Desktop & Dock", icon: "\u{F04A4}" },
|
|
{ page: "sound", label: "Sound", icon: "\u{F057E}" },
|
|
{ page: "notifications", label: "Notifications & Focus", icon: "\u{F009A}" },
|
|
{ page: "screen-intelligence", label: "Screen Intelligence", icon: "\u{F05A8}" },
|
|
{ page: "shortcuts", label: "Keyboard", icon: "\u{F030C}" },
|
|
{ page: "mouse", label: "Mouse & Touchpad", icon: "\u{F037D}" },
|
|
{ page: "privacy", label: "Privacy & Security", icon: "\u{F0483}" },
|
|
{ page: "region", label: "Region & Language", icon: "\u{F0AC2}" },
|
|
{ page: "accounts", label: "Online Accounts", icon: "\u{F0004}" },
|
|
{ page: "accessibility", label: "Accessibility", icon: "\u{F0208}" },
|
|
{ page: "power", label: "Power & Lock", icon: "\u{F0425}" },
|
|
{ page: "datetime", label: "Date & Time", icon: "\u{F0954}" },
|
|
{ page: "applications", label: "Applications", icon: "\u{F003B}" },
|
|
{ page: "storage", label: "Storage", icon: "\u{F02CA}" },
|
|
{ page: "snapshots", label: "Snapshots", icon: "\u{F0954}" },
|
|
{ page: "users", label: "Users", icon: "\u{F0004}" },
|
|
{ page: "services", label: "System Health", icon: "\u{F0493}" },
|
|
{ page: "about", label: "About", icon: "\u{F02FD}" }
|
|
]
|
|
|
|
width: 272
|
|
color: Theme.alpha(Theme.bgDark, 0.96)
|
|
border.width: 0
|
|
|
|
Column {
|
|
id: sidebarHeader
|
|
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
anchors.leftMargin: 18
|
|
anchors.rightMargin: 18
|
|
anchors.topMargin: 18
|
|
height: implicitHeight
|
|
spacing: 12
|
|
|
|
Rectangle {
|
|
width: parent.width
|
|
height: 36
|
|
radius: 10
|
|
color: Theme.alpha(Theme.fg, searchArea.containsMouse || searchInput.activeFocus ? 0.105 : 0.065)
|
|
border.width: 1
|
|
border.color: searchInput.activeFocus ? Theme.alpha(Theme.accent, 0.5) : Theme.alpha(Theme.fg, 0.055)
|
|
|
|
Text {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 11
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: "\u{F0349}"
|
|
color: Theme.fgDim
|
|
font.family: Theme.fontMono
|
|
font.pixelSize: 14
|
|
}
|
|
|
|
TextInput {
|
|
id: searchInput
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 35
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: 10
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
color: Theme.fg
|
|
selectionColor: Theme.accent
|
|
selectedTextColor: Theme.bgDark
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSize
|
|
clip: true
|
|
|
|
Text {
|
|
anchors.fill: parent
|
|
visible: !searchInput.text && !searchInput.activeFocus
|
|
text: "Search settings"
|
|
color: Theme.fgMuted
|
|
font: searchInput.font
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: searchArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.IBeamCursor
|
|
onClicked: searchInput.forceActiveFocus()
|
|
}
|
|
}
|
|
}
|
|
|
|
Flickable {
|
|
id: sidebarScroll
|
|
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.top: sidebarHeader.bottom
|
|
anchors.bottom: healthFooter.top
|
|
anchors.leftMargin: 18
|
|
anchors.rightMargin: 18
|
|
anchors.topMargin: 12
|
|
anchors.bottomMargin: 12
|
|
contentWidth: width
|
|
contentHeight: scrollContent.implicitHeight
|
|
flickableDirection: Flickable.VerticalFlick
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
clip: true
|
|
|
|
Column {
|
|
id: scrollContent
|
|
|
|
width: sidebarScroll.width
|
|
|
|
// ── Search results ──────────────────────────────────────────────
|
|
// Typing searches the settings themselves, not page names.
|
|
Column {
|
|
id: searchResults
|
|
|
|
width: parent.width
|
|
spacing: 3
|
|
visible: root.query !== ""
|
|
|
|
Text {
|
|
width: parent.width
|
|
leftPadding: 4
|
|
bottomPadding: 4
|
|
text: root.results.length === 0
|
|
? "Nothing matches"
|
|
: root.results.length + (root.results.length === 1 ? " result" : " results")
|
|
color: Theme.fgMuted
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
}
|
|
|
|
Repeater {
|
|
model: root.results
|
|
|
|
Rectangle {
|
|
id: hit
|
|
|
|
required property var modelData
|
|
|
|
width: parent.width
|
|
height: 44
|
|
radius: 10
|
|
color: hitMouse.containsMouse ? Theme.alpha(Theme.fg, 0.08) : "transparent"
|
|
border.width: 0
|
|
|
|
Column {
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.leftMargin: 12
|
|
anchors.rightMargin: 10
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 1
|
|
|
|
Text {
|
|
width: parent.width
|
|
text: hit.modelData.label
|
|
color: Theme.fg
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSize
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
Text {
|
|
width: parent.width
|
|
text: hit.modelData.kind === "shortcut"
|
|
? hit.modelData.detail
|
|
: root.pageLabel(hit.modelData.page)
|
|
color: hit.modelData.kind === "shortcut" ? Theme.accent : Theme.fgMuted
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
elide: Text.ElideRight
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: hitMouse
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
root.pageRequested(hit.modelData.page);
|
|
searchInput.text = "";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Column {
|
|
id: navigationList
|
|
|
|
width: parent.width
|
|
spacing: 4
|
|
visible: root.query === ""
|
|
|
|
Repeater {
|
|
model: root.destinations
|
|
|
|
Rectangle {
|
|
id: navItem
|
|
required property var modelData
|
|
width: parent.width
|
|
height: 40
|
|
radius: 10
|
|
color: modelData.page === root.selectedPage
|
|
? Theme.alpha(Theme.accent, 0.17)
|
|
: (navMouse.containsMouse ? Theme.alpha(Theme.fg, Theme.hoverAlpha * 0.55) : Theme.alpha(Theme.fg, 0))
|
|
border.width: modelData.page === root.selectedPage ? 1 : 0
|
|
border.color: Theme.alpha(Theme.accent, 0.26)
|
|
|
|
Rectangle {
|
|
width: 2
|
|
height: 18
|
|
radius: 1
|
|
anchors.left: parent.left
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: navItem.modelData.page === root.selectedPage
|
|
gradient: Gradient {
|
|
GradientStop { position: 0; color: Theme.accent }
|
|
GradientStop { position: 1; color: Theme.accentSecondary }
|
|
}
|
|
}
|
|
|
|
Text {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 13
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: 25
|
|
text: navItem.modelData.icon
|
|
color: navItem.modelData.page === root.selectedPage ? Theme.accent : Theme.fgDim
|
|
font.family: Theme.fontMono
|
|
font.pixelSize: 15
|
|
}
|
|
|
|
Text {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 47
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: 9
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: navItem.modelData.label
|
|
color: navItem.modelData.page === root.selectedPage ? Theme.fg : Theme.fgDim
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSize
|
|
font.weight: navItem.modelData.page === root.selectedPage ? Font.Medium : Font.Normal
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
MouseArea {
|
|
id: navMouse
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: root.pageRequested(navItem.modelData.page)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: healthFooter
|
|
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.bottom: parent.bottom
|
|
height: 54
|
|
activeFocusOnTab: true
|
|
color: footerTap.hovered || activeFocus
|
|
? Theme.alpha(Theme.fg, 0.07)
|
|
: Theme.alpha(Theme.bg, 0.35)
|
|
border.width: activeFocus ? 2 : 0
|
|
border.color: Theme.accent
|
|
|
|
function footerText(): string {
|
|
if (Health.checks.length === 0)
|
|
return Health.diagnosticUnavailable ? "Health check unavailable" : "Checking the desktop";
|
|
if (Health.status === "error")
|
|
return "Desktop needs attention";
|
|
if (Health.status === "warning") {
|
|
const count = Health.summary.warnings + Health.summary.errors;
|
|
return count + (count === 1 ? " health observation" : " health observations");
|
|
}
|
|
return "Desktop is healthy";
|
|
}
|
|
|
|
function footerColor(): color {
|
|
if (Health.checks.length === 0)
|
|
return Health.diagnosticUnavailable ? Theme.danger : Theme.fgMuted;
|
|
if (Health.status === "error")
|
|
return Theme.danger;
|
|
if (Health.status === "warning")
|
|
return Theme.warn;
|
|
return Theme.ok;
|
|
}
|
|
|
|
Rectangle {
|
|
width: 7
|
|
height: 7
|
|
radius: 4
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 19
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
color: healthFooter.footerColor()
|
|
}
|
|
|
|
Text {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 36
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: 12
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: healthFooter.footerText()
|
|
color: Theme.fgDim
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
TapHandler {
|
|
id: footerTap
|
|
onTapped: root.pageRequested("services")
|
|
}
|
|
|
|
Keys.onReturnPressed: root.pageRequested("services")
|
|
Keys.onSpacePressed: root.pageRequested("services")
|
|
}
|
|
}
|