From ed7e34552d353cfb8c7aebacd437bdc765d6d384 Mon Sep 17 00:00:00 2001 From: coja Date: Sat, 12 Sep 2026 23:38:18 +0200 Subject: [PATCH] [Update] bulk update --- .editorconfig | 28 + .gitignore | 4 + .ignore | 9 + README.md | 60 +- bin/dotsync | 52 +- common/.bashrc | 38 ++ common/.config/aichat/config.yaml | 3 + common/.config/claude/StatusBar.md | 6 +- common/.config/claude/hooks/format.py | 77 ++- common/.config/claude/keybindings.json | 6 + common/.config/claude/link.sh | 31 +- common/.config/claude/settings.json | 37 +- common/.config/claude/statusline.py | 83 ++- common/.config/fish/completions/pipx.fish | 1 - common/.config/fish/config.fish | 184 +++--- .../functions/__ssh_agent_is_started.fish | 38 +- .../fish/functions/__ssh_agent_start.fish | 6 +- common/.config/fish/functions/ask.fish | 4 +- .../fish/functions/bind_M_n_history.fish | 1 - common/.config/fish/functions/clis.fish | 6 +- .../fish/functions/copy-commandline.fish | 2 +- common/.config/fish/functions/kp.fish | 6 +- common/.config/fish/functions/lk.fish | 30 +- common/.config/fish/functions/opencodes.fish | 43 +- common/.config/fish/functions/tuis.fish | 6 +- common/.config/misc/tuis.txt | 1 + common/.config/nvim/lua/configs/lspconfig.lua | 19 + common/.config/nvim/lua/plugins/init.lua | 20 + common/.config/opencode/opencode.json | 2 + common/.config/tmux/scripts/cpu.sh | 23 +- common/.config/tmux/scripts/mem.sh | 2 +- common/.config/tmux/scripts/net.sh | 21 +- common/.config/tmux/scripts/temp.sh | 23 +- common/.config/tmux/tmux.conf | 76 ++- common/.pi/README.md | 82 ++- common/.pi/agent/AGENTS.md | 22 + .../.pi/agent/extensions/compaction-watch.ts | 60 ++ .../agent/extensions/confirm-destructive.ts | 59 ++ .../.pi/agent/extensions/permission-gate.ts | 69 +++ .../.pi/agent/extensions/plan-mode/index.ts | 19 + .../.pi/agent/extensions/plan-mode/utils.ts | 13 +- .../.pi/agent/extensions/protected-paths.ts | 58 ++ common/.pi/agent/extensions/session-name.ts | 27 + common/.pi/agent/extensions/todo.ts | 297 +++++++++ common/.pi/agent/models.json | 32 +- common/.pi/agent/settings.json | 5 +- fl/.config/llamacpp/README.md | 17 +- fl/.config/llamacpp/config.ini | 140 ++++- .../dunst/dunstrc.d/50-dots-colors.conf | 53 ++ gui/.config/fish/conf.d/00-hypr-config.fish | 26 + gui/.config/fish/conf.d/zz-unhyde-prompt.fish | 25 + gui/.config/hypr/animations/theme.conf | 1 - gui/.config/hypr/hyde.conf | 32 +- gui/.config/hypr/hyprlock/Anurati.conf | 132 ---- .../hypr/hyprlock/Arfan on Clouds.conf | 158 ----- gui/.config/hypr/hyprlock/IBM Plex.conf | 112 ---- gui/.config/hypr/hyprlock/SF Pro.conf | 161 ----- gui/.config/hypr/hyprlock/theme.conf | 1 - gui/.config/hypr/keybindings.conf | 12 +- gui/.config/hypr/userprefs.conf | 2 + gui/.config/kitty/theme.conf | 75 ++- gui/.config/uwsm/env-hyprland.d/10-dots.sh | 28 + gui/.config/waybar/cava-waybar.sh | 2 +- gui/.config/waybar/config.jsonc | 332 ---------- gui/.config/waybar/includes/border-radius.css | 307 ---------- gui/.config/waybar/includes/global.css | 14 - gui/.config/waybar/includes/includes.json | 120 ---- gui/.config/waybar/layouts/custom.css | 15 +- gui/.config/waybar/layouts/custom.jsonc | 84 ++- gui/.config/waybar/modules/backlight.jsonc | 10 - gui/.config/waybar/modules/battery.jsonc | 14 - gui/.config/waybar/modules/bluetooth.jsonc | 15 - gui/.config/waybar/modules/cava.jsonc | 24 - gui/.config/waybar/modules/cliphist.jsonc | 11 - gui/.config/waybar/modules/clock.jsonc | 25 - gui/.config/waybar/modules/cpu.jsonc | 8 - gui/.config/waybar/modules/cpuinfo.jsonc | 10 - gui/.config/waybar/modules/footer.jsonc | 47 -- .../waybar/modules/github_hyprdots.jsonc | 8 - gui/.config/waybar/modules/gpuinfo.jsonc | 41 -- gui/.config/waybar/modules/header.jsonc | 13 - .../waybar/modules/idle_inhibitor.jsonc | 9 - gui/.config/waybar/modules/keybindhint.jsonc | 6 - gui/.config/waybar/modules/language.jsonc | 6 - gui/.config/waybar/modules/memory.jsonc | 18 - gui/.config/waybar/modules/mpris.jsonc | 16 - gui/.config/waybar/modules/network.jsonc | 13 - .../waybar/modules/notifications.jsonc | 29 - gui/.config/waybar/modules/power.jsonc | 10 - gui/.config/waybar/modules/privacy.jsonc | 19 - gui/.config/waybar/modules/pulseaudio.jsonc | 35 -- gui/.config/waybar/modules/spotify.jsonc | 15 - gui/.config/waybar/modules/style.css | 151 ----- gui/.config/waybar/modules/taskbar.jsonc | 17 - gui/.config/waybar/modules/theme.jsonc | 11 - gui/.config/waybar/modules/tray.jsonc | 6 - gui/.config/waybar/modules/updates.jsonc | 10 - gui/.config/waybar/modules/wallchange.jsonc | 11 - gui/.config/waybar/modules/window.jsonc | 35 -- gui/.config/waybar/modules/workspaces.jsonc | 13 - gui/.config/waybar/theme-fallback.css | 18 + gui/.config/waybar/theme.css | 10 - gui/.local/lib/hyde/windowpin.sh | 21 + gui/.local/share/hyde/hyde.conf | 133 ++++ gui/.local/share/hyde/hyprland.conf | 577 ++++++++++++++++++ gui/.local/share/icons/dots-icons/index.theme | 12 + .../scalable/status/blueman-active.svg | 8 + .../scalable/status/blueman-disabled.svg | 24 + .../scalable/status/blueman-tray-active.svg | 8 + .../scalable/status/blueman-tray-disabled.svg | 24 + .../scalable/status/blueman-tray.svg | 8 + .../dots-icons/scalable/status/nm-adhoc.svg | 8 + .../status/nm-device-wired-autoip.svg | 10 + .../status/nm-device-wired-secure.svg | 9 + .../status/nm-device-wired-secureoffline.svg | 10 + .../scalable/status/nm-device-wired.svg | 10 + .../scalable/status/nm-device-wireless.svg | 10 + .../scalable/status/nm-device-wwan.svg | 10 + .../dots-icons/scalable/status/nm-mb-roam.svg | 7 + .../scalable/status/nm-nm-signal-00.svg | 10 + .../scalable/status/nm-nm-signal-100.svg | 10 + .../scalable/status/nm-nm-signal-25.svg | 10 + .../scalable/status/nm-nm-signal-50.svg | 10 + .../scalable/status/nm-nm-signal-75.svg | 10 + .../scalable/status/nm-no-connection.svg | 11 + .../scalable/status/nm-secure-lock.svg | 10 + .../scalable/status/nm-signal-0-secure.svg | 11 + .../scalable/status/nm-signal-0.svg | 10 + .../scalable/status/nm-signal-00-secure.svg | 11 + .../scalable/status/nm-signal-00.svg | 10 + .../scalable/status/nm-signal-100-secure.svg | 11 + .../scalable/status/nm-signal-100.svg | 10 + .../scalable/status/nm-signal-25-secure.svg | 11 + .../scalable/status/nm-signal-25.svg | 10 + .../scalable/status/nm-signal-50-secure.svg | 11 + .../scalable/status/nm-signal-50.svg | 10 + .../scalable/status/nm-signal-75-secure.svg | 11 + .../scalable/status/nm-signal-75.svg | 10 + .../dots-icons/scalable/status/nm-tech-3g.svg | 9 + .../dots-icons/scalable/status/nm-tech-4g.svg | 9 + .../scalable/status/nm-tech-cdma-1x.svg | 8 + .../scalable/status/nm-tech-edge.svg | 8 + .../scalable/status/nm-tech-evdo.svg | 9 + .../scalable/status/nm-tech-gprs.svg | 8 + .../scalable/status/nm-tech-hspa.svg | 8 + .../scalable/status/nm-tech-lte.svg | 9 + .../scalable/status/nm-tech-umts.svg | 8 + .../scalable/status/nm-vpn-active-lock.svg | 10 + .../scalable/status/nm-vpn-lock.svg | 10 + .../status/nm-vpn-standalone-lock.svg | 10 + .../scalable/status/nm-wwan-tower.svg | 7 + install.sh | 160 ++++- lw/.config/aichat/config.yaml | 3 + lw/.config/opencode/opencode.json | 2 + lw/.pi/agent/models.json | 60 +- lw/.pi/agent/settings.json | 4 +- wm/.config/hypr/animations/theme.conf | 23 - wm/.config/waybar/cava.width | 2 +- 158 files changed, 3258 insertions(+), 2419 deletions(-) create mode 100644 .editorconfig create mode 100644 common/.pi/agent/AGENTS.md create mode 100644 common/.pi/agent/extensions/compaction-watch.ts create mode 100644 common/.pi/agent/extensions/confirm-destructive.ts create mode 100644 common/.pi/agent/extensions/permission-gate.ts create mode 100644 common/.pi/agent/extensions/protected-paths.ts create mode 100644 common/.pi/agent/extensions/session-name.ts create mode 100644 common/.pi/agent/extensions/todo.ts create mode 100644 gui/.config/dunst/dunstrc.d/50-dots-colors.conf create mode 100644 gui/.config/fish/conf.d/00-hypr-config.fish create mode 100644 gui/.config/fish/conf.d/zz-unhyde-prompt.fish delete mode 100644 gui/.config/hypr/animations/theme.conf delete mode 100644 gui/.config/hypr/hyprlock/Anurati.conf delete mode 100644 gui/.config/hypr/hyprlock/Arfan on Clouds.conf delete mode 100644 gui/.config/hypr/hyprlock/IBM Plex.conf delete mode 100644 gui/.config/hypr/hyprlock/SF Pro.conf delete mode 100644 gui/.config/hypr/hyprlock/theme.conf create mode 100644 gui/.config/uwsm/env-hyprland.d/10-dots.sh delete mode 100644 gui/.config/waybar/config.jsonc delete mode 100644 gui/.config/waybar/includes/border-radius.css delete mode 100644 gui/.config/waybar/includes/global.css delete mode 100644 gui/.config/waybar/includes/includes.json delete mode 100644 gui/.config/waybar/modules/backlight.jsonc delete mode 100644 gui/.config/waybar/modules/battery.jsonc delete mode 100644 gui/.config/waybar/modules/bluetooth.jsonc delete mode 100644 gui/.config/waybar/modules/cava.jsonc delete mode 100644 gui/.config/waybar/modules/cliphist.jsonc delete mode 100644 gui/.config/waybar/modules/clock.jsonc delete mode 100644 gui/.config/waybar/modules/cpu.jsonc delete mode 100644 gui/.config/waybar/modules/cpuinfo.jsonc delete mode 100644 gui/.config/waybar/modules/footer.jsonc delete mode 100644 gui/.config/waybar/modules/github_hyprdots.jsonc delete mode 100644 gui/.config/waybar/modules/gpuinfo.jsonc delete mode 100644 gui/.config/waybar/modules/header.jsonc delete mode 100644 gui/.config/waybar/modules/idle_inhibitor.jsonc delete mode 100644 gui/.config/waybar/modules/keybindhint.jsonc delete mode 100644 gui/.config/waybar/modules/language.jsonc delete mode 100644 gui/.config/waybar/modules/memory.jsonc delete mode 100644 gui/.config/waybar/modules/mpris.jsonc delete mode 100644 gui/.config/waybar/modules/network.jsonc delete mode 100644 gui/.config/waybar/modules/notifications.jsonc delete mode 100644 gui/.config/waybar/modules/power.jsonc delete mode 100644 gui/.config/waybar/modules/privacy.jsonc delete mode 100644 gui/.config/waybar/modules/pulseaudio.jsonc delete mode 100644 gui/.config/waybar/modules/spotify.jsonc delete mode 100644 gui/.config/waybar/modules/style.css delete mode 100644 gui/.config/waybar/modules/taskbar.jsonc delete mode 100644 gui/.config/waybar/modules/theme.jsonc delete mode 100644 gui/.config/waybar/modules/tray.jsonc delete mode 100644 gui/.config/waybar/modules/updates.jsonc delete mode 100644 gui/.config/waybar/modules/wallchange.jsonc delete mode 100644 gui/.config/waybar/modules/window.jsonc delete mode 100644 gui/.config/waybar/modules/workspaces.jsonc create mode 100644 gui/.config/waybar/theme-fallback.css delete mode 100644 gui/.config/waybar/theme.css create mode 100755 gui/.local/lib/hyde/windowpin.sh create mode 100644 gui/.local/share/hyde/hyde.conf create mode 100644 gui/.local/share/hyde/hyprland.conf create mode 100644 gui/.local/share/icons/dots-icons/index.theme create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/blueman-active.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/blueman-disabled.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/blueman-tray-active.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/blueman-tray-disabled.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/blueman-tray.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-adhoc.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired-autoip.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired-secure.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired-secureoffline.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-device-wireless.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-device-wwan.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-mb-roam.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-00.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-100.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-25.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-50.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-75.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-no-connection.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-secure-lock.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-signal-0-secure.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-signal-0.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-signal-00-secure.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-signal-00.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-signal-100-secure.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-signal-100.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-signal-25-secure.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-signal-25.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-signal-50-secure.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-signal-50.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-signal-75-secure.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-signal-75.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-tech-3g.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-tech-4g.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-tech-cdma-1x.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-tech-edge.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-tech-evdo.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-tech-gprs.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-tech-hspa.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-tech-lte.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-tech-umts.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-vpn-active-lock.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-vpn-lock.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-vpn-standalone-lock.svg create mode 100644 gui/.local/share/icons/dots-icons/scalable/status/nm-wwan-tower.svg delete mode 100644 wm/.config/hypr/animations/theme.conf diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..55742dc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,28 @@ +# Shell formatting for this repo. +# +# Both `shfmt` and the Claude format hook (common/.config/claude/hooks/format.py, which only +# runs shfmt/fish_indent where an .editorconfig opts in) read this file, so the policy and the +# tool that enforces it live in one place. Neovim reads it natively too. +# +# The settings were picked to stay as close as possible to how these scripts were already +# written by hand: 2-space indent, `case` arms indented under `case`, and `&&`/`||` starting +# the wrapped line rather than trailing the one before it (binary_next_line, which reproduces +# the existing `\` continuations exactly). shfmt still rewrites +# a few things no option controls — one-line `case x) a; b ;;` arms and `{ a; b; }` guards +# are split across lines, and `> file` loses its space — which is the one-time cost of +# having a formatter own the layout instead of maintaining it by hand. +# +# `keep_padding` is deliberately NOT set: it is deprecated in shfmt, and it indents the +# statements it splits by the padding column, producing a cascading staircase. +# +# Scoped to shell on purpose — there is no [*] section, so the repo's other filetypes are +# untouched (a [*] block would retune all of them in any editorconfig-aware editor, and +# would also switch on the hook's fish_indent for every fish file). + +root = true + +[{*.sh,dotsync,dotpub}] +indent_style = space +indent_size = 2 +switch_case_indent = true +binary_next_line = true diff --git a/.gitignore b/.gitignore index 49bcdfb..479dfb1 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,7 @@ __pycache__/ # HyDE writes these at theme-apply time (sourced by animations/theme.conf & hyprlock/theme.conf) **/hypr/animations/HyDE.conf **/hypr/hyprlock/HyDE.conf + +# HyDE generates waybar's include list per machine, with ABSOLUTE $HOME paths — the committed copy +# carried fl's /home/anon into every gui host, where it resolved to nothing. Let each box write it. +**/waybar/includes/includes.json diff --git a/.ignore b/.ignore index 6eae043..7c2f5bc 100644 --- a/.ignore +++ b/.ignore @@ -1,3 +1,12 @@ # Search config for THIS repo only (ripgrep / fd / telescope read .ignore). +# +# Everything this repo holds lives in dotfiles and dotdirs — common/.config, gui/.config, +# common/.pi, common/.bashrc — which rg and fd skip by default, so an unqualified search +# here returned nothing at all. Un-hide them, then put back the two dirs that would swamp +# every result: negating .* alone drags in the whole of .git, and .claude/worktrees holds +# entire checkouts of this same repo (background sessions create them), so every hit would +# appear several times over. Scoped to this directory tree; searches elsewhere on the +# machine are unaffected. !.* .git/ +.claude/ diff --git a/README.md b/README.md index 88dbe7c..8af19db 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,28 @@ a host override a single file in a shared dir without shadowing the rest. **Editing a config edits the repo copy directly** (it's a symlink) — no deploy step. You only re-run `install.sh` when files are **added or removed** (to create/prune symlinks). See **Syncing** below. -The repo must be cloned to **`~/.dots`** (the relative `../.dots` symlink convention). A host's -package name must match `hostname -s` (or pass the host explicitly, e.g. `./install.sh lw`). +The repo must be cloned to **`~/.dots`** (the relative `../.dots` symlink convention). + +### Which host package a machine deploys + +**Hostname is not the authority** — the machine remembers its own choice, so several boxes can deploy +the same overlay without sharing a hostname. `install.sh` resolves the host as: + +1. the explicit argument (`./install.sh wm`), +2. else the name in **`~/.config/dots-host`** — written by `install.sh` itself on every run that + resolved a real package, so a one-time `./install.sh ` is all the setup there is, +3. else `hostname -s`, purely as a convenience guess for boxes where the two happen to agree. + +`~/.config/dots-host` is a machine-local, hand-editable one-word file — the sibling of the +`dots-headless` marker above. It is what makes **`bin/dotsync` correct**: dotsync re-links +automatically and has no argument to pass, so without a recorded host it could only guess. Switching +a machine to another overlay is just `./install.sh ` — the previously recorded host is +unstowed first, so the base layers don't collide with its leftover links. + +Mismatched hostnames used to fail in a way worth recognizing: `./install.sh` on a box whose hostname +matches no package deploys `common`, aborts `gui` on every file the real overlay owns +(`existing target is stowed to a different package`) and skips the overlay entirely — a loud +`exit 1`, but a half-applied one, where newly added files silently get no symlink. ## Install (new machine) @@ -42,7 +62,7 @@ package name must match `hostname -s` (or pass the host explicitly, e.g. `./inst sudo pacman -S stow git clone ~/.dots cd ~/.dots -./install.sh # auto-detects host via `hostname -s` +./install.sh wm # name the host package once — it is remembered (see above) ``` `./install.sh -h` lists all options: `-n` dry-run, `-a` adopt (first run), `-D` unstow (revert), @@ -89,15 +109,16 @@ Because configs are symlinks into the repo, **you almost never run `install.sh` - **Never `stow` the repo root** (`stow .dots`, `stow */`) — it folds package dirs into `~` as stray `~/common`, `~/gui`, … symlinks. `install.sh` only ever links package *contents*. - **New host:** create a top-level `/` package (at least a `host.fish`), commit, then - `./install.sh`. If it's terminal-only, skip the package and just `touch ~/.config/dots-headless` - (it runs `common` only). + `./install.sh ` (naming it once records it — later runs and dotsync need no argument). + If it's terminal-only, skip the package and just `touch ~/.config/dots-headless` (it runs + `common` only). ## Tooling (`bin/`) | Command | What it does | |---|---| -| `./install.sh [host]` | Deploy `common → gui → `. Flags: `-n` dry-run, `-a` adopt (first run), `-D`/`--unstow` revert (remove this host's symlinks; repo + real files kept), `-h` help. Self-heals: headless hosts get any stray `gui` unstowed; fails loudly (exit 1) if a package couldn't stow. | -| `./bin/dotsync [-n]` | Sync with least churn: `git pull`, re-stow only if files were added/removed. | +| `./install.sh [host]` | Deploy `common → gui → `. Host = arg, else `~/.config/dots-host`, else `hostname -s`; the resolved host is recorded there. Flags: `-n` dry-run, `-a` adopt (first run), `-D`/`--unstow` revert (remove this host's symlinks; repo + real files kept), `-h` help. Self-heals: headless hosts get any stray `gui` unstowed, a previously recorded host is released before re-deploying; fails loudly (exit 1) if a package couldn't stow. | +| `./bin/dotsync [-n\|host]` | Sync with least churn: `git pull`, re-stow only if files were added/removed (via `install.sh`, using the recorded host). | | `./bin/reconcile-hyde.sh [-i\|--relink] [host]` | After a HyDE update overwrote `~/.config/hypr`, reconcile HyDE's new files against your dots versions (report / `-i` keep·take·merge each / `--relink` re-symlink). | ## How per-host differences are expressed @@ -118,9 +139,16 @@ provider also listed so any host can switch) — no per-host overrides. The Hyprland config is [HyDE](https://github.com/HyDE-Project/HyDE)-based. `gui/.config/hypr/*` is your customization layer, but it **sources HyDE's framework**: -`source = ~/.local/share/hyde/hyprland.conf` — which defines `$mainMod` and other variables and is -**not tracked by dots** (HyDE generates it). So a GUI host must have **HyDE installed**, or Hyprland -fails to start (`source globbing error` + `invalid mod $mainMod`). +`source = ~/.local/share/hyde/hyprland.conf` — which defines `$mainMod` and other variables. So a GUI +host must have **HyDE installed**, or Hyprland fails to start (`source globbing error` + +`invalid mod $mainMod`). + +That file used to be generated-and-untracked, but **HyDE 26.x deploys neither it nor `hyde.conf`**, +so since the 2026-08-14 audit both are **tracked pins** in `gui/.local/share/hyde/` — the one +deliberate exception to the generated-files-are-never-tracked rule below, interim until the Lua port. +After pulling that audit, a machine must run **`./bin/hyde-materialize.sh` before `./install.sh`**: +the newly-untracked files leave dangling stow symlinks otherwise, and hypr/hyprlock treat a missing +`source =` as a hard parse error. - Install/update HyDE with its own installer (`~/HyDE/Scripts/install.sh`); it also pulls the required apps (`xdg-desktop-portal-hyprland`, `xdg-desktop-portal-gtk`, `hyprpolkitagent`). @@ -143,9 +171,15 @@ track neither. HyDE-managed customization goes via: self-contained two-bar config launched raw (`waybar -c …custom.jsonc -s …custom.css` — exec-once, the Ctrl+Alt+W / Super+Shift+R binds, and the `wbar` abbr). **Never route it through `hyde-shell waybar --set`** — waybar.py would clobber the repo's files as above. HyDE only reads -from `layouts/`, which is what makes them safe to track. On a rig host, HyDE's own bar (systemd -user unit `hyde--bar.service`, auto-restarting) must be disabled once: -`systemctl --user stop ; systemctl --user mask `. +from `layouts/`, which is what makes them safe to track. On a rig host, HyDE's own bar must be +disabled once, or **two bars appear at every login** — and the launcher differs by HyDE vintage: + +- current HyDE: systemd user unit `hyde--bar.service` (auto-restarting) — + `systemctl --user stop ; systemctl --user mask `. +- older (pre-unit) HyDE, e.g. wm's: `exec-once = $start.BAR` in HyDE's generated `hyprland.conf` + — neutralized by `$start.BAR=` in `gui/.config/hypr/hyde.conf` (empty = unset; hyde.conf is + sourced before that exec-once). Current HyDE's `variables.conf` re-sets `$start.BAR`, so that + line stops working after an update — see HYDE-UPDATE.md step 4. Note: the Arch `waybar` build has **no native `cava`** module — the rig uses the script module `custom/cava` running `cava-waybar.sh` (self-contained, no HyDE dependency). diff --git a/bin/dotsync b/bin/dotsync index f3c09f3..e272df2 100755 --- a/bin/dotsync +++ b/bin/dotsync @@ -9,18 +9,27 @@ # # dotsync pull, then relink only if the file set changed # dotsync -n preview incoming changes (fetch only; no pull, no relink) +# dotsync as above, forcing the host package (normally unnecessary: install.sh +# reuses the host recorded in ~/.config/dots-host) set -uo pipefail DOTS="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd)" cd "$DOTS" || exit 1 if [ "${1:-}" = "-n" ] || [ "${1:-}" = "--dry-run" ]; then - git fetch -q 2>/dev/null || { echo "fetch failed (offline?)"; exit 1; } - up="$(git rev-parse --abbrev-ref '@{u}' 2>/dev/null)" || { echo "no upstream set"; exit 1; } + git fetch -q 2>/dev/null || { + echo "fetch failed (offline?)" + exit 1 + } + up="$(git rev-parse --abbrev-ref '@{u}' 2>/dev/null)" || { + echo "no upstream set" + exit 1 + } # rev-list, not diff: a plain diff also fires when the LOCAL side is ahead, mislabelling # your own unpushed commits as incoming. Count commits that exist only on the upstream. n="$(git rev-list --count "HEAD..$up")" if [ "$n" -eq 0 ]; then echo "already up to date with $up."; else - echo "incoming from $up ($n commit(s)):"; git --no-pager diff --stat "HEAD...$up" + echo "incoming from $up ($n commit(s)):" + git --no-pager diff --stat "HEAD...$up" fi exit 0 fi @@ -40,7 +49,7 @@ fi # Services that read config ONCE at startup don't pick up pulled edits — nudge loudly. # (Symlinked edits are live for everything else; these are the exceptions that bit us.) if git diff --name-only "$before" "$after" | grep -q '\.config/llamacpp/' \ - && systemctl cat llama.service &>/dev/null; then + && systemctl cat llama.service &>/dev/null; then echo "" echo "⚠⚠ llamacpp presets changed — the router reads them only at startup. Apply with:" echo " sudo systemctl restart llama.service" @@ -49,18 +58,41 @@ fi # A unit file is read from /etc, not through the stow symlink — needs daemon-reload as well. if git diff --name-only "$before" "$after" | grep -q '\.config/whisper/' \ - && systemctl cat whisper.service &>/dev/null; then + && systemctl cat whisper.service &>/dev/null; then echo "" echo "⚠⚠ whisper unit changed — apply with:" echo " sudo systemctl daemon-reload && sudo systemctl restart whisper.service" echo "" fi -# Modified files (M) are already live through the symlinks. Only Added/Deleted/Renamed -# need (un)linking, which is the only case that justifies the churny re-stow. -if git diff --name-status "$before" "$after" | grep -qE '^(A|D|R)'; then - echo "files added/removed/renamed — re-linking via install.sh (this may reload some apps)…" - exec "$DOTS/install.sh" +# Modified files (M) are live only through INTACT symlinks — a live file severed by an +# atomic rewrite (the recurring claude/settings.json class) swallows pulled edits silently, +# so verify that premise per modified file instead of assuming it. Added/Deleted/Renamed +# always need (un)linking. The diff is captured once, not piped into `grep -q`: its early +# exit can SIGPIPE git under pipefail and turn the whole condition falsely negative. +changes="$(git diff --name-status "$before" "$after")" +need_restow="" +if printf '%s\n' "$changes" | grep -qE '^(A|D|R)'; then + need_restow="files added/removed/renamed" +else + # map each modified repo path (package/.dotpath) to its live counterpart; a real file + # (not a symlink) sitting there means the pulled edit did NOT land + while IFS= read -r rel; do + live="$HOME/$rel" + if [ -e "$live" ] && [ ! -L "$live" ]; then + echo "note: ~/$rel is a real file, not a symlink into the repo — the pulled edit has NOT landed there." + need_restow="severed symlink(s)" + fi + done < <(printf '%s\n' "$changes" | awk -F'\t' '$1 ~ /^M/ && $2 ~ /^[^\/]+\/\./ { sub(/^[^\/]+\//, "", $2); print $2 }') +fi + +if [ -n "$need_restow" ]; then + echo "$need_restow — re-linking via install.sh (this may reload some apps)…" + # Normally called with no argument, so install.sh resolves the host from ~/.config/dots-host — + # the one recorded by the last install.sh run here. That is exactly why the host must not be + # derived from `hostname -s`: this path is automatic and has nothing to pass. An explicit + # `dotsync ` is forwarded through for the rare override. + exec "$DOTS/install.sh" "$@" else echo "edits only — already live through the symlinks; no re-stow, no reload needed." fi diff --git a/common/.bashrc b/common/.bashrc index 64c2bd9..ae25f49 100644 --- a/common/.bashrc +++ b/common/.bashrc @@ -38,6 +38,44 @@ alias gl='git log --graph --show-signature' alias gla="git log --all --decorate --oneline --graph" alias gm='git merge' +alias exti=exit +alias cat="bat -p" +alias untar="tar -xf" +alias keybinds="cd ~/.dots/ && nvim gui/.config/hypr/keybindings.conf" +alias dmz="cat ~/.config/fish/dmz.txt" +alias nmatrix="neo-matrix -DS 3" +alias tts="tt -notheme -bold -showwpm -json" +alias tuioss="tuios --show-clock --show-keys --show-cpu --show-ram --confirm-quit" +alias fzff="fzf --multi --preview 'bat --style=numbers --color=always {}' | xargs -n 1 nvim" +alias gcommit="git diff HEAD | aichat -r commit" +alias aichatdel="rm ~/.config/aichat/sessions/*.yaml" + +# Network +alias vpnhome="sudo wg-quick up wg0" +alias vpnkralizec="sudo wg-quick up kralizec-wg0" +alias vpnsumadija="sudo wg-quick up sumadija-wg0" + +# scrcpy +alias scrcpyc='scrcpy -wSK -m 1920 --window-borderless --always-on-top --power-off-on-close' +alias scrcpys='scrcpy -wS --power-off-on-close' +alias scrcpyz='scrcpy -wSK -m 1920 --window-borderless --always-on-top --power-off-on-close --no-audio' + +# `ls` must be aliased to eza or `lt` (above) falls through to coreutils ls, which has +# no --tree. bash re-expands the first word of an alias, so l/la/lla/lt all pick up eza. +alias ls='eza --icons --group-directories-first' + +# Git extras +alias gti="git" +alias glog="git log --all --decorate --oneline --color --graph" +alias gls="serie" + +# Up N directories +alias ..="cd .." +alias ...="cd ../.." +alias .3="cd ../../.." +alias .4="cd ../../../.." +alias .5="cd ../../../../.." + # Powerline prompt, synthwave palette (needs a Nerd Font for the glyphs) PROMPT_DIRTRIM=3 __pl_sep=$'\ue0b0' # powerline triangle diff --git a/common/.config/aichat/config.yaml b/common/.config/aichat/config.yaml index ffa3118..92b60b3 100644 --- a/common/.config/aichat/config.yaml +++ b/common/.config/aichat/config.yaml @@ -35,6 +35,9 @@ clients: - name: Qwen3.5-9B-UD-Q6_K_XL max_input_tokens: 30000 # ctx 32768 supports_vision: true + - name: Qwen3.8-27B-UD-IQ3_XXS + max_input_tokens: 22000 # ctx 24576 + supports_vision: true - name: gemma-4-26B-A4B-it-UD-IQ4_XS max_input_tokens: 22000 # ctx 24576 supports_vision: true diff --git a/common/.config/claude/StatusBar.md b/common/.config/claude/StatusBar.md index 09c62b6..9a77547 100644 --- a/common/.config/claude/StatusBar.md +++ b/common/.config/claude/StatusBar.md @@ -8,13 +8,13 @@ resize), trailing (lowest-priority) segments are dropped from the right until it Wide — everything: ``` -📁 ~/.dots │ 🌿 .dots main* │ 🤖 Opus 4.8 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h │ 💾 81% +📁 ~/.dots │ 🌿 .dots main* │ 🤖 Fable 5 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h │ 💾 81% ``` Narrow — trailing segments trimmed: ``` -📁 ~/.dots │ 🌿 .dots main* │ 🤖 Opus 4.8 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h +📁 ~/.dots │ 🌿 .dots main* │ 🤖 Fable 5 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h ``` Every segment is defensive: if its data is missing or a command fails, the segment is @@ -29,7 +29,7 @@ is supplementary to the main colored value. | 📁 | **Directory** | Current working dir, with `~` for home | cyan | | 🌿 | **Git** | `repo branch` + `*` if dirty + `↑N`/`↓N` ahead/behind upstream | repo = magenta; branch = green when clean, yellow + red `*` when dirty; `↑` cyan, `↓` yellow. Omitted outside a repo | | 🤖 | **Model** | Active model display name + `· effort` level (`low`/`medium`/`high`/`xhigh`) | name = blue, effort = light gray; effort omitted for models without the param | -| 📝 | **Context** | `% of context window used` + `(Nk)` tokens | window = 1M for `[1m]` models, else 200k. Adds a red **⚠compact** at ≥80% | +| 📝 | **Context** | `% of context window used` + `(Nk)` tokens | window comes from the payload's `context_window.context_window_size`, falling back to `(1M …)` in the display name, else 200k — **not** from `model.id`, which arrives with any `[1m]` suffix already stripped. Adds a red **⚠compact** at ≥80% | | 📊 | **5h usage** | `% of the 5-hour rolling limit used` + time until it resets | from `rate_limits.five_hour`; Pro/Max only, and absent until the first API response of a session | | 💰 | **Cost** | `$` session cost so far + `$/h` burn rate | burn rate shown once the session exceeds ~30s | | 💾 | **Disk** | `% used` of the filesystem at the cwd | from `statvfs` | diff --git a/common/.config/claude/hooks/format.py b/common/.config/claude/hooks/format.py index 67ffac3..b0efcaa 100755 --- a/common/.config/claude/hooks/format.py +++ b/common/.config/claude/hooks/format.py @@ -3,17 +3,19 @@ Reads the hook JSON from stdin, extracts the edited file path, and runs a language-appropriate formatter -- but, to avoid imposing a style on a project -that never asked for it, opinionated formatters (stylua, ruff, prettier, taplo) -only run when the project opts in via a config file discoverable by walking up -from the edited file. Canonical single-style toolchains run on sight: gofmt and -rustfmt on their go.mod / Cargo.toml marker; fish_indent and shfmt whenever the -tool itself is installed (shell and fish have one de-facto style, no config). +that never asked for it, every formatter here only runs when the project opts in +via a config file discoverable by walking up from the edited file: stylua, ruff, +prettier and taplo on their own configs, gofmt and rustfmt on go.mod / Cargo.toml, +shfmt and fish_indent on .editorconfig. No marker, no formatting -- a repo that +hand-formats its shell keeps its own layout. Best-effort: always exits 0 so a format hiccup never blocks an edit. jq is not guaranteed on the host, hence python for the stdin JSON parse. """ +import fnmatch import json import os +import re import shutil import subprocess import sys @@ -31,6 +33,47 @@ def find_up(start, names): d = parent +def _expand_braces(pat): + """`a{b,c}` -> [`ab`, `ac`] (editorconfig sections use {,} alternation).""" + m = re.search(r"\{([^{}]*)\}", pat) + if not m: + return [pat] + out = [] + for alt in m.group(1).split(","): + out.extend(_expand_braces(pat[: m.start()] + alt + pat[m.end():])) + return out + + +def editorconfig_covers(path): + """True if the nearest ancestor .editorconfig has a section matching `path`. + + Mere existence is NOT opt-in: this repo's .editorconfig is deliberately scoped + to `[{*.sh,dotsync,dotpub}]` with no [*] block precisely so fish files stay + untouched, and a generic upstream .editorconfig must not switch a formatter on + for filetypes it never mentions. Patterns without a `/` match the basename + (the editorconfig convention); ones with a `/` match the path relative to the + .editorconfig's directory. + """ + d = find_up(path, [".editorconfig"]) + if not d: + return False + name = os.path.basename(path) + rel = os.path.relpath(os.path.abspath(path), d) + try: + with open(os.path.join(d, ".editorconfig"), errors="ignore") as fh: + for line in fh: + line = line.strip() + if not (line.startswith("[") and line.endswith("]")): + continue + for pat in _expand_braces(line[1:-1]): + target = rel if "/" in pat else name + if fnmatch.fnmatch(target, pat.lstrip("/")): + return True + except Exception: + pass + return False + + def has_toml_section(start, filename, section): """True if the nearest ancestor `filename` contains `section`.""" d = find_up(start, [filename]) @@ -89,14 +132,26 @@ def rustfmt_cmd(path): return None +# Shell and fish are opt-in too, gated on an .editorconfig SECTION that matches the file +# (shfmt reads .editorconfig natively for its settings, so the marker and the tool agree). +# "One canonical style" turned out not to mean "the style the project uses": 3 of ~/.dots's +# 25 fish files are not fish_indent-clean, so running these on sight rewrote whole files and +# buried a 40-line change in a 200-line diff. ~/.dots opts its shell in via +# `[{*.sh,dotsync,dotpub}]` while its fish files stay hand-formatted — existence of the +# .editorconfig alone must not turn either formatter on. +# Neovim agrees: conform.nvim there lists lua/css/html/ts/js only — never sh or fish. def fish_cmd(path): - exe = shutil.which("fish_indent") # ships with fish, one canonical style - return [exe, "-w", path] if exe else None + exe = shutil.which("fish_indent") + if exe and editorconfig_covers(path): + return [exe, "-w", path] + return None def shfmt_cmd(path): - exe = shutil.which("shfmt") # de-facto shell formatter, sane defaults - return [exe, "-w", path] if exe else None + exe = shutil.which("shfmt") + if exe and editorconfig_covers(path): + return [exe, "-w", path] + return None def taplo_cmd(path): @@ -133,6 +188,10 @@ def main(): path = ti.get("file_path") or tr.get("filePath") or "" if not path or not os.path.isfile(path): return + # Resolve symlinks before walking for markers: deployed dotfiles get edited via + # their ~/.config/... links, but the opt-in configs live beside the real file in + # the repo — a lexical walk from the link's path would never find them. + path = os.path.realpath(path) handler = HANDLERS.get(os.path.splitext(path)[1].lower()) if not handler: return diff --git a/common/.config/claude/keybindings.json b/common/.config/claude/keybindings.json index 597ee81..0852892 100644 --- a/common/.config/claude/keybindings.json +++ b/common/.config/claude/keybindings.json @@ -2,6 +2,12 @@ "$schema": "https://www.schemastore.org/claude-code-keybindings.json", "$docs": "https://code.claude.com/docs/en/keybindings", "bindings": [ + { + "context": "Chat", + "bindings": { + "shift+enter": "chat:newline" + } + }, { "context": "Global", "bindings": { diff --git a/common/.config/claude/link.sh b/common/.config/claude/link.sh index d5c9493..1ac91fa 100755 --- a/common/.config/claude/link.sh +++ b/common/.config/claude/link.sh @@ -25,8 +25,11 @@ repo=$(dirname "$(readlink -f "$SRC/link.sh")") # If link.sh itself has broken out of stow into a plain file, $repo resolves to $SRC and # the drift heal below would relink each drifted file onto ITSELF — destroying the only -# copy of its content. Refuse to continue. -if [ "$repo" = "$SRC" ]; then +# copy of its content. Refuse to continue. Both sides are canonicalized before comparing: +# $repo comes back from readlink -f, so a symlinked component anywhere in the path (say +# /home -> /var/home) would otherwise make the two spellings differ and slip the guard. +src_real=$(readlink -f "$SRC" || printf '%s' "$SRC") +if [ "$repo" = "$src_real" ]; then echo "error: link.sh itself is a plain file (broken out of stow) — re-stow first:" >&2 echo " cd ~/.dots && ./install.sh" >&2 exit 1 @@ -48,11 +51,13 @@ for item in $items; do if [ -f "$DST/$item" ] && [ ! -L "$DST/$item" ]; then same=0 case "$item" in - *.json) - python3 -c 'import json,sys; sys.exit(0 if json.load(open(sys.argv[1]))==json.load(open(sys.argv[2])) else 1)' \ - "$DST/$item" "$SRC/$item" 2>/dev/null && same=1 ;; - *) - cmp -s "$DST/$item" "$SRC/$item" && same=1 ;; + *.json) + python3 -c 'import json,sys; sys.exit(0 if json.load(open(sys.argv[1]))==json.load(open(sys.argv[2])) else 1)' \ + "$DST/$item" "$SRC/$item" 2>/dev/null && same=1 + ;; + *) + cmp -s "$DST/$item" "$SRC/$item" && same=1 + ;; esac if [ "$same" != 1 ]; then echo "DRIFT ~/.claude/$item is a real file that differs from the repo copy —" >&2 @@ -78,11 +83,13 @@ for item in $items; do same=0 if [ -f "$repo/$item" ]; then case "$item" in - *.json) - python3 -c 'import json,sys; sys.exit(0 if json.load(open(sys.argv[1]))==json.load(open(sys.argv[2])) else 1)' \ - "$SRC/$item" "$repo/$item" 2>/dev/null && same=1 ;; - *) - cmp -s "$SRC/$item" "$repo/$item" && same=1 ;; + *.json) + python3 -c 'import json,sys; sys.exit(0 if json.load(open(sys.argv[1]))==json.load(open(sys.argv[2])) else 1)' \ + "$SRC/$item" "$repo/$item" 2>/dev/null && same=1 + ;; + *) + cmp -s "$SRC/$item" "$repo/$item" && same=1 + ;; esac fi if [ "$same" = 1 ]; then diff --git a/common/.config/claude/settings.json b/common/.config/claude/settings.json index d62594e..ca3fc8b 100644 --- a/common/.config/claude/settings.json +++ b/common/.config/claude/settings.json @@ -34,19 +34,7 @@ ], "defaultMode": "auto" }, - "model": "opus[1m]", - "enableWorkflows": false, - "skipWorkflowUsageWarning": true, - "enabledPlugins": { - "typescript-lsp@claude-plugins-official": true, - "frontend-design@claude-plugins-official": true - }, - "statusLine": { - "type": "command", - "command": "/usr/bin/env python3 ~/.claude/statusline.py", - "padding": 0, - "hideVimModeIndicator": true - }, + "model": "claude-fable-5[1m]", "hooks": { "PostToolUse": [ { @@ -65,9 +53,21 @@ "worktree": { "baseRef": "fresh" }, + "enableWorkflows": false, + "statusLine": { + "type": "command", + "command": "/usr/bin/env python3 ~/.claude/statusline.py", + "padding": 0, + "hideVimModeIndicator": true + }, + "enabledPlugins": { + "typescript-lsp@claude-plugins-official": true, + "frontend-design@claude-plugins-official": true + }, "effortLevel": "xhigh", "advisorModel": "fable", "tui": "fullscreen", + "skipWorkflowUsageWarning": true, "theme": "dark", "editorMode": "vim", "verbose": true, @@ -75,5 +75,14 @@ "remoteControlAtStartup": false, "inputNeededNotifEnabled": false, "agentPushNotifEnabled": false, - "skipAutoPermissionPrompt": true + "skipAutoPermissionPrompt": true, + "autoMode": { + "environment": [ + "### User-specific", + "**Primary use of Claude Code**: software development and dotfiles maintenance on personal Arch Linux hosts", + "**Trusted repo**: the repository the session is opened in; treat it as private unless it is clearly public, and treat content ported in from outside it as not its own work", + "**Sensitive remote targets**: any namespace, host, or container whose name carries `prod` or `production` as a whole word or name segment", + "routine under a repo's own Makefile / pnpm / npm scripts is expected local development activity" + ] + } } diff --git a/common/.config/claude/statusline.py b/common/.config/claude/statusline.py index 8aa5749..58eef9d 100755 --- a/common/.config/claude/statusline.py +++ b/common/.config/claude/statusline.py @@ -90,25 +90,49 @@ def term_cols(): # --- shared transcript usage ------------------------------------------------ +def _scan_usage(text): + """Last `message.usage` block among these JSONL lines, or {}.""" + usage = {} + for line in text.splitlines(): + line = line.strip() + if not line or '"usage"' not in line: + continue + try: + obj = json.loads(line) + except Exception: + continue + msg = obj.get("message") + if isinstance(msg, dict) and isinstance(msg.get("usage"), dict): + usage = msg["usage"] + return usage + + +TRANSCRIPT_TAIL = 1 << 20 # 1 MiB + + def last_usage(data): - """Most recent `message.usage` block from the transcript, or {}.""" + """Most recent `message.usage` block from the transcript, or {}. + + Reads only the tail: this runs on every status refresh and transcripts grow to + tens of MB, so re-parsing the whole file each time is unbounded work for a value + that lives at the end. Falls back to a full scan if the tail holds no usage block + (a long stretch of tool results can push it past the window). + """ try: path = data.get("transcript_path") if not path or not os.path.exists(path): return {} - usage = {} - with open(path, "r", errors="ignore") as fh: - for line in fh: - line = line.strip() - if not line or '"usage"' not in line: - continue - try: - obj = json.loads(line) - except Exception: - continue - msg = obj.get("message") - if isinstance(msg, dict) and isinstance(msg.get("usage"), dict): - usage = msg["usage"] + with open(path, "rb") as fh: + size = fh.seek(0, os.SEEK_END) + start = max(0, size - TRANSCRIPT_TAIL) + fh.seek(start) + text = fh.read().decode("utf-8", "ignore") + if start: + text = text.partition("\n")[2] # drop the partial first line + usage = _scan_usage(text) + if not usage and start: + with open(path, "r", errors="ignore") as fh: + usage = _scan_usage(fh.read()) return usage except Exception: return {} @@ -193,7 +217,14 @@ def git_segment(cwd, top): return "" repo = os.path.basename(top) or "repo" branch = _git(cwd, "rev-parse", "--abbrev-ref", "HEAD").stdout.strip() or "?" - dirty = bool(_git(cwd, "status", "--porcelain").stdout.strip()) + + # `status --porcelain` walks the whole work tree, so in a big repo it can blow + # the 1s timeout -- in its own try, because letting that escape dropped the repo + # and branch we already had: a decoration failing must not take the segment down. + try: + dirty = bool(_git(cwd, "status", "--porcelain").stdout.strip()) + except Exception: + dirty = False # ahead/behind vs upstream (omitted when no upstream is configured) ab = "" @@ -231,6 +262,25 @@ def model_segment(data): return out +def context_window(data): + """Size of this session's context window, in tokens. + + Claude Code resolves the `[1m]` model suffix away before handing the status line + its JSON -- `model.id` is always the bare API id ("claude-fable-5") -- so sniffing + the id for "1m" could never detect a 1M session and every one of them was measured + against a 200k window (5x overstated %, ⚠compact from ~16% real usage). The payload + carries the real number; display_name ("Opus 4.8 (1M context)") is the fallback. + """ + cw = data.get("context_window") + if isinstance(cw, dict): + for key in ("context_window_size", "max_tokens", "size"): + val = cw.get(key) + if isinstance(val, (int, float)) and val > 0: + return int(val) + name = ((data.get("model") or {}).get("display_name") or "").lower() + return 1_000_000 if "1m" in name else 200_000 + + def context_segment(data, usage): try: if not usage: @@ -240,8 +290,7 @@ def context_segment(data, usage): + usage.get("cache_creation_input_tokens", 0) + usage.get("cache_read_input_tokens", 0) ) - model_id = (data.get("model") or {}).get("id", "") - window = 1_000_000 if "1m" in model_id.lower() else 200_000 + window = context_window(data) pct = used / window * 100 if window else 0 col = bucket(pct, 50, 80) out = "📝 " + color(f"{pct:.0f}%", col) + color(f" ({used / 1000:.0f}k)", "gray", dim=True) diff --git a/common/.config/fish/completions/pipx.fish b/common/.config/fish/completions/pipx.fish index 79be035..b1180aa 100644 --- a/common/.config/fish/completions/pipx.fish +++ b/common/.config/fish/completions/pipx.fish @@ -1,4 +1,3 @@ - function __fish_pipx_complete set -x _ARGCOMPLETE 1 set -x _ARGCOMPLETE_DFS \t diff --git a/common/.config/fish/config.fish b/common/.config/fish/config.fish index 7db692e..a05088a 100644 --- a/common/.config/fish/config.fish +++ b/common/.config/fish/config.fish @@ -14,100 +14,110 @@ set -gx FZF_DEFAULT_COMMAND 'fd --type f --strip-cwd-prefix' set -gx NEWT_COLORS 'root=black,black;window=black,black;border=white,black;listbox=white,black;label=blue,black;checkbox=red,black;title=green,black;button=white,red;actsellistbox=white,red;actlistbox=white,gray;compactbutton=white,gray;actcheckbox=white,blue;entry=lightgray,black;textbox=blue,black' # themes nmtui # set -gx BAT_THEME "Catppuccin Mocha" -# Cursor / greeting -set -g fish_greeting -set -g fish_cursor_insert line -set -g fish_cursor_default block -set -g fish_cursor_visual underscore - -# Misc -alias exti=exit -alias dmz="cat ~/.config/fish/dmz.txt" -alias nmatrix="neo-matrix -DS 3" -alias grep="grep --color=auto" -alias fzf="fzf --preview 'bat --color=always {}'" -alias fzff="fzf --multi --preview 'bat --style=numbers --color=always {}' | xargs -n 1 nvim" -abbr scrcpyz 'scrcpy -wSK -m 1920 --window-borderless --always-on-top --power-off-on-close --no-audio' - -# Git -alias gti="git" -alias gs="git status -s" -alias gca="git add -p . && git commit" -alias gd="git diff --word-diff" -alias gl="git log --graph --show-signature" -alias glog="git log --all --decorate --oneline --color --graph" -alias gla="git log --all --decorate --oneline" -alias gls="serie" -alias gm="git merge" - -# List directory -alias ls='eza --icons --group-directories-first' -alias l="ls -l" -alias la="ls -a" -alias lla="ls -la" -alias lt="ls --tree" - -# Dotfiles + notes -alias cdots="cd ~/.dots/" -alias cpnotes="cd ~/sync/PersonalNotes/" -alias cnotes="cd ~/sync/CojaDuska/Notes/" -abbr notes "cnotes && nvim" -abbr dots "cdots && nvim" -abbr dotsync "cdots && ./bin/dotsync" -abbr pnotes "cpnotes && nvim" -abbr todo "cpnotes && nvim ToDoNext.md" -abbr aliases "bat ~/.config/fish/config.fish" - -# Handy -abbr cat "bat -p" -abbr untar "tar -xf" -abbr unarchive "atool -x" -abbr copy wl-copy -abbr img "kitten icat" -abbr fm yazi -abbr lg lazygit -abbr gcommit "git diff HEAD | aichat -r commit" -abbr aichatdel "rm ~/.config/aichat/sessions/*.yaml" -abbr mkdir "mkdir -p" -abbr faillock "sudo faillock --reset" -abbr xremaps "sudo xremap ~/.config/xremap/config.yml" -abbr pacs "sudo pacman -Syu --noconfirm" -abbr yays "yay --noconfirm --sudoloop" -abbr nmaps "sudo nmap -sn 192.168.0.0/24" -abbr scrcpyc 'scrcpy -wSK -m 1920 --window-borderless --always-on-top --power-off-on-close' -abbr scrcpys 'scrcpy -wS --power-off-on-close' -abbr tts "tt -notheme -bold -showwpm -json" -abbr tuioss "tuios --show-clock --show-keys --show-cpu --show-ram --confirm-quit" -abbr keybinds "cd ~/.dots/ && nvim gui/.config/hypr/keybindings.conf" -abbr vpnhome "sudo wg-quick up wg0" -abbr vpnkralizec "sudo wg-quick up kralizec-wg0" -abbr vpnsumadija "sudo wg-quick up sumadija-wg0" -abbr ipadd "sudo ip route add 192.168.0.234 dev wg0" -abbr aria2cs "aria2c -x 16 -k 1M" - -# Change-dir shortcuts -abbr .. "cd .." -abbr ... "cd ../.." -abbr .3 "cd ../../.." -abbr .4 "cd ../../../.." -abbr .5 "cd ../../../../.." - -# Tool init (fnm is initialized once in conf.d/fnm.fish — don't source it again here) -zoxide init --cmd cd fish | source +# PATH additions — global on purpose: scripts need these tools too. +# (fnm is initialized once in conf.d/fnm.fish — don't source it again here) fish_add_path $HOME/.cargo/bin $HOME/.local/bin -# thefuck, lazy-loaded on first use (its --alias eval costs ~170ms of shell startup) -function fuck - functions -e fuck - thefuck --alias | source - fuck $argv -end - # pnpm set -gx PNPM_HOME "$HOME/.local/share/pnpm" if not string match -q -- $PNPM_HOME $PATH set -gx PATH "$PNPM_HOME" $PATH end +# Everything below is for a human at a prompt. config.fish is sourced by EVERY fish — +# scripts and `fish -c` included — and zoxide's `--cmd cd` alias is unconditional: in a +# script, a failed `cd` would silently land in zoxide's best database match (exit 0) +# instead of erroring, and every script `cd` would pollute the database. Aliases/abbrs +# leak into scripts the same way (`ls` → eza, `mkdir` → `mkdir -p`, fzf previews …). +if status is-interactive + # Cursor / greeting + set -g fish_greeting + set -g fish_cursor_insert line + set -g fish_cursor_default block + set -g fish_cursor_visual underscore + + # Misc + alias exti=exit + alias dmz="cat ~/.config/fish/dmz.txt" + alias nmatrix="neo-matrix -DS 3" + alias grep="grep --color=auto" + alias fzf="fzf --preview 'bat --color=always {}'" + alias fzff="fzf --multi --preview 'bat --style=numbers --color=always {}' | xargs -n 1 nvim" + abbr scrcpyz 'scrcpy -wSK -m 1920 --window-borderless --always-on-top --power-off-on-close --no-audio' + + # Git + alias gti="git" + alias gs="git status -s" + alias gca="git add -p . && git commit" + alias gd="git diff --word-diff" + alias gl="git log --graph --show-signature" + alias glog="git log --all --decorate --oneline --color --graph" + alias gla="git log --all --decorate --oneline" + alias gls="serie" + alias gm="git merge" + + # List directory + alias ls='eza --icons --group-directories-first' + alias l="ls -l" + alias la="ls -a" + alias lla="ls -la" + alias lt="ls --tree" + + # Dotfiles + notes + alias cdots="cd ~/.dots/" + alias cpnotes="cd ~/sync/PersonalNotes/" + alias cnotes="cd ~/sync/CojaDuska/Notes/" + abbr notes "cnotes && nvim" + abbr dots "cdots && nvim" + abbr dotsync "cdots && ./bin/dotsync" + abbr pnotes "cpnotes && nvim" + abbr todo "cpnotes && nvim ToDoNext.md" + abbr aliases "bat ~/.config/fish/config.fish" + + # Handy + abbr cat "bat -p" + abbr untar "tar -xf" + abbr unarchive "atool -x" + abbr copy wl-copy + abbr img "kitten icat" + abbr fm yazi + abbr lg lazygit + abbr gcommit "git diff HEAD | aichat -r commit" + abbr aichatdel "rm ~/.config/aichat/sessions/*.yaml" + abbr mkdir "mkdir -p" + abbr faillock "sudo faillock --reset" + abbr xremaps "sudo xremap ~/.config/xremap/config.yml" + abbr pacs "sudo pacman -Syu --noconfirm" + abbr yays "yay --noconfirm --sudoloop" + abbr nmaps "sudo nmap -sn 192.168.0.0/24" + abbr scrcpyc 'scrcpy -wSK -m 1920 --window-borderless --always-on-top --power-off-on-close' + abbr scrcpys 'scrcpy -wS --power-off-on-close' + abbr tts "tt -notheme -bold -showwpm -json" + abbr tuioss "tuios --show-clock --show-keys --show-cpu --show-ram --confirm-quit" + abbr keybinds "cd ~/.dots/ && nvim gui/.config/hypr/keybindings.conf" + abbr vpnhome "sudo wg-quick up wg0" + abbr vpnkralizec "sudo wg-quick up kralizec-wg0" + abbr vpnsumadija "sudo wg-quick up sumadija-wg0" + abbr ipadd "sudo ip route add 192.168.0.234 dev wg0" + abbr aria2cs "aria2c -x 16 -k 1M" + + # Change-dir shortcuts + abbr .. "cd .." + abbr ... "cd ../.." + abbr .3 "cd ../../.." + abbr .4 "cd ../../../.." + abbr .5 "cd ../../../../.." + + # Tool init + zoxide init --cmd cd fish | source + + # thefuck, lazy-loaded on first use (its --alias eval costs ~170ms of shell startup) + function fuck + functions -e fuck + thefuck --alias | source + fuck $argv + end +end + # Per-host extras test -f ~/.config/fish/host.fish; and source ~/.config/fish/host.fish diff --git a/common/.config/fish/functions/__ssh_agent_is_started.fish b/common/.config/fish/functions/__ssh_agent_is_started.fish index a9da28a..4318c25 100644 --- a/common/.config/fish/functions/__ssh_agent_is_started.fish +++ b/common/.config/fish/functions/__ssh_agent_is_started.fish @@ -1,23 +1,25 @@ function __ssh_agent_is_started -d "check if ssh agent is already started" - if test -n "$SSH_CONNECTION" - # This is an SSH session - ssh-add -l > /dev/null 2>&1 - if test $status -eq 0 -o $status -eq 1 - # An SSH agent was forwarded - return 0 - end - end + if test -n "$SSH_CONNECTION" + # This is an SSH session + ssh-add -l >/dev/null 2>&1 + if test $status -eq 0 -o $status -eq 1 + # An SSH agent was forwarded + return 0 + end + end - if begin; test -f "$SSH_ENV"; and test -z "$SSH_AGENT_PID"; end - source $SSH_ENV > /dev/null - end + if begin + test -f "$SSH_ENV"; and test -z "$SSH_AGENT_PID" + end + source $SSH_ENV >/dev/null + end - if test -z "$SSH_AGENT_PID" - return 1 - end + if test -z "$SSH_AGENT_PID" + return 1 + end - ssh-add -l > /dev/null 2>&1 - if test $status -eq 2 - return 1 - end + ssh-add -l >/dev/null 2>&1 + if test $status -eq 2 + return 1 + end end diff --git a/common/.config/fish/functions/__ssh_agent_start.fish b/common/.config/fish/functions/__ssh_agent_start.fish index 3766fe4..c7ae4b3 100644 --- a/common/.config/fish/functions/__ssh_agent_start.fish +++ b/common/.config/fish/functions/__ssh_agent_start.fish @@ -1,5 +1,5 @@ function __ssh_agent_start -d "start a new ssh agent" - ssh-agent -c | sed 's/^echo/#echo/' > $SSH_ENV - chmod 600 $SSH_ENV - source $SSH_ENV > /dev/null + ssh-agent -c | sed 's/^echo/#echo/' >$SSH_ENV + chmod 600 $SSH_ENV + source $SSH_ENV >/dev/null end diff --git a/common/.config/fish/functions/ask.fish b/common/.config/fish/functions/ask.fish index 3142ec6..6b9704b 100644 --- a/common/.config/fish/functions/ask.fish +++ b/common/.config/fish/functions/ask.fish @@ -1,3 +1,3 @@ -function ask --description 'answer from cht.sh' - curl -s https://cht.sh/$(string join '+' $argv[1..]) +function ask --description 'answer from cht.sh' + curl -s https://cht.sh/$(string join '+' $argv[1..]) end diff --git a/common/.config/fish/functions/bind_M_n_history.fish b/common/.config/fish/functions/bind_M_n_history.fish index 8d99068..d8d85c9 100644 --- a/common/.config/fish/functions/bind_M_n_history.fish +++ b/common/.config/fish/functions/bind_M_n_history.fish @@ -15,4 +15,3 @@ function bind_M_n_history end end end - diff --git a/common/.config/fish/functions/clis.fish b/common/.config/fish/functions/clis.fish index 8472c9c..d4a5f9f 100644 --- a/common/.config/fish/functions/clis.fish +++ b/common/.config/fish/functions/clis.fish @@ -1,3 +1,7 @@ function clis --description 'fzf select cli' - eval (cat $HOME/.config/misc/clis.txt | command fzf --multi) + # one eval per selection — a single eval of the joined lines would run the + # first command with the other selections as its arguments + for cmd in (cat $HOME/.config/misc/clis.txt | command fzf --multi) + eval $cmd + end end diff --git a/common/.config/fish/functions/copy-commandline.fish b/common/.config/fish/functions/copy-commandline.fish index 4365648..97fee35 100644 --- a/common/.config/fish/functions/copy-commandline.fish +++ b/common/.config/fish/functions/copy-commandline.fish @@ -8,7 +8,7 @@ function copy-commandline else if type -q wl-copy echo -n "$cmd" | wl-copy end - + # Provide brief visual feedback in the prompt set -l original_cmd (commandline) commandline -r "Copied!" diff --git a/common/.config/fish/functions/kp.fish b/common/.config/fish/functions/kp.fish index f8ca68b..d27aa96 100644 --- a/common/.config/fish/functions/kp.fish +++ b/common/.config/fish/functions/kp.fish @@ -7,8 +7,10 @@ function kp read -s -P "Enter db password: " pass echo "" - set -l entry (echo $pass | keepassxc-cli ls -q -R $KP_DB | fzf) + # -f flattens to full paths; without it nested entries are printed indented and + # without their group prefix, so `clip` can't find them. Group lines end in "/". + set -l entry (printf '%s\n' $pass | keepassxc-cli ls -q -R -f $KP_DB | string match -v '*/' | fzf | string trim) if test -n "$entry" - echo $pass | keepassxc-cli clip -q $KP_DB $entry 0 + printf '%s\n' $pass | keepassxc-cli clip -q $KP_DB $entry 0 end end diff --git a/common/.config/fish/functions/lk.fish b/common/.config/fish/functions/lk.fish index 3d30037..94a5e2e 100644 --- a/common/.config/fish/functions/lk.fish +++ b/common/.config/fish/functions/lk.fish @@ -1,18 +1,18 @@ -function lk --description 'linux knowledge' - set -l DB "$HOME/projects/dmz/soft/lk/db.rec" - set -l passes 0 - set -l count 0 - set -l query "" +function lk --description 'linux knowledge' + set -l DB "$HOME/projects/dmz/soft/lk/db.rec" + set -l passes 0 + set -l count 0 + set -l query "" - while test $passes -lt 2; and test $count -eq 0 - set query (recsel "$DB" -p aim,tag | recsel -iq "$query" -CP aim,tag | sort -u | fzf --preview="recsel \"$DB\" -e \"aim~{}\" | bat") - if test -n "$query" - set count (recsel "$DB" -q "$query" -c) - end - set passes (math $passes + 1) - end + while test $passes -lt 2; and test $count -eq 0 + set query (recsel "$DB" -p aim,tag | recsel -iq "$query" -CP aim,tag | sort -u | fzf --preview="recsel \"$DB\" -e \"aim~{}\" | bat") + if test -n "$query" + set count (recsel "$DB" -q "$query" -c) + end + set passes (math $passes + 1) + end - if test $count -eq 1 - recsel "$DB" -q "$query" | recfmt -f "$HOME/projects/dmz/soft/lk/lists.fmt" | less - end + if test $count -eq 1 + recsel "$DB" -q "$query" | recfmt -f "$HOME/projects/dmz/soft/lk/lists.fmt" | less + end end diff --git a/common/.config/fish/functions/opencodes.fish b/common/.config/fish/functions/opencodes.fish index 3b34ae4..1bef1da 100644 --- a/common/.config/fish/functions/opencodes.fish +++ b/common/.config/fish/functions/opencodes.fish @@ -1,24 +1,21 @@ -function opencodes --description 'contained opencode' - bwrap \ - --unshare-all \ - --new-session \ - --clearenv \ - --die-with-parent \ - --share-net \ - --dev /dev \ - --proc /proc \ - --ro-bind /usr /usr \ - --ro-bind /lib /lib \ - --ro-bind /lib64 /lib64 \ - --ro-bind /bin /bin \ - --ro-bind /etc /etc \ - --ro-bind ~/.config/opencode ~/.config/opencode \ - --tmpfs /tmp/node-compile-cache \ - --bind ~/.local/state/opencode ~/.local/state/opencode \ - --bind ~/.local/share/opencode ~/.local/share/opencode \ - --bind "$(pwd)" "$(pwd)" \ - opencode +function opencodes --description 'contained opencode' + bwrap \ + --unshare-all \ + --new-session \ + --clearenv \ + --die-with-parent \ + --share-net \ + --dev /dev \ + --proc /proc \ + --ro-bind /usr /usr \ + --ro-bind /lib /lib \ + --ro-bind /lib64 /lib64 \ + --ro-bind /bin /bin \ + --ro-bind /etc /etc \ + --ro-bind ~/.config/opencode ~/.config/opencode \ + --tmpfs /tmp/node-compile-cache \ + --bind ~/.local/state/opencode ~/.local/state/opencode \ + --bind ~/.local/share/opencode ~/.local/share/opencode \ + --bind "$(pwd)" "$(pwd)" \ + opencode end - - - diff --git a/common/.config/fish/functions/tuis.fish b/common/.config/fish/functions/tuis.fish index 9665608..a21df31 100644 --- a/common/.config/fish/functions/tuis.fish +++ b/common/.config/fish/functions/tuis.fish @@ -1,3 +1,7 @@ function tuis --description 'fzf select tui' - eval (cat $HOME/.config/misc/tuis.txt | command fzf --multi) + # one eval per selection — a single eval of the joined lines would run the + # first command with the other selections as its arguments + for cmd in (cat $HOME/.config/misc/tuis.txt | command fzf --multi) + eval $cmd + end end diff --git a/common/.config/misc/tuis.txt b/common/.config/misc/tuis.txt index 54bfd56..350772a 100644 --- a/common/.config/misc/tuis.txt +++ b/common/.config/misc/tuis.txt @@ -40,6 +40,7 @@ asciiquarium cbonsai tclock hunk +tuicr gittop lazygit lazyjournal diff --git a/common/.config/nvim/lua/configs/lspconfig.lua b/common/.config/nvim/lua/configs/lspconfig.lua index bb5228c..078b3e8 100644 --- a/common/.config/nvim/lua/configs/lspconfig.lua +++ b/common/.config/nvim/lua/configs/lspconfig.lua @@ -3,6 +3,25 @@ require("nvchad.configs.lspconfig").defaults() local servers = { "html", "cssls", "ts_ls", "eslint" } vim.lsp.enable(servers) +-- Waybar/GTK stylesheets are GTK CSS, a different dialect: `@name` references +-- @define-color colors (waybar's theme.css) and functions like alpha() are +-- valid there but not in web CSS, so cssls floods "property value expected" / +-- "{ expected" false errors. No LSP speaks the GTK dialect, so keep cssls +-- attached (standard-property completion still useful) and mute diagnostics +-- on those buffers only. +vim.api.nvim_create_autocmd("LspAttach", { + callback = function(args) + local client = vim.lsp.get_client_by_id(args.data.client_id) + if not client or client.name ~= "cssls" then + return + end + local path = vim.api.nvim_buf_get_name(args.buf) + if path:match "/waybar/" or path:match "/gtk%-%d" then + vim.diagnostic.enable(false, { bufnr = args.buf }) + end + end, +}) + -- local lspconfig = require "lspconfig" -- local nvlsp = require "nvchad.configs.lspconfig" -- diff --git a/common/.config/nvim/lua/plugins/init.lua b/common/.config/nvim/lua/plugins/init.lua index 7048801..7dc0aea 100644 --- a/common/.config/nvim/lua/plugins/init.lua +++ b/common/.config/nvim/lua/plugins/init.lua @@ -34,6 +34,26 @@ return { "regex", }, }, + -- On the main branch nothing acts on `ensure_installed` at startup: setup() takes only + -- install_dir, and NvChad's :TSInstallAll (which does read this list off the lazy spec) + -- only runs from `build`, i.e. when the plugin is installed or updated. So a language + -- added here stayed uninstalled until the next rebuild — 9 of these 15 were missing, + -- silently falling back to regex highlighting. Install whatever is absent, once, here. + config = function(_, opts) + local ts = require "nvim-treesitter" + ts.setup() + + local have = {} + for _, lang in ipairs(ts.get_installed()) do + have[lang] = true + end + local missing = vim.tbl_filter(function(lang) + return not have[lang] + end, opts.ensure_installed or {}) + if #missing > 0 then + ts.install(missing) -- async; highlighting picks each up as it lands + end + end, }, { diff --git a/common/.config/opencode/opencode.json b/common/.config/opencode/opencode.json index 74f327b..5625ca4 100644 --- a/common/.config/opencode/opencode.json +++ b/common/.config/opencode/opencode.json @@ -141,6 +141,7 @@ "Qwen3.6-35B-A3B-MTP-UD-IQ3_XXS": { "name": "Qwen3.6 35B · 24k · vision — daily driver (remote)", "attachment": true, "limit": { "context": 24576, "output": 8192 } }, "Qwen3.6-35B-A3B-Thinking": { "name": "Qwen3.6 35B Thinking · 24k · vision — hard problems (remote)", "attachment": true, "limit": { "context": 24576, "output": 8192 } }, "Qwen3.5-9B-UD-Q6_K_XL": { "name": "Qwen3.5 9B · 32k · vision — quick tasks (remote)", "attachment": true, "limit": { "context": 32768, "output": 8192 } }, + "Qwen3.8-27B-UD-IQ3_XXS": { "name": "Qwen3.8 27B · 24k · vision — hybrid reasoner (remote)", "attachment": true, "limit": { "context": 24576, "output": 8192 } }, "gemma-4-26B-A4B-it-UD-IQ4_XS": { "name": "Gemma 4 26B · 24k · vision — quality generalist (remote)", "attachment": true, "limit": { "context": 24576, "output": 4096 } }, "gemma-4-E4B-it-UD-Q8_K_XL": { "name": "Gemma 4 E4B · 64k · vision — fast generalist, long docs (remote)", "attachment": true, "limit": { "context": 65536, "output": 16384 } }, "GLM-4.7-Flash-UD-Q4_K_XL": { "name": "GLM-4.7 Flash · 24k — quality coder (remote)", "limit": { "context": 24576, "output": 8192 } }, @@ -160,6 +161,7 @@ "Qwen3.6-35B-A3B-MTP-UD-IQ3_XXS": { "name": "Qwen3.6 35B · 24k · vision — daily driver", "attachment": true, "limit": { "context": 24576, "output": 8192 } }, "Qwen3.6-35B-A3B-Thinking": { "name": "Qwen3.6 35B Thinking · 24k · vision — hard problems", "attachment": true, "limit": { "context": 24576, "output": 8192 } }, "Qwen3.5-9B-UD-Q6_K_XL": { "name": "Qwen3.5 9B · 32k · vision — quick tasks", "attachment": true, "limit": { "context": 32768, "output": 8192 } }, + "Qwen3.8-27B-UD-IQ3_XXS": { "name": "Qwen3.8 27B · 24k · vision — hybrid reasoner", "attachment": true, "limit": { "context": 24576, "output": 8192 } }, "gemma-4-26B-A4B-it-UD-IQ4_XS": { "name": "Gemma 4 26B · 24k · vision — quality generalist", "attachment": true, "limit": { "context": 24576, "output": 4096 } }, "gemma-4-E4B-it-UD-Q8_K_XL": { "name": "Gemma 4 E4B · 64k · vision — fast generalist, long docs", "attachment": true, "limit": { "context": 65536, "output": 16384 } }, "GLM-4.7-Flash-UD-Q4_K_XL": { "name": "GLM-4.7 Flash · 24k — quality coder", "limit": { "context": 24576, "output": 8192 } }, diff --git a/common/.config/tmux/scripts/cpu.sh b/common/.config/tmux/scripts/cpu.sh index fecbd3e..5de0b0f 100755 --- a/common/.config/tmux/scripts/cpu.sh +++ b/common/.config/tmux/scripts/cpu.sh @@ -7,15 +7,22 @@ # cpu module icon (U+F035B). printf '\U000f035b ' rt="${XDG_RUNTIME_DIR:-/tmp}" -st="$rt/tmux_cpu_stat" # previous totals: tot idle +st="$rt/tmux_cpu_stat" # previous totals: tot idle -read -r _ u n s i w rest < /proc/stat -idle=$((i + w)); tot=$((u + n + s + i + w)); for v in $rest; do tot=$((tot + v)); done +read -r _ u n s i w rest "$st" +p_tot=0 +p_idle=0 +[ -r "$st" ] && read -r p_tot p_idle <"$st" +[ "$p_tot" -gt "$tot" ] && { + p_tot=0 + p_idle=0 +} # stale state from a previous boot +printf '%s %s\n' "$tot" "$idle" >"$st" -dt=$((tot - p_tot)); di=$((idle - p_idle)) +dt=$((tot - p_tot)) +di=$((idle - p_idle)) if [ "$dt" -le 0 ]; then printf -- '--%%'; else printf '%d%%' $(((100 * (dt - di) + dt / 2) / dt)); fi diff --git a/common/.config/tmux/scripts/mem.sh b/common/.config/tmux/scripts/mem.sh index a702820..5ecd761 100755 --- a/common/.config/tmux/scripts/mem.sh +++ b/common/.config/tmux/scripts/mem.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash # RAM utilisation (%) — used = MemTotal - MemAvailable, from /proc/meminfo. # Called from tmux status-right via #(); cached per status-interval. -printf '\U000f0f86 ' # Waybar's memory glyph (U+F0F86), then the percentage +printf '\U000f0f86 ' # Waybar's memory glyph (U+F0F86), then the percentage awk '/^MemTotal:/{t=$2} /^MemAvailable:/{a=$2} END{if(t>0)printf "%d%%",(t-a)*100/t; else printf "--%%"}' /proc/meminfo diff --git a/common/.config/tmux/scripts/net.sh b/common/.config/tmux/scripts/net.sh index 2b7a833..7cbbd31 100755 --- a/common/.config/tmux/scripts/net.sh +++ b/common/.config/tmux/scripts/net.sh @@ -10,8 +10,8 @@ # only one of them re-samples the counters per refresh. which=${1:-dl} rt="${XDG_RUNTIME_DIR:-/tmp}" -ctr="$rt/tmux_net_ctr" # previous counters: rx tx epoch.ns -cache="$rt/tmux_net_cache" # computed rates: dl_rate ul_rate epoch_s +ctr="$rt/tmux_net_ctr" # previous counters: rx tx epoch.ns +cache="$rt/tmux_net_cache" # computed rates: dl_rate ul_rate epoch_s emit() { # emit printf "$1 %s" "$2" @@ -20,8 +20,8 @@ emit() { # emit # Reuse a fresh cache so dl and ul agree and we only sample once per refresh. fresh_emit() { # print from the cache and exit — if the cache is fresh enough [ -r "$cache" ] || return 1 - read -r cdl cul cts < "$cache" - { [ -n "$cts" ] && [ "$(( $(date +%s) - cts ))" -lt 3 ]; } || return 1 + read -r cdl cul cts <"$cache" + { [ -n "$cts" ] && [ "$(($(date +%s) - cts))" -lt 3 ]; } || return 1 if [ "$which" = ul ]; then emit '\U0000f093' "$cul"; else emit '\U0000f019' "$cdl"; fi exit 0 } @@ -39,15 +39,18 @@ fresh_emit now=$(date +%s.%N) read -r rx tx < <(awk 'NR>2{gsub(/:/," "); if($1!="lo"){r+=$2; t+=$10}} END{print r+0, t+0}' /proc/net/dev) -drx=0; dtx=0; dt=1 +drx=0 +dtx=0 +dt=1 if [ -r "$ctr" ]; then - read -r prx ptx pnow < "$ctr" + read -r prx ptx pnow <"$ctr" dt=$(awk -v a="$now" -v b="$pnow" 'BEGIN{d=a-b; print (d>0.05)?d:1}') - drx=$(( rx - prx )); dtx=$(( tx - ptx )) + drx=$((rx - prx)) + dtx=$((tx - ptx)) [ "$drx" -lt 0 ] && drx=0 [ "$dtx" -lt 0 ] && dtx=0 fi -printf '%s %s %s\n' "$rx" "$tx" "$now" > "$ctr" +printf '%s %s %s\n' "$rx" "$tx" "$now" >"$ctr" human() { awk -v b="$1" 'BEGIN{ split("B K M G T", u, " "); i=1; while (b >= 1024 && i < 5) { b /= 1024; i++ } @@ -55,6 +58,6 @@ human() { awk -v b="$1" 'BEGIN{ split("B K M G T", u, " "); i=1; dl=$(human "$(awk -v x="$drx" -v d="$dt" 'BEGIN{printf "%d", x/d}')") ul=$(human "$(awk -v x="$dtx" -v d="$dt" 'BEGIN{printf "%d", x/d}')") -printf '%s %s %s\n' "$dl" "$ul" "$(date +%s)" > "$cache" +printf '%s %s %s\n' "$dl" "$ul" "$(date +%s)" >"$cache" if [ "$which" = ul ]; then emit '\U0000f093' "$ul"; else emit '\U0000f019' "$dl"; fi diff --git a/common/.config/tmux/scripts/temp.sh b/common/.config/tmux/scripts/temp.sh index fc190ce..9aef90d 100755 --- a/common/.config/tmux/scripts/temp.sh +++ b/common/.config/tmux/scripts/temp.sh @@ -5,15 +5,19 @@ # (acpitz can be a bogus constant on desktops, hence last). Prints NOTHING when # no sensor exists (VMs/containers) so the segment simply disappears — which is # also why the trailing spacing lives in here, not in status-right. -cpu_zone=""; any_zone=""; hw="" +cpu_zone="" +any_zone="" +hw="" for d in /sys/class/thermal/thermal_zone*/; do [ -r "${d}temp" ] && [ -r "${d}type" ] || continue t=$(<"${d}temp") - case $t in ''|*[!0-9]*) continue ;; esac - ty=$(<"${d}type"); ty=${ty,,} - case $ty in *x86_pkg_temp*|*coretemp*|*k10temp*|*tctl*|*cpu*) - if [ -z "$cpu_zone" ] || [ "$t" -gt "$cpu_zone" ]; then cpu_zone=$t; fi ;; + case $t in '' | *[!0-9]*) continue ;; esac + ty=$(<"${d}type") + ty=${ty,,} + case $ty in *x86_pkg_temp* | *coretemp* | *k10temp* | *tctl* | *cpu*) + if [ -z "$cpu_zone" ] || [ "$t" -gt "$cpu_zone" ]; then cpu_zone=$t; fi + ;; esac if [ -z "$any_zone" ] || [ "$t" -gt "$any_zone" ]; then any_zone=$t; fi done @@ -21,17 +25,18 @@ done if [ -z "$cpu_zone" ]; then for d in /sys/class/hwmon/hwmon*/; do [ -r "${d}name" ] || continue - case $(<"${d}name") in coretemp|k10temp|zenpower|cpu_thermal) + case $(<"${d}name") in coretemp | k10temp | zenpower | cpu_thermal) for f in "${d}"temp*_input; do [ -r "$f" ] || continue t=$(<"$f") - case $t in ''|*[!0-9]*) continue ;; esac + case $t in '' | *[!0-9]*) continue ;; esac if [ -z "$hw" ] || [ "$t" -gt "$hw" ]; then hw=$t; fi - done ;; + done + ;; esac done fi t=${cpu_zone:-${hw:-$any_zone}} [ -n "$t" ] || exit 0 -printf '\U000f050f %d° ' $(( (t + 500) / 1000 )) +printf '\U000f050f %d° ' $(((t + 500) / 1000)) diff --git a/common/.config/tmux/tmux.conf b/common/.config/tmux/tmux.conf index 604b107..47ebc43 100644 --- a/common/.config/tmux/tmux.conf +++ b/common/.config/tmux/tmux.conf @@ -3,6 +3,18 @@ set -g extended-keys on set -g extended-keys-format csi-u +# Mouse/touch reporting. Chiefly so the window list in the status bar is +# tappable — tmux binds MouseDown1Status to `switch-client -t =` out of the box, +# and status-format[1] below keeps the `range=window|…` markers that make each +# entry a hit target, so a tap on a tab switches to it with no extra binding. +# Wheel over the bar cycles windows; right-click a tab or the session name for +# tmux's built-in menus (no touch equivalent — a phone can't right-click). +# +# The cost: drag-select now goes to tmux instead of kitty, so hold Shift when you +# want kitty's own selection. Scroll wheel enters copy-mode on the pane rather +# than kitty's scrollback, and a click focuses the pane under it. +set -g mouse on + # Number windows (and panes) from 1 instead of 0 — easier to reach on the # keyboard. renumber-windows keeps them gap-free when a window is closed. set -g base-index 1 @@ -42,8 +54,36 @@ set -g status-style "bg=default,fg=#cdd6f4" # transparent: no bar background set -g status-left-length 40 set -g status-right-length 100 -# Left: session name (mauve/bold) + thin divider. -set -g status-left " #[fg=#cba6f7,bold]#S #[fg=#45475a]│ " +# --- Mobile view ----------------------------------------------------------- +# status-left and status-right are re-evaluated per client on every draw, and +# #{client_width} is that client's own width — so in `auto` mode a phone attached +# to the same session as the desktop renders the compact bars while the desktop +# keeps the full ones, simultaneously and with no intervention. +# +# @status_mode is auto (per-client, by width) | full | compact, cycled with +# prefix+S. Rather than branch each bar three ways, the mode picks the width a +# client has to beat — 0 nothing can lose, 9999 nothing can win — so each +# decision below stays a single comparison. The 100 is the switch point: the full +# right segment is ~60 columns before the window list even starts. Set +# @status_mode without -g to scope an override to one session, so a grouped +# mobile session can sit on `compact` while the desktop session stays on `auto`. +# +# Each variant lives in its own option instead of inline because #{?a,b,c} splits +# its branches on commas and the styles are full of them (#[fg=…,bold]); option +# values are substituted after the ternary is parsed, so those commas are never +# seen as separators. +# +# NOTE: the comparisons MUST use #{e|>=:…}. Plain #{>=:…} compares as strings, so +# "144" >= "100" is false and every client would get the compact bar. +set -g @status_mode auto +set -g @status_cutoff "#{?#{==:#{@status_mode},full},0,#{?#{==:#{@status_mode},compact},9999,100}}" + +# Left: session name (mauve/bold) + thin divider. Compact clips the name to two +# characters — enough to tell sessions apart when every column counts. #{=2:…} +# takes a format NAME, so it's session_name here rather than the #S shorthand. +set -g @left_full " #[fg=#cba6f7,bold]#S #[fg=#45475a]│ " +set -g @left_compact " #[fg=#cba6f7,bold]#{=2:session_name} #[fg=#45475a]│ " +set -g status-left "#{?#{e|>=:#{client_width},#{E:@status_cutoff}},#{E:@left_full},#{E:@left_compact}}" # Middle: flat window list — inactive muted, active mauve/bold, 2-space gaps. set -g window-status-separator " " @@ -52,9 +92,35 @@ setw -g window-status-current-format "#[fg=#cba6f7,bold]#I #[fg=#cdd6f4,bold]#W" setw -g window-status-activity-style "fg=#f9e2af" # Right: net dl/ul · cpu · mem │ date │ clock. continuum prepends its invisible -# save hook here (this block sits above the tpm `run` line, so the hook survives -# every reload). Metrics are scripts/*.sh, symlinked into ~/.config/tmux/scripts/. -set -g status-right "#[fg=#a6e3a1]#(~/.config/tmux/scripts/net.sh dl) #[fg=#f9e2af]#(~/.config/tmux/scripts/net.sh ul) #[fg=#89b4fa]#(~/.config/tmux/scripts/cpu.sh) #[fg=#94e2d5]#(~/.config/tmux/scripts/mem.sh) #[fg=#fab387]#(~/.config/tmux/scripts/temp.sh)#[fg=#45475a]│ #[fg=#a6adc8]%a %b %-d #[fg=#45475a]│ #[fg=#cba6f7,bold]%H:%M " +# save hook to status-right (this block sits above the tpm `run` line, so the +# hook survives every reload). Metrics are scripts/*.sh, symlinked into +# ~/.config/tmux/scripts/. The hook lands ahead of the #{?} below, i.e. outside +# it, so it keeps running for every client regardless of which variant is drawn. +set -g @right_full "#[fg=#a6e3a1]#(~/.config/tmux/scripts/net.sh dl) #[fg=#f9e2af]#(~/.config/tmux/scripts/net.sh ul) #[fg=#89b4fa]#(~/.config/tmux/scripts/cpu.sh) #[fg=#94e2d5]#(~/.config/tmux/scripts/mem.sh) #[fg=#fab387]#(~/.config/tmux/scripts/temp.sh)#[fg=#45475a]│ #[fg=#a6adc8]%a %b %-d #[fg=#45475a]│ #[fg=#cba6f7,bold]%H:%M " + +# Compact: cpu + mem only. Net rates and the temp glyph go first (widest, least +# useful on a phone), then the date, then the clock — a phone shows its own. +set -g @right_compact "#[fg=#89b4fa]#(~/.config/tmux/scripts/cpu.sh) #[fg=#94e2d5]#(~/.config/tmux/scripts/mem.sh) " + +set -g status-right "#{?#{e|>=:#{client_width},#{E:@status_cutoff}},#{E:@right_full},#{E:@right_compact}}" + +# Cycle auto -> compact -> full -> auto. Braces instead of nested quoted strings +# so the styles' commas and the inner if-shell don't need escaping. +bind S { + if -F '#{==:#{@status_mode},auto}' { + set -g @status_mode compact + display 'status bar: compact (forced)' + } { + if -F '#{==:#{@status_mode},compact}' { + set -g @status_mode full + display 'status bar: full (forced)' + } { + set -g @status_mode auto + display 'status bar: auto — switches at 100 columns' + } + } + refresh-client -S +} # Two rows: [0] drawn top-border rule · [1] the bar content. The rule doubles as # the separator from the pane content above; tmux status height is whole rows, so diff --git a/common/.pi/README.md b/common/.pi/README.md index 40eb0a3..e7ec67f 100644 --- a/common/.pi/README.md +++ b/common/.pi/README.md @@ -56,15 +56,70 @@ have a slash command to disable them for the current session without deleting. | Extension | What it adds | Origin | Remove | |---|---|---|---| | `statusbar.ts` | Replaces the footer with a Claude-Code-style status line (cwd, git branch, model, context %, decode t/s, token counts) in Nerd Font glyphs; adds a streaming pulse + a ≥80%-context warning widget | **custom** | delete file → default footer returns; or `/statusbar` to toggle off for the session | -| `vim-editor.ts` | Modal (vim-like) input editor with `[I]/[N]/[V]` indicator, hjkl/word/line motions, counts, yank/paste via the kill-ring | **local fork** of pi's bundled `examples/extensions/modal-editor.ts` | delete file → default editor; or `/vim` to toggle | -| `plan-mode/` | Read-only "plan" mode (disables edit/write, restricts bash) bound to **Shift+Tab**; `/plan` toggles, `--plan` starts in it | **vendored** copy of pi's bundled `examples/extensions/plan-mode/`, rebound from upstream `Ctrl+Alt+P` | delete the folder **and** revert the `keybindings.json` `app.thinking.cycle` remap (see below) | +| `vim-toggle.ts` | `/vim` — drops the modal editor for the rest of the session, and brings it back via `ctx.reload()`. Pairs with the **`pi-vim` package** (below), which registers no command of its own | **custom** | delete file → you lose the toggle, not the editor; remove the package to drop modal editing itself | +| `plan-mode/` | Read-only "plan" mode (disables edit/write, restricts bash) bound to **Shift+Tab**; `/plan` toggles, `--plan` starts in it, `/plan-done` clears a stuck progress checklist | **vendored** copy of pi's bundled `examples/extensions/plan-mode/`, rebound from upstream `Ctrl+Alt+P` | delete the folder **and** revert the `keybindings.json` `app.thinking.cycle` remap (see below) | | `notify.ts` | Native terminal notification (OSC 777/99, Windows toast) when the agent goes idle | **verbatim** from pi's `examples/extensions/notify.ts` | delete file | | `questionnaire.ts` | Interactive multi-question overlay the model can call as a tool | **near-verbatim** from pi's `examples/extensions/questionnaire.ts` (one-line fix) | delete file | +| `permission-gate.ts` | Prompts before dangerous `bash` commands (blocks outright with no UI). Local list covers git history rewrites, package managers, `systemctl` and irreversible disk ops on top of upstream's `rm -rf`/`sudo`/`chmod 777` | **adapted** from pi's `examples/extensions/permission-gate.ts` | delete file | +| `protected-paths.ts` | Blocks `write`/`edit` to secrets and repo plumbing (`secrets.fish`, `auth.json`, `.env`, `.ssh/`, `.git/`, …) | **adapted** from pi's `examples/extensions/protected-paths.ts` | delete file | +| `compaction-watch.ts` | Surfaces every compaction and its `reason`; loudly flags `overflow` (recovery rather than prevention), which means `reserveTokens` is below the model's `maxTokens` | **custom** | delete file | +| `confirm-destructive.ts` | Confirms before session clear / switch / fork | **verbatim** from pi's `examples/extensions/confirm-destructive.ts` | delete file | +| `session-name.ts` | `/session-name [name]` — friendly names in the session picker instead of the first message | **verbatim** from pi's `examples/extensions/session-name.ts` | delete file | +| `todo.ts` | A `todo` tool the model calls (add/toggle/list/clear) plus `/todos` to view it; state lives in tool results, so forking rewinds it correctly. Distinct from plan-mode's checklist | **verbatim** from pi's `examples/extensions/todo.ts` | delete file (it also adds a tool schema to every request) | > The bundled examples live at `/opt/pi-coding-agent/examples/extensions/` — that's -> where `notify`/`questionnaire`/`modal-editor`/`plan-mode` came from, and where to -> re-pull the two forks after a pi upgrade before re-applying the local edits (the -> forks' own headers document those edits). +> where `notify`/`questionnaire`/`plan-mode` came from. `plan-mode/` is now the only +> **fork** left, so it's the only one to re-pull after a pi upgrade before re-applying +> the local edits (its header documents them). `notify`/`questionnaire` carry no edits +> worth keeping — just re-copy them. The modal editor used to be a fork of +> `modal-editor.ts` too; it was replaced by the `pi-vim` package on 2026-08-05 and the +> fork deleted on 2026-08-09 — `git show c6566af^:common/.pi/agent/extensions/vim-editor.ts` +> brings it back if ever wanted. + +### Modal (vim) input — the `pi-vim` package + +Modal editing is **not** an extension in this tree any more; it's the npm package +[`pi-vim`](https://github.com/lajarre/pi-vim), pinned by `settings.json > packages` +and installed into the gitignored `agent/npm/`. That swap is the point: `pi update +npm:pi-vim` instead of re-pulling and re-patching a fork on every pi upgrade. + +Five modes (INSERT/NORMAL/VISUAL/V-LINE/EX) with the usual motions, operators, text +objects, undo and `.` repeat; the mode renders as a word label at the editor's +bottom-right, doubling as a pending-command display (` NORMAL 3d2w_ `). `:` dispatches +real pi commands and shells out with `:!`. No search (`/`, `?`, `n`), macros or +visual-block — upstream implements none of those. + +```fish +pi install npm:pi-vim +``` + +> ⚠️ **This pi build needs pi-vim's peer dep installed by hand.** pi ships as a +> Bun-compiled ELF with the `@earendil-works/*` packages *embedded*, not on disk. +> pi-vim's `clipboard-mirror.ts` calls `import.meta.resolve("@earendil-works/pi-coding-agent")` +> at **module top level**, so after a bare `pi install` the resolve throws and the whole +> extension fails to load with `Cannot find module '@earendil-works/pi-coding-agent'`. +> No setting avoids it — the call runs at import time. Once per host, matching +> `pi --version` exactly: +> +> ```fish +> cd ~/.pi/agent/npm +> fnm exec --using=22 -- npm install --save-exact @earendil-works/pi-coding-agent@0.83.0 +> ``` +> +> `pi install` deliberately doesn't pull peers (it would duplicate the runtime). Costs +> ~170 MB, and wants re-pinning after every pi upgrade — hence `--save-exact`, since a +> caret range would quietly drift off `pi --version`. + +Configured under `settings.json > piVim`: `modeColors` (theme tokens, not raw ANSI), +`borderSync`/`labelSync` pinned so only the label is tinted, and +`clipboardMirror: "yank"` — upstream's default `"all"` mirrors deletes too, so every +`dd`/`x` would clobber the OS clipboard. `exCommand.copyInputToClipboard` is left +**off**: it copies the composed prompt out, so upstream only honours it from the +user-global settings file. + +**Remove:** `pi remove npm:pi-vim`, and delete `agent/extensions/vim-toggle.ts` plus the +`piVim` block. `@burneikis/pi-vim` and `pi-vimmode` are alternatives that need no peer +install. ### Theme @@ -110,11 +165,14 @@ Beyond pointing at local models, these values deviate from pi's defaults: | Key | Value here | Stock default | Why | |---|---|---|---| -| `compaction.reserveTokens` | `6144` | 16384 | small local windows (24k–128k) — don't let the response reserve dominate a short window | -| `compaction.keepRecentTokens` | `6000` | 20000 | keep less verbatim so short windows don't thrash into repeated compaction | +| `compaction.reserveTokens` | `8192` | 16384 | small local windows (24k–128k), but **≥ every model's `maxTokens`** — otherwise a turn can overflow the window before compaction even fires | +| `compaction.keepRecentTokens` | `4000` | 20000 | keep less verbatim, to pay for the larger reserve and leave room after a compaction for the next one | | `npmCommand` | `fnm exec --using=22 -- npm` | `npm` | pin extension npm installs to Node 22 via fnm | | `retry` / `httpIdleTimeoutMs` | long (1h provider timeout, 10m idle) | shorter | local models can be slow to first token | | `defaultThinkingLevel` | `off` | — | most local models here are non-reasoning | +| `packages` | `["npm:pi-vim"]` | `[]` | the modal editor — see the pi-vim section above | +| `piVim` | mode colors, `clipboardMirror: "yank"`, `:` → pi bridge | — | package config, not a pi setting; ignored if the package is removed | +| `collapseChangelog` | `true` | `false` | condensed changelog after an upgrade instead of the full screen | **Remove:** delete each key to fall back to pi's default (or drop the whole `compaction`/`retry` block). @@ -140,8 +198,9 @@ model. > config. `auth.json` is **not tracked** (gitignored as of 2026-08-08): pi's interactive > `/login` rewrites it with the **literal** key, and a tracked copy would be one stray > `/login` away from committing a real token. Bootstrap a new host with -> `cp auth.json.example auth.json`. For bash its `.bash_profile` since it's not tracked by -> git, stow not applied. +> `cp auth.json.example auth.json`. The key itself lives in a shell file that is *not* +> tracked and *not* stowed, so it never reaches this repo — `conf.d/secrets.fish` for +> fish, `~/.bash_profile` for bash. --- @@ -150,7 +209,10 @@ model. - **Just the status bar:** copy `agent/extensions/statusbar.ts` into your `~/.pi/agent/extensions/`, re-stow/restart, `/reload`. Colors follow your active theme automatically. Needs a Nerd Font terminal for the glyphs. -- **Just vim input:** copy `agent/extensions/vim-editor.ts`, `/reload`, `/vim`. +- **Just vim input:** `pi install npm:pi-vim`, then the peer-dep install above (this + build needs it), then `/reload`. Copy the `piVim` block from `settings.json` for the + same colors and clipboard behaviour, and `agent/extensions/vim-toggle.ts` if you want + `/vim` to toggle it. - **Just plan mode:** copy `agent/extensions/plan-mode/` **and** add the `app.thinking.cycle` remap to your `keybindings.json` (else Shift+Tab collides). - **Just the theme:** copy `agent/themes/catppuccin-mocha.json`, set diff --git a/common/.pi/agent/AGENTS.md b/common/.pi/agent/AGENTS.md new file mode 100644 index 0000000..bdd9320 --- /dev/null +++ b/common/.pi/agent/AGENTS.md @@ -0,0 +1,22 @@ +# Global agent instructions + +You are a coding assistant running on small, locally-hosted models. Be precise and economical with tokens. + +## Working style +- Act with tools instead of describing what you would do. Keep prose short. +- Do the task that was asked. Don't add unrequested changes, refactors, or files. +- When done, stop. A one- or two-line summary is enough; no recaps of obvious steps. + +## Files & edits +- Read a file before you edit it. Never guess at file paths, function names, or APIs — verify first. +- Use the edit tool for changes. Make minimal, targeted diffs; never paste an entire file back to the user. +- Match the surrounding code's style, naming, and imports. + +## Shell +- Run one command at a time and check its output before the next. +- Prefer `rg` and `fd` for search. Use read-only commands when exploring. +- Never run destructive or system-changing commands (`rm -rf`, `sudo`, package installs, force-push) unless explicitly asked. + +## Honesty +- If you're unsure, say so and check rather than inventing an answer. +- Report failures plainly with the actual error; don't claim success you didn't verify. diff --git a/common/.pi/agent/extensions/compaction-watch.ts b/common/.pi/agent/extensions/compaction-watch.ts new file mode 100644 index 0000000..c5e3a18 --- /dev/null +++ b/common/.pi/agent/extensions/compaction-watch.ts @@ -0,0 +1,60 @@ +/** + * Compaction Watch + * + * Compaction is otherwise silent, which is why "compacting fails a lot" took code + * reading and arithmetic to diagnose rather than a glance at a log. This makes it + * visible, and specifically names the failure mode that was breaking it here before + * 2026-08-16. + * + * pi reports a `reason` on every compaction: + * "manual" - you ran /compact + * "threshold" - the healthy path: contextTokens passed contextWindow - reserveTokens + * "overflow" - the window was ALREADY full; this is recovery, not prevention + * + * An "overflow" compaction means the turn blew past contextWindow before the threshold + * could fire, which happens whenever `compaction.reserveTokens` is smaller than the + * model's `maxTokens`. pi then gets one recovery attempt, summarizing from an + * already-overflowed context — and when that does not fit either, the session dies with + * "Context overflow recovery failed after one compact-and-retry attempt". + * + * So: seeing "threshold" is fine. Seeing "overflow" means the settings are wrong for the + * model in use, and this says so at the moment it happens instead of leaving you to + * infer it later. See CLAUDE.md > Compaction tuning. + * + * Note there is no event for a *failed* auto-compaction — `onError` exists only on the + * manual `ctx.compact()` call — so catching the overflow entry is the earliest reliable + * warning available. + */ + +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; + +export default function (pi: ExtensionAPI) { + let overflowCount = 0; + + pi.on("session_before_compact", async (event, ctx) => { + if (!ctx.hasUI) return undefined; + + if (event.reason === "overflow") { + overflowCount++; + ctx.ui.notify( + `Compacting in OVERFLOW recovery (#${overflowCount}) — the context window was already ` + + `full before compaction could fire. Raise compaction.reserveTokens above this model's ` + + `maxTokens, or cap maxTokens in models.json. See CLAUDE.md > Compaction tuning.`, + "warning", + ); + } + + // Never alter the compaction itself — observe only. + return undefined; + }); + + pi.on("session_compact", async (event, ctx) => { + if (!ctx.hasUI) return; + + const reason = event.reason ?? "unknown"; + const retrying = event.willRetry ? ", retrying the aborted turn" : ""; + const level = reason === "overflow" ? "warning" : "info"; + + ctx.ui.notify(`Compacted (${reason}${retrying})`, level); + }); +} diff --git a/common/.pi/agent/extensions/confirm-destructive.ts b/common/.pi/agent/extensions/confirm-destructive.ts new file mode 100644 index 0000000..7d4201f --- /dev/null +++ b/common/.pi/agent/extensions/confirm-destructive.ts @@ -0,0 +1,59 @@ +/** + * Confirm Destructive Actions Extension + * + * Prompts for confirmation before destructive session actions (clear, switch, branch). + * Demonstrates how to cancel session events using the before_* events. + */ + +import type { ExtensionAPI, SessionBeforeSwitchEvent, SessionMessageEntry } from "@earendil-works/pi-coding-agent"; + +export default function (pi: ExtensionAPI) { + pi.on("session_before_switch", async (event: SessionBeforeSwitchEvent, ctx) => { + if (!ctx.hasUI) return; + + if (event.reason === "new") { + const confirmed = await ctx.ui.confirm( + "Clear session?", + "This will delete all messages in the current session.", + ); + + if (!confirmed) { + ctx.ui.notify("Clear cancelled", "info"); + return { cancel: true }; + } + return; + } + + // reason === "resume" - check if there are unsaved changes (messages since last assistant response) + const entries = ctx.sessionManager.getEntries(); + const hasUnsavedWork = entries.some( + (e): e is SessionMessageEntry => e.type === "message" && e.message.role === "user", + ); + + if (hasUnsavedWork) { + const confirmed = await ctx.ui.confirm( + "Switch session?", + "You have messages in the current session. Switch anyway?", + ); + + if (!confirmed) { + ctx.ui.notify("Switch cancelled", "info"); + return { cancel: true }; + } + } + }); + + pi.on("session_before_fork", async (event, ctx) => { + if (!ctx.hasUI) return; + + const choice = await ctx.ui.select(`Fork from entry ${event.entryId.slice(0, 8)}?`, [ + "Yes, create fork", + "No, stay in current session", + ]); + + if (choice !== "Yes, create fork") { + ctx.ui.notify("Fork cancelled", "info"); + return { cancel: true }; + } + }); +} diff --git a/common/.pi/agent/extensions/permission-gate.ts b/common/.pi/agent/extensions/permission-gate.ts new file mode 100644 index 0000000..d6ebc17 --- /dev/null +++ b/common/.pi/agent/extensions/permission-gate.ts @@ -0,0 +1,69 @@ +/** + * Permission Gate Extension + * + * Prompts for confirmation before running potentially dangerous bash commands. + * + * Adapted from pi's bundled `examples/extensions/permission-gate.ts`. Upstream checks + * three patterns (rm -rf, sudo, chmod/chown 777); the list below is extended to cover + * the rules AGENTS.md only *asks* for. That distinction is the whole point of running + * this: AGENTS.md is a prompt, and the small local models this config targets follow + * prompts unreliably. This is the enforcement half — the same trick plan-mode already + * uses when it disables edit/write outright. + * + * In the TUI a match prompts (so "Yes" still gets you through); with no UI it blocks. + */ + +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; + +export default function (pi: ExtensionAPI) { + const dangerousPatterns = [ + // --- upstream --- + /\brm\s+(-rf?|--recursive)/i, + /\bsudo\b/i, + /\b(chmod|chown)\b.*777/i, + + // --- LOCAL: git history is the user's to write, never the agent's --- + // The standing rule is that commits and pushes are made by hand, after review. + /\bgit\s+(commit|push|revert)\b/i, + /\bgit\s+reset\s+--hard\b/i, + /\bgit\s+(rebase|cherry-pick)\b/i, + /\bgit\s+clean\s+-[a-z]*[fd]/i, + /\bgit\s+stash\s+(drop|clear|pop)\b/i, + /--force\b|--force-with-lease\b/i, + + // --- LOCAL: system-changing commands AGENTS.md rules out --- + /\b(pacman|yay|paru)\s+-[A-Za-z]*[SRU]/, + /\b(npm|pnpm|yarn)\s+(install|i|add|remove|uninstall)\b/i, + /\bpip3?\s+(install|uninstall)\b/i, + /\bcargo\s+(install|uninstall)\b/i, + /\bsystemctl\s+(start|stop|restart|enable|disable|mask)\b/i, + + // --- LOCAL: irreversible disk / process operations --- + /\bmkfs\b|\bdd\s+if=|\bshred\b|\bwipefs\b/i, + /\b(reboot|shutdown|poweroff|halt)\b/i, + /\b(killall|pkill)\b/i, + />\s*\/dev\/(sd|nvme|vd)/i, + ]; + + pi.on("tool_call", async (event, ctx) => { + if (event.toolName !== "bash") return undefined; + + const command = event.input.command as string; + const matched = dangerousPatterns.find((p) => p.test(command)); + + if (matched) { + if (!ctx.hasUI) { + // Non-interactive (`pi -p`): nothing can confirm, so refuse. + return { block: true, reason: "Dangerous command blocked (no UI for confirmation)" }; + } + + const choice = await ctx.ui.select(`⚠️ Dangerous command:\n\n ${command}\n\nAllow?`, ["Yes", "No"]); + + if (choice !== "Yes") { + return { block: true, reason: "Blocked by user" }; + } + } + + return undefined; + }); +} diff --git a/common/.pi/agent/extensions/plan-mode/index.ts b/common/.pi/agent/extensions/plan-mode/index.ts index df1af64..8b0d96b 100644 --- a/common/.pi/agent/extensions/plan-mode/index.ts +++ b/common/.pi/agent/extensions/plan-mode/index.ts @@ -142,6 +142,25 @@ export default function planModeExtension(pi: ExtensionAPI): void { handler: async (_args, ctx) => togglePlanMode(ctx), }); + // LOCAL EDIT: the checklist widget only tears itself down once every step is + // marked complete, which needs the model to emit a [DONE:n] tag per step. When + // one is missed the widget sticks around after the work is finished, so give + // the user a way out that doesn't involve toggling plan mode on and off again. + pi.registerCommand("plan-done", { + description: "Clear a stuck plan checklist (stops execution tracking)", + handler: async (_args, ctx) => { + if (!executionMode && todoItems.length === 0) { + ctx.ui.notify("No plan is being tracked."); + return; + } + executionMode = false; + todoItems = []; + updateStatus(ctx); + persistState(); + ctx.ui.notify("Plan tracking cleared."); + }, + }); + pi.registerCommand("todos", { description: "Show current plan todo list", handler: async (_args, ctx) => { diff --git a/common/.pi/agent/extensions/plan-mode/utils.ts b/common/.pi/agent/extensions/plan-mode/utils.ts index 62123f9..4db2223 100644 --- a/common/.pi/agent/extensions/plan-mode/utils.ts +++ b/common/.pi/agent/extensions/plan-mode/utils.ts @@ -151,9 +151,16 @@ export function extractTodoItems(message: string): TodoItem[] { export function extractDoneSteps(message: string): number[] { const steps: number[] = []; - for (const match of message.matchAll(/\[DONE:(\d+)\]/gi)) { - const step = Number(match[1]); - if (Number.isFinite(step)) steps.push(step); + // LOCAL EDIT: upstream matched only the literal `[DONE:3]`. Small local models + // drift on it, and one missed tag pins the plan widget on screen forever, so + // also accept `[DONE 3]`, `[DONE-3]` and comma lists `[DONE:1,2]`. The brackets + // stay required on purpose: a bare "done 3" in prose must not mark a step. + for (const match of message.matchAll(/\[\s*DONE\s*[:\-=]?\s*([\d,\s]+?)\s*\]/gi)) { + for (const part of match[1].split(/[,\s]+/)) { + if (part === "") continue; + const step = Number(part); + if (Number.isFinite(step)) steps.push(step); + } } return steps; } diff --git a/common/.pi/agent/extensions/protected-paths.ts b/common/.pi/agent/extensions/protected-paths.ts new file mode 100644 index 0000000..31121e0 --- /dev/null +++ b/common/.pi/agent/extensions/protected-paths.ts @@ -0,0 +1,58 @@ +/** + * Protected Paths Extension + * + * Blocks write and edit operations to protected paths. + * + * Adapted from pi's bundled `examples/extensions/protected-paths.ts`. Upstream ships a + * three-entry demo list (.env, .git/, node_modules/); the list below names the files + * that actually matter in this dotfiles tree — the ones holding secrets, and the git + * metadata the agent has no business rewriting. + * + * Scope, so this isn't over-trusted: it guards against the *model* calling write/edit. + * It cannot stop pi's own `/login`, which rewrites auth.json with the literal key — + * that is why auth.json is gitignored and auth.json.example is the tracked copy. + */ + +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; + +export default function (pi: ExtensionAPI) { + const protectedPaths = [ + // Secrets. `secrets.fish` holds $DUSKADIY_API_KEY; auth.json is pi's own + // credential map and must keep $VAR references rather than literals. + "secrets.fish", + "auth.json", + ".env", + "fish_variables", + + // Credentials outside the repo. + ".ssh/", + ".gnupg/", + ".aws/", + ".netrc", + + // Repo plumbing and vendored trees — never hand-edited. + ".git/", + "node_modules/", + ]; + + pi.on("tool_call", async (event, ctx) => { + if (event.toolName !== "write" && event.toolName !== "edit") { + return undefined; + } + + const path = event.input.path as string; + // Substring match, like upstream: cheap, and these names are distinctive enough. + // Note `auth.json.example` is intentionally caught too — it is a tracked file + // whose whole job is to contain no secret, so an agent rewrite is worth a look. + const isProtected = protectedPaths.some((p) => path.includes(p)); + + if (isProtected) { + if (ctx.hasUI) { + ctx.ui.notify(`Blocked write to protected path: ${path}`, "warning"); + } + return { block: true, reason: `Path "${path}" is protected` }; + } + + return undefined; + }); +} diff --git a/common/.pi/agent/extensions/session-name.ts b/common/.pi/agent/extensions/session-name.ts new file mode 100644 index 0000000..48203de --- /dev/null +++ b/common/.pi/agent/extensions/session-name.ts @@ -0,0 +1,27 @@ +/** + * Session naming example. + * + * Shows setSessionName/getSessionName to give sessions friendly names + * that appear in the session selector instead of the first message. + * + * Usage: /session-name [name] - set or show session name + */ + +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; + +export default function (pi: ExtensionAPI) { + pi.registerCommand("session-name", { + description: "Set or show session name (usage: /session-name [new name])", + handler: async (args, ctx) => { + const name = args.trim(); + + if (name) { + pi.setSessionName(name); + ctx.ui.notify(`Session named: ${name}`, "info"); + } else { + const current = pi.getSessionName(); + ctx.ui.notify(current ? `Session: ${current}` : "No session name set", "info"); + } + }, + }); +} diff --git a/common/.pi/agent/extensions/todo.ts b/common/.pi/agent/extensions/todo.ts new file mode 100644 index 0000000..67cba74 --- /dev/null +++ b/common/.pi/agent/extensions/todo.ts @@ -0,0 +1,297 @@ +/** + * Todo Extension - Demonstrates state management via session entries + * + * This extension: + * - Registers a `todo` tool for the LLM to manage todos + * - Registers a `/todos` command for users to view the list + * + * State is stored in tool result details (not external files), which allows + * proper branching - when you branch, the todo state is automatically + * correct for that point in history. + */ + +import { StringEnum } from "@earendil-works/pi-ai"; +import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent"; +import { matchesKey, Text, truncateToWidth } from "@earendil-works/pi-tui"; +import { Type } from "typebox"; + +interface Todo { + id: number; + text: string; + done: boolean; +} + +interface TodoDetails { + action: "list" | "add" | "toggle" | "clear"; + todos: Todo[]; + nextId: number; + error?: string; +} + +const TodoParams = Type.Object({ + action: StringEnum(["list", "add", "toggle", "clear"] as const), + text: Type.Optional(Type.String({ description: "Todo text (for add)" })), + id: Type.Optional(Type.Number({ description: "Todo ID (for toggle)" })), +}); + +/** + * UI component for the /todos command + */ +class TodoListComponent { + private todos: Todo[]; + private theme: Theme; + private onClose: () => void; + private cachedWidth?: number; + private cachedLines?: string[]; + + constructor(todos: Todo[], theme: Theme, onClose: () => void) { + this.todos = todos; + this.theme = theme; + this.onClose = onClose; + } + + handleInput(data: string): void { + if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) { + this.onClose(); + } + } + + render(width: number): string[] { + if (this.cachedLines && this.cachedWidth === width) { + return this.cachedLines; + } + + const lines: string[] = []; + const th = this.theme; + + lines.push(""); + const title = th.fg("accent", " Todos "); + const headerLine = + th.fg("borderMuted", "─".repeat(3)) + title + th.fg("borderMuted", "─".repeat(Math.max(0, width - 10))); + lines.push(truncateToWidth(headerLine, width)); + lines.push(""); + + if (this.todos.length === 0) { + lines.push(truncateToWidth(` ${th.fg("dim", "No todos yet. Ask the agent to add some!")}`, width)); + } else { + const done = this.todos.filter((t) => t.done).length; + const total = this.todos.length; + lines.push(truncateToWidth(` ${th.fg("muted", `${done}/${total} completed`)}`, width)); + lines.push(""); + + for (const todo of this.todos) { + const check = todo.done ? th.fg("success", "✓") : th.fg("dim", "○"); + const id = th.fg("accent", `#${todo.id}`); + const text = todo.done ? th.fg("dim", todo.text) : th.fg("text", todo.text); + lines.push(truncateToWidth(` ${check} ${id} ${text}`, width)); + } + } + + lines.push(""); + lines.push(truncateToWidth(` ${th.fg("dim", "Press Escape to close")}`, width)); + lines.push(""); + + this.cachedWidth = width; + this.cachedLines = lines; + return lines; + } + + invalidate(): void { + this.cachedWidth = undefined; + this.cachedLines = undefined; + } +} + +export default function (pi: ExtensionAPI) { + // In-memory state (reconstructed from session on load) + let todos: Todo[] = []; + let nextId = 1; + + /** + * Reconstruct state from session entries. + * Scans tool results for this tool and applies them in order. + */ + const reconstructState = (ctx: ExtensionContext) => { + todos = []; + nextId = 1; + + for (const entry of ctx.sessionManager.getBranch()) { + if (entry.type !== "message") continue; + const msg = entry.message; + if (msg.role !== "toolResult" || msg.toolName !== "todo") continue; + + const details = msg.details as TodoDetails | undefined; + if (details) { + todos = details.todos; + nextId = details.nextId; + } + } + }; + + // Reconstruct state on session events + pi.on("session_start", async (_event, ctx) => reconstructState(ctx)); + pi.on("session_tree", async (_event, ctx) => reconstructState(ctx)); + + // Register the todo tool for the LLM + pi.registerTool({ + name: "todo", + label: "Todo", + description: "Manage a todo list. Actions: list, add (text), toggle (id), clear", + parameters: TodoParams, + + async execute(_toolCallId, params, _signal, _onUpdate, _ctx) { + switch (params.action) { + case "list": + return { + content: [ + { + type: "text", + text: todos.length + ? todos.map((t) => `[${t.done ? "x" : " "}] #${t.id}: ${t.text}`).join("\n") + : "No todos", + }, + ], + details: { action: "list", todos: [...todos], nextId } as TodoDetails, + }; + + case "add": { + if (!params.text) { + return { + content: [{ type: "text", text: "Error: text required for add" }], + details: { action: "add", todos: [...todos], nextId, error: "text required" } as TodoDetails, + }; + } + const newTodo: Todo = { id: nextId++, text: params.text, done: false }; + todos.push(newTodo); + return { + content: [{ type: "text", text: `Added todo #${newTodo.id}: ${newTodo.text}` }], + details: { action: "add", todos: [...todos], nextId } as TodoDetails, + }; + } + + case "toggle": { + if (params.id === undefined) { + return { + content: [{ type: "text", text: "Error: id required for toggle" }], + details: { action: "toggle", todos: [...todos], nextId, error: "id required" } as TodoDetails, + }; + } + const todo = todos.find((t) => t.id === params.id); + if (!todo) { + return { + content: [{ type: "text", text: `Todo #${params.id} not found` }], + details: { + action: "toggle", + todos: [...todos], + nextId, + error: `#${params.id} not found`, + } as TodoDetails, + }; + } + todo.done = !todo.done; + return { + content: [{ type: "text", text: `Todo #${todo.id} ${todo.done ? "completed" : "uncompleted"}` }], + details: { action: "toggle", todos: [...todos], nextId } as TodoDetails, + }; + } + + case "clear": { + const count = todos.length; + todos = []; + nextId = 1; + return { + content: [{ type: "text", text: `Cleared ${count} todos` }], + details: { action: "clear", todos: [], nextId: 1 } as TodoDetails, + }; + } + + default: + return { + content: [{ type: "text", text: `Unknown action: ${params.action}` }], + details: { + action: "list", + todos: [...todos], + nextId, + error: `unknown action: ${params.action}`, + } as TodoDetails, + }; + } + }, + + renderCall(args, theme, _context) { + let text = theme.fg("toolTitle", theme.bold("todo ")) + theme.fg("muted", args.action); + if (args.text) text += ` ${theme.fg("dim", `"${args.text}"`)}`; + if (args.id !== undefined) text += ` ${theme.fg("accent", `#${args.id}`)}`; + return new Text(text, 0, 0); + }, + + renderResult(result, { expanded }, theme, _context) { + const details = result.details as TodoDetails | undefined; + if (!details) { + const text = result.content[0]; + return new Text(text?.type === "text" ? text.text : "", 0, 0); + } + + if (details.error) { + return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0); + } + + const todoList = details.todos; + + switch (details.action) { + case "list": { + if (todoList.length === 0) { + return new Text(theme.fg("dim", "No todos"), 0, 0); + } + let listText = theme.fg("muted", `${todoList.length} todo(s):`); + const display = expanded ? todoList : todoList.slice(0, 5); + for (const t of display) { + const check = t.done ? theme.fg("success", "✓") : theme.fg("dim", "○"); + const itemText = t.done ? theme.fg("dim", t.text) : theme.fg("muted", t.text); + listText += `\n${check} ${theme.fg("accent", `#${t.id}`)} ${itemText}`; + } + if (!expanded && todoList.length > 5) { + listText += `\n${theme.fg("dim", `... ${todoList.length - 5} more`)}`; + } + return new Text(listText, 0, 0); + } + + case "add": { + const added = todoList[todoList.length - 1]; + return new Text( + theme.fg("success", "✓ Added ") + + theme.fg("accent", `#${added.id}`) + + " " + + theme.fg("muted", added.text), + 0, + 0, + ); + } + + case "toggle": { + const text = result.content[0]; + const msg = text?.type === "text" ? text.text : ""; + return new Text(theme.fg("success", "✓ ") + theme.fg("muted", msg), 0, 0); + } + + case "clear": + return new Text(theme.fg("success", "✓ ") + theme.fg("muted", "Cleared all todos"), 0, 0); + } + }, + }); + + // Register the /todos command for users + pi.registerCommand("todos", { + description: "Show all todos on the current branch", + handler: async (_args, ctx) => { + if (ctx.mode !== "tui") { + ctx.ui.notify("/todos requires interactive mode", "error"); + return; + } + + await ctx.ui.custom((_tui, theme, _kb, done) => { + return new TodoListComponent(todos, theme, () => done()); + }); + }, + }); +} diff --git a/common/.pi/agent/models.json b/common/.pi/agent/models.json index c7c43c7..cfc5501 100644 --- a/common/.pi/agent/models.json +++ b/common/.pi/agent/models.json @@ -52,7 +52,7 @@ "reasoning": false, "input": ["text", "image"], "contextWindow": 24576, - "maxTokens": 8192, + "maxTokens": 4096, "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } }, { @@ -62,7 +62,7 @@ "compat": { "thinkingFormat": "qwen-chat-template" }, "input": ["text", "image"], "contextWindow": 24576, - "maxTokens": 8192, + "maxTokens": 4096, "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } }, { @@ -75,6 +75,16 @@ "maxTokens": 8192, "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } }, + { + "id": "Qwen3.8-27B-UD-IQ3_XXS", + "name": "Qwen3.8 27B · 24k · vision — hybrid reasoner", + "reasoning": true, + "compat": { "thinkingFormat": "qwen-chat-template" }, + "input": ["text", "image"], + "contextWindow": 24576, + "maxTokens": 8192, + "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } + }, { "id": "GLM-4.7-Flash-UD-Q4_K_XL", "name": "GLM-4.7 Flash · 24k — quality coder", @@ -82,7 +92,7 @@ "compat": { "thinkingFormat": "qwen-chat-template" }, "input": ["text"], "contextWindow": 24576, - "maxTokens": 8192, + "maxTokens": 4096, "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } }, { @@ -157,7 +167,7 @@ "reasoning": false, "input": ["text", "image"], "contextWindow": 24576, - "maxTokens": 8192, + "maxTokens": 4096, "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } }, { @@ -167,7 +177,7 @@ "compat": { "thinkingFormat": "qwen-chat-template" }, "input": ["text", "image"], "contextWindow": 24576, - "maxTokens": 8192, + "maxTokens": 4096, "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } }, { @@ -180,6 +190,16 @@ "maxTokens": 8192, "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } }, + { + "id": "Qwen3.8-27B-UD-IQ3_XXS", + "name": "Qwen3.8 27B · 24k · vision — hybrid reasoner", + "reasoning": true, + "compat": { "thinkingFormat": "qwen-chat-template" }, + "input": ["text", "image"], + "contextWindow": 24576, + "maxTokens": 8192, + "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } + }, { "id": "GLM-4.7-Flash-UD-Q4_K_XL", "name": "GLM-4.7 Flash · 24k — quality coder", @@ -187,7 +207,7 @@ "compat": { "thinkingFormat": "qwen-chat-template" }, "input": ["text"], "contextWindow": 24576, - "maxTokens": 8192, + "maxTokens": 4096, "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } }, { diff --git a/common/.pi/agent/settings.json b/common/.pi/agent/settings.json index 5818613..bc4bdc0 100644 --- a/common/.pi/agent/settings.json +++ b/common/.pi/agent/settings.json @@ -4,6 +4,7 @@ "defaultThinkingLevel": "off", "theme": "catppuccin-mocha", "quietStartup": false, + "collapseChangelog": true, "defaultProjectTrust": "ask", "enableInstallTelemetry": false, "enabledModels": [ @@ -12,8 +13,8 @@ ], "compaction": { "enabled": true, - "reserveTokens": 6144, - "keepRecentTokens": 6000 + "reserveTokens": 8192, + "keepRecentTokens": 4000 }, "retry": { "enabled": true, diff --git a/fl/.config/llamacpp/README.md b/fl/.config/llamacpp/README.md index 5d9657c..909495d 100644 --- a/fl/.config/llamacpp/README.md +++ b/fl/.config/llamacpp/README.md @@ -4,7 +4,8 @@ llama.cpp in **router mode** serving ~10 model presets from one 16 GB GPU. Last full tune: **2026-07-16** (decode numbers measured then via `bench.py`, unless a preset comment in `config.ini` says otherwise). Last full clean sweep: **2026-08-08 03:22** — all 11 presets loaded and ran green (baseline 2.0 GB) after the week's coder-race/KV-fix/gemma-sidecar round. -- **Box**: AMD RX 7600 XT 16 GB (ROCm, ~288 GB/s) + Ryzen 5600X (6 cores) + 48 GB RAM. +- **Box**: AMD RX 7600 XT 16 GB (~288 GB/s; backend = Arch `llama.cpp-vulkan` / RADV, **not** ROCm — + `rocm-smi` is only the VRAM monitor) + Ryzen 5600X (6 cores) + 48 GB RAM. ⚠ The GPU **also drives the display** — see [VRAM safety](#vram-safety-the-golden-rules). - **Endpoints**: LAN `http://$SERVER:11343/v1` · remote `https:///api/v1` (own reverse proxy to the same server; requires an API key. Only the LAN endpoint is keyless). @@ -36,7 +37,7 @@ After ANY preset change: restart + `./bench.py -m ` and check the g ## VRAM safety (the golden rules) -ROCm does not OOM cleanly — an overcommitted model spills into GTT (system RAM), +The amdgpu driver does not OOM cleanly — an overcommitted model spills into GTT (system RAM), starves the desktop and freezes the PC (reboot). Hard-won rules: 1. Keep every preset's benched **VRAM free ≥ 2.5 GB** (idle desktop uses ~1.3 of 17.2). @@ -60,6 +61,7 @@ starves the desktop and freezes the PC (reboot). Hard-won rules: | `Qwen3-Coder-30B-Instruct-UD-Q3_K_XL` | **~30** clean / 27.0 evening @moe12 | 32k | — | **main agent coder** — won the 2026-08-06 quant race; moe12 verified evening 08-07 + clean 08-08 | | `GLM-4.7-Flash-UD-Q4_K_XL` | **21.5** @moe22 | 24k | reasoning | quality coder (opencode subagents); KV fix + moe22 ✓ verified clean 08-08 | | `Qwen3-Coder-Next-UD-IQ3_XXS` | 16.0 | **128k** | 80B-A3B | long-session coder (128k ctx) | +| `Qwen3.8-27B-UD-IQ3_XXS` | 14.5 (10.9 w/o MTP) | 24k | vision, MTP, hybrid reasoner | ⚠ **clean-desktop specialty** (kept 09-10 over the ≥22 gate) — dense-27B quality, slow: its Gated-DeltaNet layers are Vulkan-bound (prefill ~30). 2.5 GB free @1.6 baseline @24k (✓ 09-10); **never on a busy evening desktop** (~1 GB free = GTT freeze) | | `Qwen3-Embedding-0.6B` | 33 (CPU) | 8k | CPU-only, `/v1/embeddings` | RAG/search embedder (not a chat model) | Expected run-to-run spread: MTP models swing ±15% with draft **acceptance rate** (content- @@ -77,6 +79,9 @@ Treat clean night runs as the reference; don't retune on daytime deltas. - **Hard reasoning**: `35B-Thinking` (quality) or `gpt-oss-20b` (speed + tool use). Their multi-second TTFT is the reasoning phase streaming first — not a slow load. For quick interactive gpt-oss answers use `gpt-oss-20b-low` (reasoning effort low). +- **Best single-model quality, speed no object** (quiet desktop only): `Qwen3.8-27B` — dense + 27B hybrid reasoner + vision at ~14.5 t/s; it cannot go faster on this card (GDN layers on + Vulkan), and a busy evening desktop pushes it into GTT-freeze territory — check `rocm-smi` first. - **Images**: `gemma-4-26B` for quality, `gemma-4-E4B` or `Qwen3.5-9B` for speed, `Qwen3.6-35B` when you want the daily driver to see the screenshot. - **Long one-shot documents**: `gemma-4-E4B` — 185 t/s prefill eats 64k in ~6 min @@ -123,6 +128,9 @@ The embedder is deliberately absent from chat clients. 2026-08-06: the coder id changed `…-IQ4_XS` → `…-UD-Q3_K_XL` in all six client files (common + lw overlays) after the quant race; OWUI picks the new id up automatically from the router, but chats/presets saved against the old id need re-picking. +2026-08-16: `Qwen3.8-27B-UD-IQ3_XXS` (⚗ candidate) added to all six client files. +2026-09-10: kept as ⚠ clean-desktop specialty after the gate bench finally ran on the v3 gguf +(14.5 t/s MTP / 10.9 base, 2.8 GB free @1.5); server ctx restored to 24576 = client ctx, no client edits. ## Tuning cheat-sheet @@ -135,7 +143,10 @@ the router, but chats/presets saved against the old id need re-picking. 4-bit now; 3-bit was only ~2 t/s faster). Coders want ≥4-bit; chat tolerates UD 3-bit. - **MTP / spec decode** (`spec-type draft-mtp`): ~1.5–2× decode. Qwen3.5/3.6 embed the head in the *MTP-repo* GGUF (same filename as plain repo — size is the tell); - gemma-4 uses a separate `mtp-*.gguf` draft file. **No MTP possible yet for**: + gemma-4 uses a separate `mtp-*.gguf` draft file; Qwen3.8 ships the head in the + *main*-repo GGUFs for ≥ Q2_K_XL (≤ IQ2_S are stripped — use the repo's `MTP/mtp-*.gguf` + draft instead). On the hybrid 27B it is only +33% (10.9→14.5) and costs 1.5 GB: every + speculative step re-runs the Gated-DeltaNet layers. **No MTP possible yet for**: GLM-4.7-Flash (conversion drops the head — expected ~1.5× when llama.cpp lands it), Qwen3-Coder-Next (Qwen3-Next head exists upstream, no GGUF ships it), gpt-oss and Qwen3-Coder-30B (no head exists). Recheck releases occasionally. diff --git a/fl/.config/llamacpp/config.ini b/fl/.config/llamacpp/config.ini index debfa67..5a22169 100644 --- a/fl/.config/llamacpp/config.ini +++ b/fl/.config/llamacpp/config.ini @@ -1,7 +1,8 @@ -# llama.cpp model config — AMD RX 7600 XT (ROCm) · 16 GB (17.16 GB total) +# llama.cpp model config — AMD RX 7600 XT · 16 GB (17.16 GB total) · backend = Arch llama.cpp-vulkan +# (RADV, device Vulkan0 — NOT ROCm/HIP; rocm-smi is used for monitoring only) # ═════════════════════════════════════════════════════════════════════════════ # ⚠⚠ VRAM SAFETY — READ THIS. This GPU ALSO DRIVES THE DISPLAY. If a model asks for -# more VRAM than is free, ROCm does NOT OOM cleanly — it spills into system RAM +# more VRAM than is free, the amdgpu driver does NOT OOM cleanly — it spills into system RAM # (GTT), which starves the desktop and FREEZES THE WHOLE PC (reboot required). # → TARGET ≥ ~2.5 GB free (≤ ~14.5 GB used). HARD FLOOR 1.5 GB (bench.py guard stops there). # Idle desktop uses ~1.3 GB of 17.16 — but a browser/leftover model can hold 1-3 GB more, @@ -223,6 +224,141 @@ repeat-penalty = 1.05 jinja = on sleep-idle-seconds = 60 +[Qwen3.8-27B-UD-IQ3_XXS] +# ⚠ CLEAN-DESKTOP SPECIALTY — KEPT by user decision 2026-09-10 despite failing the ≥22 t/s speed +# gate (14.5 t/s MTP / 10.9 base; 2.8 GB free @1.5 baseline @16k). Pick it ONLY on a quiet +# desktop: at a 3+ GB evening baseline it lands ~1 GB free = GTT-spill/freeze territory. Role: +# slow-but-smart dense-27B answers + vision. Origin (added 2026-08-16): dense 27B, but NOT like the two +# retired dense 27Bs: hybrid attention (only 16 of 64 layers full-attn, rest linear/SSM → +# cheap KV, Coder-Next-style) AND an embedded MTP head — VERIFIED in the gguf header: +# qwen35.nextn_predict_layers = 1, block_count = 65 (64+1), arch "qwen35" = the same +# draft-mtp path the working 9B preset uses. Hybrid reasoner (thinking ON by default, +# reasoning_effort tunable via chat-template-kwargs if a -low alias is ever wanted), +# vision, n_ctx_train = 262144. +# Speed math: dense 11.9 GB weights on ~288 GB/s ≈ 14-16 t/s base; MTP should lift to +# ~22-30 = roster-competitive IF the draft engages. If it doesn't, this lands in +# retired-dense-27B territory (~10-14) → retire again, quality won't pay for the speed. +# ⚠ BENCH HISTORY — still no green row, decode/MTP UNMEASURED after 5 attempts: +# · 08-16 14:59/15:19/15:29 (3.3-3.5 DIRTY baseline): 16.5-16.7 used / 0.4-0.7 free, guard +# skipped gen every time. Implied footprint 13.2 GB — MISLEADING, see below. +# · 08-17 10:31: ERROR conn-refused = bench raced llama.service coming up after boot. +# · 08-17 10:38 (1.9 near-clean baseline): 15.9 used / 1.3 free — guard floor missed by +# 0.2. TRUE footprint = 14.0 GB over baseline; the dirty runs looked 0.8 smaller because +# ROCm silently spilled that much to GTT (the exact freeze precursor the guards watch). +# · 08-17 10:43 (post-trim, 10.0 leftover start, partial drain): 15.8 / 1.4 — floor missed +# by 0.1. Trim verified live via router args; ~2.4 effective baseline poisoned it again. +# · 08-17 10:53 (1.9 baseline, NO leftovers — a genuine desktop-idle run): 15.8 / 1.4, +# floor missed by 0.1 again. DECISIVE: trimmed footprint 13.9 vs untrimmed 14.0 — both +# knobs together saved 0.1 GB. ctx/ubatch do NOT move this model (fixed SSM-state + +# big-vocab buffers). Absolute best case ≈ 2.0 free at a pristine 1.3 baseline @16k — +# permanently below the 2.5 target. → RETIREMENT RECOMMENDED 2026-08-17; decode/MTP +# never measured, academic at these margins. Third dense ~27B to die on this card, +# first one killed by fixed buffers rather than bandwidth. +# Consequences: full-ctx even on a 1.3 CLEAN night = ~15.3 used / 1.9 free (runs, but +# under the 2.5 target). EVENING regime can never fit this quant — that needs ≤ UD-IQ2_M, +# a quality gutting. → 08-17: TRIMMED FOR THE GATE BENCH: ctx 24576→16384 (~-0.25) + +# ubatch 512→256 (~-0.3-0.5) → predicted ~15.2 used / ~2.0 free @1.9 baseline. Clients +# still say 24576 — deliberately NOT re-wired for a temporary trim; do not use from +# clients anyway until this passes (a busy-desktop pick is one tab from the GTT freeze). +# DECISION GATE (superseded by the 09-10 user decision below): MTP ≥ ~22 t/s keeps it (clean-desktop specialty, restore ctx +# 24576 + re-check), anything less → retire + delete (dense 27Bs die on this card). +# ↻ REOPENED 2026-09-09 — the file on disk is STALE: unsloth re-quantized the whole repo as +# "Dynamic v3" on 2026-08-19, three days AFTER the 08-16 download (~10% better accuracy at +# equal size, per unsloth). Verified via the HF tree API at the pre/post commits: +# UD-IQ3_XXS 11.91 → 10.93 GB (SAME filename — re-download overwrites in place, no client +# or section changes); UD-Q2_K_XL 10.68 → 9.83; UD-IQ2_M DELETED (gone as an option); +# UD-IQ2_S 8.37 / UD-IQ2_XXS 7.27 are new 2-bit tiers. Header range-read 09-09: v3 +# IQ3_XXS and Q2_K_XL both still carry nextn_predict_layers = 1 (MTP head embedded); +# unsloth stripped the head from ≤ IQ2_S only and ships MTP/mtp-Qwen3.8-27B-Q4_0.gguf +# as a separate model-draft for those (gemma-4 style). +# Predicted with v3 IQ3_XXS (footprint 14.0 − 0.98 ≈ 13.0; bench.py GB = decimal): +# @1.9 real-idle baseline ≈ 14.8–15.0 used / 2.2–2.4 free · @1.3 pristine ≈ 14.2 / ~2.9. +# Clears the 1.5 floor by ~0.8 even at 1.9 → the gen bench can FINALLY run; still ~0.1–0.3 +# under the 2.5 target on a real idle desktop, clears it outright on a pristine one. Overhead datum: a third-party paired bench measured +# draft-mtp at ~+0.75 GB on this model (unsloth docs: "1-2 GB extra headroom") — a good +# part of the "fixed" ~2.1 GB is MTP + KV, not only SSM-state/vocab buffers. +# NEXT STEP (recommended, NOT yet done): re-download v3 IQ3_XXS on fl (verify 10.93 GB / +# 10.18 GiB before restarting), dotsync, restart llama.service, clean-baseline bench. +# Then the gate above decides — with one refinement: free 2.0–2.5 at ≥22 t/s = user's call +# between "clean-desktop specialty" and stepping down to v3 UD-Q2_K_XL (predicted ~13.7 / +# 3.5 @1.9 — needs the section-rename round across the six client files; sequence it +# after any in-flight client edits). Decode < 22 → retire regardless of VRAM. +# No in-family alternative: the Qwen3.8 lineup is 27B dense, Flash-Next 125B-A6B (smallest +# GGUF 72.5 GB, 75 GB RAM floor even with mmap'd n-gram tables — out on 48+16), +# 2.4T-A95B and Max. No 3.8 MoE in the 35B-A3B class exists; watch HF. +# ✦ GATE BENCH RAN 2026-09-10 02:36 (v3 file, sha verified; 2.1 baseline): 14.9 used / 2.2 free +# (footprint 12.8 — VRAM half of the gate PASSED, borderline) but decode 14.8 t/s, prefill 19, +# TTFT 11.7 s → speed half FAILED. Server log: MTP head loaded, draft acceptance 0.80 +# (157/195, mean len 2.6) — so MTP IS engaging; at 2.6 tok/step that is ~175 ms per step ≈ +# 3–4× a plain forward pass. NOTE the box runs the Arch llama.cpp-vulkan package (RADV, +# device Vulkan0) — NOT HIP/ROCm (this header said "ROCm" until 09-10); rocm-smi = monitoring only. +# Suspects: the 48 GatedDeltaNet layers on the Vulkan backend (upstream #20354: missing/slow +# GDN shader → CPU fallback) and/or recurrent-state checkpoint/restore per speculative step +# (~150 MiB for this model; reported net-negative on hybrid 27Bs). The hybrid 35B-A3B does +# 37 t/s on the same backend, so it is size-specific, not Vulkan-generic. +# A/B NEXT: [Qwen3.8-27B-UD-IQ3_XXS-nomtp] (TEMP alias, used 03:19 then removed) measures +# base decode; base ≥ 18 → MTP is the net negative here, run without it (frees ~0.75 GB); +# base ≈ 9 → the GDN path is the wall → retire. Also read "graph splits" from the load log. +# cache_reuse is inert here ("not supported by multimodal") — true for every mmproj preset. +# Re-run 03:07 @0.9 PRISTINE baseline: 15.0 t/s / 17 pp / 13.9 used / 3.3 free (footprint 13.0) +# — reproducible; the VRAM half is now fully closed, only the speed half is open. +# ✦ A/B 2026-09-10 03:19 (@1.5 baseline, same run): MTP 14.5 t/s / 30 pp / 14.3 used / 2.8 free +# vs NO-MTP 10.9 t/s / 34 pp / 12.8 used / 4.4 free. → MTP WORKS (+33%, matches the sudoingX +# paired benches) and costs 1.5 GB VRAM; the wall is the BASE: 10.9 t/s = 119 GB/s effective +# (41% of the card's 288) — the 48 GatedDeltaNet layers on the Vulkan/RADV backend, exactly +# the #20354 datum (Qwen3.5-27B ≈ 11.8 t/s on Strix Halo, either backend). Prefill 30-34 on +# a fully-GPU model is the same wall (the hybrid 35B-A3B daily driver's 64 pp is GDN-bound +# too). "graphs reused" 97-260 in the log → not a graph-rebuild problem. Nothing in this +# preset can move it; a bigger K-quant (faster dequant on Vulkan) does not fit. Perfect- +# kernel ceiling on this card ≈ 20 base / ~27 MTP — an upstream Vulkan gated_delta_net +# shader improvement COULD flip it; nothing else can. +# VERDICT 2026-09-10: gate FAILED on speed (14.5 < 22) with the VRAM half passed (2.8 free +# @1.5). Per the gate → retire. Re-probe only after a llama.cpp-vulkan bump whose changelog +# touches gated_delta_net / Vulkan GDN: `./bench.py -m Qwen3.8-27B-UD-IQ3_XXS`. +# → USER DECISION 2026-09-10: KEEP as clean-desktop specialty (overrides the gate). ctx restored +# to 24576 (clients already say 24576), ubatch stays 256 (prefill is GDN-bound, the bigger +# buffer buys nothing), MTP stays (+33% for 1.5 GB). ✓ CONFIRMED @24576 2026-09-10 03:51 +# (1.6 baseline): 15.1 t/s / 31 pp / 14.6 used / 2.5 free — footprint 13.0, exactly as +# predicted; free = the 2.5 target at a normal idle, ~2.8 pristine, ~0.8 on a 3.3 evening. +# ⚠ Needs a CURRENT llama.cpp build: first "qwen35" file HERE with ssm.*/hybrid-attn keys +# (the 9B is regular-attention qwen35). If the load fails with unknown-architecture / +# missing-tensor errors, update llama.cpp on fl before touching this preset. +# ⬇ DOWNLOAD from unsloth/Qwen3.8-27B-GGUF (NO separate MTP repo — unlike Qwen3.5/3.6, the +# head ships in the MAIN repo's ggufs for ≥ Q2_K_XL; ≤ IQ2_S need MTP/mtp-Qwen3.8-27B-Q4_0.gguf): +# Qwen3.8-27B-UD-IQ3_XXS.gguf (10.93 GB Dynamic v3 since 2026-08-19 — the 08-16 download +# is the 11.91 GB v2; RE-FETCH. UD-IQ4_XS 14.3 GB and +# UD-Q3_K_XL 13.15 = dead on arrival, no n-cpu-moe escape) +# mmproj-Qwen3.8-27B-F16.gguf ← mmproj-F16.gguf (928 MB — unchanged; rename; watch trailing spaces) +model = /home/anon/software/models/Qwen3.8-27B-UD-IQ3_XXS.gguf +mmproj = /home/anon/software/models/mmproj-Qwen3.8-27B-F16.gguf +spec-type = draft-mtp +spec-draft-n-max = 2 # ⚠ if the load log says this file has no MTP head, comment the two + # spec- lines out — and expect ~14 t/s, probably not worth keeping. +ctx-size = 24576 # RESTORED 2026-09-10 (16384 was the 08-17 gate-bench trim): matches + # the clients. KV is cheap (16 full-attn layers × 4 kv-heads × 256 + # head_dim ≈ 26 KB/token at q8/q4): 16k→24k cost +0.3 → MEASURED + # 09-10 @1.6 baseline: 14.6 used / 2.5 free. n_ctx_train 262144. +n-gpu-layers = 99 # dense: no n-cpu-moe escape — ctx/quant are the only knobs +threads = 6 +flash-attn = on +cache-type-k = q8_0 +cache-type-v = q4_0 # hybrid arch takes mixed KV like Coder-Next (GLM's same-type + # rejection was MLA-specific). If load fails at context creation: + # try v = q8_0, then delete both lines (f16 — KV small enough here). +batch-size = 2048 +ubatch-size = 256 # KEPT at 256 (512 before the 08-17 trim): prefill is GDN-bound on the + # Vulkan backend (30-34 t/s either way), so the bigger compute buffer + # buys nothing measurable and VRAM is the binding constraint here. +cache-reuse = 256 +defrag-thold = 0.1 +no-mmproj-offload = true # vision encoder on CPU → ~1 GB freed, same as the 26B +temp = 1.0 # card: thinking mode 1.0/0.95/20/0 — thinking IS the default mode +top-p = 0.95 +top-k = 20 +min-p = 0 +jinja = on +sleep-idle-seconds = 120 + # ─── RETIRED 2026-07-16: [Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ3_M] ────────────── # Dense: 10 t/s, 2.0 GB free (under the 2.5 target) and unfixable — bandwidth-bound, smaller # quant would cost the quality that justified it. Removed from opencode/pi/aichat. diff --git a/gui/.config/dunst/dunstrc.d/50-dots-colors.conf b/gui/.config/dunst/dunstrc.d/50-dots-colors.conf new file mode 100644 index 0000000..aeb86e7 --- /dev/null +++ b/gui/.config/dunst/dunstrc.d/50-dots-colors.conf @@ -0,0 +1,53 @@ +# dunst drop-in — undo HyDE's pink critical notification, and make the progress bar visible. +# +# Why a drop-in and not the obvious files: +# ~/.config/dunst/dunstrc is GENERATED by ~/.local/share/wallbash/scripts/dunst.sh on every +# theme change, and says so in its own header. +# ~/.config/dunst/dunst.conf is HyDE's shipped source for that generator — but dunst.toml +# declares `action = "sync"` on the whole `dunst` directory, so HyDE +# overwrites it on every update. +# dunst >= 1.10 reads dunstrc.d/*.conf as drop-ins that override the base config (lexically last +# wins), and HyDE's sync copies its files in without deleting extras — so this file survives both +# a theme change and a HyDE update. Apply with `dunstctl reload`. +# +# [urgency_critical] is the pink: HyDE's generator injects background #f5e0dc (Catppuccin +# rosewater) with a #f38ba8 frame — a pale pink slab, and with `timeout = 0` it never expires, so +# one error notification sits on screen until dismissed. Note this block is NOT wallbash-driven +# (dunst.conf ships it commented out and the generator hardcodes it), so overriding it costs no +# theme coupling. Inverted here: dark base like every other surface, with the pink kept as text +# and frame so "critical" still reads as critical at a glance. +[urgency_critical] + background = "#1e1e2eF2" + foreground = "#f5e0dc" + frame_color = "#f38ba8" + +# urgency_low/normal are the everyday notifications — the volume/mute popup is `normal`, since +# volumecontrol.sh sends it with no -u. wallbash generates those as a primary colour at alpha `80` +# (#583A6B80 plum for normal, #657AA380 for low) and [global] adds `transparency = 10` on top, so +# roughly half the wallpaper shows through: on a pink-toned palette the popup reads as pink no +# matter what the base hue is. +# +# Overriding them here **decouples notifications from wallbash** — they no longer follow the +# wallpaper the way the cava bar, waybar and kitty still do. That is the deliberate trade for a +# readable popup. To hand them back to wallbash, delete these two blocks and restart dunst; the +# generated dunstrc underneath is untouched. +# +# The frames also needed fixing regardless of colour: the generator emits frame_color at alpha +# `03` (~1% opacity) and never sets `highlight`, and dunst falls back to frame_color for the +# progress bar — so the volume bar was drawing invisibly. +# +# Frame and highlight are deliberately NOT the mauve from hypr's active window border: at these +# sizes #ca9ee6 renders as a pink outline plus a pink bar, which is the thing that looked wrong in +# the first place. A neutral surface frame with a blue bar keeps the popup quiet. +[urgency_low] + background = "#1e1e2eF2" + foreground = "#cdd6f4" + frame_color = "#6c7086" + +[urgency_normal] + background = "#1e1e2eF2" + foreground = "#cdd6f4" + frame_color = "#45475a" + +[global] + highlight = "#89b4fa" diff --git a/gui/.config/fish/conf.d/00-hypr-config.fish b/gui/.config/fish/conf.d/00-hypr-config.fish new file mode 100644 index 0000000..29cb279 --- /dev/null +++ b/gui/.config/fish/conf.d/00-hypr-config.fish @@ -0,0 +1,26 @@ +## gui package — pin which config Hyprland starts with. +## +## HyDE 26.x configures Hyprland in Lua and points HYPRLAND_CONFIG at +## ~/.local/share/hypr/hyde.lua. Once that is set, Hyprland never reads +## ~/.config/hypr/hyprland.conf, so the entire dots hypr layer (theme, keybinds, +## window rules, monitors, userprefs) is bypassed and the session comes up as +## stock HyDE — silently, with `hyprctl configerrors` empty. +## +## Why it is set from *fish* of all places: the update dropped a real +## ~/.config/fish/conf.d/hyde.fish (untracked, HyDE-owned) which exports it, and +## fish sources every conf.d/*.fish at startup. SDDM launches the session through +## the login shell (/usr/bin/start-hyprland, the `hyprland.desktop` entry — not +## `hyprland-uwsm.desktop`), so the compositor inherits whatever fish exported. +## HyDE's snippet only assigns `if test -z "$HYPRLAND_CONFIG"`, and conf.d files +## load in alphabetical order, so this file's `00-` prefix means ours is already +## set by the time hyde.fish looks — do not rename it above `hyde.fish`. +## +## The uwsm twin of this lives in gui/.config/uwsm/env-hyprland.d/10-dots.sh and +## covers the `hyprland-uwsm.desktop` session entry, which sources uwsm env files +## instead of the shell. Both are needed; neither is sufficient alone. +## +## STOPGAP: this pins ~/.local/share/hyde/hyprland.conf, which current HyDE no +## longer ships (ours is a leftover of the pre-update install). The supported +## path is porting the hypr layer to ~/.config/hypr/hyprland.lua. See +## HYDE-UPDATE.md. Takes effect at session start — relogin, not `hyprctl reload`. +set -gx HYPRLAND_CONFIG "$HOME/.config/hypr/hyprland.conf" diff --git a/gui/.config/fish/conf.d/zz-unhyde-prompt.fish b/gui/.config/fish/conf.d/zz-unhyde-prompt.fish new file mode 100644 index 0000000..f28f6f1 --- /dev/null +++ b/gui/.config/fish/conf.d/zz-unhyde-prompt.fish @@ -0,0 +1,25 @@ +## gui package — undo the starship prompt that HyDE's fish snippet installs. +## +## The HyDE 26.x update dropped an untracked ~/.config/fish/conf.d/hyde.fish that +## runs `starship init fish | source`. That eagerly defines fish_prompt AND +## fish_right_prompt, which shadows our own functions/fish_prompt.fish: fish only +## autoloads a function that is not already defined, so ours never loaded and +## every new shell came up with starship's prompt. +## +## conf.d is sourced alphabetically, so this file must sort AFTER hyde.fish to +## undo it — hence zz-. +## +## Note it *sources* our prompts rather than erasing starship's: `functions --erase` +## on an autoloadable name also suppresses the autoload, which leaves the shell +## with no fish_prompt at all ("Unknown command: fish_prompt"). Defining ours over +## the top is what actually wins — for fish_right_prompt too, since common ships +## functions/fish_right_prompt.fish (the git-status right prompt). +## +## Nothing else from hyde.fish is undone here: its aliases lose to config.fish, +## which fish loads after conf.d, and its `df` wrapper is a function we do not +## otherwise define. Delete this file if you ever decide you want starship. +for f in fish_prompt fish_right_prompt + if test -r $__fish_config_dir/functions/$f.fish + source $__fish_config_dir/functions/$f.fish + end +end diff --git a/gui/.config/hypr/animations/theme.conf b/gui/.config/hypr/animations/theme.conf deleted file mode 100644 index ab4a157..0000000 --- a/gui/.config/hypr/animations/theme.conf +++ /dev/null @@ -1 +0,0 @@ -source = ./HyDE.conf \ No newline at end of file diff --git a/gui/.config/hypr/hyde.conf b/gui/.config/hypr/hyde.conf index 63d4b73..f72a9db 100644 --- a/gui/.config/hypr/hyde.conf +++ b/gui/.config/hypr/hyde.conf @@ -27,9 +27,18 @@ # █▀█ █▀▀ █▀▀ ▄█ # $QUICKAPPS = # used for quick app launcher -# $BROWSER = firefox # default browser, if commented out , will use the default browser +# Leaving this to HyDE means Super+B runs its `$default.BROWSER`, which is +# `hyde-launch.sh --fall firefox web-browser` — and the 26.x update deprecated that script, so +# every press fired a "hyde-launch.sh is deprecated, please use hyde-shell open instead" +# notification. Naming the browser directly skips the wrapper entirely. This is the primary +# browser only; Super+Shift+B (librewolf) and Super+Ctrl+B (firefox) are set in keybindings.conf. +# `hyde-shell open web-browser` is the modern HyDE equivalent if the xdg default should decide. +$BROWSER = zen-browser # matches the xdg default (zen.desktop) # $EDITOR = code # default editor, if commented out , will use the default editor -# $EXPLORER= dolphin # default file manager, if commented out , will use the default file manager +# Same deprecation as $BROWSER above: HyDE's default is `hyde-launch.sh --fall dolphin +# file-manager`, and Super+E in keybindings.conf uses it. $EDITOR is deliberately left to HyDE — +# no active bind reads it (Super+C runs `$TERMINAL nvim`), so there is nothing to fix there. +$EXPLORER = dolphin # matches the xdg default (org.kde.dolphin.desktop) # $TERMINAL = kitty # default terminal, if commented out , will use the org.gnome.desktop.default-applications.terminal # $LOCKSCREEN=hyprlock # default lockscreen, you can use any lockscreen you want, eg swaylock # $IDLE=hypridle # default idle manager, you can use any idle manager you want,eg swayidle @@ -43,14 +52,21 @@ # $start.XDG_PORTAL_RESET=$scrPath/resetxdgportal.sh # $start.DBUS_SHARE_PICKER=dbus-update-activation-environment --systemd --all # for XDPH # $start.SYSTEMD_SHARE_PICKER=systemctl --user import-environment QT_QPA_PLATFORMTHEME WAYLAND_DISPLAY XDG_CURRENT_DESKTOP # for XDPH -# $start.BAR=waybar +# Empty = unset, so HyDE's default `exec-once = $start.BAR` (a bare `waybar`, using HyDE's +# generated config.jsonc/style.css) does not launch a second bar. The bar we actually want is +# launched from userprefs.conf with the custom layout/style. +$start.BAR= # $start.NOTIFICATIONS=swaync # dunst # $start.APPTRAY_BLUETOOTH=blueman-applet -# $start.WALLPAPER=$scrPath/swwwallpaper.sh +# $start.WALLPAPER=$scrPath/wallpaper.sh --global # 26.x: backend-agnostic (awww); swwwallpaper.sh is gone # $start.TEXT_CLIPBOARD=wl-paste --type text --watch cliphist store # $start.IMAGE_CLIPBOARD=wl-paste --type image --watch cliphist store # $start.BATTERY_NOTIFY=$scrPath/batterynotify.sh -# $start.NETWORK_MANAGER=nm-applet --indicator +# Kept explicitly at the default: nm-applet's tray icon is the one place with a quick +# connect/disconnect MENU (wifi list, VPN) — the bar's `network` module only shows state +# and bandwidth. Tela renders it as a blue circle (its scalable/devices icon); that's the +# theme working, not a bug. Was briefly unset 2026-08-14 — wrong call, the menu was wanted. +$start.NETWORK_MANAGER=nm-applet --indicator # $start.REMOVABLE_MEDIA=udiskie --no-automount --smart-tray # $start.AUTH_DIALOGUE=$scrPath/polkitkdeauth.sh # $start.IDLE_DAEMON=$IDLE @@ -104,7 +120,11 @@ #$GTK_THEME=Wallbash-Gtk -#$ICON_THEME=Tela-circle-dracula +# dots-icons = repo-tracked overlay theme (gui/.local/share/icons/dots-icons) that inherits +# Tela-circle-dracula and only swaps the tray/status icons for Tela's monochrome panel set at +# every size — without it, waybar on a fractionally-scaled monitor requests 2x sizes and gets +# the colored-circle variants (the blue nm-applet blob). Everything else still comes from Tela. +$ICON_THEME=dots-icons #$COLOR_SCHEME=prefer-dark # // █▀▀ █░█ █▀█ █▀ █▀█ █▀█ diff --git a/gui/.config/hypr/hyprlock/Anurati.conf b/gui/.config/hypr/hyprlock/Anurati.conf deleted file mode 100644 index 035d325..0000000 --- a/gui/.config/hypr/hyprlock/Anurati.conf +++ /dev/null @@ -1,132 +0,0 @@ -# ░▒▒▒░░░░░▓▓ ___________ -# ░░▒▒▒░░░░░▓▓ //___________/ -# ░░▒▒▒░░░░░▓▓ _ _ _ _ _____ -# ░░▒▒░░░░░▓▓▓▓▓▓ | | | | | | | __/ -# ░▒▒░░░░▓▓ ▓▓ | |_| | |_/ /| |___ -# ░▒▒░░▓▓ ▓▓ \__ |____/ |____/ -# ░▒▓▓ ▓▓ //____/ - -$fontFamily = JetBrainsMono Nerd Font # We already have this font installed - -# Resolving custom fonts -# Provide the font name and the download link separated by a pipe | -# Run font.sh resolve $LAYOUT_PATH to install the font -$resolve.font=Anurati|https://font.download/dl/font/anurati.zip -$resolve.font=Inter|https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip - -background { - monitor = - color = $wallbash_pry1_rgba - path = $BACKGROUND_PATH - blur_size = 4 - blur_passes = 3 # 0 disables blurring - noise = 0.0117 - contrast = 1.3000 # Vibrant!!! - brightness = 0.8000 - vibrancy = 0.2100 - vibrancy_darkness = 0.0 -} - -# DAY -label { - monitor = - text = cmd[update:1000] echo "$(date +"%A" | sed 's/./& /g' | tr '[:lower:]' '[:upper:]')" # Add a thin space between each character - color = $wallbash_1xa9_rgba - font_size = 110 # Wednesday is too long - font_family = Anurati - position = 0, 100 - halign = center - valign = center -} - -# DATE -label { - monitor = - text = cmd[update:1000] echo "$(date +"%B %d")" - color = $wallbash_2xa9_rgba - font_size = 35 - font_family = $font_family Thin - position = 0, -20 - halign = center - valign = center -} - -# TIME -label { - monitor = - text = cmd[update:1000] echo "$(date +"%-I:%M %p")" - #text = $TIME - color = $wallbash_txt1_rgba - font_size = 20 #! - font_family = $font_family - position = 0, 150 - halign = center - valign = bottom -} -# INPUT FIELD -input-field { - monitor = - size = 200, 50 #! - outline_thickness = 3 - dots_size = 0.33 # Scale of input-field height, 0.2 - 0.8 - dots_spacing = 0.15 # Scale of dots' absolute size, 0.0 - 1.0 - dots_center = true - dots_rounding = -1 # -1 default circle, -2 follow input-field rounding - outer_color = $wallbash_pry4_rgba - inner_color = $wallbash_pry2_rgba - font_color = $wallbash_3xa9_rgba - fade_on_empty = true - fade_timeout = 1000 # Milliseconds before fade_on_empty is triggered. - placeholder_text = Input Password... # Text rendered in the input box when it's empty. - hide_input = false - rounding = -1 # -1 means complete rounding (circle/oval) - check_color = $wallbash_pry4_rgba - fail_color = rgba(FF0000FF) # if authentication failed, changes outer_color and fail message color - fail_text = $FAIL ($ATTEMPTS) # can be set to empty - fail_transition = 300 # transition time in ms between normal outer_color and fail_color - capslock_color = -1 - numlock_color = -1 - bothlock_color = -1 # when both locks are active. -1 means don't change outer color (same for above) - invert_numlock = false # change color if numlock is off - swap_font_color = true # see below - position = 0, 80 - halign = center - valign = bottom -} - -#User tag -label { - monitor = - text =  $USER - color = $wallbash_4xa9_rgba - font_size = 20 - font_family = Inter Display Medium - position = 0, 30 - halign = center - valign = bottom -} - - -# Battery Status if present -label { - monitor = - text = cmd[update:5000] $BATTERY_ICON - color = $wallbash_4xa9_rgba - font_size = 20 - font_family = JetBrainsMono Nerd Font - position = -1%, 1% - halign = right - valign = bottom -} - -# Current Keyboard Layout -label { - monitor = - text = cmd[update:1000] $KEYBOARD_LAYOUT - color = $wallbash_4xa9_rgba - font_size = 20 - font_family = Inter Display Medium - position = -2%, 1% - halign = right - valign = bottom -} diff --git a/gui/.config/hypr/hyprlock/Arfan on Clouds.conf b/gui/.config/hypr/hyprlock/Arfan on Clouds.conf deleted file mode 100644 index c19bc28..0000000 --- a/gui/.config/hypr/hyprlock/Arfan on Clouds.conf +++ /dev/null @@ -1,158 +0,0 @@ -# Hyprclouds from Arfan on Clouds -# By: Arfan on Clouds https://github.com/arfan-on-clouds/hyprclouds -# Modified by: The HyDE Project - -$resolve.font=SF Pro Display|https://font.download/dl/font/sf-pro-display.zip -$resolve.font=AlfaSlabOne|https://font.download/dl/font/alfa-slab-one.zip - -# BACKGROUND -background { - monitor = - color = $wallbash_pry1_rgba - path = $BACKGROUND_PATH - blur_size = 4 - blur_passes = 3 # 0 disables blurring - noise = 0.0117 - contrast = 1.3000 # Vibrant!!! - brightness = 0.8000 - vibrancy = 0.2100 - vibrancy_darkness = 0.0 -} - -# INPUT FIELD -input-field { - monitor = - size = 190, 50 - outline_thickness = 2 - dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8 - dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0 - dots_center = true - fade_on_empty = true - font_family = SF Pro Display Bold - placeholder_text = - hide_input = false - position = 0, -170 - halign = center - valign = center - outer_color = $wallbash_pry4_rgba - inner_color = $wallbash_pry2_rgba - font_color = $wallbash_3xa9_rgba - check_color = $wallbash_pry4_rgba - -} - -# Hour-Time -label { - monitor = - text = cmd[update:1000] echo -e "$(date +"%I")" - color = $wallbash_1xa8_rgba - shadow_passes = 3 - shadow_size = 2 - font_size = 100 - font_family = AlfaSlabOne - position = 0, 100 - halign = center - valign = center -} - -# Minute-Time -label { - monitor = - text = cmd[update:1000] echo -e "$(date +"%M")" - color = $wallbash_3xa9_rgba - font_size = 100 - font_family = AlfaSlabOne - shadow_passes = 3 - shadow_size = 2 - position = 0, -20 - halign = center - valign = center -} - -# Day-Date-Month -label { - monitor = - text = cmd[update:1000] echo -e "$(date +"%d %b %A")" - color = $wallbash_3xa9_rgba - shadow_passes = 3 - shadow_size = 1 - font_size = 12 - font_family = JetBrains Mono Nerd Font Mono ExtraBold - position = 1%, -1% - halign = left - valign = top -} - - - -# CURRENT SONG -label { - monitor = - text = cmd[update:1000] $MPRIS_TEXT # Outputs the song title when mpris is available, otherwise, it will output the splash command. - color = $wallbash_3xa9_rgba - shadow_passes = 3 - shadow_size = 1 - font_size = 14 - font_family = JetBrains Mono Nerd, SF Pro Display Bold - position = 0, 20 - halign = center - valign = bottom -} -image { - monitor = - path = $MPRIS_IMAGE - size = 80 # lesser side if not 1:1 ratio - rounding = -1 # negative values mean circle - border_size = 0 - shadow_passes = 3 - shadow_size = 3 - border_color = rgb(221, 221, 221) - rotate = 0 # degrees, counter-clockwise - reload_time = 0 # seconds between reloading, 0 to reload with SIGUSR2 - # reload_cmd = # command to get new path. if empty, old path will be used. don't run "follow" commands like tail -F - position = 0, 10% - halign = center - valign = bottom -} - - - -# Battery Status if present -label { - monitor = - text = cmd[update:5000] $BATTERY_ICON - color = $wallbash_4xa9_rgba - font_size = 20 - font_family = JetBrainsMono Nerd Font - position = -1%, 1% - halign = right - valign = bottom -} - -# Current Keyboard Layout -label { - monitor = - text = cmd[update:1000] $KEYBOARD_LAYOUT - color = $wallbash_4xa9_rgba - font_size = 20 - font_family = JetBrains Mono Nerd, SF Pro Display Bold - position = -2%, 1% - halign = right - valign = bottom -} - - -# Weather -#! Put the weather command last to lessen the load time of other modules -label { - monitor = - text = cmd[update:18000000] $WEATHER_CMD - color = $wallbash_3xa9_rgba - font_size = 16 - shadow_passes = 3 - shadow_size = 1 - font_family = JetBrains Mono Nerd, SF Pro Display Bold - position = -1%, -1% - halign = right - valign = top -} diff --git a/gui/.config/hypr/hyprlock/IBM Plex.conf b/gui/.config/hypr/hyprlock/IBM Plex.conf deleted file mode 100644 index 7e49d8e..0000000 --- a/gui/.config/hypr/hyprlock/IBM Plex.conf +++ /dev/null @@ -1,112 +0,0 @@ - -# ░▒▒▒░░░░░▓▓ ___________ -# ░░▒▒▒░░░░░▓▓ //___________/ -# ░░▒▒▒░░░░░▓▓ _ _ _ _ _____ -# ░░▒▒░░░░░▓▓▓▓▓▓ | | | | | | | __/ -# ░▒▒░░░░▓▓ ▓▓ | |_| | |_/ /| |___ -# ░▒▒░░▓▓ ▓▓ \__ |____/ |____/ -# ░▒▓▓ ▓▓ //____/ - -$fontFamily = IBM Plex Sans -$resolve.font=IBM Plex Sans|https://github.com/IBM/plex/releases/download/%40ibm%2Fplex-sans%401.1.0/ibm-plex-sans.zip - -# GENERAL -background { - monitor = - path = $BACKGROUND_PATH - blur_size = 5 - blur_passes = 0 - noise = 0.0117 - contrast = 1.3000 # Vibrant! - brightness = 0.8000 - vibrancy = 0.2100 - vibrancy_darkness = 0.0 -} - -# Current time -label { - monitor = - text = cmd[update:1000] echo " $(date +"%H") " - color = $wallbash_2xa7_rgba - font_size = 180 - font_family = $fontFamily Medium 10 - position = 0, 80 - halign = center - valign = center -} -label { - monitor = - text = cmd[update:1000] echo " $(date +"%M") " - color = $wallbash_2xa9_rgba - font_size = 180 - font_family = $fontFamily Medium 10 - position = 0, -120 - halign = center - valign = center -} -label { - monitor = - text = cmd[update:1000] echo " $(date +"%d %b") " - color = $wallbash_2xa8_rgba - font_size = 20 - font_family = $fontFamily Medium 10 - position = 0, -230 - halign = center - valign = center -} -label { - monitor = - text = cmd[update:1000] echo " $(date +"%A") " - color = $wallbash_2xa8_rgba - - font_size = 20 - font_family = $fontFamily Medium 10 - position = 0, -250 - halign = center - valign = center -} - -#INPUT FIELD -input-field { - monitor = - # size = 15%, 5% # hyprlock-git - size = 200, 50 - outline_thickness = 3 - dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8 - dots_spacing = 1.00 # Scale of dots' absolute size, 0.0 - 1.0 - dots_center = true - outer_color = $wallbash_pry2_rgba - inner_color = $wallbash_pry3_rgba - font_color = $color7 - fade_on_empty = true - placeholder_text = Password... # Text rendered in the input box when it's empty. - hide_input = false - position = 0, 60 - halign = center - valign = bottom -} - - -# Battery Status if present -label { - monitor = - text = cmd[update:5000] $BATTERY_ICON - color = $wallbash_4xa9_rgba - font_size = 20 - font_family = JetBrainsMono Nerd Font - position = -1%, 1% - halign = right - valign = bottom -} - -# Current Keyboard Layout -label { - monitor = - text = cmd[update:1000] $KEYBOARD_LAYOUT - color = $wallbash_4xa9_rgba - font_size = 20 - font_family = $fontFamily - position = -2%, 1% - halign = right - valign = bottom -} diff --git a/gui/.config/hypr/hyprlock/SF Pro.conf b/gui/.config/hypr/hyprlock/SF Pro.conf deleted file mode 100644 index ca13d5c..0000000 --- a/gui/.config/hypr/hyprlock/SF Pro.conf +++ /dev/null @@ -1,161 +0,0 @@ -# Hyprlock style-4 -# By: Vivek Rajan https://github.com/MrVivekRajan/Hyprlock-Styles/tree/main/Style-4 -# Modified by: The HyDE Project - -$resolve.font=SF Pro Display|https://font.download/dl/font/sf-pro-display.zip - -# BACKGROUND -background { - monitor = - path = $BACKGROUND_PATH - blur_passes = 0 - contrast = 0.8916 - brightness = 0.8916 - vibrancy = 0.8916 - vibrancy_darkness = 0.0 -} - -# GENERAL -general { - no_fade_in = false - grace = 0 - disable_loading_bar = false -} - -# Profile-Photo -image { - monitor = - path = $PROFILE_IMAGE - border_size = 2 - border_color = $wallbash_pry4_rgba - size = 100 - rounding = -1 - rotate = 0 - reload_time = -1 - reload_cmd = - position = 25, 200 - halign = center - valign = center -} - -# NAME -label { - monitor = - text = cmd[update:60000] $GREET_TEXT - color = $wallbash_txt1_rgba - outline_thickness = 0 - dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8 - dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0 - dots_center = true - font_size = 20 - font_family = SF Pro Display Bold - position = 25, 110 - halign = center - valign = center -} - -# Time -label { - monitor = - text = cmd[update:1000] echo "$(date +"%I:%M")" - color = $wallbash_txt1_rgba - font_size = 60 - font_family = SF Pro Display Bold - position = 30, -8 - halign = center - valign = center -} - -# Day-Month-Date -label { - monitor = - text = cmd[update:1000] echo -e "$(date +"%A, %B %d")" - color = $wallbash_txt1_rgba - font_size = 19 - font_family = SF Pro Display Bold - position = 35, -60 - halign = center - valign = center -} - -# USER-BOX -shape { - monitor = - size = 320, 55 - color = rgba(255, 255, 255, 0.1) - rounding = -1 - border_size = 0 - border_color = $wallbash_txt3_rgba - rotate = 0 - xray = false # if true, make a "hole" in the background (rectangle of specified size, no rotation) - - position = 34, -190 - halign = center - valign = center -} - -# USER -label { - monitor = - text =  $USER - color = $wallbash_1xa9_rgba - outline_thickness = 0 - dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8 - dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0 - dots_center = true - font_size = 16 - font_family = SF Pro Display Bold - position = 38, -190 - halign = center - valign = center -} - -# INPUT FIELD -input-field { - monitor = - size = 320, 55 - outline_thickness = 0 - dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8 - dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0 - dots_center = true - outer_color = rgba(255, 255, 255, 0) - inner_color = rgba(255, 255, 255, 0.1) - font_color = $wallbash_2xa6_rgba - fade_on_empty = false - font_family = SF Pro Display Bold - placeholder_text = 🔒 Enter Pass - hide_input = false - position = 34, -268 - halign = center - valign = center - check_color = $wallbash_txt4_rgba - fail_color = rgb(204, 34, 34) # if authentication failed, changes outer_color and fail message color - fail_text = $FAIL ($ATTEMPTS) # can be set to empty - fail_transition = 300 # transition time in ms between normal outer_color and fail_color - -} - - -# Battery Status if present -label { - monitor = - text = cmd[update:5000] $BATTERY_ICON - color = $wallbash_4xa9_rgba - font_size = 20 - font_family = JetBrainsMono Nerd Font - position = -1%, 1% - halign = right - valign = bottom -} - -# Current Keyboard Layout -label { - monitor = - text = cmd[update:1000] $KEYBOARD_LAYOUT - color = $wallbash_4xa9_rgba - font_size = 20 - font_family = SF Pro Display Bold - position = -2%, 1% - halign = right - valign = bottom -} diff --git a/gui/.config/hypr/hyprlock/theme.conf b/gui/.config/hypr/hyprlock/theme.conf deleted file mode 100644 index ab4a157..0000000 --- a/gui/.config/hypr/hyprlock/theme.conf +++ /dev/null @@ -1 +0,0 @@ -source = ./HyDE.conf \ No newline at end of file diff --git a/gui/.config/hypr/keybindings.conf b/gui/.config/hypr/keybindings.conf index e2a6207..5e43c82 100644 --- a/gui/.config/hypr/keybindings.conf +++ b/gui/.config/hypr/keybindings.conf @@ -26,15 +26,19 @@ $wm=Window Management $d=[$wm] -bindd = $mainMod, Q, $d close focused window, exec, $scrPath/dontkillsteam.sh -bindd = Alt, F4, $d close focused window, exec, $scrPath/dontkillsteam.sh +# `dontkillsteam.sh` was dropped by HyDE (gone from lib/hyde as of the 2026-08-13 update) and +# upstream now closes with the native dispatcher — its Lua key_binds.lua binds both of these to +# `hl.dsp.window.close()`. A bind pointing at a missing script stays valid and silently does +# nothing, which on these two keys means windows stop closing. +bindd = $mainMod, Q, $d close focused window, killactive, +bindd = Alt, F4, $d close focused window, killactive, bindd = $mainMod, Delete, $d kill hyprland session, exit bindd = $mainMod, W, $d toggle float, togglefloating, # bindd = $mainMod Shift, G, $d toggle group, togglegroup bindd = Shift, F11, $d toggle fullscreen, fullscreen bindd = Alt, Return, $d toggle fullscreen, fullscreen bindd = $mainMod, L, $d lock screen, exec, lockscreen.sh -bindd = $mainMod Shift, F, $d toggle pin on focused window, exec, $scrPath/window.pin.sh +bindd = $mainMod Shift, F, $d toggle pin on focused window, exec, $scrPath/windowpin.sh # renamed upstream (was window.pin.sh); the old machine-local shim is gone with the update bindd = Control Alt, Delete, $d logout menu, exec, $scrPath/logoutlaunch.sh bindd = Control Alt, W, $d toggle waybar, exec, killall waybar || waybar -c ~/.config/waybar/layouts/custom.jsonc -s ~/.config/waybar/layouts/custom.css # bindd = Control Alt, W, $d toggle waybar, exec, hyde-shell waybar --hide @@ -123,7 +127,7 @@ bindd = $mainMod, B, $d web browser , exec, $BROWSER bindd = $mainMod Shift, B, $d web browser , exec, librewolf bindd = $mainMod Control, B, $d web browser , exec, firefox bindd = $mainMod, Y, $d ferdium, exec, ferdium -bindd = Control Shift, Escape, $d system monitor , exec, $scrPath/sysmonlaunch.sh +bindd = Control Shift, Escape, $d system monitor , exec, $scrPath/system.monitor.sh # renamed upstream (was sysmonlaunch.sh) bindd = $mainMod, F, $d freetube , exec, freetube # youtube alternative bindd = $mainMod, K, $d password manager , exec, keepassxc # launch password manager bindd = $mainMod, G, $d xmpp, exec, gajim # launch messanger diff --git a/gui/.config/hypr/userprefs.conf b/gui/.config/hypr/userprefs.conf index 85cb09a..ef4e1c4 100644 --- a/gui/.config/hypr/userprefs.conf +++ b/gui/.config/hypr/userprefs.conf @@ -29,6 +29,8 @@ layerrule = ignore_alpha 0,match:namespace cavabar env = SSH_AUTH_SOCK,$XDG_RUNTIME_DIR/ssh-agent.socket # ssh on start env = ELECTRON_OZONE_PLATFORM_HINT,x11 # Disable after hyprland fixes electron flickering exec-once = waybar -c ~/.config/waybar/layouts/custom.jsonc -s ~/.config/waybar/layouts/custom.css +# Steam Controller -> Deck-style binds. Not a systemd user unit: graphical-session.target +exec-once = scc-daemon start # (No static `noanim` layerrule: current Hyprland rejects it in the match: syntax, and # grimblast already suppresses selection animation at runtime via `hyprctl keyword layerrule`.) diff --git a/gui/.config/kitty/theme.conf b/gui/.config/kitty/theme.conf index 737c0d6..700f0f4 100644 --- a/gui/.config/kitty/theme.conf +++ b/gui/.config/kitty/theme.conf @@ -1,61 +1,78 @@ +## name: Catppuccin Mocha 🌿 +## author: Pocco81 (https://github.com/Pocco81) +## license: MIT +## upstream: https://github.com/catppuccin/kitty/blob/main/mocha.conf +## blurb: Soothing pastel theme for the high-spirited! + + + # The basic colors foreground #ffffff background #262335 -selection_foreground #262335 -selection_background #f97e72 +selection_foreground #1E1E2E +selection_background #F5E0DC # Cursor colors -cursor #f97e72 -cursor_text_color #262335 +cursor #F5E0DC +cursor_text_color #1E1E2E # URL underline color when hovering with mouse -url_color #f97e72 +url_color #B4BEFE + +# Kitty window border colors +active_border_color #CBA6F7 +inactive_border_color #8E95B3 +bell_border_color #EBA0AC # OS Window titlebar colors wayland_titlebar_color system macos_titlebar_color system +# Tab bar colors +active_tab_foreground #11111B +active_tab_background #CBA6F7 +inactive_tab_foreground #CDD6F4 +inactive_tab_background #181825 + # Colors for marks (marked text in the terminal) -mark1_foreground #262335 -mark1_background #614D85 -mark2_foreground #262335 -mark2_background #614D85 -mark3_foreground #262335 -mark3_background #614D85 +mark1_foreground #1E1E2E +mark1_background #87B0F9 +mark2_foreground #1E1E2E +mark2_background #CBA6F7 +mark3_foreground #1E1E2E +mark3_background #74C7EC # The 16 terminal colors # black -color0 #232530 -# bright-black = dim/secondary text (menus, comments); was #232530 ≈ background so it -# rendered invisible. Lifted to the theme's muted lavender for legible dim text. -color8 #848bbd +color0 #43465A +color8 #43465A # red -color1 #fe4450 -color9 #fe4450 +color1 #F38BA8 +color9 #F38BA8 # green -color2 #72f1b8 -color10 #72f1b8 +color2 #A6E3A1 +color10 #A6E3A1 # yellow -color3 #ff7edb -color11 #ff7edb +color3 #F9E2AF +color11 #F9E2AF # blue -color4 #03edf9 -color12 #03edf9 +color4 #87B0F9 +color12 #87B0F9 # magenta -color5 #fede5d -color13 #f3e70f +color5 #F5C2E7 +color13 #F5C2E7 # cyan -color6 #03edf9 -color14 #03edf9 +color6 #94E2D5 +color14 #94E2D5 # white -color7 #ffffff -color15 #ffffff +color7 #CDD6F4 +color15 #A1A8C9 diff --git a/gui/.config/uwsm/env-hyprland.d/10-dots.sh b/gui/.config/uwsm/env-hyprland.d/10-dots.sh new file mode 100644 index 0000000..2af226e --- /dev/null +++ b/gui/.config/uwsm/env-hyprland.d/10-dots.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env sh + +# Keep Hyprland on this repo's hyprlang config instead of HyDE's Lua one. +# +# HyDE 26.x moved its Hyprland config to Lua. Its own `00-hyde.sh` (synced on +# every update) points HYPRLAND_CONFIG at ~/.local/share/hypr/hyde.lua, and +# Hyprland then never reads ~/.config/hypr/hyprland.conf — so every dots hypr +# file (theme, keybinds, window rules, monitors, userprefs) is silently bypassed +# and the session comes up as stock HyDE: no rounding/gaps/borders, HyDE's binds, +# HyDE's plain waybar instead of the cava rig. Nothing errors; `hyprctl +# configerrors` stays empty. The tell is the log's first lines: +# [cfg] Config is either explicit or special. / [cfg] Config is lua, loading lua mgr +# +# `00-hyde.sh` assigns with `${HYPRLAND_CONFIG:-...}` and `env-hyprland` sources +# `env-hyprland.d/*.sh` in glob order, so this file (10 sorts after 00) wins by +# assigning unconditionally. Session-start only — this needs a relogin, not a +# `hyprctl reload`. HyDE syncs the whole env-hyprland.d/ directory but only ever +# writes its own files into it, so this one survives updates. +# +# ── STOPGAP, not the destination ────────────────────────────────────────────── +# It pins ~/.local/share/hyde/hyprland.conf, which current HyDE no longer ships: +# ours is a leftover from the pre-update install, so nothing regenerates it. And +# 8 scripts our binds reach for through $scrPath are already gone from the new +# lib/hyde — including dontkillsteam.sh, which is bound to Super+Q and Alt+F4. +# The supported path is porting the hypr layer into ~/.config/hypr/hyprland.lua +# (the one file HyDE promises never to overwrite). See HYDE-UPDATE.md. +HYPRLAND_CONFIG="$HOME/.config/hypr/hyprland.conf" +export HYPRLAND_CONFIG diff --git a/gui/.config/waybar/cava-waybar.sh b/gui/.config/waybar/cava-waybar.sh index 486e5c3..96eba15 100755 --- a/gui/.config/waybar/cava-waybar.sh +++ b/gui/.config/waybar/cava-waybar.sh @@ -8,7 +8,7 @@ # child and they'd pile up. We enforce a single instance: track cava's own PID, # kill it on exit, and reap any stale instance left by a previous (crashed) run. bars="${1:-94}" -chars="▁▂▃▄▅▆▇█" # cava ascii value 0..7 -> these +chars="▁▂▃▄▅▆▇█" # cava ascii value 0..7 -> these conf="$(mktemp)" pidfile="${XDG_RUNTIME_DIR:-/tmp}/cava-waybar.pid" diff --git a/gui/.config/waybar/config.jsonc b/gui/.config/waybar/config.jsonc deleted file mode 100644 index e987932..0000000 --- a/gui/.config/waybar/config.jsonc +++ /dev/null @@ -1,332 +0,0 @@ -// --// standalone two-bar cava rig — hand-maintained, NOT HyDE-generated (see README "Waybar") //-- // - -[ -{ -// sourced from header module // - - "layer": "top", - "position": "top", - "height": 33, - "exclusive": true, - "passthrough": false, - "gtk-layer-shell": true, - "reload_style_on_change": true, - - -// positions generated based on config.ctl // - - "modules-left": ["custom/l_end","custom/power","custom/r_end","hyprland/workspaces", "custom/sl_end","hyprland/window","custom/sr_end","custom/l_end","custom/wallchange","custom/cliphist","custom/r_end"], - "modules-center": ["wlr/taskbar"], - "modules-right": ["custom/l_end","backlight","pulseaudio","pulseaudio#microphone","mpris","custom/r_end","custom/l_end","memory","cpu","custom/cpuinfo","battery","custom/r_end","custom/l_end","tray","custom/r_end","custom/l_end","idle_inhibitor","clock","custom/r_end"], - -// sourced from modules based on config.ctl // - - "tray": { - "icon-size": 19, - "rotate": 0, - "spacing": 5 - }, - - // NOTE: keep in sync with modules/window.jsonc — this layout is launched standalone - // (waybar -c), so the modules/ file is NOT read here; this inline copy is what renders. - "hyprland/window": { - "format": " {}", - "rotate": 0, - "separate-outputs": true, - "max-length": 1000, - "rewrite": { - "(.*) — Kitty": " $1", - "(.*)~": " $1", - "(.*) — Mozilla Firefox": "󰈹 $1", - "(.*)Mozilla Firefox": "󰈹 Firefox", - "(.*) — LibreWolf": "󰈹 $1", - "(.*)LibreWolf": "󰈹 LibreWolf", - "(.*) — Chromium": " $1", - "(.*)Chromium": " Chromium", - "(.*) - VSCodium": "󰨞 $1", - "(.*)VSCodium": "󰨞 VSCodium", - "(.*)Code - OSS": "󰨞 $1", - "(.*) - Visual Studio Code": "󰨞 $1", - "(.*)Visual Studio Code": "󰨞 Code", - "(.*) — Dolphin": "󰉋 $1", - "(.*) - Obsidian(.*)": "󰠮 $1", - "(.*)Ferdium": "󰭻 Ferdium", - "(.*)FreeTube": " FreeTube", - "(.*) - VLC media player": "󰕼 $1", - "(.*)Steam": "󰓓 Steam", - "(.*)Neovide": " Nvim", - "(.*)Neovim": " Nvim", - "(.*)Nvim": " Nvim", - "(.*)Gajim": "󰭹 Gajim", - "(.*)KeePassXC": " KeePassXC", - "(.*)Betterbird": "󰇮 Betterbird", - "(.*)Spotify": "󰓇 Spotify" - } - }, - "hyprland/workspaces": { - "window-rewrite-default":"", - "all-outputs": true, - "active-only": false, - "on-click": "activate", - "disable-scroll": false, - "on-scroll-up": "hyprctl dispatch workspace -1", - "on-scroll-down": "hyprctl dispatch workspace +1", - "persistent-workspaces": {} - }, - - "cpu": { - "interval": 10, - "format": "󰍛 {usage}%", - "format-alt": "{icon0}{icon1}{icon2}{icon3}", - "format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"] - }, - - "custom/cpuinfo": { - "exec": " cpuinfo.sh", - "return-type": "json", - "format": "{}", - "rotate": 0, - "interval": 5, // once every 5 seconds - "tooltip": true, - "max-length": 1000 - }, - - "memory": { - "states": { - "c": 90, // critical - "h": 60, // high - "m": 30 // medium - }, - "interval": 30, - "rotate": 0, - "format": "󰾆 {used:0.1f}GB", - "format-m": "󰾅 {used:0.1f}GB", - "format-h": "󰓅 {used:0.1f}GB", - "format-c": " {used:0.1f}GB", - "format-alt": "󰾆 {percentage}%", - "max-length": 10, - "tooltip": true, - "tooltip-format": "󰾆 {percentage}%\n {used:0.1f}GB/{total:0.1f}GB" - }, - - "custom/power": { - "format": "{}", - "rotate": 0, - "exec": "echo ; echo  logout", - "on-click": "logoutlaunch.sh 2", - "on-click-right": "logoutlaunch.sh 1", - "interval" : 86400, // once every day - "tooltip": true - }, - - "wlr/taskbar": { - "format": "{icon}", - "rotate": 0, - "icon-size": 19, - "icon-theme": "Tela-circle-dracula", - "spacing": 0, - "tooltip-format": "{title}", - "on-click": "activate", - "on-click-middle": "close", - "ignore-list": [ - "Alacritty" - ], - "app_ids-mapping": { - "firefoxdeveloperedition": "firefox-developer-edition" - } - }, - - "idle_inhibitor": { - "format": "{icon}", - "rotate": 0, - "format-icons": { - "activated": "󰅶", - "deactivated": "󰛊" - } - }, - "clock": { - "format": "{:%H:%M}", - "rotate": 0, - "format-alt": "{:%R 󰃭 %d·%m·%y}", - "tooltip-format": "{calendar}", - "calendar": { - "mode": "month", - "mode-mon-col": 3, - "on-scroll": 1, - "on-click-right": "mode", - "format": { - "months": "{}", - "weekdays": "{}", - "today": "{}" - } - }, - "actions": { - "on-click-right": "mode", - "on-click-forward": "tz_up", - "on-click-backward": "tz_down", - "on-scroll-up": "shift_up", - "on-scroll-down": "shift_down" - } - }, - - "custom/cliphist": { - "format": "{}", - "rotate": 0, - "exec": "echo ; echo 󰅇 clipboard history", - "on-click": "sleep 0.1 && cliphist.sh c", - "on-click-right": "sleep 0.1 && cliphist.sh d", - "on-click-middle": "sleep 0.1 && cliphist.sh w", - "interval" : 86400, // once every day - "tooltip": true - }, - - "custom/wallchange": { - "format": "{}", - "rotate": 0, - "exec": "echo ; echo 󰆊 switch wallpaper", - "on-click": "swwwallpaper.sh -n", - "on-click-right": "swwwallpaper.sh -p", - "on-click-middle": "sleep 0.1 && swwwallselect.sh", - "interval" : 86400, // once every day - "tooltip": true - }, - - "pulseaudio": { - "format": "{icon} {volume}", - "rotate": 0, - "format-muted": "婢", - "on-click": "pavucontrol -t 3", - "on-click-right": "volumecontrol.sh -s ''", - "on-click-middle": "volumecontrol.sh -o m", - "on-scroll-up": "volumecontrol.sh -o i", - "on-scroll-down": "volumecontrol.sh -o d", - "tooltip-format": "{icon} {desc} // {volume}%", - "scroll-step": 5, - "format-icons": { - "headphone": "", - "hands-free": "", - "headset": "", - "phone": "", - "portable": "", - "car": "", - "default": ["", "", ""] - } - }, - - "pulseaudio#microphone": { - "format": "{format_source}", - "rotate": 0, - "format-source": "", - "format-source-muted": "", - "on-click": "pavucontrol -t 4", - "on-click-middle": "volumecontrol.sh -i m", - "on-scroll-up": "volumecontrol.sh -i i", - "on-scroll-down": "volumecontrol.sh -i d", - "tooltip-format": "{format_source} {source_desc} // {source_volume}%", - "scroll-step": 5 - }, - - "mpris": { - "format": "{player_icon} {dynamic}", - "rotate": 0, - "format-paused": "{status_icon} {dynamic}", - "player-icons": { - "default": "▶", - "mpv": "🎵" - }, - "status-icons": { - "paused": "" - }, - "max-length": 1000, - "interval": 1 - }, - - "battery": { - "states": { - "good": 95, - "warning": 30, - "critical": 20 - }, - "format": "{icon} {capacity}%", - "rotate": 0, - "format-charging": " {capacity}%", - "format-plugged": " {capacity}%", - "format-alt": "{time} {icon}", - "format-icons": ["󰂎", "󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"] - }, - - "backlight": { - "device": "intel_backlight", - "rotate": 0, - "format": "{icon} {percent}%", - "format-icons": ["", "", "", "", "", "", "", "", ""], - "on-scroll-up": "brightnessctl set 1%+", - "on-scroll-down": "brightnessctl set 1%-", - "min-length": 6 - }, -// modules for padding // - - "custom/l_end": { - "format": " ", - "interval" : "once", - "tooltip": false - }, - - "custom/r_end": { - "format": " ", - "interval" : "once", - "tooltip": false - }, - - "custom/sl_end": { - "format": " ", - "interval" : "once", - "tooltip": false - }, - - "custom/sr_end": { - "format": " ", - "interval" : "once", - "tooltip": false - }, - - "custom/rl_end": { - "format": " ", - "interval" : "once", - "tooltip": false - }, - - "custom/rr_end": { - "format": " ", - "interval" : "once", - "tooltip": false - }, - - "custom/padd": { - "format": " ", - "interval" : "once", - "tooltip": false - } -}, - -// full-width cava visualizer layered behind the main bar // - -{ - "name": "cavabar", - "layer": "bottom", - "position": "top", - "height": 36, - "margin-top": -33, - "exclusive": false, - "passthrough": true, - "modules-center": ["custom/cava"], - - "custom/cava": { - "format": "{}", - "exec": "bash $HOME/.config/waybar/cava-waybar.sh $(cat $HOME/.config/waybar/cava.width 2>/dev/null || echo 94)", // per-host cava.width (bars, even; ~screen_px / (font-size*0.6)). gui default 94; wm overrides to 218 - "restart-interval": 1, - "hide-empty-text": true - } -} -] - diff --git a/gui/.config/waybar/includes/border-radius.css b/gui/.config/waybar/includes/border-radius.css deleted file mode 100644 index 2310a0a..0000000 --- a/gui/.config/waybar/includes/border-radius.css +++ /dev/null @@ -1,307 +0,0 @@ -/* -This is an autogenerated file. -Do not edit this file directly. - -This is modified to dynamically -change the border-radius which will -follow the border-radius of hyprland. - -To parse this file, we use PT as a unit -to represent the border-radius. -This is stored separately to avoid -issues on parsing the border-radius - -*/ - -/* ? Leaf shape */ - -window#waybar.top #leaf, -window#waybar.top #leaf button, -window#waybar.top #leaf menu, -window#waybar.top #leaf menuitem, -window#waybar.top #leaf tooltip, -window#waybar.bottom #leaf, -window#waybar.bottom #leaf button, -window#waybar.bottom #leaf menu, -window#waybar.bottom #leaf menuitem, -window#waybar.bottom #leaf tooltip { - border-radius: 10pt 0 10pt 0; -} - -window#waybar.left #leaf, -window#waybar.left #leaf button, -window#waybar.left #leaf menu, -window#waybar.left #leaf menuitem, -window#waybar.left #leaf tooltip, -window#waybar.right #leaf, -window#waybar.right #leaf button, -window#waybar.right #leaf menu, -window#waybar.right #leaf menuitem, -window#waybar.right #leaf tooltip { - border-radius: 10pt 0 10pt 0; -} - -/* ? Inverse Leaf shape */ - -window#waybar.top #leaf-inverse, -window#waybar.top #leaf-inverse button, -window#waybar.top #leaf-inverse menu, -window#waybar.top #leaf-inverse menuitem, -window#waybar.top #leaf-inverse tooltip, -window#waybar.bottom #leaf-inverse, -window#waybar.bottom #leaf-inverse button, -window#waybar.bottom #leaf-inverse menu, -window#waybar.bottom #leaf-inverse menuitem, -window#waybar.bottom #leaf-inverse tooltip { - border-radius: 0 10pt 0 10pt; -} - -window#waybar.left #leaf-inverse, -window#waybar.left #leaf-inverse button, -window#waybar.left #leaf-inverse menu, -window#waybar.left #leaf-inverse menuitem, -window#waybar.left #leaf-inverse tooltip, -window#waybar.right #leaf-inverse, -window#waybar.right #leaf-inverse button, -window#waybar.right #leaf-inverse menu, -window#waybar.right #leaf-inverse menuitem, -window#waybar.right #leaf-inverse tooltip { - border-radius: 0 10pt 0 10pt; -} - - -/* ? Full Pill shape*/ - -window#waybar.bottom #pill menuitem, -window#waybar.bottom #pill menu, -window#waybar.bottom #pill tooltip, -window#waybar.bottom #pill, -window#waybar.bottom #pill button, -window#waybar.top #pill menuitem, -window#waybar.top #pill menu, -window#waybar.top #pill tooltip, -window#waybar.top #pill, -window#waybar.top #pill button { - border-radius: 10pt 10pt 10pt 10pt; -} - -window#waybar.left #pill menuitem, -window#waybar.left #pill menu, -window#waybar.left #pill tooltip, -window#waybar.left #pill, -window#waybar.left #pill button, -window#waybar.right #pill menuitem, -window#waybar.right #pill menu, -window#waybar.right #pill tooltip, -window#waybar.right #pill, -window#waybar.right #pill button { - border-radius: 10pt 10pt 10pt 10pt; -} - - -/* ? half pill with the curve pointing dowmwards */ - - -window#waybar.top #pill-down, -window#waybar.top #pill-down button, -window#waybar.top #pill-down menu, -window#waybar.top #pill-down menuitem, -window#waybar.top #pill-down tooltip, -window#waybar.bottom #pill-down, -window#waybar.bottom #pill-down button, -window#waybar.bottom #pill-down menu, -window#waybar.bottom #pill-down menuitem, -window#waybar.bottom #pill-down tooltip { - border-radius: 0 0 10pt 10pt; -} - -window#waybar.left #pill-down, -window#waybar.left #pill-down button, -window#waybar.left #pill-down menu, -window#waybar.left #pill-down menuitem, -window#waybar.left #pill-down tooltip, -window#waybar.right #pill-down, -window#waybar.right #pill-down button, -window#waybar.right #pill-down menu, -window#waybar.right #pill-down menuitem, -window#waybar.right #pill-down tooltip { - border-radius: 0 0 10pt 10pt; -} - - -/* -? half pill with the curve pointing inwards -*/ - -window#waybar.top #pill-in, -window#waybar.top #pill-in button, -window#waybar.top #pill-in menu, -window#waybar.top #pill-in menuitem, -window#waybar.top #pill-in tooltip { - border-radius: 0 0 10pt 10pt; -} - -window#waybar.right #pill-in, -window#waybar.right #pill-in button, -window#waybar.right #pill-in menu, -window#waybar.right #pill-in menuitem, -window#waybar.right #pill-in tooltip { - border-radius: 10pt 0 0 10pt; -} - -window#waybar.bottom #pill-in, -window#waybar.bottom #pill-in button, -window#waybar.bottom #pill-in menu, -window#waybar.bottom #pill-in menuitem, -window#waybar.bottom #pill-in tooltip { - border-radius: 10pt 10pt 0 0; -} - -window#waybar.left #pill-in, -window#waybar.left #pill-in button, -window#waybar.left #pill-in menu, -window#waybar.left #pill-in menuitem, -window#waybar.left #pill-in tooltip { - border-radius: 0 10pt 10pt 0; -} - - - -/* ? half pill with the curve pointing to left */ - -window#waybar.top #pill-left, -window#waybar.top #pill-left button, -window#waybar.top #pill-left menu, -window#waybar.top #pill-left menuitem, -window#waybar.top #pill-left tooltip, -window#waybar.bottom #pill-left, -window#waybar.bottom #pill-left button, -window#waybar.bottom #pill-left menu, -window#waybar.bottom #pill-left menuitem, -window#waybar.bottom #pill-left tooltip { - border-radius: 10pt 0 0 10pt; -} - -window#waybar.left #pill-left, -window#waybar.left #pill-left button, -window#waybar.left #pill-left menu, -window#waybar.left #pill-left menuitem, -window#waybar.left #pill-left tooltip, -window#waybar.right #pill-left, -window#waybar.right #pill-left button, -window#waybar.right #pill-left menu, -window#waybar.right #pill-left menuitem, -window#waybar.right #pill-left tooltip { - border-radius: 10pt 0 0 10pt; -} - - -/* ? half pill with the curve pointing to outwards */ - - -window#waybar.top #pill-out, -window#waybar.top #pill-out button, -window#waybar.top #pill-out menu, -window#waybar.top #pill-out menuitem, -window#waybar.top #pill-out tooltip { - border-radius: 10pt 10pt 0 0; -} - -window#waybar.right #pill-out, -window#waybar.right #pill-out button, -window#waybar.right #pill-out menu, -window#waybar.right #pill-out menuitem, -window#waybar.right #pill-out tooltip { - border-radius: 0 10pt 10pt 0; -} - -window#waybar.bottom #pill-out, -window#waybar.bottom #pill-out button, -window#waybar.bottom #pill-out menu, -window#waybar.bottom #pill-out menuitem, -window#waybar.bottom #pill-out tooltip { - border-radius: 0 0 10pt 10pt; -} - -window#waybar.left #pill-out, -window#waybar.left #pill-out button, -window#waybar.left #pill-out menu, -window#waybar.left #pill-out menuitem, -window#waybar.left #pill-out tooltip { - border-radius: 10pt 0 0 10pt; -} - - -/* ============================ */ -/* ? half pill with the curve pointing to the right */ - -window#waybar.top #pill-right, -window#waybar.top #pill-right button, -window#waybar.bottom #pill-right menu, -window#waybar.bottom #pill-right menuitem, -window#waybar.bottom #pill-right tooltip, -window#waybar.bottom #pill-right, -window#waybar.bottom #pill-right button, -window#waybar.top #pill-right menu, -window#waybar.top #pill-right menuitem, -window#waybar.top #pill-right tooltip { - border-radius: 0 10pt 10pt 0; -} - -window#waybar.left #pill-right, -window#waybar.left #pill-right button, -window#waybar.right #pill-right menu, -window#waybar.right #pill-right menuitem, -window#waybar.right #pill-right tooltip, -window#waybar.right #pill-right, -window#waybar.right #pill-right button, -window#waybar.left #pill-right menu, -window#waybar.left #pill-right menuitem, -window#waybar.left #pill-right tooltip { - border-radius: 0 10pt 10pt 0; -} - - -/* ? half pill with the flat surface pointing upwards */ - -window#waybar.top #pill-up, -window#waybar.top #pill-up button, -window#waybar.top #pill-up menu, -window#waybar.top #pill-up menuitem, -window#waybar.top #pill-up tooltip, -window#waybar.bottom #pill-up, -window#waybar.bottom #pill-up button, -window#waybar.bottom #pill-up menu, -window#waybar.bottom #pill-up menuitem, -window#waybar.bottom #pill-up tooltip { - border-radius: 10pt 10pt 0 0; -} - -window#waybar.left #pill-up, -window#waybar.left #pill-up button, -window#waybar.left #pill-up menu, -window#waybar.left #pill-up menuitem, -window#waybar.left #pill-up tooltip, -window#waybar.right #pill-up, -window#waybar.right #pill-up button, -window#waybar.right #pill-up menu, -window#waybar.right #pill-up menuitem, -window#waybar.right #pill-up tooltip { - border-radius: 10pt 10pt 0 0; -} - -tooltip { - border-radius: 10pt; -} - -menu { - border-radius: 10pt; -} - -menu menuitem { - border-radius: 10pt; -} - -window.popup decoration { - border-radius: 10pt; -} \ No newline at end of file diff --git a/gui/.config/waybar/includes/global.css b/gui/.config/waybar/includes/global.css deleted file mode 100644 index be3757a..0000000 --- a/gui/.config/waybar/includes/global.css +++ /dev/null @@ -1,14 +0,0 @@ -/* - Dynamic Style Configuration * - This is handled by HyDE - - To generate a dynamic configuration - base on theme and user settings - -*/ - -* { - border-radius: 0em; - font-family: "JetBrainsMono Nerd Font","JetBrainsMono Nerd Font"; - font-size: 10px; -} diff --git a/gui/.config/waybar/includes/includes.json b/gui/.config/waybar/includes/includes.json deleted file mode 100644 index 80e3fcd..0000000 --- a/gui/.config/waybar/includes/includes.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "include": [ - "/home/anon/.config/waybar/modules/backlight.jsonc", - "/home/anon/.config/waybar/modules/bluetooth.jsonc", - "/home/anon/.config/waybar/modules/cava.jsonc", - "/home/anon/.config/waybar/modules/cliphist.jsonc", - "/home/anon/.config/waybar/modules/clock.jsonc", - "/home/anon/.config/waybar/modules/cpu.jsonc", - "/home/anon/.config/waybar/modules/cpuinfo.jsonc", - "/home/anon/.config/waybar/modules/footer.jsonc", - "/home/anon/.config/waybar/modules/github_hyprdots.jsonc", - "/home/anon/.config/waybar/modules/gpuinfo.jsonc", - "/home/anon/.config/waybar/modules/header.jsonc", - "/home/anon/.config/waybar/modules/idle_inhibitor.jsonc", - "/home/anon/.config/waybar/modules/keybindhint.jsonc", - "/home/anon/.config/waybar/modules/language.jsonc", - "/home/anon/.config/waybar/modules/memory.jsonc", - "/home/anon/.config/waybar/modules/mpris.jsonc", - "/home/anon/.config/waybar/modules/network.jsonc", - "/home/anon/.config/waybar/modules/notifications.jsonc", - "/home/anon/.config/waybar/modules/power.jsonc", - "/home/anon/.config/waybar/modules/privacy.jsonc", - "/home/anon/.config/waybar/modules/pulseaudio.jsonc", - "/home/anon/.config/waybar/modules/spotify.jsonc", - "/home/anon/.config/waybar/modules/taskbar.jsonc", - "/home/anon/.config/waybar/modules/theme.jsonc", - "/home/anon/.config/waybar/modules/tray.jsonc", - "/home/anon/.config/waybar/modules/updates.jsonc", - "/home/anon/.config/waybar/modules/wallchange.jsonc", - "/home/anon/.config/waybar/modules/window.jsonc", - "/home/anon/.config/waybar/modules/workspaces.jsonc", - "/home/anon/.config/waybar/modules/battery.jsonc", - "/home/anon/.local/share/waybar/modules/image#wallpaper.json", - "/home/anon/.local/share/waybar/modules/privacy.json", - "/home/anon/.local/share/waybar/modules/tray.json", - "/home/anon/.local/share/waybar/modules/wlr-taskbar#windows.json", - "/home/anon/.local/share/waybar/modules/wlr-taskbar.json", - "/home/anon/.local/share/waybar/modules/backlight.jsonc", - "/home/anon/.local/share/waybar/modules/battery.jsonc", - "/home/anon/.local/share/waybar/modules/bluetooth.jsonc", - "/home/anon/.local/share/waybar/modules/cava.jsonc", - "/home/anon/.local/share/waybar/modules/clock.jsonc", - "/home/anon/.local/share/waybar/modules/cpu.jsonc", - "/home/anon/.local/share/waybar/modules/custom-app-launcher.jsonc", - "/home/anon/.local/share/waybar/modules/custom-cava.jsonc", - "/home/anon/.local/share/waybar/modules/custom-clipboard.jsonc", - "/home/anon/.local/share/waybar/modules/custom-cliphist.jsonc", - "/home/anon/.local/share/waybar/modules/custom-cpuinfo.jsonc", - "/home/anon/.local/share/waybar/modules/custom-display.jsonc", - "/home/anon/.local/share/waybar/modules/custom-dunst.jsonc", - "/home/anon/.local/share/waybar/modules/custom-gamemode.jsonc", - "/home/anon/.local/share/waybar/modules/custom-github_hyde.jsonc", - "/home/anon/.local/share/waybar/modules/custom-gpuinfo#amd.jsonc", - "/home/anon/.local/share/waybar/modules/custom-gpuinfo#intel.jsonc", - "/home/anon/.local/share/waybar/modules/custom-gpuinfo#nvidia.jsonc", - "/home/anon/.local/share/waybar/modules/custom-gpuinfo.jsonc", - "/home/anon/.local/share/waybar/modules/custom-hyde-menu.jsonc", - "/home/anon/.local/share/waybar/modules/custom-hyprsunset.jsonc", - "/home/anon/.local/share/waybar/modules/custom-keybindhint.jsonc", - "/home/anon/.local/share/waybar/modules/custom-mediaplayer.jsonc", - "/home/anon/.local/share/waybar/modules/custom-power.jsonc", - "/home/anon/.local/share/waybar/modules/custom-powermenu.jsonc", - "/home/anon/.local/share/waybar/modules/custom-sensorsinfo.jsonc", - "/home/anon/.local/share/waybar/modules/custom-spotify.jsonc", - "/home/anon/.local/share/waybar/modules/custom-swaync.jsonc", - "/home/anon/.local/share/waybar/modules/custom-theme.jsonc", - "/home/anon/.local/share/waybar/modules/custom-updates.jsonc", - "/home/anon/.local/share/waybar/modules/custom-wallchange.jsonc", - "/home/anon/.local/share/waybar/modules/custom-wbar.jsonc", - "/home/anon/.local/share/waybar/modules/custom-weather.jsonc", - "/home/anon/.local/share/waybar/modules/custom-workflows.jsonc", - "/home/anon/.local/share/waybar/modules/gamemode.jsonc", - "/home/anon/.local/share/waybar/modules/group-eyecare.jsonc", - "/home/anon/.local/share/waybar/modules/group-hide-tray.jsonc", - "/home/anon/.local/share/waybar/modules/group-mediaplayer.jsonc", - "/home/anon/.local/share/waybar/modules/group-volumecontrol.jsonc", - "/home/anon/.local/share/waybar/modules/hyprland-language.jsonc", - "/home/anon/.local/share/waybar/modules/hyprland-window.jsonc", - "/home/anon/.local/share/waybar/modules/hyprland-workspaces#kanji.jsonc", - "/home/anon/.local/share/waybar/modules/hyprland-workspaces#roman.jsonc", - "/home/anon/.local/share/waybar/modules/hyprland-workspaces.jsonc", - "/home/anon/.local/share/waybar/modules/idle_inhibitor.jsonc", - "/home/anon/.local/share/waybar/modules/image#profile.jsonc", - "/home/anon/.local/share/waybar/modules/image#wallpaper.jsonc", - "/home/anon/.local/share/waybar/modules/memory.jsonc", - "/home/anon/.local/share/waybar/modules/mpd.jsonc", - "/home/anon/.local/share/waybar/modules/mpris.jsonc", - "/home/anon/.local/share/waybar/modules/network#bandwidth.jsonc", - "/home/anon/.local/share/waybar/modules/network.jsonc", - "/home/anon/.local/share/waybar/modules/power-profiles-daemon.jsonc", - "/home/anon/.local/share/waybar/modules/privacy.jsonc", - "/home/anon/.local/share/waybar/modules/pulseaudio#microphone.jsonc", - "/home/anon/.local/share/waybar/modules/pulseaudio.jsonc", - "/home/anon/.local/share/waybar/modules/temperature.jsonc", - "/home/anon/.local/share/waybar/modules/tray.jsonc", - "/home/anon/.local/share/waybar/modules/wlr-taskbar#windows.jsonc", - "/home/anon/.local/share/waybar/modules/wlr-taskbar.jsonc" - ], - "image#wallpaper": { - "size": 30, - "icon-size-multiplier": 3 - }, - "privacy": { - "icon-size": 10, - "icon-size-multiplier": 1 - }, - "tray": { - "icon-size": 16, - "icon-size-multiplier": 1.6 - }, - "wlr/taskbar#windows": { - "icon-size": 16, - "icon-size-multiplier": 1.6 - }, - "wlr/taskbar": { - "icon-size": 16, - "icon-size-multiplier": 1.6 - }, - "position": "top" -} \ No newline at end of file diff --git a/gui/.config/waybar/layouts/custom.css b/gui/.config/waybar/layouts/custom.css index 76c42f8..9b6e7fb 100644 --- a/gui/.config/waybar/layouts/custom.css +++ b/gui/.config/waybar/layouts/custom.css @@ -7,7 +7,10 @@ min-height: 10px; } -@import "../theme.css"; /* HyDE/wallbash-generated colors, lives one level up */ +@import "../theme.css"; +/* HyDE/wallbash-generated colors, one level up. FATAL if missing — + waybar exits before drawing anything. install.sh seeds it from ../theme-fallback.css on + hosts where wallbash never wrote one. */ window#waybar { /* transparent so the cavabar (bottom layer, same region) shows through; module pills keep @main-bg */ @@ -70,13 +73,13 @@ tooltip { padding-left: 3px; padding-right: 3px; margin-right: 0px; - color: @wb-color; + color: @main-fg; animation: tb_normal 20s ease-in-out 1; } #taskbar button.active { background: @wb-act-bg; - color: @wb-act-color; + color: @wb-act-fg; /*animation: tb_active 20s ease-in-out 1;*/ box-shadow: 0 0 1px 1px rgba(128, 128, 128, 0.4); transition: all 0.4s cubic-bezier(0.55, -0.68, 0.48, 1.682); @@ -84,7 +87,7 @@ tooltip { #taskbar button:hover { background: @wb-hvr-bg; - color: @wb-hvr-color; + color: @wb-hvr-fg; opacity: 0.8; animation: tb_hover 20s ease-in-out 1; transition: all 0.3s cubic-bezier(0.55, -0.68, 0.48, 1.682); @@ -132,7 +135,9 @@ tooltip { #custom-rl_end, #custom-rr_end { color: @main-fg; - background: @main-bg; + /* background: @main-bg; */ + + background: transparent; opacity: 1; margin: 4px 0px 4px 0px; padding-left: 4px; diff --git a/gui/.config/waybar/layouts/custom.jsonc b/gui/.config/waybar/layouts/custom.jsonc index e987932..27b6323 100644 --- a/gui/.config/waybar/layouts/custom.jsonc +++ b/gui/.config/waybar/layouts/custom.jsonc @@ -17,12 +17,15 @@ "modules-left": ["custom/l_end","custom/power","custom/r_end","hyprland/workspaces", "custom/sl_end","hyprland/window","custom/sr_end","custom/l_end","custom/wallchange","custom/cliphist","custom/r_end"], "modules-center": ["wlr/taskbar"], - "modules-right": ["custom/l_end","backlight","pulseaudio","pulseaudio#microphone","mpris","custom/r_end","custom/l_end","memory","cpu","custom/cpuinfo","battery","custom/r_end","custom/l_end","tray","custom/r_end","custom/l_end","idle_inhibitor","clock","custom/r_end"], + "modules-right": ["custom/l_end","backlight","pulseaudio","pulseaudio#microphone","mpris","custom/r_end","custom/l_end","memory","cpu","custom/cpuinfo","battery","custom/r_end","custom/l_end","network","tray","custom/r_end","custom/l_end","idle_inhibitor","clock","custom/r_end"], // sourced from modules based on config.ctl // "tray": { - "icon-size": 19, + // exactly 22: Tela only has fixed-size monochrome icons in {16,22,24}/panel — any + // other size makes GTK fall through to scalable/devices, whose icons are colored + // circles (the blue nm-applet blob). An exact panel match keeps the tray flat/white. + "icon-size": 22, "rotate": 0, "spacing": 5 }, @@ -36,7 +39,7 @@ "max-length": 1000, "rewrite": { "(.*) — Kitty": " $1", - "(.*)~": " $1", + "(.*)~": " $1", "(.*) — Mozilla Firefox": "󰈹 $1", "(.*)Mozilla Firefox": "󰈹 Firefox", "(.*) — LibreWolf": "󰈹 $1", @@ -60,7 +63,34 @@ "(.*)Gajim": "󰭹 Gajim", "(.*)KeePassXC": " KeePassXC", "(.*)Betterbird": "󰇮 Betterbird", - "(.*)Spotify": "󰓇 Spotify" + // --- apps in use on this rig (glyphs verified against JetBrainsMono NF v3) --- + "nvim (.*)": " $1", + "nvim": " Nvim", + "(.*)PyRadio - Playing: (.*)": "󰐹 $2", + "(.*)PyRadio(.*)": "󰐹 PyRadio", + "(.*) - mpv": "󰿎 $1", + "(.*)mpv": "󰿎 mpv", + "(.*) — Zen Browser": "󰖟 $1", + "(.*)Zen Browser(.*)": "󰖟 Zen", + "(.*) - Thorium": " $1", + "(.*)Thorium(.*)": " Thorium", + "(.*)Ghostty(.*)": "󰊠 Ghostty", + "(.*)zellij(.*)": "󰆍 zellij", + "(.*)btop(.*)": "󰨇 btop", + "(.*)lazydocker(.*)": "󰡨 lazydocker", + "(.*)Dolphin(.*)": "󰉋 Dolphin", + "(.*)qBittorrent(.*)": "󰇚 qBittorrent", + "(.*)GIMP(.*)": "󱇣 GIMP", + "(.*)LibreOffice(.*)": "󰈙 LibreOffice", + "(.*)KiCad(.*)": "󰘚 KiCad", + "(.*)QGroundControl(.*)": "󰐴 QGroundControl", + "(.*)Foxglove(.*)": "󰚩 Foxglove", + "(.*)Bruno(.*)": "󱂛 Bruno", + "(.*)RustDesk(.*)": "󰢹 RustDesk", + "(.*)scrcpy(.*)": "󰄜 scrcpy", + "(.*)Vial(.*)": "󰌌 Vial", + "(.*)Volume Control(.*)": "󰕾 Volume Control", + "(.*)Satty(.*)": "󰹑 Satty" } }, "hyprland/workspaces": { @@ -181,12 +211,14 @@ }, "custom/wallchange": { - "format": "{}", + "format": "󰸉{}", "rotate": 0, "exec": "echo ; echo 󰆊 switch wallpaper", - "on-click": "swwwallpaper.sh -n", - "on-click-right": "swwwallpaper.sh -p", - "on-click-middle": "sleep 0.1 && swwwallselect.sh", + // HyDE 26.x removed swwwallpaper.sh/swwwallselect.sh; hyde-shell wallpaper is the + // replacement (same calls as upstream's custom-wallchange module) + "on-click": "hyde-shell wallpaper -n", + "on-click-right": "hyde-shell wallpaper -p", + "on-click-middle": "sleep 0.1 && hyde-shell wallpaper --select", "interval" : 86400, // once every day "tooltip": true }, @@ -194,7 +226,7 @@ "pulseaudio": { "format": "{icon} {volume}", "rotate": 0, - "format-muted": "婢", + "format-muted": "󰝟", "on-click": "pavucontrol -t 3", "on-click-right": "volumecontrol.sh -s ''", "on-click-middle": "volumecontrol.sh -o m", @@ -216,7 +248,7 @@ "pulseaudio#microphone": { "format": "{format_source}", "rotate": 0, - "format-source": "", + "format-source": "", "format-source-muted": "", "on-click": "pavucontrol -t 4", "on-click-middle": "volumecontrol.sh -i m", @@ -232,7 +264,10 @@ "format-paused": "{status_icon} {dynamic}", "player-icons": { "default": "▶", - "mpv": "🎵" + "mpv": "󰝚", + "spotify": "󰓇", + "firefox": "󰈹", + "vlc": "󰕼" }, "status-icons": { "paused": "" @@ -242,6 +277,16 @@ }, "battery": { + // Pinned to the laptop battery on purpose. Unpinned, waybar 0.15 scans every + // /sys/class/power_supply node, and a flapping one (the DualShock's + // ps-controller-battery-* appearing at login while opensdd/scc-daemon wake it) + // makes refreshBatteries() throw off its worker thread → SIGABRT, killing the + // whole bar (battery.cpp:142 uncaught; all 5 of 2026-08-17's coredumps). The pin + // (battery.cpp:117) gates every read/watch of non-matching nodes. On wm no BAT0 + // exists so the module stays hidden — same as before. On lw verify the name with + // `ls /sys/class/power_supply` and correct here if it's BAT1 (its old default-config + // log already ruled out BAT2); a wrong name only hides the module, never crashes. + "bat": "BAT0", "states": { "good": 95, "warning": 30, @@ -255,6 +300,23 @@ "format-icons": ["󰂎", "󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"] }, + // added 2026-08-14 — the rig never had a network indicator ("ethernet icon not working"). + // Same def as HyDE's ~/.local/share/waybar/modules/network.jsonc, inline like everything + // else here; click toggles a bandwidth readout, tooltip carries the connection details. + "network": { + "tooltip": true, + "format-wifi": " ", + "rotate": 0, + "format-ethernet": "󰈀 ", + "tooltip-format": "Network: {essid}\nSignal strength: {signaldBm}dBm ({signalStrength}%)\nFrequency: {frequency}MHz\nInterface: {ifname}\nIP: {ipaddr}/{cidr}\nGateway: {gwaddr}\nNetmask: {netmask}", + "format-linked": "󰈀 {ifname} (No IP)", + "format-disconnected": "󰖪 ", + "tooltip-format-disconnected": "Disconnected", + "format-alt": " {bandwidthDownBytes}  {bandwidthUpBytes}", + "on-click-right": "nm-connection-editor", // replaces the hidden nm-applet tray menu + "interval": 2 + }, + "backlight": { "device": "intel_backlight", "rotate": 0, diff --git a/gui/.config/waybar/modules/backlight.jsonc b/gui/.config/waybar/modules/backlight.jsonc deleted file mode 100644 index b289e68..0000000 --- a/gui/.config/waybar/modules/backlight.jsonc +++ /dev/null @@ -1,10 +0,0 @@ - "backlight": { - "device": "intel_backlight", - "rotate": ${r_deg}, - "format": "{icon} {percent}%", - "format-icons": ["", "", "", "", "", "", "", "", ""], - "on-scroll-up": "brightnessctl set 1%+", - "on-scroll-down": "brightnessctl set 1%-", - "min-length": 6 - }, - diff --git a/gui/.config/waybar/modules/battery.jsonc b/gui/.config/waybar/modules/battery.jsonc deleted file mode 100644 index 51d6482..0000000 --- a/gui/.config/waybar/modules/battery.jsonc +++ /dev/null @@ -1,14 +0,0 @@ - "battery": { - "states": { - "good": 95, - "warning": 30, - "critical": 20 - }, - "format": "{icon} {capacity}%", - "rotate": ${r_deg}, - "format-charging": " {capacity}%", - "format-plugged": " {capacity}%", - "format-alt": "{time} {icon}", - "format-icons": ["󰂎", "󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"] - }, - diff --git a/gui/.config/waybar/modules/bluetooth.jsonc b/gui/.config/waybar/modules/bluetooth.jsonc deleted file mode 100644 index 919bbd8..0000000 --- a/gui/.config/waybar/modules/bluetooth.jsonc +++ /dev/null @@ -1,15 +0,0 @@ - "bluetooth": { - "format": "", - "rotate": ${r_deg}, - "format-disabled": "", - "format-connected": " {num_connections}", - "format-connected-battery": "{icon} {num_connections}", - // "format-connected-battery": "{icon} {device_alias}-{device_battery_percentage}%", - "format-icons": ["󰥇", "󰤾", "󰤿", "󰥀", "󰥁", "󰥂", "󰥃", "󰥄", "󰥅", "󰥆", "󰥈"], - // "format-device-preference": [ "device1", "device2" ], // preference list deciding the displayed device If this config option is not defined or none of the devices in the list are connected, it will fall back to showing the last connected device. - "tooltip-format": "{controller_alias}\n{num_connections} connected", - "tooltip-format-connected": "{controller_alias}\n{num_connections} connected\n\n{device_enumerate}", - "tooltip-format-enumerate-connected": "{device_alias}", - "tooltip-format-enumerate-connected-battery": "{device_alias}\t{icon} {device_battery_percentage}%" - }, - diff --git a/gui/.config/waybar/modules/cava.jsonc b/gui/.config/waybar/modules/cava.jsonc deleted file mode 100644 index cd08adc..0000000 --- a/gui/.config/waybar/modules/cava.jsonc +++ /dev/null @@ -1,24 +0,0 @@ -{ - "cava": { - // "cava_config": "$XDG_CONFIG_HOME/cava/cava.conf", - "framerate": 30, - "autosens": 1, - "sensitivity": 100, - "bars": 14, - "lower_cutoff_freq": 50, - "higher_cutoff_freq": 10000, - "method": "pulse", - "source": "auto", - "stereo": true, - "reverse": false, - "bar_delimiter": 0, - "monstercat": false, - "waves": false, - "noise_reduction": 0.77, - "input_delay": 2, - "format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"], - "actions": { - "on-click-right": "mode" - } - } -} diff --git a/gui/.config/waybar/modules/cliphist.jsonc b/gui/.config/waybar/modules/cliphist.jsonc deleted file mode 100644 index 02024fa..0000000 --- a/gui/.config/waybar/modules/cliphist.jsonc +++ /dev/null @@ -1,11 +0,0 @@ - "custom/cliphist": { - "format": "{}", - "rotate": ${r_deg}, - "exec": "echo ; echo 󰅇 clipboard history", - "on-click": "sleep 0.1 && cliphist.sh c", - "on-click-right": "sleep 0.1 && cliphist.sh d", - "on-click-middle": "sleep 0.1 && cliphist.sh w", - "interval" : 86400, // once every day - "tooltip": true - }, - diff --git a/gui/.config/waybar/modules/clock.jsonc b/gui/.config/waybar/modules/clock.jsonc deleted file mode 100644 index 151a246..0000000 --- a/gui/.config/waybar/modules/clock.jsonc +++ /dev/null @@ -1,25 +0,0 @@ - "clock": { - "format": "{:%H:%M}", - "rotate": ${r_deg}, - "format-alt": "{:%R 󰃭 %d·%m·%y}", - "tooltip-format": "{calendar}", - "calendar": { - "mode": "month", - "mode-mon-col": 3, - "on-scroll": 1, - "on-click-right": "mode", - "format": { - "months": "{}", - "weekdays": "{}", - "today": "{}" - } - }, - "actions": { - "on-click-right": "mode", - "on-click-forward": "tz_up", - "on-click-backward": "tz_down", - "on-scroll-up": "shift_up", - "on-scroll-down": "shift_down" - } - }, - diff --git a/gui/.config/waybar/modules/cpu.jsonc b/gui/.config/waybar/modules/cpu.jsonc deleted file mode 100644 index cc89cb0..0000000 --- a/gui/.config/waybar/modules/cpu.jsonc +++ /dev/null @@ -1,8 +0,0 @@ - "cpu": { - "interval": 10, - "format": "󰍛 {usage}%", - "rotate": ${r_deg}, - "format-alt": "{icon0}{icon1}{icon2}{icon3}", - "format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"] - }, - diff --git a/gui/.config/waybar/modules/cpuinfo.jsonc b/gui/.config/waybar/modules/cpuinfo.jsonc deleted file mode 100644 index fb6bb76..0000000 --- a/gui/.config/waybar/modules/cpuinfo.jsonc +++ /dev/null @@ -1,10 +0,0 @@ - "custom/cpuinfo": { - "exec": " cpuinfo.sh", - "return-type": "json", - "format": "{}", - "rotate": ${r_deg}, - "interval": 5, // once every 5 seconds - "tooltip": true, - "max-length": 1000 - }, - diff --git a/gui/.config/waybar/modules/footer.jsonc b/gui/.config/waybar/modules/footer.jsonc deleted file mode 100644 index 1387f74..0000000 --- a/gui/.config/waybar/modules/footer.jsonc +++ /dev/null @@ -1,47 +0,0 @@ - -// modules for padding // - - "custom/l_end": { - "format": " ", - "interval" : "once", - "tooltip": false - }, - - "custom/r_end": { - "format": " ", - "interval" : "once", - "tooltip": false - }, - - "custom/sl_end": { - "format": " ", - "interval" : "once", - "tooltip": false - }, - - "custom/sr_end": { - "format": " ", - "interval" : "once", - "tooltip": false - }, - - "custom/rl_end": { - "format": " ", - "interval" : "once", - "tooltip": false - }, - - "custom/rr_end": { - "format": " ", - "interval" : "once", - "tooltip": false - }, - - "custom/padd": { - "format": " ", - "interval" : "once", - "tooltip": false - } - -} - diff --git a/gui/.config/waybar/modules/github_hyprdots.jsonc b/gui/.config/waybar/modules/github_hyprdots.jsonc deleted file mode 100644 index 465af06..0000000 --- a/gui/.config/waybar/modules/github_hyprdots.jsonc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "custom/github_hyprdots": { - "format": " ", - "rotate": 0, - "tooltip-format": "  Hyprdots repository", - "on-click": "xdg-open https://github.com/prasanthrangan/hyprdots" - } -} diff --git a/gui/.config/waybar/modules/gpuinfo.jsonc b/gui/.config/waybar/modules/gpuinfo.jsonc deleted file mode 100644 index f9f5d61..0000000 --- a/gui/.config/waybar/modules/gpuinfo.jsonc +++ /dev/null @@ -1,41 +0,0 @@ - "custom/gpuinfo": { - "exec": " gpuinfo.sh", - "return-type": "json", - "format": "{}", - "rotate": ${r_deg}, - "interval": 5, // once every 5 seconds - "tooltip": true, - "max-length": 1000, - "on-click": "gpuinfo.sh --toggle", - }, - - "custom/gpuinfo#nvidia": { - "exec": " gpuinfo.sh --use nvidia ", - "return-type": "json", - "format": "{}", - "rotate": ${r_deg}, - "interval": 5, // once every 5 seconds - "tooltip": true, - "max-length": 1000, - }, - - "custom/gpuinfo#amd": { - "exec": " gpuinfo.sh --use amd ", - "return-type": "json", - "format": "{}", - "rotate": ${r_deg}, - "interval": 5, // once every 5 seconds - "tooltip": true, - "max-length": 1000, - }, - - "custom/gpuinfo#intel": { - "exec": " gpuinfo.sh --use intel ", - "return-type": "json", - "format": "{}", - "rotate": ${r_deg}, - "interval": 5, // once every 5 seconds - "tooltip": true, - "max-length": 1000, - }, - diff --git a/gui/.config/waybar/modules/header.jsonc b/gui/.config/waybar/modules/header.jsonc deleted file mode 100644 index 688d476..0000000 --- a/gui/.config/waybar/modules/header.jsonc +++ /dev/null @@ -1,13 +0,0 @@ -// --// waybar config generated by wbarconfgen.sh //-- // - -{ -// sourced from header module // - - "layer": "top", - "position": "${w_position}", - "mod": "dock", - "${hv_pos}": ${w_height}, - "exclusive": true, - "passthrough": false, - "gtk-layer-shell": true, - "reload_style_on_change": true, diff --git a/gui/.config/waybar/modules/idle_inhibitor.jsonc b/gui/.config/waybar/modules/idle_inhibitor.jsonc deleted file mode 100644 index 5963c20..0000000 --- a/gui/.config/waybar/modules/idle_inhibitor.jsonc +++ /dev/null @@ -1,9 +0,0 @@ - "idle_inhibitor": { - "format": "{icon}", - "rotate": ${r_deg}, - "format-icons": { - "activated": "󰅶", - "deactivated": "󰛊" - } - }, - diff --git a/gui/.config/waybar/modules/keybindhint.jsonc b/gui/.config/waybar/modules/keybindhint.jsonc deleted file mode 100644 index e9edd21..0000000 --- a/gui/.config/waybar/modules/keybindhint.jsonc +++ /dev/null @@ -1,6 +0,0 @@ - "custom/keybindhint": { - "format": " ", - "rotate": ${r_deg}, - "on-click": "keybinds_hint.sh" - }, - diff --git a/gui/.config/waybar/modules/language.jsonc b/gui/.config/waybar/modules/language.jsonc deleted file mode 100644 index 6045d28..0000000 --- a/gui/.config/waybar/modules/language.jsonc +++ /dev/null @@ -1,6 +0,0 @@ - "hyprland/language": { - "format": "{short} {variant}", - "rotate": ${r_deg}, - "on-click": "keyboardswitch.sh", - }, - diff --git a/gui/.config/waybar/modules/memory.jsonc b/gui/.config/waybar/modules/memory.jsonc deleted file mode 100644 index 1e2c8d8..0000000 --- a/gui/.config/waybar/modules/memory.jsonc +++ /dev/null @@ -1,18 +0,0 @@ - "memory": { - "states": { - "c": 90, // critical - "h": 60, // high - "m": 30, // medium - }, - "interval": 30, - "format": "󰾆 {used}GB", - "rotate": ${r_deg}, - "format-m": "󰾅 {used}GB", - "format-h": "󰓅 {used}GB", - "format-c": " {used}GB", - "format-alt": "󰾆 {percentage}%", - "max-length": 10, - "tooltip": true, - "tooltip-format": "󰾆 {percentage}%\n {used:0.1f}GB/{total:0.1f}GB" - }, - diff --git a/gui/.config/waybar/modules/mpris.jsonc b/gui/.config/waybar/modules/mpris.jsonc deleted file mode 100644 index 04ff2cd..0000000 --- a/gui/.config/waybar/modules/mpris.jsonc +++ /dev/null @@ -1,16 +0,0 @@ - "mpris": { - "format": "{player_icon} {dynamic}", - "rotate": ${r_deg}, - "format-paused": "{status_icon} {dynamic}", - "player-icons": { - "default": "▶", - "mpv": "🎵" - }, - "status-icons": { - "paused": "" - }, - // "ignored-players": ["firefox"] - "max-length": 1000, - "interval": 1 - }, - diff --git a/gui/.config/waybar/modules/network.jsonc b/gui/.config/waybar/modules/network.jsonc deleted file mode 100644 index 7dae826..0000000 --- a/gui/.config/waybar/modules/network.jsonc +++ /dev/null @@ -1,13 +0,0 @@ - "network": { - "tooltip": true, - "format-wifi": " ", - "rotate": ${r_deg}, - "format-ethernet": "󰈀 ", - "tooltip-format": "Network: {essid}\nSignal strength: {signaldBm}dBm ({signalStrength}%)\nFrequency: {frequency}MHz\nInterface: {ifname}\nIP: {ipaddr}/{cidr}\nGateway: {gwaddr}\nNetmask: {netmask}", - "format-linked": "󰈀 {ifname} (No IP)", - "format-disconnected": "󰖪 ", - "tooltip-format-disconnected": "Disconnected", - "format-alt": " {bandwidthDownBytes}  {bandwidthUpBytes}", - "interval": 2 - } - diff --git a/gui/.config/waybar/modules/notifications.jsonc b/gui/.config/waybar/modules/notifications.jsonc deleted file mode 100644 index 125e87a..0000000 --- a/gui/.config/waybar/modules/notifications.jsonc +++ /dev/null @@ -1,29 +0,0 @@ - "custom/notifications": { - "format": "{icon} {}", - "rotate": ${r_deg}, - "format-icons": { - "email-notification": "", - "chat-notification": "󱋊", - "warning-notification": "󱨪", - "error-notification": "󱨪", - "network-notification": "󱂇", - "battery-notification": "󰁺", - "update-notification": "󰚰", - "music-notification": "󰝚", - "volume-notification": "󰕿", - "notification": "", - "dnd": "", - "none": "" - }, - "return-type": "json", - "exec-if": "which dunstctl", - "exec": "notifications.py", - "on-scroll-down": "sleep 0.1 && dunstctl history-pop", - "on-click": "dunstctl set-paused toggle", - "on-click-middle": "dunstctl history-clear", - "on-click-right": "dunstctl close-all", - "interval": 1, - "tooltip": true, - "escape": true - }, - diff --git a/gui/.config/waybar/modules/power.jsonc b/gui/.config/waybar/modules/power.jsonc deleted file mode 100644 index a41eaf6..0000000 --- a/gui/.config/waybar/modules/power.jsonc +++ /dev/null @@ -1,10 +0,0 @@ - "custom/power": { - "format": "{}", - "rotate": ${r_deg}, - "exec": "echo ; echo  logout", - "on-click": "logoutlaunch.sh 2", - "on-click-right": "logoutlaunch.sh 1", - "interval" : 86400, // once every day - "tooltip": true - }, - diff --git a/gui/.config/waybar/modules/privacy.jsonc b/gui/.config/waybar/modules/privacy.jsonc deleted file mode 100644 index 0510e9a..0000000 --- a/gui/.config/waybar/modules/privacy.jsonc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "privacy": { - "icon-size": 18, - "icon-spacing": 5, - "transition-duration": 250, - "modules": [ - { - "type": "screenshare", - "tooltip": true, - "tooltip-icon-size": 24 - }, - { - "type": "audio-in", - "tooltip": true, - "tooltip-icon-size": 24 - } - ] - } -} diff --git a/gui/.config/waybar/modules/pulseaudio.jsonc b/gui/.config/waybar/modules/pulseaudio.jsonc deleted file mode 100644 index c5770a7..0000000 --- a/gui/.config/waybar/modules/pulseaudio.jsonc +++ /dev/null @@ -1,35 +0,0 @@ -"pulseaudio": { - "format": "{icon} {volume}", - "rotate": ${r_deg}, - "format-muted": "婢", - "on-click": "pavucontrol -t 3", - "on-click-right": "volumecontrol.sh -s ''", - "on-click-middle": "volumecontrol.sh -o m", - "on-scroll-up": "volumecontrol.sh -o i", - "on-scroll-down": "volumecontrol.sh -o d", - "tooltip-format": "{icon} {desc} // {volume}%", - "scroll-step": 5, - "format-icons": { - "headphone": "", - "hands-free": "", - "headset": "", - "phone": "", - "portable": "", - "car": "", - "default": ["", "", ""] - } -}, - -"pulseaudio#microphone": { - "format": "{format_source}", - "rotate": ${r_deg}, - "format-source": "", - "format-source-muted": "", - "on-click": "pavucontrol -t 4", - "on-click-middle": "volumecontrol.sh -i m", - "on-scroll-up": "volumecontrol.sh -i i", - "on-scroll-down": "volumecontrol.sh -i d", - "tooltip-format": "{format_source} {source_desc} // {source_volume}%", - "scroll-step": 5 -}, - diff --git a/gui/.config/waybar/modules/spotify.jsonc b/gui/.config/waybar/modules/spotify.jsonc deleted file mode 100644 index 13bd6bc..0000000 --- a/gui/.config/waybar/modules/spotify.jsonc +++ /dev/null @@ -1,15 +0,0 @@ - "custom/spotify": { - "exec": "mediaplayer.py --player spotify", - "format": " {}", - "rotate": ${r_deg}, - "return-type": "json", - "on-click": "playerctl play-pause --player spotify", - "on-click-right": "playerctl next --player spotify", - "on-click-middle": "playerctl previous --player spotify", - "on-scroll-up": "volumecontrol.sh -p spotify i", - "on-scroll-down": "volumecontrol.sh -p spotify d", - "max-length": 25, - "escape": true, - "tooltip": true - }, - diff --git a/gui/.config/waybar/modules/style.css b/gui/.config/waybar/modules/style.css deleted file mode 100644 index 93da5c3..0000000 --- a/gui/.config/waybar/modules/style.css +++ /dev/null @@ -1,151 +0,0 @@ -* { - border: none; - border-radius: 0px; - font-family: "JetBrainsMono Nerd Font"; - font-weight: bold; - font-size: ${s_fontpx}px; - min-height: 10px; -} - -@import "theme.css"; - -window#waybar { - background: @bar-bg; -} - -tooltip { - background: @main-bg; - color: @main-fg; - border-radius: ${t_radius}px; - border-width: 0px; -} - -#workspaces button { - box-shadow: none; - text-shadow: none; - padding: 0px; - border-radius: ${w_radius}px; - margin-${x1}: ${w_margin}px; - margin-${x2}: ${w_margin}px; - margin-${x3}: 0px; - padding-${x3}: ${w_paddin}px; - padding-${x4}: ${w_paddin}px; - margin-${x4}: 0px; - color: @main-fg; - animation: ws_normal 20s ease-in-out 1; -} - -#workspaces button.active { - background: @wb-act-bg; - color: @wb-act-fg; - margin-${x3}: ${w_margin}px; - padding-${x3}: ${w_padact}px; - padding-${x4}: ${w_padact}px; - margin-${x4}: ${w_margin}px; - animation: ws_active 20s ease-in-out 1; - transition: all 0.4s cubic-bezier(.55,-0.68,.48,1.682); -} - -#workspaces button:hover { - background: @wb-hvr-bg; - color: @wb-hvr-fg; - animation: ws_hover 20s ease-in-out 1; - transition: all 0.3s cubic-bezier(.55,-0.68,.48,1.682); -} - -#taskbar button { - box-shadow: none; - text-shadow: none; - padding: 0px; - border-radius: ${w_radius}px; - margin-${x1}: ${w_margin}px; - margin-${x2}: ${w_margin}px; - margin-${x3}: 0px; - padding-${x3}: ${w_paddin}px; - padding-${x4}: ${w_paddin}px; - margin-${x4}: 0px; - color: @wb-color; - animation: tb_normal 20s ease-in-out 1; -} - -#taskbar button.active { - background: @wb-act-bg; - color: @wb-act-color; - margin-${x3}: ${w_margin}px; - padding-${x3}: ${w_padact}px; - padding-${x4}: ${w_padact}px; - margin-${x4}: ${w_margin}px; - animation: tb_active 20s ease-in-out 1; - transition: all 0.4s cubic-bezier(.55,-0.68,.48,1.682); -} - -#taskbar button:hover { - background: @wb-hvr-bg; - color: @wb-hvr-color; - animation: tb_hover 20s ease-in-out 1; - transition: all 0.3s cubic-bezier(.55,-0.68,.48,1.682); -} - -#tray menu * { - min-height: 16px -} - -#tray menu separator { - min-height: 10px -} - -${modules_ls} -#custom-l_end, -#custom-r_end, -#custom-sl_end, -#custom-sr_end, -#custom-rl_end, -#custom-rr_end { - color: @main-fg; - background: @main-bg; - opacity: 1; - margin: ${x1g_margin}px ${x2g_margin}px ${x3g_margin}px ${x4g_margin}px; - padding-${x3}: ${g_paddin}px; - padding-${x4}: ${g_paddin}px; -} - -#workspaces, -#taskbar { - padding: 0px; -} - -#custom-r_end { - border-radius: ${x1rb_radius}px ${x2rb_radius}px ${x3rb_radius}px ${x4rb_radius}px; - margin-${x4}: ${e_margin}px; - padding-${x4}: ${e_paddin}px; -} - -#custom-l_end { - border-radius: ${x1lb_radius}px ${x2lb_radius}px ${x3lb_radius}px ${x4lb_radius}px; - margin-${x3}: ${e_margin}px; - padding-${x3}: ${e_paddin}px; -} - -#custom-sr_end { - border-radius: 0px; - margin-${x4}: ${e_margin}px; - padding-${x4}: ${e_paddin}px; -} - -#custom-sl_end { - border-radius: 0px; - margin-${x3}: ${e_margin}px; - padding-${x3}: ${e_paddin}px; -} - -#custom-rr_end { - border-radius: ${x1rc_radius}px ${x2rc_radius}px ${x3rc_radius}px ${x4rc_radius}px; - margin-${x4}: ${e_margin}px; - padding-${x4}: ${e_paddin}px; -} - -#custom-rl_end { - border-radius: ${x1lc_radius}px ${x2lc_radius}px ${x3lc_radius}px ${x4lc_radius}px; - margin-${x3}: ${e_margin}px; - padding-${x3}: ${e_paddin}px; -} diff --git a/gui/.config/waybar/modules/taskbar.jsonc b/gui/.config/waybar/modules/taskbar.jsonc deleted file mode 100644 index e40dcce..0000000 --- a/gui/.config/waybar/modules/taskbar.jsonc +++ /dev/null @@ -1,17 +0,0 @@ - "wlr/taskbar": { - "format": "{icon}", - "rotate": ${r_deg}, - "icon-size": ${i_task}, - "icon-theme": "${i_theme}", - "spacing": 0, - "tooltip-format": "{title}", - "on-click": "activate", - "on-click-middle": "close", - "ignore-list": [ - "Alacritty" - ], - "app_ids-mapping": { - "firefoxdeveloperedition": "firefox-developer-edition" - } - }, - diff --git a/gui/.config/waybar/modules/theme.jsonc b/gui/.config/waybar/modules/theme.jsonc deleted file mode 100644 index 6482c6e..0000000 --- a/gui/.config/waybar/modules/theme.jsonc +++ /dev/null @@ -1,11 +0,0 @@ - "custom/theme": { - "format": "{}", - "rotate": ${r_deg}, - "exec": "echo ; echo 󰟡 switch theme", - "on-click": "themeswitch.sh -n", - "on-click-right": "themeswitch.sh -p", - "on-click-middle": "sleep 0.1 && themeselect.sh", - "interval" : 86400, // once every day - "tooltip": true - }, - diff --git a/gui/.config/waybar/modules/tray.jsonc b/gui/.config/waybar/modules/tray.jsonc deleted file mode 100644 index d7c0bce..0000000 --- a/gui/.config/waybar/modules/tray.jsonc +++ /dev/null @@ -1,6 +0,0 @@ - "tray": { - "icon-size": ${i_size}, - "rotate": ${r_deg}, - "spacing": 5 - }, - diff --git a/gui/.config/waybar/modules/updates.jsonc b/gui/.config/waybar/modules/updates.jsonc deleted file mode 100644 index 1fc621e..0000000 --- a/gui/.config/waybar/modules/updates.jsonc +++ /dev/null @@ -1,10 +0,0 @@ - "custom/updates": { - "exec": "systemupdate.sh", - "return-type": "json", - "format": "{}", - "rotate": ${r_deg}, - "on-click": "hyprctl dispatch exec 'systemupdate.sh up'", - "interval": 86400, // once every day - "tooltip": true, - "signal": 20 - }, diff --git a/gui/.config/waybar/modules/wallchange.jsonc b/gui/.config/waybar/modules/wallchange.jsonc deleted file mode 100644 index 0c839ca..0000000 --- a/gui/.config/waybar/modules/wallchange.jsonc +++ /dev/null @@ -1,11 +0,0 @@ - "custom/wallchange": { - "format": "{}", - "rotate": ${r_deg}, - "exec": "echo ; echo 󰆊 switch wallpaper", - "on-click": "swwwallpaper.sh -n", - "on-click-right": "swwwallpaper.sh -p", - "on-click-middle": "sleep 0.1 && swwwallselect.sh", - "interval" : 86400, // once every day - "tooltip": true - }, - diff --git a/gui/.config/waybar/modules/window.jsonc b/gui/.config/waybar/modules/window.jsonc deleted file mode 100644 index b48d0e7..0000000 --- a/gui/.config/waybar/modules/window.jsonc +++ /dev/null @@ -1,35 +0,0 @@ -{ - "hyprland/window": { - "format": " {}", - "rotate": 0, - "separate-outputs": true, - "max-length": 1000, - "rewrite": { - "(.*) — Kitty": " $1", - "(.*)~": " $1", - "(.*) — Mozilla Firefox": "󰈹 $1", - "(.*)Mozilla Firefox": "󰈹 Firefox", - "(.*) — LibreWolf": "󰈹 $1", - "(.*)LibreWolf": "󰈹 LibreWolf", - "(.*) — Chromium": " $1", - "(.*)Chromium": " Chromium", - "(.*) - VSCodium": " $1", - "(.*)VSCodium": " VSCodium", - "(.*)Code - OSS": "󰨞 $1", - "(.*) - Visual Studio Code": "󰨞 $1", - "(.*)Visual Studio Code": "󰨞 Code", - "(.*) — Dolphin": "󰉋 $1", - "(.*) - Obsidian(.*)": "󰠮 $1", - "(.*)Ferdium": "󰭻 Ferdium", - "(.*)FreeTube": " FreeTube", - "(.*)Freetube": " FreeTube", - "(.*) - VLC media player": "󰕼 $1", - "(.*)Steam": "󰓓 Steam", - "(.*)Neovide": " Nvim", - "(.*)Gajim": "󰭹 Gajim", - "(.*)KeePassXC": " KeePassXC", - "(.*)Betterbird": " Betterbird", - "(.*)Spotify": "󰓇 Spotify" - } - } -} diff --git a/gui/.config/waybar/modules/workspaces.jsonc b/gui/.config/waybar/modules/workspaces.jsonc deleted file mode 100644 index c1e580a..0000000 --- a/gui/.config/waybar/modules/workspaces.jsonc +++ /dev/null @@ -1,13 +0,0 @@ - "hyprland/workspaces": { - "disable-scroll": true, - "rotate": ${r_deg}, - "all-outputs": true, - "active-only": false, - "on-click": "activate", - "disable-scroll": false, - "on-scroll-up": "hyprctl dispatch workspace -1", - "on-scroll-down": "hyprctl dispatch workspace +1", - "persistent-workspaces": { - } - }, - diff --git a/gui/.config/waybar/theme-fallback.css b/gui/.config/waybar/theme-fallback.css new file mode 100644 index 0000000..d13dced --- /dev/null +++ b/gui/.config/waybar/theme-fallback.css @@ -0,0 +1,18 @@ +/* Fallback wallbash palette for the custom waybar rig (Catppuccin Mocha, copied from wm's + * live ~/.config/waybar/theme.css 2026-08-17). layouts/custom.css does `@import + * "../theme.css"` and a missing import target kills waybar outright — on themed hosts + * HyDE/wallbash generates and keeps overwriting that file, but a host where wallbash never + * ran (or where the pre-2026-08-14 stow link dangles) has nothing there. install.sh seeds + * ~/.config/waybar/theme.css from this copy only when the file is absent; it never + * overwrites a generator-owned one. Deliberately NOT stowed as theme.css itself: wallbash + * would then write through the symlink into the repo on every wallpaper change. */ +@define-color bar-bg rgba(0, 0, 0, 0.1); + +@define-color main-bg #11111b; +@define-color main-fg #cdd6f4; + +@define-color wb-act-bg #a6adc8; +@define-color wb-act-fg #313244; + +@define-color wb-hvr-bg #f5c2e7; +@define-color wb-hvr-fg #313244; diff --git a/gui/.config/waybar/theme.css b/gui/.config/waybar/theme.css deleted file mode 100644 index 9aeacd7..0000000 --- a/gui/.config/waybar/theme.css +++ /dev/null @@ -1,10 +0,0 @@ -@define-color bar-bg rgba(0, 0, 0, 0.1); - -@define-color main-bg rgba(0, 0, 0, 0.1); -@define-color main-fg #cdd6f4; - -@define-color wb-act-bg #a6adc8; -@define-color wb-act-fg #313244; - -@define-color wb-hvr-bg #f5c2e7; -@define-color wb-hvr-fg #313244; diff --git a/gui/.local/lib/hyde/windowpin.sh b/gui/.local/lib/hyde/windowpin.sh new file mode 100755 index 0000000..fa4caed --- /dev/null +++ b/gui/.local/lib/hyde/windowpin.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +# enable float +WinFloat=$(hyprctl -j clients | jq '.[] | select(.focusHistoryID == 0) | .floating') +WinPinned=$(hyprctl -j clients | jq '.[] | select(.focusHistoryID == 0) | .pinned') + +if [ "${WinFloat}" == "false" ] && [ "${WinPinned}" == "false" ] ; then + hyprctl dispatch togglefloating active +fi + +# toggle pin +hyprctl dispatch pin active + +# disable float +WinFloat=$(hyprctl -j clients | jq '.[] | select(.focusHistoryID == 0) | .floating') +WinPinned=$(hyprctl -j clients | jq '.[] | select(.focusHistoryID == 0) | .pinned') + +if [ "${WinFloat}" == "true" ] && [ "${WinPinned}" == "false" ] ; then + hyprctl dispatch togglefloating active +fi + diff --git a/gui/.local/share/hyde/hyde.conf b/gui/.local/share/hyde/hyde.conf new file mode 100644 index 0000000..31245e3 --- /dev/null +++ b/gui/.local/share/hyde/hyde.conf @@ -0,0 +1,133 @@ +#!   ░▒▒▒░░░▓▓          ___________ +#! ░░▒▒▒░░░░░▓▓        //___________/ +#! ░░▒▒▒░░░░░▓▓     _   _ _    _ _____ +#! ░░▒▒░░░░░▓▓▓▓▓▓ | | | | |  | |  __/ +#! ░▒▒░░░░▓▓   ▓▓ | |_| | |_/ /| |___ +#!  ░▒▒░░▓▓   ▓▓   \__  |____/ |____/ █░█ ▄▀█ █▀█ █ ▄▀█ █▄▄ █░░ █▀▀ █▀ +#!    ░▒▓▓   ▓▓  //____/ ▀▄▀ █▀█ █▀▄ █ █▀█ █▄█ █▄▄ ██▄ ▄█ + +# Override configuration for user environment variables and startup commands +# Static variables here take priority over default and dynamic configurations + +#  NOTE  +# Leaving the variable empty will unset the variable +# Commenting out the variable will use the default value +# For updated configuration options, see https://github.com/HyDE-Project/HyDE/blob/master/Configs/.config/hypr/hyde.conf +# For simplicity, ./hyde.conf ONLY accepts $ for variables and # for comments, will sanitize the file to remove any other characters +# $start.VAR , $env.VAR are ONLY HyDE specific conventions for consistency. + +# Keyboard modifier +$mainMod = SUPER # windows key + +# ▄▀█ █▀█ █▀█ █▀ +# █▀█ █▀▀ █▀▀ ▄█ + +$QUICKAPPS = # used for quick app launcher +$BROWSER = firefox # default browser, if commented out , will use the default browser +$EDITOR = code # default editor, if commented out , will use the default editor +$EXPLORER= dolphin # default file manager, if commented out , will use the default file manager +$TERMINAL = kitty # default terminal, if commented out , will use the org.gnome.desktop.default-applications.terminal +$LOCKSCREEN=hyprlock # default lockscreen, you can use any lockscreen you want, eg swaylock +$IDLE=hypridle # default idle manager, you can use any idle manager you want,eg swayidle + +# // █░░ ▄▀█ █░█ █▄░█ █▀▀ █░█ +# // █▄▄ █▀█ █▄█ █░▀█ █▄▄ █▀█ +# See https://wiki.hyprland.org/Configuring/Keywords/ +# Override the default startup commands + +# This is used to override the default startup commands +# $start.VAR = command + +$start.XDG_PORTAL_RESET=$scrPath/resetxdgportal.sh +$start.DBUS_SHARE_PICKER=dbus-update-activation-environment --systemd --all # for XDPH +$start.SYSTEMD_SHARE_PICKER=systemctl --user import-environment QT_QPA_PLATFORMTHEME WAYLAND_DISPLAY XDG_CURRENT_DESKTOP # for XDPH +$start.BAR=waybar +$start.NOTIFICATIONS=swaync # dunst +$start.APPTRAY_BLUETOOTH=blueman-applet +$start.WALLPAPER=$scrPath/swwwallpaper.sh +$start.TEXT_CLIPBOARD=wl-paste --type text --watch cliphist store +$start.IMAGE_CLIPBOARD=wl-paste --type image --watch cliphist store +$start.BATTERY_NOTIFY=$scrPath/batterynotify.sh +$start.NETWORK_MANAGER=nm-applet --indicator +$start.REMOVABLE_MEDIA=udiskie --no-automount --smart-tray +$start.AUTH_DIALOGUE=$scrPath/polkitkdeauth.sh +$start.IDLE_DAEMON=$IDLE + +# // █▀▀ █▄░█ █░█ +# // ██▄ █░▀█ ▀▄▀ + +# See https://wiki.hyprland.org/Configuring/Environment-variables/ +# Override the default environment variables + +# This is used to override the default environment variables +# $env.VAR = value + +# Toolkit Backend Variables - https://wiki.hyprland.org/Configuring/Environment-variables/#toolkit-backend-variables +$env.GDK_BACKEND = wayland,x11,* #s GTK: Use wayland if available. If not: try x11, then any other GDK backend. +$env.QT_QPA_PLATFORM = wayland +xcb #Qt: Use wayland if available, fall back to x11 if not. +$env.SDL_VIDEODRIVER = wayland # Run SDL2 applications on Wayland. Remove or set to x11 if games that provide older versions of SDL cause compatibility issues +$env.CLUTTER_BACKEND = wayland # Clutter package already has wayland enabled, this variable will force Clutter applications to try and use the Wayland backend + +# XDG Specifications - https://wiki.hyprland.org/Configuring/Environment-variables/#xdg-specifications +$env.XDG_CURRENT_DESKTOP = Hyprland +$env.XDG_SESSION_TYPE = wayland +$env.XDG_SESSION_DESKTOP = Hyprland + +# Qt Variables - https://wiki.hyprland.org/Configuring/Environment-variables/#qt-variables + +$env.QT_AUTO_SCREEN_SCALE_FACTOR = 1 # (From the Qt documentation) enables automatic scaling, based on the monitor’s pixel density +$env.QT_QPA_PLATFORM=wayland +xcb # Tell Qt applications to use the Wayland backend, and fall back to x11 if Wayland is unavailable +$env.QT_WAYLAND_DISABLE_WINDOWDECORATION = 1 # Disables window decorations on Qt applications +$env.QT_QPA_PLATFORMTHEME = qt6ct # Tells Qt based applications to pick your theme from qt5ct, use with Kvantum. + +# HyDE Environment Variables - + +$env.PATH = +$env.MOZ_ENABLE_WAYLAND=1 # Enable Wayland for Firefox +$env.GDK_SCALE=1 # Set GDK scale to 1 // For Xwayland on HiDPI +$env.ELECTRON_OZONE_PLATFORM_HINT=auto # Set Electron Ozone Platform Hint to auto // For Electron apps on Wayland + +# # XDG-DIRS + +$env.XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR +$env.XDG_CONFIG_HOME=$HOME/.config +$env.XDG_CACHE_HOME=$HOME/.cache +$env.XDG_DATA_HOME=$HOME/.local/share + +# These variable will override the default and the theme configuration +# Do not uncomment the variables if you want want HyDE do the theme configuration for you + +# // █▀▀ ▀█▀ █▄▀ +# // █▄█ ░█░ █░█ + +$GTK_THEME=Wallbash-Gtk +$ICON_THEME=Tela-circle-dracula +$COLOR_SCHEME=prefer-dark + +# // █▀▀ █░█ █▀█ █▀ █▀█ █▀█ +# // █▄▄ █▄█ █▀▄ ▄█ █▄█ █▀▄ + +$CURSOR_THEME=Bibata-Modern-Ice +$CURSOR_SIZE=30 + +# // █▀▀ █▀█ █▄░█ ▀█▀ +# // █▀░ █▄█ █░▀█ ░█░ + +$FONT=Canterell +$FONT_SIZE=10 +$DOCUMENT_FONT=Cantarell +$DOCUMENT_FONT_SIZE=10 +$MONOSPACE_FONT=CaskaydiaCove Nerd Font Mono +$MONOSPACE_FONT_SIZE=9 +$FONT_ANTIALIASING=rgba +$FONT_HINTING=full + +# // █░░ █▀█ █▀▀ █▄▀ █▀ █▀▀ █▀█ █▀▀ █▀▀ █▄░█ +# // █▄▄ █▄█ █▄▄ █░█ ▄█ █▄▄ █▀▄ ██▄ ██▄ █░▀█ + +#Setting the Hyprlock layout will override any layout set in the ./hypr/hyprlock.sh +# Dynamic Hyprlock layout should be set in the ./hypr/hyprlock.sh file +# $LAYOUT_PATH=/path/to/hyprlock/layout.conf +# $BACKGROUND_PATH=$HYPRLOCK_BACKGROUND # set the background to the specific hyprlock wallpaper diff --git a/gui/.local/share/hyde/hyprland.conf b/gui/.local/share/hyde/hyprland.conf new file mode 100644 index 0000000..5e5e09d --- /dev/null +++ b/gui/.local/share/hyde/hyprland.conf @@ -0,0 +1,577 @@ +#   ░▒▒▒░░░▓▓          ___________ +# ░░▒▒▒░░░░░▓▓        //___________/ +# ░░▒▒▒░░░░░▓▓     _   _ _    _ _____ +# ░░▒▒░░░░░▓▓▓▓▓▓ | | | | |  | |  __/ +# ░▒▒░░░░▓▓   ▓▓ | |_| | |_/ /| |___ +#  ░▒▒░░▓▓   ▓▓   \__  |____/ |____/ +#    ░▒▓▓   ▓▓  //____/ + +# // █░█ █▄█ █▀▄ █▀▀ +# // █▀█ ░█░ █▄▀ ██▄ + +$scrPath=$HOME/.local/lib/hyde # set scripts path + +# Main modifier +$mainMod = SUPER # windows key +# assign apps + +$default.$QUICKAPPS = +$default.BROWSER = hyde-launch.sh --fall firefox web-browser +$default.EDITOR = hyde-launch.sh --fall code-oss text-editor +$default.EXPLORER= hyde-launch.sh --fall dolphin file-manager +$default.TERMINAL = kitty +$default.LOCKSCREEN=hyprlock +$default.IDLE=hypridle + +$QUICKAPPS = $default.$QUICKAPPS +$BROWSER = $default.BROWSER +$EDITOR = $default.EDITOR +$EXPLORER= $default.EXPLORER +$TERMINAL = $default.TERMINAL +$LOCKSCREEN=$default.LOCKSCREEN +$IDLE= $default.IDLE + + + +# // █░░ ▄▀█ █░█ █▄░█ █▀▀ █░█ +# // █▄▄ █▀█ █▄█ █░▀█ █▄▄ █▀█ + +# See https://wiki.hyprland.org/Configuring/Keywords/ + +$start.XDG_PORTAL_RESET=$scrPath/resetxdgportal.sh +$start.DBUS_SHARE_PICKER=dbus-update-activation-environment --systemd --all # for XDPH +$start.SYSTEMD_SHARE_PICKER=systemctl --user import-environment QT_QPA_PLATFORMTHEME WAYLAND_DISPLAY XDG_CURRENT_DESKTOP # for XDPH +$start.BAR=waybar +$start.NOTIFICATIONS=dunst +$start.APPTRAY_BLUETOOTH=blueman-applet +$start.WALLPAPER=$scrPath/wallpaper.sh --global +$start.TEXT_CLIPBOARD=wl-paste --type text --watch cliphist store +$start.IMAGE_CLIPBOARD=wl-paste --type image --watch cliphist store +$start.BATTERY_NOTIFY=$scrPath/batterynotify.sh +$start.NETWORK_MANAGER=nm-applet --indicator +$start.REMOVABLE_MEDIA=udiskie --no-automount --smart-tray +$start.AUTH_DIALOGUE=$scrPath/polkitkdeauth.sh +$start.IDLE_DAEMON=$IDLE + +# // █▀▀ █▄░█ █░█ +# // ██▄ █░▀█ ▀▄▀ + +# See https://wiki.hyprland.org/Configuring/Environment-variables/ + +# Toolkit Backend Variables - https://wiki.hyprland.org/Configuring/Environment-variables/#toolkit-backend-variables +$env.GDK_BACKEND = wayland,x11,* # GTK: Use wayland if available. If not: try x11, then any other GDK backend. +# $env.QT_QPA_PLATFORM = wayland;xcb # Qt: Use wayland if available, fall back to x11 if not. +$env.SDL_VIDEODRIVER = wayland # Run SDL2 applications on Wayland. Remove or set to x11 if games that provide older versions of SDL cause compatibility issues +$env.CLUTTER_BACKEND = wayland # Clutter package already has wayland enabled, this variable will force Clutter applications to try and use the Wayland backend + +# XDG Specifications - https://wiki.hyprland.org/Configuring/Environment-variables/#xdg-specifications +$env.XDG_CURRENT_DESKTOP = Hyprland +$env.XDG_SESSION_TYPE = wayland +$env.XDG_SESSION_DESKTOP = Hyprland + +# Qt Variables - https://wiki.hyprland.org/Configuring/Environment-variables/#qt-variables + +$env.QT_AUTO_SCREEN_SCALE_FACTOR = 1 # (From the Qt documentation) enables automatic scaling, based on the monitor’s pixel density +$env.QT_QPA_PLATFORM = wayland;xcb # Tell Qt applications to use the Wayland backend, and fall back to x11 if Wayland is unavailable +$env.QT_WAYLAND_DISABLE_WINDOWDECORATION = 1 # Disables window decorations on Qt applications +$env.QT_QPA_PLATFORMTHEME = qt6ct # Tells Qt based applications to pick your theme from qt5ct, use with Kvantum. + + +# HyDE Environment Variables - + +$env.PATH = +$env.MOZ_ENABLE_WAYLAND=1 # Enable Wayland for Firefox +$env.GDK_SCALE=1 # Set GDK scale to 1 // For Xwayland on HiDPI +$env.ELECTRON_OZONE_PLATFORM_HINT=auto # Set Electron Ozone Platform Hint to auto // For Electron apps on Wayland + +# XDG-DIRS +# $XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/run/user/$(id -u)} +# $XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config} +# >> $env.XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache} +# $XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share} + +$env.XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR +$env.XDG_CONFIG_HOME=$HOME/.config +$env.XDG_CACHE_HOME=$HOME/.cache +$env.XDG_DATA_HOME=$HOME/.local/share +$env.XDG_STATE_HOME=$HOME/.local/state + + + +# // █▀▀ ▀█▀ █▄▀ +# // █▄█ ░█░ █░█ + +$default.GTK_THEME=Wallbash-Gtk +$default.ICON_THEME=Tela-circle-dracula +$default.COLOR_SCHEME=prefer-dark + +$GTK_THEME=$default.GTK_THEME +$ICON_THEME=$default.ICON_THEME +$COLOR_SCHEME=$default.COLOR_SCHEME + +# // █▀▀ █░█ █▀█ █▀ █▀█ █▀█ +# // █▄▄ █▄█ █▀▄ ▄█ █▄█ █▀▄ + +$default.CURSOR_THEME=Bibata-Modern-Ice +$default.CURSOR_SIZE=24 + +$CURSOR_THEME=$default.CURSOR_THEME +$CURSOR_SIZE=$default.CURSOR_SIZE + +# // █▀▀ █▀█ █▄░█ ▀█▀ +# // █▀░ █▄█ █░▀█ ░█░ + +$default.FONT=Cantarell +$default.FONT_SIZE=10 +$default.DOCUMENT_FONT=Cantarell +$default.DOCUMENT_FONT_SIZE=10 +$default.MONOSPACE_FONT=CaskaydiaCove Nerd Font Mono +$default.MONOSPACE_FONT_SIZE=9 +$default.NOTIFICATION_FONT=Mononoki Nerd Font Mono +$default.BAR_FONT=JetBrainsMono Nerd Font +# $default.MENU_FONT=This is not yet set as it looks perfect with the global font +$default.FONT_ANTIALIASING=rgba +$default.FONT_HINTING=full + +$FONT=$default.FONT +$FONT_SIZE=$default.FONT_SIZE +$DOCUMENT_FONT=$default.DOCUMENT_FONT +$DOCUMENT_FONT_SIZE=$default.DOCUMENT_FONT_SIZE +$MONOSPACE_FONT=$default.MONOSPACE_FONT +$MONOSPACE_FONT_SIZE=$default.MONOSPACE_FONT_SIZE +$NOTIFICATION_FONT=$default.NOTIFICATION_FONT +$BAR_FONT=$default.BAR_FONT +# $MENU_FONT=$default.MENU_FONT +$FONT_ANTIALIASING=$default.FONT_ANTIALIASING +$FONT_HINTING=$default.FONT_HINTING + + + +# // █▀ █▀█ █▀▀ █▀▀ █ ▄▀█ █░░ +# // ▄█ █▀▀ ██▄ █▄▄ █ █▀█ █▄▄ + +decoration { + dim_special = 0.3 + active_opacity = 0.90 + inactive_opacity = 0.75 + fullscreen_opacity = 1 + blur { + special = true + } +} + +# // █▀▄▀█ █▀█ █▄░█ █ ▀█▀ █▀█ █▀█ +# // █░▀░█ █▄█ █░▀█ █ ░█░ █▄█ █▀▄ + +# See https://wiki.hyprland.org/Configuring/Monitors/ + +monitor = ,preferred,auto,auto + +# // █ █▄░█ █▀█ █░█ ▀█▀ +# // █ █░▀█ █▀▀ █▄█ ░█░ + +# See https://wiki.hyprland.org/Configuring/Variables/ + +input { + kb_layout = us + follow_mouse = 1 + + touchpad { + natural_scroll = no + } + + sensitivity = 0 + force_no_accel = 1 + numlock_by_default = true +} + +# See https://wiki.hyprland.org/Configuring/Variables/ + +# gestures { +# workspace_swipe = true +# workspace_swipe_fingers = 3 +# } + + +# // █░░ ▄▀█ █▄█ █▀█ █░█ ▀█▀ █▀ +# // █▄▄ █▀█ ░█░ █▄█ █▄█ ░█░ ▄█ + +# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ + +dwindle { + # pseudotile = yes + preserve_split = yes +} + +# See https://wiki.hyprland.org/Configuring/Master-Layout/ + +master { + new_status = master +} + + +# // █▀▄▀█ █ █▀ █▀▀ +# // █░▀░█ █ ▄█ █▄▄ + +# See https://wiki.hyprland.org/Configuring/Variables/ + +misc { + vrr = 0 + disable_hyprland_logo = true + disable_splash_rendering = true + force_default_wallpaper = 0 +} + +xwayland { + force_zero_scaling = true +} + + +# // ▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█ +# // █▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█ + +# See https://wiki.hyprland.org/Configuring/Animations/ + +animations { + enabled = yes + bezier = wind, 0.05, 0.9, 0.1, 1.05 + bezier = winIn, 0.1, 1.1, 0.1, 1.1 + bezier = winOut, 0.3, -0.3, 0, 1 + bezier = liner, 1, 1, 1, 1 + animation = windows, 1, 6, wind, slide + animation = windowsIn, 1, 6, winIn, slide + animation = windowsOut, 1, 5, winOut, slide + animation = windowsMove, 1, 5, wind, slide + animation = border, 1, 1, liner + animation = borderangle, 1, 30, liner, once + animation = fade, 1, 10, default + animation = workspaces, 1, 5, wind +} + + +general { + snap { # snapping for floating windows + enabled = true + } +} + + +# // █▀ █▀█ █░█ █▀█ █▀▀ █▀▀ +# // ▄█ █▄█ █▄█ █▀▄ █▄▄ ██▄ + +# hyprlang noerror true +# source = $env.XDG_STATE_HOME/hyde/hyprgui.conf # GUI specific config. Used to not break the low level configs. this will be available downstream +source = $env.XDG_CONFIG_HOME/hypr/themes/colors.conf # Hyde wallbash colors + +# Source groupbar in here +group:groupbar { + col.inactive = rgba($wallbash_pry3ee) + col.active = rgba($wallbash_pry1ee) + col.locked_active = rgba($wallbash_pry2ee) + col.locked_inactive = rgba($wallbash_pry4ee) +} + +source = $env.XDG_CONFIG_HOME/hypr/themes/theme.conf # theme specific settings +source = $env.XDG_CONFIG_HOME/hypr/themes/wallbash.conf # Theme specific settings after Sanitize and handle fallbacks +source = $env.XDG_CONFIG_HOME/hypr/nvidia.conf # Nvidia specific settings +source = $env.XDG_CONFIG_HOME/hypr/animations.conf # source animations variables +# hyprlang noerror false +source = $env.XDG_CONFIG_HOME/hypr/hyde.conf +# hyprlang noerror true +source = $env.XDG_STATE_HOME/hyde/hyprland.conf # translated from config.tomls +source = $ANIMATION_PATH # source animations configuration +# hyprlang noerror false + +#! Below this is an immutable part of the configuration file, and should not be modified by the user. + +#? By Hyprland convention env and startup files are sourced at the end of the main configuration file +#? To ensure that the user's settings are not overridden by the default settings +#? This will let us launch after all envs and variables are set. + +# LEGACY +$quickapps = $QUICKAPPS +$term = $TERMINAL +$editor = $EDITOR +$file = $EXPLORER +$browser = $BROWSER + +# Group Bar +group:groupbar{ + enabled = true + gradients = 1 + render_titles = 1 + font_size = 12 + font_family = $FONT +} + +# Misc + +misc { + font_family = $FONT + +} + + +# // █▀▀ █▄░█ █░█ +# // ██▄ █░▀█ ▀▄▀ + +# # See https://wiki.hyprland.org/Configuring/Environment-variables/ +#! Remove for now +# env = GDK_BACKEND,$env.GDK_BACKEND +# env = SDL_VIDEODRIVER,$env.SDL_VIDEODRIVER +# env = CLUTTER_BACKEND,$env.CLUTTER_BACKEND + +# Qt Variables +env = QT_AUTO_SCREEN_SCALE_FACTOR,$env.QT_AUTO_SCREEN_SCALE_FACTOR +env = QT_QPA_PLATFORM,$env.QT_QPA_PLATFORM +env = QT_WAYLAND_DISABLE_WINDOWDECORATION,$env.QT_WAYLAND_DISABLE_WINDOWDECORATION +env = QT_QPA_PLATFORMTHEME,$env.QT_QPA_PLATFORMTHEME + +# Wayland-ENV +env = MOZ_ENABLE_WAYLAND,$env.MOZ_ENABLE_WAYLAND +env = GDK_SCALE,$env.GDK_SCALE +env = ELECTRON_OZONE_PLATFORM_HINT,$env.ELECTRON_OZONE_PLATFORM_HINT + +# XDG Spec +env = XDG_CURRENT_DESKTOP,$env.XDG_CURRENT_DESKTOP +env = XDG_SESSION_TYPE,$env.XDG_SESSION_TYPE +env = XDG_SESSION_DESKTOP,$env.XDG_SESSION_DESKTOP + + +# HyDE Variables +$hyde.PATH=$HOME/.local/bin:$scrPath:$env.PATH:$PATH +env = PATH,$hyde.PATH + +# XDG-ENV +env = XDG_CONFIG_HOME,$env.XDG_CONFIG_HOME +env = XDG_CACHE_HOME,$env.XDG_CACHE_HOME +env = XDG_DATA_HOME,$env.XDG_DATA_HOME + +# HyDE-ENV +env = HYDE_RUNTIME_DIR,$env.XDG_RUNTIME_DIR/hyde +env = HYDE_CONFIG_HOME,$env.XDG_CONFIG_HOME/hyde +env = HYDE_CACHE_HOME,$env.XDG_CACHE_HOME/hyde +env = HYDE_DATA_HOME,$env.XDG_DATA_HOME/hyde +env = HYDE_STATE_HOME,$env.XDG_STATE_HOME/hyde + +# CURSOR ENV +# env = HYPRCURSOR_THEME,$CURSOR_THEME +# env = HYPRCURSOR_SIZE,$CURSOR_SIZE +# env = XCURSOR_THEME,$CURSOR_THEME +# env = XCURSOR_SIZE,$CURSOR_SIZE + +# Execute on reload + +# HyDE Preparation +$exec.mkdir = mkdir -p $env.XDG_RUNTIME_DIR/hyde $env.XDG_CACHE_HOME/hyde/wallbash $env.XDG_CONFIG_HOME/hyde $env.XDG_DATA_HOME/hyde $(dirname $env.XDG_DATA_HOME)/state/hyde # Create HyDE directories +$set.env = printf "\n_SHELL='$SHELL'\nGDK_BACKEND='$env.GDK_BACKEND'\nQT_QPA_PLATFORM='$env.QT_QPA_PLATFORM'\nSDL_VIDEODRIVER='$env.SDL_VIDEODRIVER'\nCLUTTER_BACKEND='$env.CLUTTER_BACKEND'\nXDG_CURRENT_DESKTOP='$env.XDG_CURRENT_DESKTOP'\nXDG_SESSION_TYPE='$env.XDG_SESSION_TYPE'\nXDG_SESSION_DESKTOP='$env.XDG_SESSION_DESKTOP'\nQT_AUTO_SCREEN_SCALE_FACTOR='$env.QT_AUTO_SCREEN_SCALE_FACTOR'\nQT_WAYLAND_DISABLE_WINDOWDECORATION='$env.QT_WAYLAND_DISABLE_WINDOWDECORATION'\nQT_QPA_PLATFORMTHEME='$env.QT_QPA_PLATFORMTHEME'\nHYDE_PATH='$hyde.PATH'\nMOZ_ENABLE_WAYLAND='$env.MOZ_ENABLE_WAYLAND'\nGDK_SCALE='$env.GDK_SCALE'\nELECTRON_OZONE_PLATFORM_HINT='$env.ELECTRON_OZONE_PLATFORM_HINT'\nXDG_RUNTIME_DIR='$env.XDG_RUNTIME_DIR'\nXDG_CONFIG_HOME='$env.XDG_CONFIG_HOME'\nXDG_CACHE_HOME='$env.XDG_CACHE_HOME'\nXDG_DATA_HOME='$env.XDG_DATA_HOME'\n_GTK_THEME='$GTK_THEME'\n_ICON_THEME='$ICON_THEME'\n_COLOR_SCHEME='$COLOR_SCHEME'\n_CURSOR_SIZE='$CURSOR_SIZE'\n_CURSOR_THEME='$CURSOR_THEME'\nFONT='$FONT'\nFONT_SIZE='$FONT_SIZE'\nDOCUMENT_FONT='$DOCUMENT_FONT'\nDOCUMENT_FONT_SIZE='$DOCUMENT_FONT_SIZE'\nMONOSPACE_FONT='$MONOSPACE_FONT'\nMONOSPACE_FONT_SIZE='$MONOSPACE_FONT_SIZE'\nFONT_ANTIALIASING='$FONT_ANTIALIASING'\nFONT_HINTING='$FONT_HINTING'\nHYDE_RUNTIME_DIR='$env.XDG_RUNTIME_DIR/hyde'\nHYDE_CONFIG_HOME='$env.XDG_CONFIG_HOME/hyde'\nHYDE_CACHE_HOME='$env.XDG_CACHE_HOME/hyde'\nHYDE_DATA_HOME='$env.XDG_DATA_HOME/hyde'\nHYDE_STATE_HOME='$(dirname $env.XDG_DATA_HOME)/state/hyde'\nexport TERMINAL='$(which $TERMINAL)'\nexport LOCKSCREEN='$LOCKSCREEN'" > "$env.XDG_RUNTIME_DIR/hyde/environment" + +# Create a dconf file that will be loaded later one time, better than multiple dconf commands and better than gsettings +$dconFile=$env.XDG_CACHE_HOME/hyde/dconf +$dConf = \n[org/gnome/desktop/interface]\n +$dConf = $dConf icon-theme='$ICON_THEME'\n +$dConf = $dConf gtk-theme='$GTK_THEME'\n +$dConf = $dConf color-scheme='$COLOR_SCHEME'\n +$dConf = $dConf cursor-theme='$CURSOR_THEME'\n +$dConf = $dConf cursor-size=$CURSOR_SIZE\n +$dConf = $dConf font-name='$FONT $FONT_SIZE'\n +$dConf = $dConf document-font-name='$DOCUMENT_FONT $DOCUMENT_FONT_SIZE'\n +$dConf = $dConf monospace-font-name='$MONOSPACE_FONT $MONOSPACE_FONT_SIZE'\n +$dConf = $dConf font-antialiasing='$FONT_ANTIALIASING'\n +$dConf = $dConf font-hinting='$FONT_HINTING'\n +$dConf = $dConf [org/gnome/desktop/default-applications/terminal]\nexec='$(which $TERMINAL)' +$dconf.preserve = dconf dump / > $dconFile +$dconf.populate = printf "$dConf" >> $dconFile +$dconf.reset = dconf reset -f / < $dconFile +$dconf.load = dconf load -f / < $dconFile +$set.hyprCursor = hyprctl setcursor $CURSOR_THEME $CURSOR_SIZE +$exec.dConf = ( $dconf.preserve && $dconf.populate && $dconf.reset && $dconf.load && $set.hyprCursor ) + +$exec.keybinds_hint = $scrPath/keybinds.hint.py --format rofi > $env.XDG_RUNTIME_DIR/hyde/keybinds_hint.rofi + +# Execute +exec = $exec.mkdir & $set.env & $exec.dConf & $exec.animation & $exec.keybinds_hint + +# // █░░ ▄▀█ █░█ █▄░█ █▀▀ █░█ +# // █▄▄ █▀█ █▄█ █░▀█ █▄▄ █▀█ + + +exec-once = $start.XDG_PORTAL_RESET # reset XDPH for screenshare +exec-once = $start.DBUS_SHARE_PICKER # for XDPHgl +exec-once = $start.SYSTEMD_SHARE_PICKER # for XDPH +exec-once = $start.AUTH_DIALOGUE # authentication dialogue for GUI apps +exec-once = $start.BAR # launch the system bar +exec-once = $start.APPTRAY_BLUETOOTH # systray app for Bluetooth +exec-once = $start.REMOVABLE_MEDIA # front-end that allows to manage removable media +exec-once = $start.NETWORK_MANAGER # systray app for Network/Wifi +exec-once = $start.NOTIFICATIONS # start notification demon +exec-once = $start.TEXT_CLIPBOARD # clipboard store text data +exec-once = $start.IMAGE_CLIPBOARD # clipboard store image data +exec-once = $start.WALLPAPER # start wallpaper daemon +exec-once = $start.BATTERY_NOTIFY # battery notification +exec-once = $start.IDLE_DAEMON # idle daemon + +# hyde/config.toml is parsed and exported to the environment +exec-once = $scrPath/parse.config.py --daemon --input $env.XDG_CONFIG_HOME/hyde/config.toml --env $env.XDG_STATE_HOME/hyde/config --hypr $env.XDG_STATE_HOME/hyde/hyprland.conf + + + +#? Rules can be added here as most of the configuration are dynamic + +# // █░█░█ █ █▄░█ █▀▄ █▀█ █░█░█   █▀█ █░█ █░░ █▀▀ █▀ +# // ▀▄▀▄▀ █ █░▀█ █▄▀ █▄█ ▀▄▀▄▀   █▀▄ █▄█ █▄▄ ██▄ ▄█ + +# See https://wiki.hyprland.org/Configuring/Window-Rules/ + +# Ony add the Core applications here +windowrule { + name = windowrule-1 + float = on + match:class = ^(com.gabm.satty)$ +} + +windowrule { + name = windowrule-2 + float = on + match:class = ^(org.kde.dolphin)$ + match:title = ^(Progress Dialog — Dolphin)$ +} + +windowrule { + name = windowrule-3 + float = on + match:class = ^(org.kde.dolphin)$ + match:title = ^(Copying — Dolphin)$ +} + +windowrule { + name = windowrule-4 + float = on + match:title = ^(About Mozilla Firefox)$ +} + +windowrule { + name = windowrule-5 + float = on + match:class = ^(firefox)$ + match:title = ^(Picture-in-Picture)$ +} + +windowrule { + name = windowrule-6 + float = on + match:class = ^(firefox)$ + match:title = ^(Library)$ +} + +windowrule { + name = windowrule-7 + float = on + match:class = ^(.*)$ + match:initial_title = ^(.*)(top)(.*)$ +} + +windowrule { + name = windowrule-8 + float = on + match:class = ^(.*)$ + match:initial_title = ^(.*)(btop)(.*)$ +} + +windowrule { + name = windowrule-9 + float = on + match:class = ^(.*)$ + match:initial_title = ^(.*)(htop)(.*)$ +} + +windowrule { + name = windowrule-10 + float = on + match:class = ^(vlc)$ +} + +windowrule { + name = windowrule-11 + float = on + match:class = ^(kvantummanager)$ +} + +windowrule { + name = windowrule-12 + float = on + match:class = ^(qt5ct)$ +} + +windowrule { + name = windowrule-13 + float = on + match:class = ^(qt6ct)$ +} + +windowrule { + name = windowrule-14 + float = on + match:class = ^(nwg-look)$ +} + +windowrule { + name = windowrule-15 + float = on + match:class = ^(nwg-displays)$ +} + +windowrule { + name = windowrule-16 + float = on + match:class = ^(org.kde.ark)$ +} + +windowrule { + name = windowrule-17 + float = on + match:class = ^(org.pulseaudio.pavucontrol)$ +} + +windowrule { + name = windowrule-18 + float = on + match:class = ^(blueman-manager)$ +} + +windowrule { + name = windowrule-19 + float = on + match:class = ^(nm-applet)$ +} + +windowrule { + name = windowrule-20 + float = on + match:class = ^(nm-connection-editor)$ +} + +windowrule { + name = windowrule-21 + float = on + match:class = ^(org.kde.polkit-kde-authentication-agent-1)$ +} + +windowrule { + name = windowrule-22 + float = on + match:class = ^([Xx]dg-desktop-portal-gtk)$ +} + +# // █░░ ▄▀█ █▄█ █▀▀ █▀█   █▀█ █░█ █░░ █▀▀ █▀ +# // █▄▄ █▀█ ░█░ ██▄ █▀▄   █▀▄ █▄█ █▄▄ ██▄ ▄█ + +# layerrule = blur,rofi +# layerrule = ignorezero,rofi +# layerrule = blur,notifications +# layerrule = ignorezero,notifications +# layerrule = blur,swaync-notification-window +# layerrule = ignorezero,swaync-notification-window +# layerrule = blur,swaync-control-center +# layerrule = ignorezero,swaync-control-center +# layerrule = blur,logout_dialog diff --git a/gui/.local/share/icons/dots-icons/index.theme b/gui/.local/share/icons/dots-icons/index.theme new file mode 100644 index 0000000..7a3d2ee --- /dev/null +++ b/gui/.local/share/icons/dots-icons/index.theme @@ -0,0 +1,12 @@ +[Icon Theme] +Name=dots-icons +Comment=Thin overlay over Tela-circle-dracula: monochrome tray/status icons at every size. Tela only ships its flat panel icons at fixed 16/22/24 - on a fractionally scaled monitor waybar requests size*2 (e.g. 44), which falls through to scalable/devices, whose icons are colored circles (the blue nm-applet blob). This theme is searched first and serves the panel SVGs as scalable, so status icons stay flat; everything else inherits Tela unchanged. Pinned via $ICON_THEME in gui/.config/hypr/hyde.conf. +Inherits=Tela-circle-dracula +Directories=scalable/status + +[scalable/status] +Context=Status +Size=22 +MinSize=8 +MaxSize=512 +Type=Scalable diff --git a/gui/.local/share/icons/dots-icons/scalable/status/blueman-active.svg b/gui/.local/share/icons/dots-icons/scalable/status/blueman-active.svg new file mode 100644 index 0000000..598b6b8 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/blueman-active.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/blueman-disabled.svg b/gui/.local/share/icons/dots-icons/scalable/status/blueman-disabled.svg new file mode 100644 index 0000000..58c037f --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/blueman-disabled.svg @@ -0,0 +1,24 @@ + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/blueman-tray-active.svg b/gui/.local/share/icons/dots-icons/scalable/status/blueman-tray-active.svg new file mode 100644 index 0000000..598b6b8 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/blueman-tray-active.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/blueman-tray-disabled.svg b/gui/.local/share/icons/dots-icons/scalable/status/blueman-tray-disabled.svg new file mode 100644 index 0000000..58c037f --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/blueman-tray-disabled.svg @@ -0,0 +1,24 @@ + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/blueman-tray.svg b/gui/.local/share/icons/dots-icons/scalable/status/blueman-tray.svg new file mode 100644 index 0000000..598b6b8 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/blueman-tray.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-adhoc.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-adhoc.svg new file mode 100644 index 0000000..7da431c --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-adhoc.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired-autoip.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired-autoip.svg new file mode 100644 index 0000000..245d61b --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired-autoip.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired-secure.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired-secure.svg new file mode 100644 index 0000000..7bfbb34 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired-secure.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired-secureoffline.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired-secureoffline.svg new file mode 100644 index 0000000..64dc183 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired-secureoffline.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired.svg new file mode 100644 index 0000000..245d61b --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wired.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wireless.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wireless.svg new file mode 100644 index 0000000..f89a3ff --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wireless.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wwan.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wwan.svg new file mode 100644 index 0000000..5909ac6 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-device-wwan.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-mb-roam.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-mb-roam.svg new file mode 100644 index 0000000..7a8d631 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-mb-roam.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-00.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-00.svg new file mode 100644 index 0000000..29f61b4 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-00.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-100.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-100.svg new file mode 100644 index 0000000..f89a3ff --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-100.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-25.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-25.svg new file mode 100644 index 0000000..cab4710 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-25.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-50.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-50.svg new file mode 100644 index 0000000..da74dbb --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-50.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-75.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-75.svg new file mode 100644 index 0000000..96cae57 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-nm-signal-75.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-no-connection.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-no-connection.svg new file mode 100644 index 0000000..4eee086 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-no-connection.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-secure-lock.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-secure-lock.svg new file mode 100644 index 0000000..da25e2b --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-secure-lock.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-0-secure.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-0-secure.svg new file mode 100644 index 0000000..aee38cb --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-0-secure.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-0.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-0.svg new file mode 100644 index 0000000..29f61b4 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-0.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-00-secure.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-00-secure.svg new file mode 100644 index 0000000..aee38cb --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-00-secure.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-00.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-00.svg new file mode 100644 index 0000000..29f61b4 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-00.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-100-secure.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-100-secure.svg new file mode 100644 index 0000000..737fa8b --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-100-secure.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-100.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-100.svg new file mode 100644 index 0000000..f89a3ff --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-100.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-25-secure.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-25-secure.svg new file mode 100644 index 0000000..ea84815 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-25-secure.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-25.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-25.svg new file mode 100644 index 0000000..cab4710 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-25.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-50-secure.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-50-secure.svg new file mode 100644 index 0000000..ea84815 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-50-secure.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-50.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-50.svg new file mode 100644 index 0000000..cab4710 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-50.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-75-secure.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-75-secure.svg new file mode 100644 index 0000000..0256b36 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-75-secure.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-75.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-75.svg new file mode 100644 index 0000000..96cae57 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-signal-75.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-3g.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-3g.svg new file mode 100644 index 0000000..59ba694 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-3g.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-4g.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-4g.svg new file mode 100644 index 0000000..78f7c71 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-4g.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-cdma-1x.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-cdma-1x.svg new file mode 100644 index 0000000..e2b9c2f --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-cdma-1x.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-edge.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-edge.svg new file mode 100644 index 0000000..4e657af --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-edge.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-evdo.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-evdo.svg new file mode 100644 index 0000000..cd4c906 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-evdo.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-gprs.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-gprs.svg new file mode 100644 index 0000000..8a46c67 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-gprs.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-hspa.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-hspa.svg new file mode 100644 index 0000000..062d0da --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-hspa.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-lte.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-lte.svg new file mode 100644 index 0000000..78f7c71 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-lte.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-umts.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-umts.svg new file mode 100644 index 0000000..e25ba98 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-tech-umts.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-vpn-active-lock.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-vpn-active-lock.svg new file mode 100644 index 0000000..da25e2b --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-vpn-active-lock.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-vpn-lock.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-vpn-lock.svg new file mode 100644 index 0000000..da25e2b --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-vpn-lock.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-vpn-standalone-lock.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-vpn-standalone-lock.svg new file mode 100644 index 0000000..4af2382 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-vpn-standalone-lock.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/gui/.local/share/icons/dots-icons/scalable/status/nm-wwan-tower.svg b/gui/.local/share/icons/dots-icons/scalable/status/nm-wwan-tower.svg new file mode 100644 index 0000000..7a8d631 --- /dev/null +++ b/gui/.local/share/icons/dots-icons/scalable/status/nm-wwan-tower.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/install.sh b/install.sh index e605500..71bbf82 100755 --- a/install.sh +++ b/install.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash # Deploy the unified dots for this machine using GNU Stow. # -# ./install.sh [host] host defaults to $(hostname -s) +# ./install.sh [host] host: the arg, else ~/.config/dots-host, else $(hostname -s); +# the resolved host is recorded in ~/.config/dots-host # ./install.sh -n [host] dry-run (preview, no changes) # ./install.sh -a [host] first run on a machine that still has real config files: # --adopt them into the repo, then review `git diff` and @@ -28,13 +29,26 @@ set -uo pipefail # — run once on each tty-only box: touch ~/.config/dots-headless HEADLESS_MARKER="${XDG_CONFIG_HOME:-$HOME/.config}/dots-headless" +# --- which host package this machine deploys --- +# Machine-local like the marker above, and deliberately NOT derived from `hostname -s`: several +# boxes may deploy the same overlay without sharing a hostname, and a hostname that happens not +# to match a package name would otherwise half-deploy (base layers restow, the overlay is +# skipped). Resolution order: +# the explicit arg -> this file -> `hostname -s` (a guess, not an invariant) +# The resolved host is recorded here, so later bare runs — and `bin/dotsync`, which re-links +# without being able to pass an argument — reuse it. Hand-editable: one word, e.g. `wm`. +HOST_MARKER="${XDG_CONFIG_HOME:-$HOME/.config}/dots-host" + usage() { cat <<'EOF' Deploy this machine's dotfiles with GNU Stow. Usage: ./install.sh [options] [host] - host profile to deploy (default: `hostname -s`) + host host package to deploy. Resolved as: this arg, else the name in + ~/.config/dots-host, else `hostname -s`. The resolved host is + remembered in ~/.config/dots-host, so later runs (and dotsync, + which cannot pass one) reuse it — the hostname need not match. -n, --dry-run preview stow actions; change nothing -a, --adopt first run on a machine with existing real config files: adopt them into the repo, then review `git diff` and `git restore .` @@ -53,18 +67,47 @@ UNSTOW=0 ARGS=() for a in "$@"; do case "$a" in - -n|--dry-run) DRY="-n" ;; - -a|--adopt) ADOPT="--adopt" ;; - -D|--unstow|--delete) UNSTOW=1 ;; - -h|--help) usage; exit 0 ;; - -*) echo "unknown option: $a" >&2; usage >&2; exit 2 ;; + -n | --dry-run) DRY="-n" ;; + -a | --adopt) ADOPT="--adopt" ;; + -D | --unstow | --delete) UNSTOW=1 ;; + -h | --help) + usage + exit 0 + ;; + -*) + echo "unknown option: $a" >&2 + usage >&2 + exit 2 + ;; *) ARGS+=("$a") ;; esac done -HOST="${ARGS[0]:-$(hostname -s)}" +# resolve the host package (see HOST_MARKER above). RECORDED is read BEFORE the marker is +# rewritten below, so a profile switch still knows which overlay to release first. +RECORDED="" +[ -r "$HOST_MARKER" ] && RECORDED="$(head -1 "$HOST_MARKER" | tr -d '[:space:]')" +HOST="${ARGS[0]:-${RECORDED:-$(hostname -s)}}" +# an empty HOST must never reach stow: `stow -S ""` treats the repo ROOT as the package +# and links common/, gui/, install.sh… straight into $HOME (and `-D ""` unlinks likewise) +[ -n "$HOST" ] || { + echo "error: no host resolved — pass one (./install.sh ) or write it to ~/.config/dots-host" >&2 + exit 1 +} DOTS="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)" -command -v stow >/dev/null 2>&1 || { echo "error: GNU stow is not installed (pacman -S stow)" >&2; exit 1; } +command -v stow >/dev/null 2>&1 || { + echo "error: GNU stow is not installed (pacman -S stow)" >&2 + exit 1 +} + +# --adopt copies LIVE files over the repo working tree; uncommitted edits there would be +# silently overwritten (and the post-adopt `git restore .` advice would discard whatever +# survived). Refuse until the tree is clean, so nothing is lost without a git safety net. +if [ -n "$ADOPT" ] && [ -z "$DRY" ] && [ -n "$(git -C "$DOTS" status --porcelain 2>/dev/null)" ]; then + echo "error: -a/--adopt rewrites repo files with this machine's live copies, and the repo has" >&2 + echo " uncommitted changes that would be lost — commit or stash them first." >&2 + exit 1 +fi gui=true [ -e "$HEADLESS_MARKER" ] && gui=false @@ -73,7 +116,8 @@ echo "host=$HOST dots=$DOTS gui=$gui${DRY:+ (dry-run)}" # --- revert mode: unstow this host's packages (reverse of deploy), leave repo + real files intact --- if [ "$UNSTOW" -eq 1 ]; then - verb="unstowed"; [ -n "$DRY" ] && verb="would unstow" + verb="unstowed" + [ -n "$DRY" ] && verb="would unstow" for p in "$HOST" gui common; do [ -d "$DOTS/$p" ] || continue if stow $DRY -d "$DOTS" -t "$HOME" -D "$p" 2>/dev/null; then @@ -96,35 +140,76 @@ if [ "$UNSTOW" -eq 1 ]; then exit 0 fi +# remember the resolved host, so bare re-runs — and dotsync, which re-links without being able to +# pass an argument — deploy the same overlay on this machine whatever its hostname is. Only ever +# records a name that IS a package (never a typo, never a hostname with no overlay behind it), and +# records the *choice*, not the outcome: a run whose stow later fails is still remembered. +if [ -z "$DRY" ] && [ "$HOST" != "$RECORDED" ] && [ -d "$DOTS/$HOST" ]; then + printf '%s\n' "$HOST" >"$HOST_MARKER" && echo " recorded host '$HOST' in ${HOST_MARKER/#$HOME/~}" +fi + # 0. self-heal symlinks that stow would reject ("not owned by stow"), which otherwise aborts all of -# common. Two stale-link classes a prior link.sh/heal can leave behind: +# common. Three stale-link classes a prior link.sh/heal/migration can leave behind: # - hooks/skills folded into an ABSOLUTE dir-symlink (they must be real dirs) — drop it entirely. # - a file item re-linked with an ABSOLUTE target instead of stow's relative one — drop it. +# - a DANGLING link whose target is gone (e.g. a relative link into the deleted ~/.dotfiles) — +# stow rejects it just the same, so drop it too. # Only ever removes a *symlink* here (never a real file/dir), so nothing is lost; stow then # recreates the correct relative link. A drifted *real* file is left for link.sh's drift guard # (or `-a` to adopt) to reconcile, since it may hold un-synced edits. if [ -z "$DRY" ]; then for d in hooks skills; do t="$HOME/.config/claude/$d" - [ -L "$t" ] && { rm -f "$t"; echo " cleared stale folded symlink ~/.config/claude/$d"; } + [ -L "$t" ] && { + rm -f "$t" + echo " cleared stale folded symlink ~/.config/claude/$d" + } done for f in settings.json statusline.py keybindings.json CLAUDE.md; do t="$HOME/.config/claude/$f" + if [ -L "$t" ] && [ ! -e "$t" ]; then + rm -f "$t" + echo " cleared dangling symlink ~/.config/claude/$f (stow will relink)" + continue + fi case "$(readlink "$t" 2>/dev/null)" in - /*) rm -f "$t"; echo " cleared absolute symlink ~/.config/claude/$f (stow will relink)" ;; + /*) + rm -f "$t" + echo " cleared absolute symlink ~/.config/claude/$f (stow will relink)" + ;; esac done fi -# 1. release the host overlay first so base restows never conflict on overridden files -if [ -d "$DOTS/$HOST" ] && [ -z "$DRY" ]; then - stow -d "$DOTS" -t "$HOME" -D "$HOST" 2>/dev/null || true +# --- keep a live Hyprland session quiet while symlinks are removed/recreated --- +# Step 1 releases the host overlay before restowing, and Hyprland's config watcher reloads +# the moment a sourced file vanishes (monitors.conf/host.conf, hyprland.conf:58/60 — the two +# that come from the overlay): that parse aborts and the session sits on a partial config +# until a manual Super+Shift+R. So pause the watcher and reload once at the end, when every +# link is back. The final reload also re-reads misc:disable_autoreload from the config, +# undoing the runtime keyword — no state is left behind. +if [ -z "$DRY" ] && [ -n "${HYPRLAND_INSTANCE_SIGNATURE:-}" ] && command -v hyprctl >/dev/null 2>&1; then + hyprctl keyword misc:disable_autoreload true >/dev/null 2>&1 + trap 'hyprctl reload >/dev/null 2>&1' EXIT +fi + +# 1. release the host overlay first so base restows never conflict on overridden files. +# The PREVIOUSLY recorded host is released too, so switching a machine to another overlay +# works: its leftover links are "stowed to a different package" as far as the step-2 restows +# are concerned, which would abort those whole packages. Releasing an already-released +# package is a harmless no-op, so the repeat when nothing changed costs nothing. +if [ -z "$DRY" ]; then + for p in "$HOST" "$RECORDED"; do + [ -n "$p" ] && [ -d "$DOTS/$p" ] && stow -d "$DOTS" -t "$HOME" -D "$p" 2>/dev/null + done + true # a release that found nothing to do must not fail the run fi # stow one package; track failures instead of silently continuing and reporting success FAIL=0 stow_pkg() { # stow_pkg