Save things where the rest of the software already saves them
Screenshots and recordings offered three folders to choose between, and three
guesses cannot include the folder somebody's other software already writes to --
which is the only folder that matters. This machine has had ~/Pictures/Screenshots
and ~/Videos/Screencasts since long before Panama, and Panama was writing
recordings to a Videos/Recordings it invented. Both are free text now, and the
recording default is the folder that was already there.
Wallpapers were swept from four directories at once, so the distribution's stock
images arrived mixed in with the user's own and there was no way to ask for just
one. Where wallpapers live is something somebody knows about their own machine.
It is a setting, not a search.
All three accept an absolute path as well as one relative to home, which meant
fixing Capture: it prefixed $HOME unconditionally, so naming /mnt/captures would
have written screenshots to ~/mnt/captures and left nobody able to find them.
The generator turned out to skip any entry whose comment sits inside the braces
rather than above them -- it looks for `key:` immediately after `{`. Three
settings were invisible in the reference because of it, one of them dockScreens,
which has never appeared there at all. The staleness contract could not see it
either: regenerating reproduced the same omission, so the copy was current and
incomplete at once. It now counts what was declared against what it could read
and refuses rather than quietly documenting less than exists.
Claude-Session: https://claude.ai/code/session_01Q84axqUE5inJhf5Jz9CFy1
This commit is contained in:
@@ -101,11 +101,12 @@ Singleton {
|
||||
{ value: "right", label: "Right" }
|
||||
]
|
||||
},
|
||||
|
||||
// A "json" value: the screen names the Dock appears on. Empty means
|
||||
// every screen, which is both the sensible default and the right
|
||||
// answer for the common single-monitor case -- storing a list of
|
||||
// names there would go stale the moment a display is unplugged.
|
||||
{
|
||||
// A "json" value: the screen names the Dock appears on. Empty means
|
||||
// every screen, which is both the sensible default and the right
|
||||
// answer for the common single-monitor case -- storing a list of
|
||||
// names there would go stale the moment a display is unplugged.
|
||||
key: "dockScreens", type: "json", def: [], group: "dock",
|
||||
label: "Screens",
|
||||
detail: "Which displays show the Dock"
|
||||
@@ -859,6 +860,19 @@ Singleton {
|
||||
// ── Desktop background ──────────────────────────────────────────────
|
||||
// Applied through hyprpaper's IPC. Not an hl.config option, so it has
|
||||
// no `hypr` block; services/Wallpaper.qml owns applying it.
|
||||
|
||||
// One folder, not four. The picker used to sweep ~/Pictures/Wallpapers,
|
||||
// ~/Pictures/Backgrounds, ~/.local/share/backgrounds and
|
||||
// /usr/share/backgrounds, which meant the distribution's stock
|
||||
// images turned up mixed in with the user's own and there was no
|
||||
// way to say "only mine". Where wallpapers live is a thing somebody
|
||||
// knows about their own machine; it is a setting, not a search.
|
||||
{
|
||||
key: "wallpaperDir", type: "string", def: "Pictures/Wallpapers", group: "wallpaper",
|
||||
pattern: "^~?/?[A-Za-z0-9 ._/+@'-]{1,160}$",
|
||||
label: "Wallpaper folder",
|
||||
detail: "Where the picker looks. Relative to your home folder unless it starts with /"
|
||||
},
|
||||
{
|
||||
key: "wallpaperPath", type: "string", def: "", group: "wallpaper",
|
||||
// Reaches hyprpaper as the "<output>,<path>" argument form, so a
|
||||
@@ -1271,25 +1285,23 @@ Singleton {
|
||||
// Directories and encoder arguments are enums rather than free text:
|
||||
// both are handed to a recorder process, and an arbitrary string there
|
||||
// is a much larger surface than a settings page needs to expose.
|
||||
|
||||
// Free text rather than three choices. The three were a guess at
|
||||
// where somebody keeps screenshots, and a guess cannot include the
|
||||
// folder the rest of their software already writes to -- which is
|
||||
// the only folder that matters. A path starting with / is taken as
|
||||
// absolute, so a drive that is not under home works too.
|
||||
{
|
||||
key: "screenshotDir", type: "enum", def: "Pictures/Screenshots", group: "capture",
|
||||
key: "screenshotDir", type: "string", def: "Pictures/Screenshots", group: "capture",
|
||||
pattern: "^~?/?[A-Za-z0-9 ._/+@'-]{1,160}$",
|
||||
label: "Screenshot folder",
|
||||
detail: "Folder under your home directory for screenshots",
|
||||
options: [
|
||||
{ value: "Pictures/Screenshots", label: "Pictures / Screenshots" },
|
||||
{ value: "Pictures", label: "Pictures" },
|
||||
{ value: "Desktop", label: "Desktop" }
|
||||
]
|
||||
detail: "Where screenshots are saved. Relative to your home folder unless it starts with /"
|
||||
},
|
||||
{
|
||||
key: "recordingDir", type: "enum", def: "Videos/Recordings", group: "capture",
|
||||
key: "recordingDir", type: "string", def: "Videos/Screencasts", group: "capture",
|
||||
pattern: "^~?/?[A-Za-z0-9 ._/+@'-]{1,160}$",
|
||||
label: "Recording folder",
|
||||
detail: "Folder under your home directory for screen recordings",
|
||||
options: [
|
||||
{ value: "Videos/Recordings", label: "Videos / Recordings" },
|
||||
{ value: "Videos", label: "Videos" },
|
||||
{ value: "Desktop", label: "Desktop" }
|
||||
]
|
||||
detail: "Where screen recordings are saved. Relative to your home folder unless it starts with /"
|
||||
},
|
||||
{
|
||||
key: "recorderArgs", type: "enum", def: "-c h264_vaapi -d /dev/dri/renderD128",
|
||||
|
||||
@@ -74,7 +74,9 @@ SettingsPage {
|
||||
title: "Background"
|
||||
subtitle: Wallpaper.lastError !== ""
|
||||
? Wallpaper.lastError
|
||||
: "Applied to every display. Looked for in ~/Pictures/Wallpapers, ~/Pictures/Backgrounds, ~/.local/share/backgrounds, and /usr/share/backgrounds."
|
||||
: "Applied to every display, from the folder below."
|
||||
|
||||
TextEntryRow { setting: "wallpaperDir"; placeholder: "Pictures/Wallpapers" }
|
||||
|
||||
WallpaperControls {
|
||||
id: wallpaperControls
|
||||
|
||||
@@ -45,8 +45,8 @@ SettingsPage {
|
||||
title: "Capture preferences"
|
||||
subtitle: "Choose where captures go and how recordings are encoded."
|
||||
|
||||
ChoiceRow { setting: "screenshotDir" }
|
||||
ChoiceRow { setting: "recordingDir" }
|
||||
TextEntryRow { setting: "screenshotDir"; placeholder: "Pictures/Screenshots" }
|
||||
TextEntryRow { setting: "recordingDir"; placeholder: "Videos/Screencasts" }
|
||||
ChoiceRow { setting: "recorderArgs"; divider: false }
|
||||
}
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ Item {
|
||||
width: parent.width - 40
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
text: "No images found. Looked in ~/Pictures/Wallpapers, ~/Pictures/Backgrounds, ~/.local/share/backgrounds, and /usr/share/backgrounds."
|
||||
text: "No images in " + Wallpaper.searchRoots[0] + ". Change the folder above, or put some pictures there."
|
||||
color: Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
|
||||
@@ -73,6 +73,22 @@ def read_entries():
|
||||
# Each entry begins at `key:` and ends at the closing brace of its block.
|
||||
# Nested braces (options, hypr) are skipped by counting depth.
|
||||
entries = []
|
||||
# An entry is only found when `key:` is the first thing inside its brace, so
|
||||
# a comment written INSIDE the literal rather than above it makes the whole
|
||||
# setting invisible here -- silently, and the staleness contract cannot see
|
||||
# it either, because regenerating reproduces the same omission. Three
|
||||
# settings were undocumented this way before anyone noticed, one of them for
|
||||
# weeks. Counting what was declared against what was parsed is what turns
|
||||
# that from silence into an error.
|
||||
declared = {m.group(1) for m in re.finditer(r'\bkey:\s*"([^"]+)"', text)}
|
||||
parsed = {m.group(1) for m in re.finditer(r'\{\s*\n\s*key:\s*"([^"]+)"', text)}
|
||||
invisible = sorted(declared - parsed)
|
||||
if invisible:
|
||||
raise SchemaError(
|
||||
"these settings are declared but cannot be read, which means they would "
|
||||
"be silently missing from the reference: " + ", ".join(invisible)
|
||||
+ ". Move the comment above the entry's opening brace.")
|
||||
|
||||
for match in re.finditer(r'\{\s*\n\s*key:\s*"([^"]+)"', text):
|
||||
name = match.group(1)
|
||||
start = match.start()
|
||||
|
||||
@@ -77,8 +77,24 @@ Singleton {
|
||||
|
||||
// ── Paths ───────────────────────────────────────────────────────────────
|
||||
readonly property string _home: Quickshell.env("HOME") || "/home/gib"
|
||||
readonly property string shotDir: root._home + "/" + Settings.screenshotDir
|
||||
readonly property string recDir: root._home + "/" + Settings.recordingDir
|
||||
|
||||
// Both folders are settings now rather than a choice of three, so both can
|
||||
// name somewhere outside home -- a second drive, a network mount. Prefixing
|
||||
// $HOME unconditionally, as this did, would have turned /mnt/captures into
|
||||
// ~/mnt/captures and written screenshots somewhere nobody looks.
|
||||
function _resolve(folder: string): string {
|
||||
const trimmed = String(folder ?? "").trim();
|
||||
if (trimmed === "")
|
||||
return root._home;
|
||||
if (trimmed.startsWith("/"))
|
||||
return trimmed;
|
||||
if (trimmed.startsWith("~/"))
|
||||
return root._home + trimmed.slice(1);
|
||||
return root._home + "/" + trimmed;
|
||||
}
|
||||
|
||||
readonly property string shotDir: root._resolve(Settings.screenshotDir)
|
||||
readonly property string recDir: root._resolve(Settings.recordingDir)
|
||||
|
||||
property string _freezePath: ""
|
||||
property string _prevFreezePath: ""
|
||||
|
||||
@@ -51,12 +51,20 @@ Singleton {
|
||||
return root.primaryFirstOutputs(outputs);
|
||||
}
|
||||
|
||||
readonly property var searchRoots: [
|
||||
`${Quickshell.env("HOME")}/Pictures/Wallpapers`,
|
||||
`${Quickshell.env("HOME")}/Pictures/Backgrounds`,
|
||||
`${Quickshell.env("HOME")}/.local/share/backgrounds`,
|
||||
"/usr/share/backgrounds"
|
||||
]
|
||||
// One folder, chosen in Settings, rather than four swept blindly. A leading
|
||||
// slash means an absolute path -- a drive that is not under home -- and
|
||||
// anything else is read relative to it, which is how the capture folders
|
||||
// work too.
|
||||
readonly property var searchRoots: {
|
||||
const configured = String(DesktopPreferences.get("wallpaperDir") ?? "").trim();
|
||||
const folder = configured === "" ? "Pictures/Wallpapers" : configured;
|
||||
const home = Quickshell.env("HOME");
|
||||
if (folder.startsWith("/"))
|
||||
return [folder];
|
||||
if (folder.startsWith("~/"))
|
||||
return [home + folder.slice(1)];
|
||||
return [`${home}/${folder}`];
|
||||
}
|
||||
|
||||
Process {
|
||||
id: scan
|
||||
|
||||
Reference in New Issue
Block a user