Generate the settings reference from the schema
Every other form of documentation here has drifted at least once today: search routing that pointed at a page not containing the setting, a contract that pinned the bug the same commit fixed, and a comment in shell.qml that failed to stop me making the exact mistake it described. Prose describing 127 settings would drift the day after it was written. So docs/settings.md is generated, and a contract fails the moment the committed copy stops matching the schema. The document cannot be wrong for longer than it takes to run the suite. It reads the schema by parsing rather than importing, since there is no QML interpreter here and requiring a compositor to build documentation would be worse. That parser is the risk, so it FAILS LOUDLY: if it stops recognising the file it exits non-zero with the reason and writes nothing, because a partial reference is worse than a stale one -- stale is caught by --check, partial reads as complete. Verified: with the entry pattern broken it reports "only 0 entries parsed" and leaves the committed file untouched. The contract also proves --check actually compares content, by appending a line and confirming it fails, rather than trusting a command that returns success to mean anything. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
This commit is contained in:
+214
@@ -0,0 +1,214 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Generate the settings reference from PreferenceSchema.qml.
|
||||
|
||||
Every other form of documentation in this repository has drifted at least once:
|
||||
routing that pointed at a page not containing the setting, contracts that pinned
|
||||
the bug they were meant to prevent, and a comment that failed to stop the person
|
||||
who read it making the exact mistake it warned about. Prose describing 137
|
||||
settings would drift the day after it was written.
|
||||
|
||||
So this is generated, and a contract fails when the committed copy no longer
|
||||
matches what the schema says. The document cannot be wrong for longer than it
|
||||
takes to run the suite.
|
||||
|
||||
Usage:
|
||||
panama-settings-docs write docs/settings.md
|
||||
panama-settings-docs --check exit 1 if the committed copy is stale
|
||||
panama-settings-docs --stdout print without writing
|
||||
|
||||
Parsing rather than importing: the schema is QML, there is no QML interpreter
|
||||
here, and a regex reader that FAILS LOUDLY when it stops recognising the file is
|
||||
better than a dependency on a shell that has to start a compositor.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
# scripts/ -> quickshell/ -> dot/ -> config/ -> repo root. resolve() first, so
|
||||
# this still works when invoked through the ~/.config/quickshell symlink.
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[4]
|
||||
SCHEMA = ROOT / "config/dot/quickshell/config/PreferenceSchema.qml"
|
||||
SEARCH = ROOT / "config/dot/quickshell/services/SettingsSearch.qml"
|
||||
OUTPUT = ROOT / "docs/settings.md"
|
||||
|
||||
# Page id -> the name a person sees in the sidebar.
|
||||
PAGE_TITLES = {
|
||||
"home": "Home", "appearance": "Appearance", "displays": "Displays",
|
||||
"connectivity": "Network & Devices", "home-phone": "Home & Phone",
|
||||
"desktop": "Desktop & Dock", "sound": "Sound",
|
||||
"notifications": "Notifications & Focus",
|
||||
"screen-intelligence": "Screen Intelligence", "shortcuts": "Keyboard",
|
||||
"mouse": "Mouse & Touchpad", "privacy": "Privacy & Security",
|
||||
"region": "Region & Language", "accounts": "Online Accounts",
|
||||
"accessibility": "Accessibility", "power": "Power & Lock",
|
||||
"datetime": "Date & Time", "applications": "Applications",
|
||||
"services": "System Health", "about": "About",
|
||||
}
|
||||
|
||||
|
||||
class SchemaError(RuntimeError):
|
||||
"""The schema stopped looking the way this reader expects."""
|
||||
|
||||
|
||||
def read_routes():
|
||||
"""group -> page id, from SettingsSearch."""
|
||||
text = SEARCH.read_text()
|
||||
block = re.search(r"groupPages:\s*\(\{(.*?)\}\)", text, re.S)
|
||||
if not block:
|
||||
raise SchemaError("could not find groupPages in SettingsSearch.qml")
|
||||
routes = dict(re.findall(r'"([a-zA-Z]+)"\s*:\s*"([a-z-]+)"', block.group(1)))
|
||||
if not routes:
|
||||
raise SchemaError("groupPages matched but contained no routes")
|
||||
return routes
|
||||
|
||||
|
||||
def read_entries():
|
||||
"""Every schema entry, as dicts, in declaration order."""
|
||||
text = SCHEMA.read_text()
|
||||
|
||||
# Each entry begins at `key:` and ends at the closing brace of its block.
|
||||
# Nested braces (options, hypr) are skipped by counting depth.
|
||||
entries = []
|
||||
for match in re.finditer(r'\{\s*\n\s*key:\s*"([^"]+)"', text):
|
||||
name = match.group(1)
|
||||
start = match.start()
|
||||
depth = 0
|
||||
end = None
|
||||
for index in range(start, len(text)):
|
||||
if text[index] == "{":
|
||||
depth += 1
|
||||
elif text[index] == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
end = index
|
||||
break
|
||||
if end is None:
|
||||
raise SchemaError(f"entry {name!r} is not brace-balanced")
|
||||
body = text[start:end]
|
||||
|
||||
def scalar(field):
|
||||
found = re.search(rf'\b{field}:\s*("([^"]*)"|[^,\n]+)', body)
|
||||
if not found:
|
||||
return None
|
||||
return (found.group(2) if found.group(2) is not None
|
||||
else found.group(1).strip())
|
||||
|
||||
entry = {
|
||||
"key": name,
|
||||
"type": scalar("type"),
|
||||
"default": scalar("def"),
|
||||
"group": scalar("group"),
|
||||
"label": scalar("label"),
|
||||
"detail": scalar("detail"),
|
||||
"unit": scalar("unit"),
|
||||
"min": scalar("min"),
|
||||
"max": scalar("max"),
|
||||
"internal": scalar("internal") == "true",
|
||||
"option": scalar("option"),
|
||||
"options": re.findall(r'\{\s*value:\s*("?[^",]+)"?,\s*label:\s*"([^"]+)"', body),
|
||||
}
|
||||
if not entry["type"] or not entry["group"]:
|
||||
raise SchemaError(f"entry {name!r} is missing a type or group")
|
||||
entries.append(entry)
|
||||
|
||||
if len(entries) < 50:
|
||||
raise SchemaError(
|
||||
f"only {len(entries)} entries parsed; the schema has far more, so "
|
||||
"this reader no longer recognises the file"
|
||||
)
|
||||
return entries
|
||||
|
||||
|
||||
def render(entries, routes):
|
||||
lines = [
|
||||
"# Settings reference",
|
||||
"",
|
||||
"**Generated from `config/dot/quickshell/config/PreferenceSchema.qml`.**",
|
||||
"Do not edit this file. Run `quickshell/scripts/panama-settings-docs`",
|
||||
"after changing the schema; a contract fails when this copy is stale.",
|
||||
"",
|
||||
f"{len([e for e in entries if not e['internal']])} settings across "
|
||||
f"{len({e['group'] for e in entries if not e['internal']})} groups. "
|
||||
f"{len([e for e in entries if e['option']])} of them are applied to the "
|
||||
"compositor and confirmed by reading the value back.",
|
||||
"",
|
||||
]
|
||||
|
||||
by_group = {}
|
||||
for entry in entries:
|
||||
by_group.setdefault(entry["group"], []).append(entry)
|
||||
|
||||
for group in sorted(by_group):
|
||||
visible = [e for e in by_group[group] if not e["internal"]]
|
||||
if not visible:
|
||||
continue
|
||||
page = routes.get(group)
|
||||
title = PAGE_TITLES.get(page, page or "—")
|
||||
lines += [f"## {group}", "", f"Found on **{title}**.", ""]
|
||||
lines += ["| Setting | Default | What it does |", "|---|---|---|"]
|
||||
for entry in visible:
|
||||
default = entry["default"] or "—"
|
||||
if entry["unit"]:
|
||||
default = f"{default} {entry['unit']}"
|
||||
if entry["options"]:
|
||||
choices = ", ".join(label for _value, label in entry["options"])
|
||||
detail = f"{entry['detail'] or ''} Choices: {choices}."
|
||||
else:
|
||||
detail = entry["detail"] or ""
|
||||
if entry["min"] is not None and entry["max"] is not None:
|
||||
# Schema details are written without trailing punctuation, so
|
||||
# one is added before appending a second sentence.
|
||||
if detail and not detail.rstrip().endswith((".", "!", "?")):
|
||||
detail = detail.rstrip() + "."
|
||||
detail += f" Range {entry['min']}–{entry['max']}."
|
||||
label = entry["label"] or entry["key"]
|
||||
compositor = f" `{entry['option']}`" if entry["option"] else ""
|
||||
lines.append(
|
||||
f"| **{label}**<br>`{entry['key']}`{compositor} | {default} | "
|
||||
f"{detail.strip()} |"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--check", action="store_true")
|
||||
parser.add_argument("--stdout", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
rendered = render(read_entries(), read_routes())
|
||||
except SchemaError as error:
|
||||
# Loudly, and without writing. A partial reference is worse than a stale
|
||||
# one: stale is caught by --check, partial reads as complete.
|
||||
print(f"panama-settings-docs: {error}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
if args.stdout:
|
||||
print(rendered, end="")
|
||||
return 0
|
||||
|
||||
if args.check:
|
||||
if not OUTPUT.exists():
|
||||
print("panama-settings-docs: docs/settings.md has never been generated",
|
||||
file=sys.stderr)
|
||||
return 1
|
||||
if OUTPUT.read_text() != rendered:
|
||||
print("panama-settings-docs: docs/settings.md is stale; re-run this "
|
||||
"script and commit the result", file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT.write_text(rendered)
|
||||
print(f"wrote {OUTPUT.relative_to(ROOT)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,317 @@
|
||||
# Settings reference
|
||||
|
||||
**Generated from `config/dot/quickshell/config/PreferenceSchema.qml`.**
|
||||
Do not edit this file. Run `quickshell/scripts/panama-settings-docs`
|
||||
after changing the schema; a contract fails when this copy is stale.
|
||||
|
||||
127 settings across 26 groups. 67 of them are applied to the compositor and confirmed by reading the value back.
|
||||
|
||||
## accessibility
|
||||
|
||||
Found on **Accessibility**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Magnifier**<br>`magnifierFactor` `cursor:zoom_factor` | 1.0 | Magnifies the screen around the pointer. 1.0 is off. Range 1.0–5.0. |
|
||||
| **Magnifier follows in steps**<br>`magnifierRigid` `cursor:zoom_rigid` | false | Moves the magnified view in increments rather than gliding with the pointer |
|
||||
| **Dim inactive windows**<br>`dimInactive` `decoration:dim_inactive` | false | Darkens every window except the focused one, so the active window is unmistakable |
|
||||
| **Dim amount**<br>`dimStrength` `decoration:dim_strength` | 0.5 | How much darker unfocused windows are. Range 0.05–0.9. |
|
||||
| **Pointer size**<br>`cursorSize` | 24 px | Applies to the compositor and to applications. Range 16–64. |
|
||||
| **Text size**<br>`textScale` | 1.0 | Scales interface text everywhere; 1.00 is the design size. Range 0.75–2.0. |
|
||||
|
||||
## appearance
|
||||
|
||||
Found on **Appearance**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Appearance**<br>`colorScheme` | dark | Light and dark share one identity, not two themes Choices: Dark, Light. |
|
||||
| **Accent color**<br>`accentName` | blue | Drives the focused window border, the bar hairline, and every active state Choices: Prism blue, Orchid, Teal, Green, Amber, Orange, Rose, Slate. |
|
||||
|
||||
## capture
|
||||
|
||||
Found on **Screen Intelligence**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Screenshot folder**<br>`screenshotDir` | Pictures/Screenshots | Folder under your home directory for screenshots Choices: Pictures / Screenshots, Pictures, Desktop. |
|
||||
| **Recording folder**<br>`recordingDir` | Videos/Recordings | Folder under your home directory for screen recordings Choices: Videos / Recordings, Videos, Desktop. |
|
||||
| **Recording encoder**<br>`recorderArgs` | -c h264_vaapi -d /dev/dri/renderD128 | Hardware encoding keeps recording off the processor while gaming Choices: VAAPI H.264, VAAPI HEVC, CPU x264. |
|
||||
|
||||
## clock
|
||||
|
||||
Found on **Appearance**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **24-hour time**<br>`use24Hour` | false | Use 18:30 instead of 6:30 PM |
|
||||
| **Show seconds**<br>`showSeconds` | true | Keep a precise clock in the center of the bar |
|
||||
| **Show weekday**<br>`showWeekday` | true | Include the abbreviated weekday before the date |
|
||||
|
||||
## display
|
||||
|
||||
Found on **Displays**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Game-aware HDR**<br>`autoHdr` `render:cm_auto_hdr` | true | Hand HDR to fullscreen games while the desktop stays SDR |
|
||||
| **Variable refresh rate**<br>`vrrPolicy` `misc:vrr` | 3 | Matches the display's refresh rate to what is on screen Choices: Off, Always on, Fullscreen only, Fullscreen games. |
|
||||
| **Direct scanout**<br>`directScanoutPolicy` `render:direct_scanout` | 2 | Lets fullscreen content bypass compositing Choices: Off, Always on, Automatic. |
|
||||
|
||||
## dock
|
||||
|
||||
Found on **Desktop & Dock**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Automatically hide the Dock**<br>`dockAutohide` | true | Reveal it at the bottom edge when a workspace is occupied |
|
||||
| **Reveal delay**<br>`dockRevealDelayMs` | 0 ms | Zero reveals the Dock the instant the pointer reaches the edge. Range 0–1000. |
|
||||
| **Hide delay**<br>`dockHideDelayMs` | 250 ms | Prevents flicker when crossing between icons. Range 0–2000. |
|
||||
| **Pinned applications**<br>`dockPinned` | [ | Applications that stay in the Dock whether or not they are running |
|
||||
|
||||
## edges
|
||||
|
||||
Found on **Desktop & Dock**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Resize by dragging the border**<br>`resizeOnBorder` `general:resize_on_border` | true | Drag a window's edge to resize it, instead of only with the keyboard |
|
||||
| **Border grab area**<br>`borderGrabArea` `general:extend_border_grab_area` | 15 px | How far outside the border still counts as grabbing it. Larger is easier to hit. Range 0–40. |
|
||||
| **Show the resize cursor**<br>`hoverIconOnBorder` `general:hover_icon_on_border` | true | Change the pointer when it is over a resizable border |
|
||||
| **Snap distance between windows**<br>`snapWindowGap` `general:snap:window_gap` | 10 px | How close two floating windows must be before they snap together. Range 0–60. |
|
||||
| **Snap distance to screen edges**<br>`snapMonitorGap` `general:snap:monitor_gap` | 10 px | How close a floating window must be to an edge before it snaps to it. Range 0–60. |
|
||||
| **Snapping respects gaps**<br>`snapRespectGaps` `general:snap:respect_gaps` | false | Snapped windows keep the configured gap instead of touching |
|
||||
|
||||
## effects
|
||||
|
||||
Found on **Appearance**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Blur**<br>`blurEnabled` `decoration:blur:enabled` | true | Blur the desktop behind translucent surfaces |
|
||||
| **Blur radius**<br>`blurSize` `decoration:blur:size` | 8 | Larger is softer and costs more frame time. Range 1–20. |
|
||||
| **Blur passes**<br>`blurPasses` `decoration:blur:passes` | 3 | More passes look smoother and cost more frame time. Range 1–5. |
|
||||
| **Window shadows**<br>`shadowEnabled` `decoration:shadow:enabled` | true | Lift windows off the wallpaper with a soft shadow |
|
||||
| **Shadow size**<br>`shadowRange` `decoration:shadow:range` | 20 px | How far the shadow spreads from the window edge. Range 0–60. |
|
||||
| **Hard-edged shadow**<br>`shadowSharp` `decoration:shadow:sharp` | false | A crisp shadow instead of a soft falloff |
|
||||
| **Shadow falloff**<br>`shadowRenderPower` `decoration:shadow:render_power` | 3 | How sharply the shadow fades out. Higher is tighter to the window. Range 1–4. |
|
||||
| **Focus glow**<br>`glowEnabled` `decoration:glow:enabled` | true | A faint halo behind the focused window |
|
||||
| **Glow size**<br>`glowRange` `decoration:glow:range` | 8 px | Kept small deliberately: the gradient border is the signature. Range 0–30. |
|
||||
| **Animations**<br>`animationsEnabled` `animations:enabled` | true | Window, workspace, and panel motion |
|
||||
|
||||
## focus
|
||||
|
||||
Found on **Desktop & Dock**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Focus session length**<br>`focusDurationMinutes` | 45 min | How long a focus session runs before it ends itself. Range 5–180. |
|
||||
|
||||
## idle
|
||||
|
||||
Found on **Power & Lock**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Turn the screen off after**<br>`screenBlankMinutes` | 5 min | Blanks the display; nothing is locked yet. Range 0–120. |
|
||||
| **Lock the screen after**<br>`lockMinutes` | 10 min | Counted from when the session went idle, not from blanking. Range 0–240. |
|
||||
| **Suspend after**<br>`suspendMinutes` | 0 min | This is a desktop, so Panama ships with automatic suspend off. Range 0–480. |
|
||||
| **Lock before sleeping**<br>`lockOnSleep` | true | Requires your password when the machine wakes |
|
||||
|
||||
## input
|
||||
|
||||
Found on **Keyboard**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Keyboard layout**<br>`keyboardLayout` `input:kb_layout` | us | XKB layout name, or a comma-separated list to switch between |
|
||||
| **Layout variant**<br>`keyboardVariant` `input:kb_variant` | — | XKB variant, such as dvorak or colemak. Empty for the standard layout |
|
||||
| **Keyboard options**<br>`keyboardOptions` `input:kb_options` | caps:escape_shifted_capslock | XKB options, such as compose:ralt to make right Alt a compose key |
|
||||
| **Num Lock on login**<br>`numlockByDefault` `input:numlock_by_default` | true | Turn Num Lock on when the session starts |
|
||||
| **Repeat delay**<br>`keyRepeatDelay` `input:repeat_delay` | 500 ms | How long a key is held before it starts repeating. Range 150–1000. |
|
||||
| **Repeat rate**<br>`keyRepeatRate` `input:repeat_rate` | 33 /s | How many characters a second a held key produces. Range 5–100. |
|
||||
|
||||
## lockAppearance
|
||||
|
||||
Found on **Appearance**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Background**<br>`lockBackgroundMode` | screenshot | What appears behind the lock screen Choices: Blurred desktop, Current wallpaper, Solid color. |
|
||||
| **Background blur**<br>`lockBlurLevel` | 3 | Softens what is behind the password field. Range 0–5. |
|
||||
| **Show clock**<br>`lockShowClock` | true | Use the desktop's 12 or 24-hour format |
|
||||
| **Show date**<br>`lockShowDate` | true | Show the weekday and full date |
|
||||
| **Show user name**<br>`lockShowUser` | true | Identify the signed-in account |
|
||||
| **Hide password field until typing**<br>`lockFadeOnEmpty` | false | Keep the empty field out of the way |
|
||||
|
||||
## master
|
||||
|
||||
Found on **Desktop & Dock**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Master area size**<br>`masterFactor` `master:mfact` | 0.55 | How much of the screen the master window takes. Range 0.1–0.9. |
|
||||
| **Master area position**<br>`masterOrientation` `master:orientation` | left | Which side of the screen the master window occupies Choices: Left, Right, Top, Bottom, Center. |
|
||||
| **New windows become**<br>`masterNewStatus` `master:new_status` | slave | Whether a new window takes the master area or joins the stack Choices: The master window, Part of the stack, Whatever the focused window is. |
|
||||
| **Add new windows at the top**<br>`masterNewOnTop` `master:new_on_top` | false | New stack windows go above the others rather than below |
|
||||
|
||||
## multitasking
|
||||
|
||||
Found on **Desktop & Dock**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Tiling layout**<br>`windowLayout` `general:layout` | dwindle | Dwindle splits the focused window; master keeps one large window beside a stack Choices: Dwindle, Master and stack. |
|
||||
| **Keep split direction**<br>`preserveSplit` `dwindle:preserve_split` | true | New windows follow the split of the window they replace, instead of always halving the longer side |
|
||||
| **New windows open**<br>`forceSplit` `dwindle:force_split` | 0 | Where a new window lands relative to the one that was focused Choices: Where the pointer is, Always left or above, Always right or below. |
|
||||
| **Snap floating windows**<br>`windowSnapping` `general:snap:enabled` | true | Floating windows stick to screen edges and to each other as you drag them |
|
||||
| **Switch back and forth**<br>`workspaceBackAndForth` `binds:workspace_back_and_forth` | false | Selecting the workspace you are already on returns you to the previous one |
|
||||
| **Wrap around at the ends**<br>`allowWorkspaceCycles` `binds:allow_workspace_cycles` | false | Moving past the last workspace continues from the first |
|
||||
| **Let applications take focus**<br>`focusOnActivate` `misc:focus_on_activate` | false | An application asking for attention is switched to, rather than only highlighted |
|
||||
| **Pointer changes active display**<br>`mouseMoveFocusesMonitor` `misc:mouse_move_focuses_monitor` | true | Moving the pointer to another display makes it the active one |
|
||||
|
||||
## nightLight
|
||||
|
||||
Found on **Displays**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Night Light**<br>`nightLightEnabled` | false | Shift the display warmer to reduce blue light |
|
||||
| **Schedule automatically**<br>`nightLightAutomatic` | false | Turn Night Light on and off at the scheduled hours |
|
||||
| **Color temperature**<br>`nightLightTemperature` | 3500 K | Lower is warmer. Range 2000–6500. |
|
||||
| **Turns on at**<br>`nightLightFrom` | 17.0 | Only used when Night Light follows a schedule. Range 0–23.5. |
|
||||
| **Turns off at**<br>`nightLightTo` | 10.0 | A time earlier than the start simply means the next morning. Range 0–23.5. |
|
||||
|
||||
## notices
|
||||
|
||||
Found on **Desktop & Dock**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Hyprland wallpaper**<br>`hyprlandLogo` `misc:disable_hyprland_logo` | false | The stock background Hyprland draws when no wallpaper is set |
|
||||
| **Splash text**<br>`hyprlandSplash` `misc:disable_splash_rendering` | false | The line of text Hyprland renders over the stock background |
|
||||
| **Update announcements**<br>`hyprlandUpdateNews` `ecosystem:no_update_news` | false | The window Hyprland opens after an update to describe what changed |
|
||||
| **Donation reminders**<br>`hyprlandDonationNag` `ecosystem:no_donation_nag` | false | The prompt Hyprland shows twice a year asking for support |
|
||||
|
||||
## notifications
|
||||
|
||||
Found on **Notifications & Focus**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Notification duration**<br>`notificationTimeoutMs` | 5000 ms | How long ordinary notification banners remain visible. Range 1000–30000. |
|
||||
| **Critical notification duration**<br>`notificationTimeoutCriticalMs` | 0 ms | Zero keeps critical notification banners visible until dismissed. Range 0–60000. |
|
||||
| **Notification history**<br>`notificationHistoryLimit` | 100 | Maximum notifications retained in the notification center. Range 10–500. |
|
||||
| **Visible banners**<br>`maxVisibleToasts` | 4 | Maximum notification banners shown at once. Range 1–8. |
|
||||
|
||||
## pointer
|
||||
|
||||
Found on **Mouse & Touchpad**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Pointer focus**<br>`followMouse` `input:follow_mouse` | 1 | What moving the pointer does to which window is focused Choices: Click to focus, Focus follows pointer, Pointer detached, Pointer fully separate. |
|
||||
| **Pointer speed**<br>`pointerSensitivity` `input:sensitivity` | 0.0 | Zero is flat, unaccelerated response. Range -1.0–1.0. |
|
||||
| **Hide pointer after**<br>`cursorInactiveTimeout` `cursor:inactive_timeout` | 4 s | Seconds of stillness before the pointer fades out; 0 never hides it. Range 0–60. |
|
||||
| **Natural scrolling**<br>`naturalScroll` `input:natural_scroll` | false | Content follows the direction of your fingers, as on a phone |
|
||||
| **Acceleration**<br>`accelProfile` `input:accel_profile` | flat | Flat moves the pointer the same distance however fast you move Choices: Flat, Adaptive. |
|
||||
| **Scroll speed**<br>`scrollFactor` `input:scroll_factor` | 1.0 | Multiplies how far one notch of the wheel scrolls. Range 0.1–4.0. |
|
||||
| **Left-handed**<br>`leftHanded` `input:left_handed` | false | Swap the primary and secondary buttons |
|
||||
| **Middle-click paste**<br>`middleClickPaste` `misc:middle_click_paste` | true | Paste the primary selection in GTK and native Wayland applications |
|
||||
|
||||
## themes
|
||||
|
||||
Found on **Appearance**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Pointer theme**<br>`cursorTheme` | oreo_blue_cursors | The pointer design used by applications and Hyprland |
|
||||
| **Application icons**<br>`iconTheme` | Adwaita | The icon set used by GTK applications |
|
||||
|
||||
## titlebar
|
||||
|
||||
Found on **Appearance**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Button side**<br>`titlebarButtonSide` | right | Place application titlebar buttons on the left or right Choices: Left, Right. |
|
||||
| **Maximize button**<br>`titlebarMaximizeButton` | false | Show a maximize button in application titlebars that support it |
|
||||
| **Double-click titlebar**<br>`titlebarDoubleClick` | toggle-maximize | Choose what a double-click on an application titlebar does Choices: Toggle maximize, Do nothing. |
|
||||
|
||||
## touchpad
|
||||
|
||||
Found on **Mouse & Touchpad**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Tap to click**<br>`touchpadTapToClick` `input:touchpad:tap-to-click` | true | A tap counts as a click without pressing down |
|
||||
| **Natural scrolling**<br>`touchpadNaturalScroll` `input:touchpad:natural_scroll` | true | Content follows the direction of your fingers |
|
||||
| **Disable while typing**<br>`touchpadDisableWhileTyping` `input:touchpad:disable_while_typing` | true | Ignore the touchpad briefly after a keystroke, so a palm cannot move the pointer |
|
||||
| **Scroll speed**<br>`touchpadScrollFactor` `input:touchpad:scroll_factor` | 1.0 | Multiplies how far a two-finger scroll travels. Range 0.1–4.0. |
|
||||
| **Drag lock**<br>`touchpadDragLock` `input:touchpad:drag_lock` | 0 | Keeps a tap-and-drag active when you lift a finger mid-drag Choices: Off, On, On, until you tap again. |
|
||||
| **Middle-click by pressing both buttons**<br>`touchpadMiddleButtonEmulation` `input:touchpad:middle_button_emulation` | false | Pressing left and right together acts as a middle click |
|
||||
|
||||
## typography
|
||||
|
||||
Found on **Appearance**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Interface font**<br>`interfaceFont` | Adwaita Sans | Used for every piece of text in the shell |
|
||||
| **Icon font**<br>`iconFont` | VictorMono Nerd Font | Draws the shell's glyphs, so it must be a Nerd Font |
|
||||
| **Interface text size**<br>`interfaceFontSize` | 13 px | The base size the rest of the shell's type scales from. Range 10–18. |
|
||||
| **Application font**<br>`applicationFont` | Adwaita Sans | Used by menus, controls, and labels in applications |
|
||||
| **Application text size**<br>`applicationFontSize` | 11 pt | The base text size used by applications. Range 6–32. |
|
||||
| **Document font**<br>`documentFont` | Adwaita Sans | Used for document content when an application follows the system choice |
|
||||
| **Document text size**<br>`documentFontSize` | 12 pt | The default text size for document content. Range 6–32. |
|
||||
| **Monospace font**<br>`monospaceFont` | VictorMono Nerd Font | Used by terminals, editors, and code fields that follow the system choice |
|
||||
| **Monospace text size**<br>`monospaceFontSize` | 10 pt | The default text size for terminals and code. Range 6–32. |
|
||||
| **Font hinting**<br>`fontHinting` | slight | How strongly text aligns to the pixel grid Choices: None, Slight, Medium, Full. |
|
||||
| **Text smoothing**<br>`fontAntialiasing` | rgba | How application text softens its edges Choices: None, Grayscale, Subpixel. |
|
||||
|
||||
## vitals
|
||||
|
||||
Found on **Appearance**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Processor**<br>`showCpu` | true | Show processor usage beside the workspace indicator |
|
||||
| **Memory**<br>`showMemory` | true | Show memory usage beside the workspace indicator |
|
||||
| **Graphics**<br>`showGpu` | true | Show graphics usage beside the workspace indicator |
|
||||
| **Vitals refresh**<br>`vitalsIntervalMs` | 2000 ms | How often processor, memory, and graphics usage update. Range 500–10000. |
|
||||
|
||||
## wallpaper
|
||||
|
||||
Found on **Appearance**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Wallpaper**<br>`wallpaperPath` | — | Shown on every output |
|
||||
| **Wallpaper mode**<br>`wallpaperMode` | single | Use one image, rotate a collection, or choose per display Choices: Single, Slideshow, Per display. |
|
||||
| **Change background every**<br>`wallpaperIntervalMinutes` | 30 min | Time between slideshow images. Range 5–1440. |
|
||||
| **Shuffle**<br>`wallpaperShuffle` | true | Show every selected image before repeating |
|
||||
|
||||
## weather
|
||||
|
||||
Found on **Home**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Temperature unit**<br>`temperatureUnit` | fahrenheit | Choose Fahrenheit or Celsius for the weather card Choices: Fahrenheit, Celsius. |
|
||||
| **Weather refresh**<br>`weatherRefreshMinutes` | 20 min | How often Panama updates the current conditions. Range 5–120. |
|
||||
|
||||
## windows
|
||||
|
||||
Found on **Appearance**.
|
||||
|
||||
| Setting | Default | What it does |
|
||||
|---|---|---|
|
||||
| **Inner gaps**<br>`gapsIn` `general:gaps_in` | 5 px | Space between neighboring tiled windows. Range 0–40. |
|
||||
| **Outer gaps**<br>`gapsOut` `general:gaps_out` | 10 px | Space between the tiled area and the screen edge. Range 0–80. |
|
||||
| **Border width**<br>`borderSize` `general:border_size` | 2 px | Thickness of the gradient border on the focused window. Range 0–10. |
|
||||
| **Corner radius**<br>`windowRounding` `decoration:rounding` | 18 px | Matches the shell's popover radius so windows and panels agree. Range 0–40. |
|
||||
| **Unfocused window opacity**<br>`inactiveOpacity` `decoration:inactive_opacity` | 1.0 | Fade windows that do not have focus. Range 0.5–1.0. |
|
||||
| **Focused window opacity**<br>`activeOpacity` `decoration:active_opacity` | 1.0 | Fade even the focused window; 1.0 is fully opaque. Range 0.5–1.0. |
|
||||
| **Fullscreen opacity**<br>`fullscreenOpacity` `decoration:fullscreen_opacity` | 1.0 | Applied instead of the focused opacity when a window is fullscreen. Range 0.5–1.0. |
|
||||
| **Corner shape**<br>`roundingPower` `decoration:rounding_power` | 2.0 | 2 is a circular corner; higher values approach a squircle. Range 2.0–10.0. |
|
||||
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# The settings reference is generated, and must not be stale.
|
||||
#
|
||||
# Every other kind of documentation here has drifted at least once: routing that
|
||||
# pointed at a page not containing the setting, contracts that pinned the bug
|
||||
# they were meant to prevent, a comment that failed to stop the person who read
|
||||
# it from making the exact mistake it described. Prose describing 127 settings
|
||||
# would drift the day after it was written.
|
||||
#
|
||||
# So docs/settings.md is generated from the schema, and this fails the moment
|
||||
# the committed copy stops matching. That is the whole mechanism: the document
|
||||
# cannot be wrong for longer than it takes to run the suite.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
generator="$repo_dir/config/dot/quickshell/scripts/panama-settings-docs"
|
||||
doc="$repo_dir/docs/settings.md"
|
||||
|
||||
fail() {
|
||||
printf 'settings docs contract: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ -x "$generator" ]] || fail 'the generator is missing or not executable'
|
||||
|
||||
"$generator" --check || fail 'docs/settings.md is stale -- run quickshell/scripts/panama-settings-docs and commit the result'
|
||||
|
||||
# A generator that silently emitted nothing would also pass --check against an
|
||||
# equally empty file, so the output is checked for substance too.
|
||||
[[ -s "$doc" ]] || fail 'docs/settings.md is empty'
|
||||
|
||||
settings="$(grep -c '^| \*\*' "$doc")"
|
||||
schema_keys="$(grep -c 'key: "' "$repo_dir/config/dot/quickshell/config/PreferenceSchema.qml")"
|
||||
|
||||
# Internal keys are deliberately omitted, so the document is smaller than the
|
||||
# schema -- but not by much. A large gap means the reader stopped recognising
|
||||
# entries and quietly documented a fraction of them.
|
||||
(( settings > schema_keys * 8 / 10 )) \
|
||||
|| fail "the reference documents only $settings of $schema_keys schema keys, which suggests the generator stopped parsing partway"
|
||||
|
||||
# The compositor-backed settings are the ones worth naming precisely, since
|
||||
# that is the string someone would search the Hyprland docs for.
|
||||
grep -q 'cursor:zoom_factor' "$doc" \
|
||||
|| fail 'compositor option names are missing from the reference'
|
||||
|
||||
# A stale copy must be detectable, not merely regenerable. Prove the check
|
||||
# actually compares content rather than always returning success.
|
||||
scratch="$(mktemp -d /tmp/panama-docs.XXXXXX)"
|
||||
trap 'rm -rf "$scratch"' EXIT
|
||||
cp "$doc" "$scratch/settings.md"
|
||||
printf '\n<!-- drift -->\n' >>"$doc"
|
||||
if "$generator" --check >/dev/null 2>&1; then
|
||||
cp "$scratch/settings.md" "$doc"
|
||||
fail '--check reported success on a modified file, so staleness would never be caught'
|
||||
fi
|
||||
cp "$scratch/settings.md" "$doc"
|
||||
|
||||
printf 'settings docs contract: PASS (%d settings documented)\n' "$settings"
|
||||
Reference in New Issue
Block a user