From 98f9a77b2396644de6c3cdf81b77bffcf899ca4f Mon Sep 17 00:00:00 2001 From: thomas Date: Wed, 26 Aug 2026 21:05:03 +0100 Subject: [PATCH] feature: float bitwarden extension window --- dotfiles/.config/hypr/core/startup.lua | 3 + .../hypr/scripts/FloatBitwardenPopup.sh | 73 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100755 dotfiles/.config/hypr/scripts/FloatBitwardenPopup.sh diff --git a/dotfiles/.config/hypr/core/startup.lua b/dotfiles/.config/hypr/core/startup.lua index 6824928..1878add 100644 --- a/dotfiles/.config/hypr/core/startup.lua +++ b/dotfiles/.config/hypr/core/startup.lua @@ -33,6 +33,9 @@ hl.on("hyprland.start", function() -- Starting hypridle to start hyprlock hl.exec_cmd("hypridle") + -- Float + center the Bitwarden extension pop-out (window rules can't catch it — see the script) + hl.exec_cmd(scriptsDir .. "/FloatBitwardenPopup.sh") + hl.dispatch(hl.dsp.focus({ workspace = 3 })) -- NOTE: the trailing "&&" here is a stray leftover from the original .conf (dangling, does nothing) — ported as-is diff --git a/dotfiles/.config/hypr/scripts/FloatBitwardenPopup.sh b/dotfiles/.config/hypr/scripts/FloatBitwardenPopup.sh new file mode 100755 index 0000000..2c772fa --- /dev/null +++ b/dotfiles/.config/hypr/scripts/FloatBitwardenPopup.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# Float + resize (to 781x702) + center the Bitwarden browser-extension pop-out window. +# +# Why this exists instead of a window rule: Hyprland's `float`/`center`/`size` +# rules are "static" — evaluated exactly once, at the instant a window is +# created — and this popup opens with the generic title "Zen Browser" (same +# initialTitle/initialClass as the main browser window), only updating to +# "Extension: (Bitwarden Password Manager) - Bitwarden..." a moment after +# it's already mapped. By then the static rule has already missed its one +# chance and there's no field available at creation time to tell the popup +# apart from a normal browser window. See layout/windowrules.lua and +# https://github.com/hyprwm/Hyprland/issues/7402 for the same problem class. +# +# No socat/ncat is installed, so instead of an event-driven IPC listener this +# just polls `hyprctl clients` at a short interval with jq (already +# installed). +# +# Dispatch syntax note: Hyprland 0.55+'s Lua config changed `hyprctl dispatch` +# to take a literal Lua expression calling hl.dsp.* (e.g. +# `hyprctl dispatch 'hl.dsp.window.center()'`), not the old +# "centerwindow"-style dispatcher strings. Verified live against the real +# API in /usr/share/hypr/stubs/hl.meta.lua before wiring this in. +# +# Why it re-asserts for a few seconds instead of firing once: right after the +# popup leaves the tiled layout, Hyprland briefly re-tiles the sibling window +# to fill the freed space, and during that reflow this popup was observed +# reporting `floating: false` again for one poll tick — snapping it back to +# its old tiled geometry. Re-asserting float+center for a short settle window +# after first detection rides that out. After the settle window it's left +# alone, so a manual re-tile/move by you later isn't fought. + +TITLE_PATTERN='Extension: \(Bitwarden Password Manager\)' +POLL_INTERVAL=0.3 +SETTLE_TICKS=6 # ~2s of re-assertion after first detection, at POLL_INTERVAL +TARGET_WIDTH=781 +TARGET_HEIGHT=702 + +declare -A ticks_left # address -> remaining re-assert ticks + +while true; do + sleep "$POLL_INTERVAL" + + # Forget any tracked window that's no longer open. + open_addresses=$(hyprctl clients -j | jq -r '.[].address') + for addr in "${!ticks_left[@]}"; do + grep -qF "$addr" <<<"$open_addresses" || unset 'ticks_left[$addr]' + done + + # Process substitution (not a pipe) so the while loop runs in this shell + # and its writes to ticks_left actually persist across iterations. + while IFS= read -r win; do + title=$(jq -r '.title' <<<"$win") + [[ "$title" =~ $TITLE_PATTERN ]] || continue + + address=$(jq -r '.address' <<<"$win") + floating=$(jq -r '.floating' <<<"$win") + + # First time we've seen this window: start its settle window. + [[ -v ticks_left[$address] ]] || ticks_left[$address]=$SETTLE_TICKS + + # Already floating and past its settle window: leave it alone. + [[ "$floating" == "true" && ${ticks_left[$address]} -le 0 ]] && continue + + hyprctl dispatch "hl.dsp.focus({ window = \"address:$address\" })" >/dev/null + hyprctl dispatch 'hl.dsp.window.float({ action = "enable" })' >/dev/null + # resize({x=W,y=H}) sets the absolute size (not a delta) — verified live. + # Must happen before center(), since resizing shifts what "centered" means. + hyprctl dispatch "hl.dsp.window.resize({ x = $TARGET_WIDTH, y = $TARGET_HEIGHT })" >/dev/null + hyprctl dispatch 'hl.dsp.window.center()' >/dev/null + + ticks_left[$address]=$((ticks_left[$address] - 1)) + done < <(hyprctl clients -j | jq -c '.[] | {address, title, floating}') +done -- 2.49.1