Files
Panama/config/dot/quickshell/services/SettingsSearch.qml
T
Gabriel Brown 8be3fc2fdd Right-click a bar widget to open its settings
Four places in the entire shell could reach Settings. The bar, where a
person looks first, was not one of them -- and Pill has routed
right-click to a secondaryActivated signal all along, which nothing
connected, so the gesture did nothing on every widget in the bar.

Each widget now opens the page that owns its settings: the clock and the
calendar reminder open Date & Time, weather opens Home, the vitals
readout opens Appearance, the status glyphs open Network & Devices, the
media readout opens Sound, and the privacy indicator opens Privacy &
Security. Left-click behaviour is untouched.

Two routing bugs found while picking those destinations, both of the
same kind and both invisible from the code, since each page reads
perfectly well on its own:

  weather routed to Appearance while every weather control lives on
  Home, so searching "temperature unit" opened a page without it.

  vitals routed to Appearance, but the refresh interval sat on Home
  while the toggles it governs sat on Appearance -- one concept split
  across two pages, which is exactly what the ownership rule forbids.
  The interval now sits beside the toggles and Home's stub card is gone.

The jump contract guards the failure mode these share. openSettings()
falls back to Home for an unknown page, sensibly and completely
silently, so a typo or a later rename turns a right-click into "opens
the wrong page" with nothing logged. It also fails a Pill-based bar
widget that leaves right-click unconnected, since that is how the
gesture came to be inert everywhere in the first place.

A third instance of the routing bug is still open: followMouse and
pointerSensitivity sit in the input group, which routes to Keyboard,
while both render on Mouse. Fixing it is a two-line group change in
PreferenceSchema.qml, which codex currently owns, so the contract that
catches all three lands with that fix rather than red.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-18 14:46:04 -04:00

123 lines
5.2 KiB
QML

pragma Singleton
// Search across every setting, not just page names.
//
// The sidebar's field used to filter the twelve page labels, so "gaps",
// "wallpaper", and "repeat delay" all found nothing — which is precisely the
// thing that makes a settings app feel smaller than it is. This indexes the
// schema itself, so any setting is reachable by typing what it does, and a new
// schema entry becomes searchable with no change here.
//
// Shortcuts are indexed too: "screenshot" should find the key that takes one.
import Quickshell
import QtQuick
import qs.config
Singleton {
id: root
// SettingsSearch is part of the always-constructed sidebar. Touching the
// desktop-style service here gives application preferences their startup
// replay even when Appearance is not the page that opens first.
Component.onCompleted: DesktopStyle.ensureStarted()
// Which page shows the settings in a given schema group. A group with no
// entry here still appears in results and routes to Home rather than being
// dropped, so adding a group can never make a setting unreachable.
readonly property var groupPages: ({
"clock": "appearance",
"vitals": "appearance",
"typography": "appearance",
"themes": "appearance",
"titlebar": "appearance",
"windows": "appearance",
"effects": "appearance",
"wallpaper": "appearance",
"dock": "desktop",
"focus": "desktop",
"display": "displays",
"nightLight": "displays",
"idle": "power",
"accessibility": "accessibility",
"input": "shortcuts",
"pointer": "mouse",
"touchpad": "mouse",
"multitasking": "desktop",
"edges": "desktop",
"master": "desktop",
"notices": "desktop",
"weather": "home",
"notifications": "notifications",
"capture": "screen-intelligence"
})
// Settings that are real but have no schema entry, because the system owns
// them rather than Panama. Without these, searching "timezone" would fail
// on a settings app that plainly has one.
readonly property var extraEntries: [
{ label: "Timezone", detail: "Set the system timezone", page: "datetime" },
{ label: "Network time", detail: "Synchronise the clock with a time server", page: "datetime" },
{ label: "Wi-Fi", detail: "Managed by GNOME Settings", page: "connectivity" },
{ label: "Bluetooth", detail: "Managed by GNOME Settings", page: "connectivity" },
{ label: "Printers", detail: "Managed by GNOME Settings", page: "connectivity" },
{ label: "Default applications", detail: "Browser, mail, files", page: "applications" },
{ label: "Restore defaults", detail: "Return every Panama setting to its shipped value", page: "desktop" },
{ label: "Keyboard shortcuts", detail: "Every shortcut the compositor has bound", page: "shortcuts" },
{ label: "System Health", detail: "Check Panama services, integrations, tools, and recovery actions", page: "services" },
{ label: "Copy health report", detail: "Copy a redacted Panama doctor report", page: "services" }
]
function pageFor(group: string): string {
return root.groupPages[group] ?? "home";
}
// [{ label, detail, page, kind }] for a query. Empty query yields nothing:
// the sidebar shows its normal navigation in that case.
function search(query: string): var {
const needle = String(query).trim().toLowerCase();
if (needle === "")
return [];
const results = [];
const seen = {};
function add(label, detail, page, kind) {
const dedupe = `${kind}:${label}:${page}`;
if (seen[dedupe])
return;
seen[dedupe] = true;
results.push({ label: label, detail: detail, page: page, kind: kind });
}
for (const entry of PreferenceSchema.entries) {
if (entry.internal)
continue;
const haystack = `${entry.label} ${entry.detail ?? ""} ${entry.group}`.toLowerCase();
if (haystack.indexOf(needle) >= 0)
add(entry.label, entry.detail ?? "", root.pageFor(entry.group), "setting");
}
for (const entry of root.extraEntries) {
if (`${entry.label} ${entry.detail}`.toLowerCase().indexOf(needle) >= 0)
add(entry.label, entry.detail, entry.page, "setting");
}
for (const bind of Keybinds.binds) {
if (bind.description.toLowerCase().indexOf(needle) >= 0)
add(bind.description, bind.chord, "shortcuts", "shortcut");
}
// Exact prefix matches first: typing "blur" should put "Blur" above
// "Blur radius", and both above a setting that merely mentions blur in
// its explanation.
return results.sort((a, b) => {
const al = a.label.toLowerCase();
const bl = b.label.toLowerCase();
const ap = al === needle ? 0 : (al.indexOf(needle) === 0 ? 1 : 2);
const bp = bl === needle ? 0 : (bl.indexOf(needle) === 0 ? 1 : 2);
return ap !== bp ? ap - bp : al.localeCompare(bl);
}).slice(0, 40);
}
}