Panama had no idea whether it was running on a laptop. No upower, no battery, no lid, no AC: hypridle.conf says "This is a desktop" in its own header, and that was true of the code as well as the machine. panama-hw answers hardware questions one at a time, exits 0 or 1, and prints nothing, so scripts, services and contracts all ask the same way. The definition the rest of the laptop work hangs on is one line: clamshell is lid-closed AND an external monitor. A machine with no mains supply at all reports as being on wall power, because a desktop cannot run out of it. The battery service follows Vitals: sysfs through FileView, an availability flag, and no subprocess on the timer. Globbing is the one thing QML cannot do -- a battery is BAT0 or BAT1 or CMB0, mains is AC or ADP1 or ACAD -- so panama-battery resolves the names once and the shell reads the files directly after. Nothing falls back to a plausible zero: a desktop shows no indicator, no card, and no charge limit control where the firmware has no ceiling. Also repairs two contracts that were already failing and had not been noticed, because only the full suite runs them. The dependency scanner treated line-initial variable assignments, case labels, comments and heredoc bodies as commands, and `count`, `host`, `cancel` and `import` are all real binaries on Fedora, so `command -v` could not filter them out. It now drops comments and heredoc bodies and requires a command to be followed by whitespace. Verified it still catches a genuinely undeclared dependency rather than passing quietly. The launcher command contract had not been told about the fourteen commands added earlier today.
207 lines
13 KiB
QML
207 lines
13 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: ({
|
|
"appearance": "appearance",
|
|
"clock": "appearance",
|
|
"vitals": "appearance",
|
|
"battery": "power",
|
|
"typography": "appearance",
|
|
"themes": "appearance",
|
|
"titlebar": "appearance",
|
|
"windows": "appearance",
|
|
"effects": "appearance",
|
|
"wallpaper": "appearance",
|
|
"lockAppearance": "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",
|
|
"gaming": "gaming"
|
|
})
|
|
|
|
// 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: "Synchronize 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: "User account", detail: "Your name, picture, and password", page: "users" },
|
|
{ label: "Profile picture", detail: "The avatar shown on the lock screen and in the Control Center", page: "users" },
|
|
{ label: "Change password", detail: "Set a new password for signing in", page: "users" },
|
|
{ label: "Add a user", detail: "Create another account on this machine", page: "users" },
|
|
{ label: "Automatic login", detail: "Sign in without typing a password", page: "users" },
|
|
{ label: "Administrator", detail: "Which accounts can manage this machine", page: "users" },
|
|
{ label: "Printers", detail: "Add a printer and see what is queued", page: "printers" },
|
|
{ label: "Print queue", detail: "What is waiting to print, and cancelling it", page: "printers" },
|
|
{ label: "Add a printer", detail: "Find a printer on the network or enter its address", page: "printers" },
|
|
{ label: "Default printer", detail: "Where applications print unless told otherwise", page: "printers" },
|
|
{ label: "Firewall", detail: "What another machine can reach on this one", page: "firewall" },
|
|
{ label: "Open ports", detail: "Which ports the firewall permits", page: "firewall" },
|
|
{ label: "Firewall zones", detail: "Which rules apply to each network connection", page: "firewall" },
|
|
{ label: "Exposed services", detail: "What is listening and reachable from the network", page: "firewall" },
|
|
{ label: "Containers", detail: "Rootless podman stacks you run for development", page: "containers" },
|
|
{ label: "SSH keys", detail: "The keys this machine signs in with", page: "ssh-keys" },
|
|
{ label: "SSH agent", detail: "Which keys are held for this session", page: "ssh-keys" },
|
|
{ label: "Known hosts", detail: "Machines this one has connected to before", page: "ssh-keys" },
|
|
{ label: "Public key", detail: "Copy the half you paste into a server", page: "ssh-keys" },
|
|
{ label: "Podman", detail: "Running containers, images and volumes", page: "containers" },
|
|
{ label: "Container logs", detail: "Follow what a container is printing", page: "containers" },
|
|
{ label: "Reclaim container space", detail: "Remove images and volumes nothing uses", page: "containers" },
|
|
{ label: "Published ports", detail: "Which containers are reachable from the network", page: "containers" },
|
|
{ label: "Remote login", detail: "Sign in to this machine over SSH", page: "sharing" },
|
|
{ label: "Remote desktop", detail: "See and control this desktop from elsewhere", page: "sharing" },
|
|
{ label: "Network name", detail: "The name other machines see", page: "sharing" },
|
|
{ label: "File sharing", detail: "Share folders on the network", page: "sharing" },
|
|
{ label: "Game Mode", detail: "What happens while a game is running", page: "gaming" },
|
|
{ label: "Performance overlay", detail: "Frame rate and sensors on top of the game", page: "gaming" },
|
|
{ label: "Proton", detail: "Compatibility tools available to Steam", page: "gaming" },
|
|
{ label: "Graphics card", detail: "Temperature, power draw, and video memory", page: "gaming" },
|
|
{ label: "Software update", detail: "Packages, applications, and firmware", page: "updates" },
|
|
{ label: "Updates", detail: "What is waiting to be installed", page: "updates" },
|
|
{ label: "Firmware", detail: "Updates for the hardware itself", page: "updates" },
|
|
{ label: "Security updates", detail: "Packages that carry a security advisory", page: "updates" },
|
|
{ label: "Automatic updates", detail: "Keep applications current in the background", page: "updates" },
|
|
{ label: "Snapshots", detail: "Points in time you can go back to", page: "snapshots" },
|
|
{ label: "Restore a file", detail: "Take a file or folder back out of a snapshot", page: "snapshots" },
|
|
{ label: "Backups", detail: "Automatic snapshots of the system and your home folder", page: "snapshots" },
|
|
{ label: "File history", detail: "Earlier versions of your files", page: "snapshots" },
|
|
{ label: "Undo a change", detail: "Put back a file as it was at an earlier point", page: "snapshots" },
|
|
{ label: "Free space", detail: "How full each drive and filesystem is", page: "storage" },
|
|
{ label: "Disk usage", detail: "What is using the space on this machine", page: "storage" },
|
|
{ label: "Drive health", detail: "Temperature, hours powered on, and reported warnings", page: "storage" },
|
|
{ label: "Removable drives", detail: "Unmount a USB drive or memory card safely", page: "storage" },
|
|
{ label: "Encryption", detail: "Whether the filesystem is encrypted", page: "storage" },
|
|
{ label: "Output volume", detail: "Choose the output device and its level", page: "sound" },
|
|
{ label: "Input volume", detail: "Choose the microphone and its level", page: "sound" },
|
|
{ label: "Per-application volume", detail: "Set the level of each application separately", page: "sound" },
|
|
{ label: "Event sounds", detail: "Play alerts and interface event sounds", page: "sound" },
|
|
{ label: "Saved passwords", detail: "The login keyring and what is stored in it", page: "privacy" },
|
|
{ label: "Camera and microphone", detail: "Which applications may use them", page: "privacy" },
|
|
{ label: "Screen sharing", detail: "Which applications may capture the screen", page: "privacy" },
|
|
{ label: "File history and trash", detail: "What is remembered and when it is cleared", page: "privacy" },
|
|
{ label: "Device security", detail: "Secure boot and firmware protections", page: "privacy" },
|
|
{ label: "Language", detail: "The system language, applied to programs started afterwards", page: "region" },
|
|
{ label: "Regional formats", detail: "How dates, times, and numbers are written", page: "region" },
|
|
{ label: "Online accounts", detail: "Sign in to mail, calendar, and contacts", page: "accounts" },
|
|
{ label: "Home Assistant", detail: "Connect the desktop to a Home Assistant server", page: "home-phone" },
|
|
{ label: "Phone", detail: "Pair a phone for messages and notifications", page: "home-phone" },
|
|
{ label: "System information", detail: "Kernel, distribution, and hardware", page: "about" },
|
|
{ label: "Desktop version", detail: "Which Hyprland and Quickshell this session runs", page: "about" },
|
|
{ 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" },
|
|
{ label: "Lock screen background", detail: "Choose a blurred desktop, wallpaper, or solid color", page: "appearance" },
|
|
{ label: "Password field", detail: "Choose whether the empty lock-screen field stays visible", page: "appearance" },
|
|
{ label: "Per-display wallpaper", detail: "Assign a different image to each connected display", page: "appearance" },
|
|
{ label: "Arrange displays", detail: "Drag connected displays into their physical positions", page: "displays" },
|
|
{ label: "Monitor position", detail: "Set where each display sits in the desktop", page: "displays" },
|
|
{ label: "Primary display", detail: "Choose the display that anchors the desktop", page: "displays" },
|
|
{ label: "Theme profiles", detail: "Switch between Moon, Moon Rose, Day, and saved themes", page: "appearance" },
|
|
{ label: "Advanced accent", detail: "Adjust primary and secondary hue, saturation, and value", page: "appearance" },
|
|
{ label: "Pick colour from screen", detail: "Sample an accent colour with hyprpicker", page: "appearance" }
|
|
]
|
|
|
|
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 optionLabels = (entry.options ?? []).map(option => option.label).join(" ");
|
|
const haystack = `${entry.label} ${entry.detail ?? ""} ${entry.group} ${optionLabels}`.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. An exact enum option also leads: "slideshow" is a
|
|
// mode choice, so Wallpaper mode belongs above the interval row that
|
|
// merely explains it.
|
|
return results.sort((a, b) => {
|
|
const aSpec = PreferenceSchema.entries.find(entry => entry.label === a.label);
|
|
const bSpec = PreferenceSchema.entries.find(entry => entry.label === b.label);
|
|
const ao = (aSpec?.options ?? []).some(option => option.label.toLowerCase() === needle);
|
|
const bo = (bSpec?.options ?? []).some(option => option.label.toLowerCase() === needle);
|
|
if (ao !== bo)
|
|
return ao ? -1 : 1;
|
|
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);
|
|
}
|
|
}
|