From 375cfe1f000df466120be3d4d42a334e0dc5aa47 Mon Sep 17 00:00:00 2001 From: Erik Simon Date: Wed, 29 Jul 2026 20:36:41 +0200 Subject: [PATCH] Unload the config-installed plugin copy in buildAndLoad.sh The session loads its own copy of the plugin from the Hyprland config, a separate dlopen with its own decorations - so iterating with a dev build on top of it drew two frames on every window. Grep the path back out of $XDG_CONFIG_HOME/hypr rather than hardcoding it: a home-manager-installed copy lives at a /nix/store path that changes on every rebuild. -R, since those config files are symlinks into the store. hyprctl plugin list reports no path, so the config is the only place to read it from. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 2 ++ README.md | 7 +++++-- buildAndLoad.sh | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8c0cfb4..30e7c54 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,6 +42,8 @@ This builds and hot-reloads the plugin into a running Hyprland session in one st The script copies the built `.so` to a uniquely-suffixed filename (`hypr-chrome-.so`) before loading it, and unloads/deletes prior copies. This works around Hyprland's plugin loader never fully `dlmunmap`ping a `.so` on unload — reloading the exact same path just re-serves the stale old mapping instead of the freshly built code. +It also unloads whatever copy the user's own Hyprland config loads (grepped out of `$XDG_CONFIG_HOME/hypr` rather than hardcoded, since a home-manager-installed one lives at a `/nix/store` path that changes on every rebuild). That copy is a separate `dlopen` with its own decorations, so leaving it loaded draws two frames per window. + Manual equivalent: ```sh diff --git a/README.md b/README.md index 8cc9d19..e09eb26 100644 --- a/README.md +++ b/README.md @@ -87,5 +87,8 @@ hyprctl plugin unload "$(pwd)/hypr-chrome.so" loader never fully unmaps a `.so` once `dlopen`'d, so reloading the same path just re-serves the old (possibly stale) mapping — the script works around this by copying each build to a uniquely-suffixed filename before loading it -and cleaning up prior copies. It hardcodes an absolute checkout path at the -top; check it matches your checkout before running it. +and cleaning up prior copies. It also unloads any copy your own Hyprland +config loads (found by grepping `$XDG_CONFIG_HOME/hypr` for a `hypr-chrome` +`.so` path), since that one is a separate `dlopen` that would draw a second +frame on every window. It hardcodes an absolute checkout path at the top; +check it matches your checkout before running it. diff --git a/buildAndLoad.sh b/buildAndLoad.sh index df629ed..b6ac1ad 100755 --- a/buildAndLoad.sh +++ b/buildAndLoad.sh @@ -21,4 +21,19 @@ for old in hypr-chrome.so hypr-chrome-*.so; do [ "$old" = "hypr-chrome.so" ] || rm -f "$old" done +# The session also loads the plugin declaratively from the personal Hyprland +# config (home-manager's wayland.windowManager.hyprland.plugins, which lands +# as a /nix/store/.../lib/libhypr-chrome.so). That copy is a separate dlopen +# with its own decorations, so leaving it loaded means every window gets two +# frames drawn on top of each other. Its store path changes on every rebuild, +# so read it back out of the config instead of hardcoding it (-R to follow +# home-manager's symlinks into the store). +HYPR_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/hypr" +if [ -d "$HYPR_CONFIG_DIR" ]; then + for configured in $(grep -Rho '/[^[:space:]"'"'"']*hypr-chrome[^[:space:]"'"'"']*\.so' "$HYPR_CONFIG_DIR" 2>/dev/null | sort -u || true); do + [ "$configured" = "$DIR/$NEW_SO" ] && continue + hyprctl plugin unload "$configured" >/dev/null 2>&1 || true + done +fi + hyprctl plugin load "$DIR/$NEW_SO"