diff --git a/.gitignore b/.gitignore
index 76395ae..d1204ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ __pycache__/
# Generated from the color scheme; machine state, not configuration.
/config/dot/gtk-3.0/settings.ini
+/config/dot/gtk-3.0/bookmarks
/config/dot/gtk-4.0/settings.ini
/config/dot/tmux/current-theme.conf
/config/dot/hypr/hyprlock.conf
diff --git a/config/dot/espanso/match/base.yml b/config/dot/espanso/match/base.yml
index 6c4d2ba..c80e075 100644
--- a/config/dot/espanso/match/base.yml
+++ b/config/dot/espanso/match/base.yml
@@ -139,18 +139,6 @@ matches:
- trigger: "dokcer"
replace: "docker"
- - trigger: "gborwn"
- replace: "gbrown"
-
- - trigger: "gbrwon"
- replace: "gbrown"
-
- - trigger: "gbronw"
- replace: "gbrown"
-
- - trigger: "grbown"
- replace: "gbrown"
-
- trigger: "restrat"
replace: "restart"
diff --git a/config/dot/gtk-3.0/bookmarks b/config/dot/gtk-3.0/bookmarks
deleted file mode 100644
index f0644ed..0000000
--- a/config/dot/gtk-3.0/bookmarks
+++ /dev/null
@@ -1,8 +0,0 @@
-file:///home/gib/Documents Documents
-file:///home/gib/Documents/Code Code
-file:///home/gib/Downloads Downloads
-file:///home/gib/Music Music
-file:///home/gib/Pictures Pictures
-file:///home/gib/Videos Videos
-file:///home/gib/.local/share/Panama Panama
-smb://server-gib.local/homes/ Gib's Server
diff --git a/config/dot/gtk-3.0/bookmarks.template b/config/dot/gtk-3.0/bookmarks.template
new file mode 100644
index 0000000..cf5c4e8
--- /dev/null
+++ b/config/dot/gtk-3.0/bookmarks.template
@@ -0,0 +1,6 @@
+file://$HOME/Documents Documents
+file://$HOME/Downloads Downloads
+file://$HOME/Music Music
+file://$HOME/Pictures Pictures
+file://$HOME/Videos Videos
+file://$HOME/.local/share/Panama Panama
diff --git a/config/dot/hypr/DESKTOP-PARITY.md b/config/dot/hypr/DESKTOP-PARITY.md
index bbb02e5..4d9dea9 100644
--- a/config/dot/hypr/DESKTOP-PARITY.md
+++ b/config/dot/hypr/DESKTOP-PARITY.md
@@ -25,7 +25,7 @@ Last live audit: 2026-08-17, Fedora 44, Hyprland 0.56.2, Quickshell 0.3.0.
| Calendar agenda | Existing GNOME, Google, iCloud, and Nextcloud sources through Evolution Data Server | Live |
| Control Center | Wi-Fi, Bluetooth, audio input/output, Caffeine, Night Light, Focus, Home Assistant favorites, and KDE Connect phone continuity | Live |
| Focus sessions | Persistent workspace-bound timer, DND/caffeine coordination, Signal Glass controls | Live |
-| Brightness | Appears only when a backlight device exists | Live, not applicable on this monitor |
+| Brightness | Appears only when a backlight device exists | Live where the hardware exposes it |
| AppIndicator / tray | Native StatusNotifierItem host and themed menus | Live |
| Screenshot and recording | Quickshell picker, grim/slurp/satty/wf-recorder | Live |
| Screen text and code recognition | Local Tesseract/ZBar Read mode, selectable Prism result sheet | Live after package install |
diff --git a/config/dot/hypr/hyprpaper.conf b/config/dot/hypr/hyprpaper.conf
index cef63bf..4b6020d 100644
--- a/config/dot/hypr/hyprpaper.conf
+++ b/config/dot/hypr/hyprpaper.conf
@@ -17,20 +17,16 @@
# frame time for nothing.
# ─────────────────────────────────────────────────────────────────────────────
+# One block for every output: per-monitor wallpaper is applied at runtime by
+# the shell (services/Wallpaper.qml) over IPC, so a per-connector block here
+# would just be one machine's connector name in everyone's config.
wallpaper {
- monitor = DP-2
+ monitor =
path = ~/Pictures/Wallpapers/faroe_islands.jpg
# cover = fill the output, cropping overflow. Matches the "zoom" picture
# option this setup used under GNOME.
fit_mode = cover
}
-# Fallback for any output without its own block above.
-wallpaper {
- monitor =
- path = ~/Pictures/Wallpapers/faroe_islands.jpg
- fit_mode = cover
-}
-
ipc = on
splash = false
diff --git a/config/dot/quickshell/config/PreferenceSchema.qml b/config/dot/quickshell/config/PreferenceSchema.qml
index 63108de..d218777 100644
--- a/config/dot/quickshell/config/PreferenceSchema.qml
+++ b/config/dot/quickshell/config/PreferenceSchema.qml
@@ -1297,28 +1297,44 @@ Singleton {
detail: "Which GPU the graphics readout in the bar measures"
},
+ // ── Search ──────────────────────────────────────────────────────────
+ // The launcher's web search appends the query to this. It shipped
+ // pointing at the author's personal bang redirector once; a stranger's
+ // searches belong to no one's server but the engine they chose.
+ {
+ key: "webSearchUrl", type: "string",
+ def: "https://duckduckgo.com/?q=", group: "search",
+ pattern: "^https://[^\\s]{1,200}$",
+ label: "Web search engine",
+ detail: "Where the launcher's web search sends a query; the search text is appended"
+ },
+
// ── Weather location ────────────────────────────────────────────────
// Coordinates rather than a place name, because that is what Open-Meteo
// takes and it needs no API key. weatherLocation is only the label shown
// in the UI; it is never sent anywhere, so it can say whatever makes the
// reading recognizable.
{
- key: "weatherLatitude", type: "real", def: 27.7375, min: -90, max: 90, step: 0.0001,
+ key: "weatherLatitude", type: "real", def: 0, min: -90, max: 90, step: 0.0001,
group: "weather", internal: true,
label: "Latitude",
detail: "Set by choosing a location"
},
{
- key: "weatherLongitude", type: "real", def: -82.6861, min: -180, max: 180, step: 0.0001,
+ key: "weatherLongitude", type: "real", def: 0, min: -180, max: 180, step: 0.0001,
group: "weather", internal: true,
label: "Longitude",
detail: "Set by choosing a location"
},
+ // Empty until a location is chosen. The shipped value was once the
+ // author's home town, which confidently reported his weather on every
+ // machine anywhere; an unset location fetches nothing and says so
+ // instead.
{
- key: "weatherLocation", type: "string", def: "Local weather", group: "weather",
+ key: "weatherLocation", type: "string", def: "", group: "weather",
internal: true,
// Display only -- never sent to the weather service.
- pattern: "^[^\\n]{1,64}$",
+ pattern: "^[^\\n]{0,64}$",
label: "Weather location",
detail: "The place the weather reading is for"
},
diff --git a/config/dot/quickshell/modules/settings/ApplicationsPage.qml b/config/dot/quickshell/modules/settings/ApplicationsPage.qml
index 61710a0..4864586 100644
--- a/config/dot/quickshell/modules/settings/ApplicationsPage.qml
+++ b/config/dot/quickshell/modules/settings/ApplicationsPage.qml
@@ -207,6 +207,8 @@ SettingsPage {
title: "Search"
subtitle: "Applications, files, the calculator, clipboard history, emoji, and open windows are all searched from the launcher."
+ TextEntryRow { setting: "webSearchUrl"; placeholder: "https://duckduckgo.com/?q=" }
+
TextRow {
label: "Launcher"
detail: SystemSettings.vicinaeActive
diff --git a/config/dot/quickshell/modules/settings/HomePage.qml b/config/dot/quickshell/modules/settings/HomePage.qml
index f768faf..cea0808 100644
--- a/config/dot/quickshell/modules/settings/HomePage.qml
+++ b/config/dot/quickshell/modules/settings/HomePage.qml
@@ -96,7 +96,17 @@ SettingsPage {
return match[2] ? match[1] + " " + match[2].toUpperCase() : match[1];
}
- title: `${root.greeting}, Gabriel`
+ // Greets whoever is signed in, from the same accountsservice record the
+ // lock screen shows. This once hardcoded the author's first name, which
+ // made the first screen of Settings wrong for every other human.
+ readonly property string greetingName: {
+ if (!UserAccounts.me)
+ return "";
+ const full = UserAccounts.displayName(UserAccounts.me);
+ return full.split(" ")[0] || "";
+ }
+
+ title: root.greetingName !== "" ? `${root.greeting}, ${root.greetingName}` : root.greeting
lede: Weather.available
? Math.round(Weather.temperature) + Weather.unitSuffix + " and "
+ Weather.description.toLowerCase() + " in " + Settings.weatherLocation
@@ -275,7 +285,7 @@ SettingsPage {
label: "Location"
detail: "Only the search term is sent; the name is a label kept on this machine"
- value: Settings.weatherLocation
+ value: Settings.weatherLocation !== "" ? Settings.weatherLocation : "Not set — choose one to see weather"
LocationPicker {
width: parent.width
diff --git a/config/dot/quickshell/modules/settings/SettingsSidebar.qml b/config/dot/quickshell/modules/settings/SettingsSidebar.qml
index a52e01f..ce5f214 100644
--- a/config/dot/quickshell/modules/settings/SettingsSidebar.qml
+++ b/config/dot/quickshell/modules/settings/SettingsSidebar.qml
@@ -55,6 +55,22 @@ Rectangle {
{ page: "about", label: "About", icon: "\u{F02FD}" }
]
+ // Pages whose entire backing stack can be absent hide once a scan has
+ // proven it absent: a Containers page with no podman and a Snapshots page
+ // with no snapper configuration render permanently empty, which reads as
+ // broken rather than inapplicable. Until the scan lands they stay
+ // visible, so machines that have the stack never see a blink -- and both
+ // scans fire when Settings opens (HomePage.onCompleted).
+ function pageAvailable(page: string): bool {
+ switch (page) {
+ case "containers":
+ return !Containers.scanned || Containers.available;
+ case "snapshots":
+ return !Snapshots.scanned || Snapshots.configs.length > 0;
+ }
+ return true;
+ }
+
width: 272
color: Theme.alpha(Theme.bgDark, 0.96)
border.width: 0
@@ -236,6 +252,7 @@ Rectangle {
Rectangle {
id: navItem
required property var modelData
+ visible: root.pageAvailable(modelData.page)
width: parent.width
height: 40
radius: 10
diff --git a/config/dot/quickshell/services/Capture.qml b/config/dot/quickshell/services/Capture.qml
index d99e1e0..22ba190 100644
--- a/config/dot/quickshell/services/Capture.qml
+++ b/config/dot/quickshell/services/Capture.qml
@@ -76,7 +76,7 @@ Singleton {
property real coordinateScale: 1.0
// ── Paths ───────────────────────────────────────────────────────────────
- readonly property string _home: Quickshell.env("HOME") || "/home/gib"
+ readonly property string _home: Quickshell.env("HOME") || ""
// 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
diff --git a/config/dot/quickshell/services/GraphicsDevices.qml b/config/dot/quickshell/services/GraphicsDevices.qml
index 715a002..e9fb056 100644
--- a/config/dot/quickshell/services/GraphicsDevices.qml
+++ b/config/dot/quickshell/services/GraphicsDevices.qml
@@ -42,6 +42,13 @@ Singleton {
const parsed = JSON.parse(this.text);
root.devices = Array.isArray(parsed) ? parsed : [];
root.lastError = "";
+ // The shipped default is one machine's card index, and
+ // card numbering means nothing across machines. When the
+ // stored path is not present here and there is exactly
+ // one real answer, adopt it -- a single-GPU laptop should
+ // not lose its vitals readout to a desktop's enumeration.
+ if (root.selectionMissing && root.devices.length === 1)
+ root.select(root.devices[0].path);
} catch (error) {
root.devices = [];
root.lastError = "The graphics devices could not be read.";
diff --git a/config/dot/quickshell/services/SettingsSearch.qml b/config/dot/quickshell/services/SettingsSearch.qml
index 2707e3c..1e19641 100644
--- a/config/dot/quickshell/services/SettingsSearch.qml
+++ b/config/dot/quickshell/services/SettingsSearch.qml
@@ -54,7 +54,8 @@ Singleton {
"weather": "home",
"notifications": "notifications",
"capture": "screen-intelligence",
- "gaming": "gaming"
+ "gaming": "gaming",
+ "search": "applications"
})
// Settings that are real but have no schema entry, because the system owns
diff --git a/config/dot/quickshell/services/Weather.qml b/config/dot/quickshell/services/Weather.qml
index ce7a6e6..4c16bd9 100644
--- a/config/dot/quickshell/services/Weather.qml
+++ b/config/dot/quickshell/services/Weather.qml
@@ -59,7 +59,15 @@ Singleton {
}
}
+ // Nothing was chosen yet: fetch nothing, claim nothing. The card offers
+ // the location picker instead.
+ readonly property bool configured: Settings.weatherLocation !== ""
+
function refresh(): void {
+ if (!root.configured) {
+ root.available = false;
+ return;
+ }
// Skip if the previous fetch is somehow still in flight.
if (!fetch.running)
fetch.running = true;
diff --git a/config/local/share/vicinae/extensions/panama-search/src/search.tsx b/config/local/share/vicinae/extensions/panama-search/src/search.tsx
index 23d894d..b3f32b0 100644
--- a/config/local/share/vicinae/extensions/panama-search/src/search.tsx
+++ b/config/local/share/vicinae/extensions/panama-search/src/search.tsx
@@ -6,7 +6,25 @@ import { Action, ActionPanel, Icon, List } from "@vicinae/api";
// search engine to be bounced, and falls through to an ordinary search when
// there is no bang. Bang support is a property of this URL, not of this
// extension, which is why there is no bang parsing below.
-const ENGINE = "https://bang.gibbyb.com/?q=";
+// The engine is Panama's webSearchUrl preference (Applications page), the
+// same key the fallback script command reads -- launcher-search-contract pins
+// the two to one default. Read once per command launch; DuckDuckGo when the
+// preference is unset or unreadable.
+const DEFAULT_ENGINE = "https://duckduckgo.com/?q=";
+const ENGINE = (() => {
+ try {
+ const configHome =
+ process.env.XDG_CONFIG_HOME || `${process.env.HOME}/.config`;
+ const stored = JSON.parse(
+ require("fs").readFileSync(`${configHome}/panama/settings.json`, "utf8"),
+ ).webSearchUrl;
+ return typeof stored === "string" && stored.startsWith("https://")
+ ? stored
+ : DEFAULT_ENGINE;
+ } catch {
+ return DEFAULT_ENGINE;
+ }
+})();
// The suggestion endpoint Firefox's address bar uses. Answers with
// [query, [suggestion, ...], ...] and needs no key.
diff --git a/config/local/share/vicinae/scripts/search-web b/config/local/share/vicinae/scripts/search-web
index 6557eea..f003591 100755
--- a/config/local/share/vicinae/scripts/search-web
+++ b/config/local/share/vicinae/scripts/search-web
@@ -25,5 +25,8 @@
#
# xdg-open rather than a named browser: the default browser is already a setting
# this desktop owns, on the Applications page, and naming one here would quietly
-# outrank it.
-exec xdg-open "https://bang.gibbyb.com/?q=$1"
+# outrank it. The engine is the webSearchUrl preference (Applications page);
+# the shipped default asks DuckDuckGo, not somebody's personal redirector.
+search_url="$(jq -r '.webSearchUrl // empty' "${XDG_CONFIG_HOME:-$HOME/.config}/panama/settings.json" 2>/dev/null)"
+[ -n "$search_url" ] || search_url="https://duckduckgo.com/?q="
+exec xdg-open "${search_url}$1"
diff --git a/config/local/share/vicinae/scripts/settings-applications b/config/local/share/vicinae/scripts/settings-applications
index f752ef4..4498a0c 100755
--- a/config/local/share/vicinae/scripts/settings-applications
+++ b/config/local/share/vicinae/scripts/settings-applications
@@ -5,6 +5,6 @@
# @vicinae.mode silent
# @vicinae.icon ../../icons/hicolor/scalable/apps/panama-settings.svg
# @vicinae.description Open Applications in Settings.
-# @vicinae.keywords ["settings", "default applications"]
+# @vicinae.keywords ["settings", "web search engine", "default applications"]
exec "$HOME/.config/quickshell/scripts/panama-action" settings-page applications
diff --git a/docs/settings.md b/docs/settings.md
index 92c214d..b5ddc3c 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -4,7 +4,7 @@
Do not edit this file. Run `quickshell/scripts/panama-settings-docs`
after changing the schema; a contract fails when this copy is stale.
-148 settings across 29 groups. 70 of them are applied to the compositor and confirmed by reading the value back.
+149 settings across 30 groups. 70 of them are applied to the compositor and confirmed by reading the value back.
## accessibility
@@ -255,6 +255,14 @@ Found on **Mouse & Touchpad**.
| **Left-handed**
`leftHanded` `input:left_handed` | false | Swap the primary and secondary buttons |
| **Middle-click paste**
`middleClickPaste` `misc:middle_click_paste` | true | Paste the primary selection in GTK and native Wayland applications |
+## search
+
+Found on **Applications**.
+
+| Setting | Default | What it does |
+|---|---|---|
+| **Web search engine**
`webSearchUrl` | https://duckduckgo.com/?q= | Where the launcher's web search sends a query; the search text is appended |
+
## themes
Found on **Appearance**.
diff --git a/setup/scripts/link-dotfiles b/setup/scripts/link-dotfiles
index 926d98d..e16e97d 100755
--- a/setup/scripts/link-dotfiles
+++ b/setup/scripts/link-dotfiles
@@ -255,6 +255,22 @@ for icon_file in "$PANAMA_ICON_DIR"/*.svg; do
done
gtk-update-icon-cache -f -t "$HOME/.local/share/icons/hicolor" 2>/dev/null || true
+# File-manager sidebar bookmarks, generated per machine from the template.
+# The tracked file used to carry absolute /home/ paths -- seven dead
+# sidebar entries in every file dialog for anyone else. Nautilus edits the
+# generated instance in place (Ctrl+D writes this file through the symlink),
+# which is exactly why it is gitignored: bookmarks are user data, not config.
+BOOKMARKS_TEMPLATE="$PANAMA_DOT/gtk-3.0/bookmarks.template"
+BOOKMARKS_FILE="$PANAMA_DOT/gtk-3.0/bookmarks"
+if [ -r "$BOOKMARKS_TEMPLATE" ]; then
+ if [ -e "$BOOKMARKS_FILE" ]; then
+ log "Keeping existing GTK bookmarks at $BOOKMARKS_FILE"
+ else
+ sed "s|\$HOME|$HOME|g" "$BOOKMARKS_TEMPLATE" > "$BOOKMARKS_FILE"
+ log "Generated GTK bookmarks for $HOME"
+ fi
+fi
+
# The pointer theme. oreo_blue_cursors is packaged by no Fedora repository -- it
# is a GitHub project -- so rather than download it at install time and depend on
# a URL that can rot, the theme is vendored into this repository and exposed the
diff --git a/user/espanso/identity.yml b/user/espanso/identity.yml
index 4ff1623..6700d22 100644
--- a/user/espanso/identity.yml
+++ b/user/espanso/identity.yml
@@ -9,3 +9,11 @@ matches:
replace: "gib@gibbyb.com"
- trigger: ":empro"
replace: "me@gbrown.org"
+ - trigger: "gborwn"
+ replace: "gbrown"
+ - trigger: "gbrwon"
+ replace: "gbrown"
+ - trigger: "gbronw"
+ replace: "gbrown"
+ - trigger: "grbown"
+ replace: "gbrown"