New plugin:hyprchrome:shadow_size (px, 0 = off), shadow_color and shadow_offset (vec2). Off by default, so existing setups are unchanged. The silhouette is the frame's own outer boundary - extracted out of GetBorderTexture into AppendFrameOuterPath so the shadow and the shape casting it can't drift apart - filled into an A8 mask, blurred with three box passes, then tinted. The frame's cutouts (side notches, the strip beside the title bar) are part of that outline, so they cast their shape for free. Three things worth not undoing later: - The window's interior is cleared after the blur, not before. Punching it first leaves the blur smearing shadow inward across the window's own content. Clearing afterwards puts the cut exactly on the ring's inner boundary, where the frame's opaque pixels hide it. - The shadow is not part of the decoration's reserved extents, since reserving it would push neighbouring windows away by the shadow's width. It's drawn past the decoration's box instead, which is why damageEntire() and boundingBox() expand by ShadowMarginLogical(). - It renders at no more than kShadowMaxDim px on the long edge and lets the GPU upscale. A blurred blob loses nothing to that, and a full-resolution blur would otherwise be re-run per frame for the whole length of a resize animation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
hypr-chrome
A Hyprland plugin that draws a chamfered HUD-style border frame behind each window, expanded past the window's own edges, with a floating title bar hanging off the top-left corner. The frame's corners are chamfered by an amount inferred from the window's own border rounding, so the border peeking out around a rounded window's corners reads as a matching diagonal cut rather than a hard rectangular corner. Border color tracks Hyprland's own active/inactive border color live, gradients included.
Config
plugin {
hyprchrome {
enabled = true # bool
extent = 12 # int, px the border extends past the window edge
follow_hyprland_border_color = true # bool, use Hyprland's own active/inactive border color instead of active_color/inactive_color below
active_color = 0xFFD063FF # focused-window border color, used when follow_hyprland_border_color is false
inactive_color = 0xFF6C6C6C # unfocused-window border color, used when follow_hyprland_border_color is false
titlebar_height = 8 # int, px height of the floating title bar
titlebar_text_size = 12 # int, px font size of the title bar's text
titlebar_font = sans-serif # string, passed to cairo_select_font_face
}
}
active_color/inactive_color take the same syntax as Hyprland's own
general:col.active_border — a single color, or several plus an optional
angle for a gradient:
active_color = rgba(ff0000ff) rgba(00ff00ff) 45deg
Installing
hyprpm add https://git.mgaction.town/darman/hypr-chrome.git
hyprpm enable hypr-chrome
hyprpm builds the plugin from source against your own installed Hyprland,
using the build commands declared in hyprpm.toml (the same cmake
invocations as below).
NixOS
flake.nix exposes packages.x86_64-linux.default, built against
pkgs.hyprland from this flake's own nixpkgs input (with Hyprland's own
compiler/stdenv, for ABI correctness). Add this repo as a flake input and
set inputs.hypr-chrome.inputs.nixpkgs.follows = "nixpkgs" (your own
system's nixpkgs input) so it's built against the exact Hyprland your
system runs - then add inputs.hypr-chrome.packages.${system}.default
directly to your wayland.windowManager.hyprland.plugins
(NixOS/home-manager) list. The package installs as lib/libhypr-chrome.so
(the lib<pname>.so convention that module derives its path from
automatically) even though the plain hyprctl/hyprpm path above uses
the unprefixed hypr-chrome.so.
Building
Requires the Hyprland headers/libs (hyprland, libdrm, libinput,
libudev, wayland-server, xkbcommon, pixman-1, cairo, etc.), ABI-locked
to the exact Hyprland build you'll load the plugin into. A pinned Nix flake
dev shell provides all of it:
nix develop
cmake -B build -S .
cmake --build build
This produces hypr-chrome.so at the repo root (not inside build/, so the
hyprctl/buildAndLoad.sh commands below can find it directly).
Developing
Load/reload the plugin into a running Hyprland session:
hyprctl plugin load "$(pwd)/hypr-chrome.so"
hyprctl plugin unload "$(pwd)/hypr-chrome.so"
buildAndLoad.sh chains build + reload for iteration. Hyprland's plugin
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 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.