From 075093462231dce3521d984538765091d8f2f1c2 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Fri, 1 May 2026 22:18:37 -0700 Subject: [PATCH] hyprland: clean session startup and scratchpads --- dotfiles/config/hypr/hyprland.lua | 76 +++++++++++++++---- nixos/hyprland.nix | 120 +++++++++++++++++++++++------- 2 files changed, 156 insertions(+), 40 deletions(-) diff --git a/dotfiles/config/hypr/hyprland.lua b/dotfiles/config/hypr/hyprland.lua index 4c0349a4..b2b12d09 100644 --- a/dotfiles/config/hypr/hyprland.lua +++ b/dotfiles/config/hypr/hyprland.lua @@ -8,7 +8,8 @@ local launcher_command = shell_ui_command .. " launcher" local run_menu = shell_ui_command .. " run" local max_workspace = 9 -local scratchpad_top_margin = 60 +local scratchpad_size_ratio = 0.95 +local dropdown_height_ratio = 0.5 local columns_layout = "nStack" local large_main_layout = "master" local grid_layout = "grid" @@ -221,13 +222,12 @@ local function apply_hyprwinview_config() app_icon_backplate_col = "rgba(00000066)", app_icon_backplate_padding = 6, animation = "workspace_zoom", - animation_in_ms = 340, - animation_out_ms = 170, - animation_speed = 0.5, + animation_in_ms = 280, + animation_out_ms = 220, + animation_speed = 1.0, animation_scale = 0.94, animation_stagger_ms = 16, animation_stagger_max_ms = 120, - animation_workspace_zoom_stage_ratio = 0.50, }, }, }) @@ -1076,6 +1076,53 @@ local function scratchpad_workspace(name) return "special:scratch-" .. name end +local function as_number(value, default) + local number = tonumber(value) + if number == nil then + return default + end + return number +end + +local function logical_monitor_dimension(value, scale) + value = as_number(value, 0) + scale = as_number(scale, 1) + if scale <= 0 then + scale = 1 + end + return math.floor((value / scale) + 0.5) +end + +local function monitor_workarea(monitor) + local width = logical_monitor_dimension(monitor.width, monitor.scale) + local height = logical_monitor_dimension(monitor.height, monitor.scale) + local reserved = monitor.reserved or {} + local left = math.floor(as_number(reserved[1], 0)) + local top = math.floor(as_number(reserved[2], 0)) + local right = math.floor(as_number(reserved[3], 0)) + local bottom = math.floor(as_number(reserved[4], 0)) + local work_width = width - left - right + local work_height = height - top - bottom + + if work_width <= 0 then + left = 0 + right = 0 + work_width = width + end + if work_height <= 0 then + top = 0 + bottom = 0 + work_height = height + end + + return { + x = math.floor(as_number(monitor.x, 0)) + left, + y = math.floor(as_number(monitor.y, 0)) + top, + width = work_width, + height = work_height, + } +end + local function matching_scratchpad_windows(name) local def = scratchpads[name] local windows = {} @@ -1099,20 +1146,21 @@ local function apply_scratchpad_geometry(name, window, target_monitor) return end + local workarea = monitor_workarea(monitor) local width local height local x local y if def.dropdown then - width = monitor.width - height = math.floor(monitor.height * 0.5) - x = monitor.x - y = monitor.y + scratchpad_top_margin + width = workarea.width + height = math.floor(workarea.height * dropdown_height_ratio) + x = workarea.x + y = workarea.y else - width = math.floor(monitor.width * 0.95) - height = math.min(math.floor(monitor.height * 0.95), monitor.height - scratchpad_top_margin) - x = monitor.x + math.floor((monitor.width - width) / 2) - y = monitor.y + scratchpad_top_margin + width = math.floor(workarea.width * scratchpad_size_ratio) + height = math.floor(workarea.height * scratchpad_size_ratio) + x = workarea.x + math.floor((workarea.width - width) / 2) + y = workarea.y + math.floor((workarea.height - height) / 2) end local selector = window_selector(window) @@ -1954,7 +2002,7 @@ hl.on("hyprland.start", function() apply_hyprexpo_config() apply_hyprwinview_config() apply_rules() - hl.exec_cmd("sh -lc 'export IMALISON_SESSION_TYPE=wayland; dbus-update-activation-environment --systemd XDG_RUNTIME_DIR WAYLAND_DISPLAY DISPLAY XAUTHORITY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP XDG_SESSION_TYPE IMALISON_SESSION_TYPE; systemctl --user start graphical-session.target hyprland-session.target'") + hl.exec_cmd("sh -lc '/run/current-system/sw/bin/uwsm finalize HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP XDG_SESSION_TYPE XAUTHORITY IMALISON_SESSION_TYPE=wayland IMALISON_WINDOW_MANAGER=hyprland || dbus-update-activation-environment --systemd XDG_RUNTIME_DIR WAYLAND_DISPLAY DISPLAY XAUTHORITY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP XDG_SESSION_TYPE IMALISON_SESSION_TYPE IMALISON_WINDOW_MANAGER; systemctl --user start hyprland-session.target'") hl.exec_cmd("hypridle") hl.exec_cmd("wl-paste --type text --watch cliphist store") hl.exec_cmd("wl-paste --type image --watch cliphist store") diff --git a/nixos/hyprland.nix b/nixos/hyprland.nix index d548ea27..64965c5e 100644 --- a/nixos/hyprland.nix +++ b/nixos/hyprland.nix @@ -5,10 +5,90 @@ makeEnable, inputs, ... -}: -let +}: let system = pkgs.stdenv.hostPlatform.system; hyprlandInput = inputs.hyprland; + baseHyprlandPackage = hyprlandInput.packages.${system}.hyprland; + cleanupStaleGraphicalSession = pkgs.writeShellScript "cleanup-stale-graphical-session" '' + set -u + + # Only clean targets that are plainly stale. If a compositor is still + # running, let the active session own its own shutdown path. + if ${pkgs.procps}/bin/pgrep -u "$(${pkgs.coreutils}/bin/id -u)" -f '(^|/)(Hyprland|\.Hyprland-wrapped|river|kwin_wayland)( |$)' >/dev/null 2>&1; then + exit 0 + fi + + ${pkgs.systemd}/bin/systemctl --user stop \ + hyprland-session.target \ + river-xmonad-session.target \ + graphical-session.target \ + graphical-session-pre.target \ + tray.target \ + 2>/dev/null || true + + ${pkgs.systemd}/bin/systemctl --user unset-environment \ + WAYLAND_DISPLAY \ + DISPLAY \ + XAUTHORITY \ + HYPRLAND_INSTANCE_SIGNATURE \ + XDG_CURRENT_DESKTOP \ + XDG_SESSION_DESKTOP \ + XDG_SESSION_TYPE \ + IMALISON_SESSION_TYPE \ + IMALISON_WINDOW_MANAGER \ + 2>/dev/null || true + + ${pkgs.systemd}/bin/systemctl --user reset-failed 2>/dev/null || true + ''; + makeHyprlandLuaPackage = package: + (pkgs.symlinkJoin { + name = "${package.name}-lua-config"; + inherit (package) version; + paths = [package]; + passthru = + (package.passthru or {}) + // { + providedSessions = package.passthru.providedSessions or ["hyprland"]; + }; + postBuild = '' + mkdir -p "$out/bin" "$out/share/wayland-sessions" + printf '%s\n' \ + '#!${pkgs.runtimeShell}' \ + 'config_path="''${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprland.lua"' \ + 'exec "${package}/bin/Hyprland" --config "$config_path" "$@"' \ + > "$out/bin/start-hyprland-lua" + chmod +x "$out/bin/start-hyprland-lua" + + printf '%s\n' \ + '#!${pkgs.runtimeShell}' \ + '${cleanupStaleGraphicalSession}' \ + 'exec ${pkgs.uwsm}/bin/uwsm start -e -D Hyprland hyprland.desktop' \ + > "$out/bin/start-hyprland-uwsm-clean" + chmod +x "$out/bin/start-hyprland-uwsm-clean" + + rm -f "$out/share/wayland-sessions/hyprland.desktop" + substitute \ + "${package}/share/wayland-sessions/hyprland.desktop" \ + "$out/share/wayland-sessions/hyprland.desktop" \ + --replace-fail \ + "Exec=${package}/bin/start-hyprland" \ + "Exec=$out/bin/start-hyprland-lua" + + rm -f "$out/share/wayland-sessions/hyprland-uwsm.desktop" + substitute \ + "${package}/share/wayland-sessions/hyprland-uwsm.desktop" \ + "$out/share/wayland-sessions/hyprland-uwsm.desktop" \ + --replace-fail \ + "Exec=uwsm start -e -D Hyprland hyprland.desktop" \ + "Exec=$out/bin/start-hyprland-uwsm-clean" + ''; + }) + // { + override = {enableXWayland ? true, ...} @ args: + makeHyprlandLuaPackage (package.override args); + overrideAttrs = f: makeHyprlandLuaPackage (package.overrideAttrs f); + }; + hyprlandPackage = makeHyprlandLuaPackage baseHyprlandPackage; hyprlandPluginPackages = [ inputs.hyprNStack.packages.${system}.hyprNStack inputs.hyprland-plugins-lua.packages.${system}.hyprexpo @@ -20,7 +100,7 @@ let runtimeInputs = [ pkgs.python3 pkgs.rofi - hyprlandInput.packages.${system}.hyprland + hyprlandPackage ]; text = '' exec python3 ${../dotfiles/lib/bin/hypr_rofi_window} "$@" @@ -78,7 +158,6 @@ let options = "persist"; rules = "float;size monitor_w monitor_h*0.5;move 0 60;noborder;noshadow;animation slide"; }; - }; enabledModule = makeEnable config "myModules.hyprland" true { # Install both shell service units so `desktop_shell_ui set ...` can switch @@ -87,7 +166,7 @@ let myModules.taffybar.enable = lib.mkDefault true; # Needed for hyprlock authentication without PAM fallback warnings. - security.pam.services.hyprlock = { }; + security.pam.services.hyprlock = {}; # DDC/CI monitor control for keyboard-driven input switching. hardware.i2c = { @@ -98,7 +177,7 @@ let programs.hyprland = { enable = true; # Keep Hyprland and plugins on a matched flake input for ABI compatibility. - package = hyprlandInput.packages.${system}.hyprland; + package = hyprlandPackage; # Let UWSM manage the Hyprland session targets withUWSM = true; }; @@ -106,13 +185,7 @@ let home-manager.sharedModules = [ inputs.hyprscratch.homeModules.default ( - { config, lib, ... }: - let - hyprConfig = - name: - config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/dotfiles/dotfiles/config/hypr/${name}"; - in - { + {lib, ...}: { services.kanshi = { enable = true; systemdTarget = "graphical-session.target"; @@ -157,7 +230,7 @@ let programs.hyprscratch = { enable = false; - settings = { }; + settings = {}; }; xdg.configFile."hyprscratch/config.conf" = lib.mkIf false { @@ -166,23 +239,18 @@ let }; }; - xdg.configFile."hypr/hyprland.lua" = { - force = true; - source = hyprConfig "hyprland.lua"; - }; - - xdg.configFile."hypr/hypridle.conf".source = hyprConfig "hypridle.conf"; - - xdg.configFile."hypr/hyprlock.conf".source = hyprConfig "hyprlock.conf"; - xdg.configFile."hypr/scripts".enable = false; + + xdg.configFile."systemd/user/wayland-wm@hyprland.desktop.service.d/10-cleanup-stale-session.conf".text = '' + [Service] + ExecStopPost=${cleanupStaleGraphicalSession} + ''; } ) ]; # Hyprland-specific packages - environment.systemPackages = - with pkgs; + environment.systemPackages = with pkgs; [ # Hyprland utilities hyprpaper # Wallpaper @@ -207,4 +275,4 @@ let ++ hyprlandPluginPackages; }; in -enabledModule + enabledModule