Working towards parity with GNOME Settings, which splits pointing devices into their own panel. Panama had pointer speed and focus-follows buried under a page called "Input & Shortcuts", and had nothing at all for scroll direction, acceleration profile, scroll speed, left-handed buttons, or any touchpad setting -- all of which could only be changed by editing hypr/input.lua by hand, which is the thing this app exists to stop. Every new mapping was read back off the running compositor rather than assumed, and two were not what they look like: touchpad drag lock is an int with three states, not a switch, and scroll factors are floats even at their default of exactly 1. Getting either wrong makes every write to that setting look rejected. The shape contract now covers 35 mapped options, up from 23. The touchpad card renders only when a touchpad is attached, which is what InputDevices is for. On a desktop it would be worse than useless: every switch on it would appear to work, because the preference is stored and Hyprland accepts an option for a device class it has no member of, so the settings would silently affect nothing. Keyboard layout was read-only text, justified by a note saying changes needed a compositor reload. That is not true in 0.56.2 -- setting input:kb_variant through hl.config re-keymaps attached keyboards immediately, verified by watching active_keymap on a real keyboard change to "English (US, intl., with dead keys)" and back. So layout, variant, and options are now real controls, joined by a TextEntryRow that commits on Enter or focus loss rather than per keystroke, since half a layout name is a valid string meaning something else. Rejected input is shown as rejected rather than sanitised: these strings are serialised into an hl.config payload, where stripping an unexpected character would turn a typo into a different working setting. Verified each new pointer option applies and reverts against the live compositor. Schema, search, commit/reset, and system contracts pass. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
109 lines
4.4 KiB
QML
109 lines
4.4 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
|
|
|
|
// 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",
|
|
"windows": "appearance",
|
|
"effects": "appearance",
|
|
"wallpaper": "appearance",
|
|
"dock": "desktop",
|
|
"focus": "desktop",
|
|
"display": "displays",
|
|
"nightLight": "displays",
|
|
"idle": "power",
|
|
"accessibility": "accessibility",
|
|
"input": "shortcuts",
|
|
"pointer": "mouse",
|
|
"touchpad": "mouse",
|
|
"weather": "appearance",
|
|
"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" }
|
|
]
|
|
|
|
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);
|
|
}
|
|
}
|