diff --git a/nix/flake.lock b/nix/flake.lock index 05680fc..07782de 100755 --- a/nix/flake.lock +++ b/nix/flake.lock @@ -11,11 +11,11 @@ ] }, "locked": { - "lastModified": 1779768519, - "narHash": "sha256-2n/447oNfAZrl1yncafLPgXMx5tuTF6T2B+zI/zFYkI=", + "lastModified": 1780375472, + "narHash": "sha256-Q8RAJoYlakA6B2I5DVcuxzNbhCNU1nnIjqQZYP1KGrM=", "owner": "caelestia-dots", "repo": "cli", - "rev": "64a5507e74f6c7d0c29f9131964412f8f8c4dd89", + "rev": "d1c8c8fc09738d1b40576e97c274b4a11a2e0ac7", "type": "github" }, "original": { @@ -27,17 +27,18 @@ "caelestia-shell": { "inputs": { "caelestia-cli": "caelestia-cli", + "m3shapes": "m3shapes", "nixpkgs": [ "nixpkgs" ], "quickshell": "quickshell" }, "locked": { - "lastModified": 1780196414, - "narHash": "sha256-iXmyWULTZuRd68xRL79e9GyYL9FZ6gfh6zl1PPlWX2A=", + "lastModified": 1781178648, + "narHash": "sha256-z6V1T7MHShM7TfFIMCDp94Bi1Wk9LJfK96vm8YFGY5M=", "owner": "caelestia-dots", "repo": "shell", - "rev": "63bb82762bb29ac9b7fcd5b97839abae721ce860", + "rev": "a16c957a8bd13e0cfd09928ca0a22fecd3681e44", "type": "github" }, "original": { @@ -52,11 +53,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1780709764, - "narHash": "sha256-TmYUn7Gr92j2qsrBiNdxxyboeUgsbCQ9xEn+aBsOcxQ=", + "lastModified": 1781239065, + "narHash": "sha256-f0RhbIrYxEcdKdFbiXDWPLz+IssS+E8ORAheC0/vUY0=", "owner": "sadjow", "repo": "claude-code-nix", - "rev": "621404fd3264455b1b451e90e90566c4b67c2fb4", + "rev": "23a9a631d03d0e06af789996c5c97d7b0b6a1bf1", "type": "github" }, "original": { @@ -87,11 +88,11 @@ ] }, "locked": { - "lastModified": 1779036909, - "narHash": "sha256-zXcwYQGCT6pzinK+1dBB2ekTVtfxGZAapb3Evdcu4fY=", + "lastModified": 1781190061, + "narHash": "sha256-QRMpLbsmlciMGv4yx75FUoIl54K02JbIX1tgdPHPw1s=", "owner": "lnl7", "repo": "nix-darwin", - "rev": "56c666e108467d87d13508936aade6d567f2a501", + "rev": "f2b3fdb347f91dfd344ad196666a0b0fe8aad05c", "type": "github" }, "original": { @@ -144,11 +145,11 @@ ] }, "locked": { - "lastModified": 1780361225, - "narHash": "sha256-wnV9ttf4fPWNonBIQmvlrSlNpQYgx5HgWWd007mwIFA=", + "lastModified": 1781184346, + "narHash": "sha256-cZRlW47U6A2nWvAmnZeeO6Xvq23gxYbVLel4KxqOrcQ=", "owner": "nix-community", "repo": "home-manager", - "rev": "e28654b71096e08c019d4861ca26acb646f583d8", + "rev": "ea6d221d7aa85652d014b6f719dddf036037515b", "type": "github" }, "original": { @@ -158,16 +159,33 @@ "type": "github" } }, + "m3shapes": { + "flake": false, + "locked": { + "lastModified": 1781017666, + "narHash": "sha256-kfHyzZaPHgqZML48OA+5JwBOsLdQJ2ci/aGPShvUB4Y=", + "owner": "soramanew", + "repo": "m3shapes", + "rev": "bdc327b29f95394a732baf3c9b19658ba23755b6", + "type": "github" + }, + "original": { + "owner": "soramanew", + "repo": "m3shapes", + "rev": "bdc327b29f95394a732baf3c9b19658ba23755b6", + "type": "github" + } + }, "nixos-hardware": { "inputs": { "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1780310866, - "narHash": "sha256-fPBRVf6A5xlACYcOI59shGrjURuvwu0lRsDoSCEXt/I=", + "lastModified": 1781168557, + "narHash": "sha256-LOnLQ2tpYF9gqIDDr3+j3DbpJJr/QCH6zPRT2GzEUOE=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "4ed851c979641e28597a05086332d75cdc9e395f", + "rev": "6358ff76821101c178e3ab4919a62799bfe3652e", "type": "github" }, "original": { @@ -179,11 +197,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1780365719, - "narHash": "sha256-QfWfccTN+70ZQ4m2qlU9PiKfz2Yppq94058iJyARNwc=", + "lastModified": 1781173989, + "narHash": "sha256-fnzKKPvS+oieI/pTzotA5tkoM47EB1NpaBcgk4R97hE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ffa10e26ae11d676b2db836259889f1f571cb14f", + "rev": "8c91a71d13451abc40eb9dae8910f972f979852f", "type": "github" }, "original": { @@ -195,11 +213,11 @@ }, "nixpkgs-darwin": { "locked": { - "lastModified": 1780614800, - "narHash": "sha256-xq6r60DU54c2C//D+ok+y5A7fp/zpFcNUeX5uGqKyMQ=", + "lastModified": 1781088061, + "narHash": "sha256-ccyIWjP94eLTyvzTNXH91iXGd+AfGseTQtXAirxlogk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "cc598dfd09b0a543d574a238d4b3473bb1c6d587", + "rev": "2262daccaac76e0319dc0590c2ef711a3c79c5bd", "type": "github" }, "original": { @@ -224,11 +242,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1780453794, - "narHash": "sha256-bXMRa9VTsHSPXL4Cw8R6JJLQeY3Y/IP4+YJCYVmQ7FY=", + "lastModified": 1780902259, + "narHash": "sha256-q8yYEC5f1mFlQO9RGna4LTc9QrcvWunX6FYp83munkQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "6b316287bae2ee04c9b93c8c858d930fd07d7338", + "rev": "bd0ff2d3eac24699c3664d5966b9ef36f388e2ca", "type": "github" }, "original": { @@ -262,11 +280,11 @@ ] }, "locked": { - "lastModified": 1779430452, - "narHash": "sha256-zTslhsxLqUlRTML506iougTGzyR38Fzhzn7t4KDEuuE=", + "lastModified": 1780303737, + "narHash": "sha256-7HgdJBG4BgAPDyHKKxWtxj7nziqsQo6zQCXtwy+L9fs=", "ref": "refs/heads/master", - "rev": "4b4fca3224ab977dc515ac0bb78d00b3dfa71e00", - "revCount": 819, + "rev": "b66495fcc5022681b56b61f928c7acbe910e722c", + "revCount": 821, "type": "git", "url": "https://git.outfoxxed.me/outfoxxed/quickshell" }, @@ -342,11 +360,11 @@ }, "unstable-pkgs": { "locked": { - "lastModified": 1780686478, - "narHash": "sha256-zLfvU5+FhNZVpdpAdCToKbxvJMtGWPOw1xfjkV45od0=", + "lastModified": 1781173989, + "narHash": "sha256-fnzKKPvS+oieI/pTzotA5tkoM47EB1NpaBcgk4R97hE=", "owner": "nixos", "repo": "nixpkgs", - "rev": "891eaa77f0eed53352194677991bd30978f9b0ad", + "rev": "8c91a71d13451abc40eb9dae8910f972f979852f", "type": "github" }, "original": { diff --git a/nix/modules/home/hosts/wisteria.nix b/nix/modules/home/hosts/wisteria.nix index a9f3b1e..9ec5e19 100755 --- a/nix/modules/home/hosts/wisteria.nix +++ b/nix/modules/home/hosts/wisteria.nix @@ -242,6 +242,17 @@ in { enable = true; configFile = { + # Caelestia (theme.enableGtk) rewrites ~/.config/gtk-4.0/gtk.css at + # runtime on every scheme change with its dynamic palette. The copy the + # HM gtk module generates (because gtk.theme is set) is just an @import + # of adw-gtk3-dark, redundant on wisteria where GTK_THEME=adw-gtk3-dark + # is exported system-wide. Without this, every second `nixos-rebuild + # switch` aborts HM activation: backing up the live Caelestia file + # collides with the stale gtk.css.backup left by the previous + # activation (same class as the cava/config and Ghostty theme-seed + # collisions). Let Caelestia own the file outright. + "gtk-4.0/gtk.css".enable = false; + "wofi/style.css".text = '' window { margin: 0px; @@ -327,13 +338,16 @@ in { # are vendored + patched in ./wisteria/ghostty-shaders/ (rather than # referenced from the read-only Nix store) so their top-of-file consts # match the README example and stay editable in-tree. - # `custom-shader-animation = always` avoids the ripple-on-unfocus freeze - # noted in the upstream README (line cursor turns hollow on unfocus). + # `custom-shader-animation = true` (the default) only renders while the + # cursor is active, so idle windows don't burn GPU (with `always`, three + # idle terminals drew ~25W / 87°C and spun the fans). The upstream + # README's ripple-on-unfocus freeze doesn't apply: our vendored shaders + # gate on the Ghostty 1.3+ iFocus / cursor-style uniforms. custom-shader = [ "${./wisteria/ghostty-shaders/cursor_warp.glsl}" "${./wisteria/ghostty-shaders/ripple_cursor.glsl}" ]; - custom-shader-animation = "always"; + custom-shader-animation = true; # Theme is regenerated by Caelestia on every scheme change # (template at ~/.config/caelestia/templates/ghostty, output at this @@ -1307,11 +1321,12 @@ in { "$mod SHIFT, N, global, caelestia:clearNotifs" "$mod, Return, exec, ghostty" # Nothing on wisteria spawns a special-left window (that's a dahlia - # thing), so plain togglespecialworkspace is all we need. The old - # ALT_R variant is gone: ALT_R is a keysym, not a valid bind - # modifier (valid mods: SHIFT CAPS CTRL ALT MOD2 MOD3 SUPER MOD5), - # so that bind never registered. + # thing), so plain togglespecialworkspace is all we need. The ALT + # variant matches either Alt key — ALT_R was a keysym, not a valid + # bind modifier (valid mods: SHIFT CAPS CTRL ALT MOD2 MOD3 SUPER + # MOD5), and Hyprland can't restrict a bind to right-alt only. "$mod, backslash, togglespecialworkspace" + "ALT, backslash, togglespecialworkspace" "$mod, 0, workspace, 10" "$mod, TAB, exec, rofi -show window" "$mod, Delete, exec, hyprctl dismissnotify" diff --git a/nix/modules/home/hosts/wisteria/ghostty-shaders/cursor_warp.glsl b/nix/modules/home/hosts/wisteria/ghostty-shaders/cursor_warp.glsl index e0bd8a3..240b1e3 100644 --- a/nix/modules/home/hosts/wisteria/ghostty-shaders/cursor_warp.glsl +++ b/nix/modules/home/hosts/wisteria/ghostty-shaders/cursor_warp.glsl @@ -3,8 +3,11 @@ // smear) rather than the README's "faded warp" tweaks — the fade gradient // and shorter duration made the trail too faint. The ripple_cursor.glsl in // this dir still uses the README's customized ripple values. -// Currently identical to upstream defaults (DURATION 0.2, -// THRESHOLD_MIN_DISTANCE 1.5, FADE_ENABLED 0.0). +// Consts are identical to upstream defaults (DURATION 0.2, +// THRESHOLD_MIN_DISTANCE 1.5, FADE_ENABLED 0.0). One logic patch beyond +// upstream: the trail is gated on iFocus (Ghostty 1.3+) so unfocusing +// mid-animation can't freeze a half-drawn smear on screen (rendering +// stops on unfocus with `custom-shader-animation = true`). // Vendored (rather than referenced from the Nix store) so the top-of-file // consts are editable in-tree. Resync from upstream when the input bumps. @@ -185,7 +188,10 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord){ float baseProgress = iTime - iTimeCursorChange; - if (lineLength > minDist && baseProgress < DURATION - 0.001) { + // iFocus gate (Ghostty 1.3+): rendering stops once the surface is + // unfocused, so a trail mid-animation at that moment would freeze on + // screen. Skip the trail on the final (unfocused) frame instead. + if (lineLength > minDist && baseProgress < DURATION - 0.001 && iFocus > 0) { // defining corners of cursors // Y (Height) with TRAIL_THICKNESS diff --git a/nix/modules/home/hosts/wisteria/ghostty-shaders/ripple_cursor.glsl b/nix/modules/home/hosts/wisteria/ghostty-shaders/ripple_cursor.glsl index d43365e..50e2ab9 100644 --- a/nix/modules/home/hosts/wisteria/ghostty-shaders/ripple_cursor.glsl +++ b/nix/modules/home/hosts/wisteria/ghostty-shaders/ripple_cursor.glsl @@ -6,6 +6,12 @@ // BLUR 3.0 -> 3.5 // ANIMATION_START_OFFSET 0.0 -> 0.01 // (DURATION and RING_THICKNESS already match the upstream defaults.) +// Additionally patched beyond upstream: the ripple is gated on iFocus and +// on hollow-cursor transitions (Ghostty 1.3+ uniforms) so that focus +// changes neither freeze a mid-ripple ring on screen nor fire a spurious +// ripple. This fixes the upstream README's ripple-on-unfocus glitch and +// lets us use `custom-shader-animation = true` (idle windows stop +// rendering, so they don't burn GPU) instead of `always`. // Vendored (rather than referenced from the Nix store) so the top-of-file // consts are editable in-tree. Resync from upstream when the input bumps. // @@ -104,12 +110,25 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord){ float isModeChange = step(widthThresholdNorm, widthChange); + // Suppress the ripple around focus changes (Ghostty 1.3+ uniforms): + // 1. iFocus gate — Ghostty stops scheduling frames once the surface is + // unfocused, so a ripple started by the unfocus event would freeze + // mid-ring on screen. + // 2. hollow-style gate — focus in/out swaps the cursor between its + // normal style and CURSORSTYLE_BLOCK_HOLLOW, which reads as a width + // change and would otherwise fire a spurious ripple on refocus. + // Real vim mode changes (block <-> bar) never involve the hollow + // style, so they still ripple. + float involvesHollow = + float(iCurrentCursorStyle == CURSORSTYLE_BLOCK_HOLLOW || + iPreviousCursorStyle == CURSORSTYLE_BLOCK_HOLLOW); + // ANIMATION float rippleProgress = (iTime - iTimeCursorChange) / DURATION + ANIMATION_START_OFFSET; // don't clamp yet; we need to know if it's > 1.0 (finished) float isAnimating = 1.0 - step(1.0, rippleProgress); // progress < 1.0 ? 1.0: 0.0 - if (isModeChange > 0.0 && isAnimating > 0.0) { + if (isModeChange > 0.0 && isAnimating > 0.0 && iFocus > 0 && involvesHollow < 0.5) { // Apply easing to progress // float easedProgress = rippleProgress; // float easedProgress = easeOutQuad(rippleProgress);