From 74bd7e76da624f41913d7f7dad248fd34d34c94d Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Thu, 14 May 2026 23:30:58 -0700 Subject: [PATCH] nixos: add hyprland plugin dev tooling --- dotfiles/config/hypr/hyprland/binds.lua | 31 +- dotfiles/config/hypr/hyprland/core.lua | 57 ++- dotfiles/config/hypr/hyprland/events.lua | 14 +- dotfiles/config/hypr/hyprland/settings.lua | 77 +++- dotfiles/config/hypr/hyprland/state.lua | 21 +- dotfiles/config/taffybar/taffybar | 2 +- dotfiles/lib/bin/hypr_perf_snapshot | 87 ++++ dotfiles/lib/bin/hypr_plugin_dev_session | 416 ++++++++++++++++++ nixos/flake.lock | 64 ++- nixos/flake.nix | 19 +- nixos/flake/per-system.nix | 13 +- nixos/hyprland.nix | 19 +- nixos/packages/hyprexpo-plus/default.nix | 75 ++++ nixos/packages/hyprspace-lua-api.patch | 10 +- ...rwobbly-safe-geometry-and-idle-timer.patch | 270 ++++++++++++ nixos/taffybar.nix | 51 +++ 16 files changed, 1164 insertions(+), 62 deletions(-) create mode 100755 dotfiles/lib/bin/hypr_perf_snapshot create mode 100755 dotfiles/lib/bin/hypr_plugin_dev_session create mode 100644 nixos/packages/hyprexpo-plus/default.nix create mode 100644 nixos/packages/hyprwobbly-safe-geometry-and-idle-timer.patch diff --git a/dotfiles/config/hypr/hyprland/binds.lua b/dotfiles/config/hypr/hyprland/binds.lua index 4d7738ba..52e2e153 100644 --- a/dotfiles/config/hypr/hyprland/binds.lua +++ b/dotfiles/config/hypr/hyprland/binds.lua @@ -26,6 +26,7 @@ function M.setup(ctx) bind(main_mod .. " + Q", exec("hyprctl reload"), desc("Reload Hyprland")) bind(main_mod .. " + R", exec("hyprctl reload"), desc("Reload Hyprland")) bind(hyper .. " + SHIFT + L", exec("hyprlock"), desc("Lock screen")) + bind(hyper .. " + SHIFT + V", toggle_visual_performance_mode, desc("Toggle Hyprland performance mode")) bind(hyper .. " + slash", function() hl.exec_cmd("toggle_taffybar") refresh_monitor_reserved_cache(0.25) @@ -88,11 +89,7 @@ function M.setup(ctx) local function setup_window_overview_bindings() bind(main_mod .. " + SHIFT + C", hl.dsp.window.close(), desc("Close active window")) bind(main_mod .. " + SHIFT + Q", hl.dsp.exit(), desc("Exit Hyprland")) - bind(main_mod .. " + Tab", hyprwinview({ - action = "show", - start_in_filter_mode = true, - default_action = "select", - }), desc("Show window overview", overview_bind_opts)) + bind(main_mod .. " + Tab", hyprexpo("toggle"), desc("Toggle workspace expo", overview_bind_opts)) bind(main_mod .. " + SHIFT + Tab", hyprwinview({ action = "show", include_current_workspace = false, @@ -105,7 +102,7 @@ function M.setup(ctx) start_in_filter_mode = true, default_action = "select", }), desc("Toggle alternate window overview", overview_bind_opts)) - bind("ALT + SHIFT + Tab", hyprexpo("open"), desc("Open workspace expo", overview_bind_opts)) + bind("ALT + SHIFT + Tab", hyprexpo("on"), desc("Open workspace expo", overview_bind_opts)) bind(main_mod .. " + G", hyprwinview({ action = "show", start_in_filter_mode = true, @@ -201,6 +198,28 @@ function M.setup(ctx) bind("Escape", hl.dsp.submap("reset"), desc("Exit window picker")) bind("catchall", hl.dsp.submap("reset"), desc("Exit window picker")) end) + + hl.define_submap("hyprexpo", function() + bind("W", hyprexpo_dispatch("kb_focus", "up"), desc("Focus workspace tile above")) + bind("A", hyprexpo_dispatch("kb_focus", "left"), desc("Focus workspace tile to the left")) + bind("S", hyprexpo_dispatch("kb_focus", "down"), desc("Focus workspace tile below")) + bind("D", hyprexpo_dispatch("kb_focus", "right"), desc("Focus workspace tile to the right")) + bind("Up", hyprexpo_dispatch("kb_focus", "up"), desc("Focus workspace tile above")) + bind("Left", hyprexpo_dispatch("kb_focus", "left"), desc("Focus workspace tile to the left")) + bind("Down", hyprexpo_dispatch("kb_focus", "down"), desc("Focus workspace tile below")) + bind("Right", hyprexpo_dispatch("kb_focus", "right"), desc("Focus workspace tile to the right")) + bind("Return", hyprexpo_dispatch("kb_confirm"), desc("Select focused workspace tile")) + bind("Space", hyprexpo_dispatch("kb_confirm"), desc("Select focused workspace tile")) + + for i = 1, max_workspace do + local workspace_id = i + local key = tostring(i % 10) + bind(key, hyprexpo_dispatch("kb_selectn", workspace_id), desc("Select workspace " .. workspace_id)) + end + + bind("Escape", hyprexpo("off"), desc("Close workspace expo")) + bind("catchall", hyprexpo("off"), desc("Close workspace expo")) + end) end local function setup_window_resize_and_monitor_bindings() diff --git a/dotfiles/config/hypr/hyprland/core.lua b/dotfiles/config/hypr/hyprland/core.lua index 5d49d5d5..b81d61e5 100644 --- a/dotfiles/config/hypr/hyprland/core.lua +++ b/dotfiles/config/hypr/hyprland/core.lua @@ -14,6 +14,7 @@ function M.setup(ctx) end verify_config = command_line_contains("--verify-config") + dev_session = os.getenv("IMALISON_HYPRLAND_DEV_SESSION") == "1" local function exec(command) return hl.dsp.exec_cmd(command) @@ -105,12 +106,11 @@ function M.setup(ctx) return "address:" .. tostring(window.address) end - local function hyprexpo(action) - action = action or "toggle" + local function hyprexpo_call(method, arg) return function() - overview_trace("hyprexpo " .. tostring(action)) - if hl.plugin and hl.plugin.hyprexpo and hl.plugin.hyprexpo.expo then - hl.plugin.hyprexpo.expo(action) + overview_trace("hyprexpo:" .. method .. (arg and (" " .. tostring(arg)) or "")) + if hl.plugin and hl.plugin.hyprexpo and hl.plugin.hyprexpo[method] then + hl.plugin.hyprexpo[method](arg) else hl.notification.create({ text = "hyprexpo is not loaded", @@ -123,6 +123,14 @@ function M.setup(ctx) end end + local function hyprexpo(action) + return hyprexpo_call("expo", action or "toggle") + end + + local function hyprexpo_dispatch(dispatcher, arg) + return hyprexpo_call(dispatcher, arg) + end + local function hyprwinview(action) return function() local label = "hyprwinview" @@ -219,15 +227,41 @@ function M.setup(ctx) plugin = { hyprexpo = { columns = 3, - gap_size = 5, - bg_col = "rgba(111111ff)", + gaps_in = 5, + gaps_out = 0, + bg_col = 0xff111111, workspace_method = "center current", skip_empty = false, - max_workspace = max_workspace, - show_workspace_numbers = true, - workspace_number_color = "rgba(edb443ff)", gesture_distance = 200, - cancel_key = "escape", + keynav_enable = 1, + keynav_wrap_h = 1, + keynav_wrap_v = 1, + keynav_reading_order = 0, + border_width = 2, + border_color_current = "rgb(66ccff)", + border_color_focus = "rgb(edb443)", + border_color_hover = "rgb(aabbcc)", + tile_rounding = 5, + tile_rounding_power = 2.0, + label_enable = 1, + label_font_size = 28, + label_text_mode = "token", + label_token_map = "1,2,3,4,5,6,7,8,9", + label_position = "center", + label_offset_x = 6, + label_offset_y = 6, + label_show = "always", + label_color_default = 0xffffffff, + label_color_hover = 0xffeeeeee, + label_color_focus = 0xffedb443, + label_color_current = 0xff66ccff, + label_bg_enable = 1, + label_bg_color = 0xcc000000, + label_bg_shape = "rounded", + label_bg_rounding = 10, + label_padding = 12, + label_font_bold = 1, + label_pixel_snap = 1, }, }, }) @@ -567,6 +601,7 @@ function M.setup(ctx) ctx.overview_trace = overview_trace ctx.window_selector = window_selector ctx.hyprexpo = hyprexpo + ctx.hyprexpo_dispatch = hyprexpo_dispatch ctx.hyprwinview = hyprwinview ctx.workspacehistory = workspacehistory ctx.hyprspace = hyprspace diff --git a/dotfiles/config/hypr/hyprland/events.lua b/dotfiles/config/hypr/hyprland/events.lua index 7a0dab42..21481cdd 100644 --- a/dotfiles/config/hypr/hyprland/events.lua +++ b/dotfiles/config/hypr/hyprland/events.lua @@ -40,12 +40,16 @@ function M.setup(ctx) apply_nstack_config() apply_hyprexpo_config() apply_hyprwinview_config() + apply_hyprwobbly_config() apply_hyprglass_config() + apply_visual_performance_mode() apply_rules() - 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") + if not dev_session then + 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") + end write_layout_state() schedule_nstack_count_update() refresh_monitor_reserved_cache(0.25) @@ -55,7 +59,9 @@ function M.setup(ctx) hl.on("config.reloaded", apply_nstack_config) hl.on("config.reloaded", apply_hyprexpo_config) hl.on("config.reloaded", apply_hyprwinview_config) + hl.on("config.reloaded", apply_hyprwobbly_config) hl.on("config.reloaded", apply_hyprglass_config) + hl.on("config.reloaded", apply_visual_performance_mode) hl.on("config.reloaded", apply_rules) hl.on("config.reloaded", refresh_shell_workarea_and_scratchpads) hl.on("layer.opened", refresh_shell_workarea_and_scratchpads) diff --git a/dotfiles/config/hypr/hyprland/settings.lua b/dotfiles/config/hypr/hyprland/settings.lua index 14b1b21d..349a292e 100644 --- a/dotfiles/config/hypr/hyprland/settings.lua +++ b/dotfiles/config/hypr/hyprland/settings.lua @@ -2,6 +2,10 @@ local M = {} function M.setup(ctx) local _ENV = ctx + local function plugin_path(env_name, default) + return os.getenv(env_name) or default + end + if enable_nstack and not verify_config then hl.plugin.load("/run/current-system/sw/lib/libhyprNStack.so") end @@ -9,7 +13,7 @@ function M.setup(ctx) hl.plugin.load("/run/current-system/sw/lib/libhyprexpo.so") end if enable_hyprspace and not verify_config then - hl.plugin.load("/run/current-system/sw/lib/libHyprspace.so") + hl.plugin.load(plugin_path("HYPRLAND_HYPRSPACE_PLUGIN", "/run/current-system/sw/lib/libHyprspace.so")) end if enable_hyprwinview and not verify_config then hl.plugin.load("/run/current-system/sw/lib/libhyprwinview.so") @@ -17,6 +21,9 @@ function M.setup(ctx) if enable_workspace_history and not verify_config then hl.plugin.load("/run/current-system/sw/lib/libhypr-workspace-history.so") end + if enable_hyprwobbly and not verify_config then + hl.plugin.load("/run/current-system/sw/lib/libhyprwobbly.so") + end if enable_hyprglass and not verify_config then hl.plugin.load("/run/current-system/sw/lib/hyprglass.so") end @@ -173,6 +180,71 @@ function M.setup(ctx) }) end + local function apply_hyprwobbly_config() + if verify_config or not enable_hyprwobbly then + return + end + + hl.config({ + plugin = { + hyprwobbly = { + enabled = hypr_visual_performance_mode and 0 or 1, + mode = "always", + grid_width = 4, + grid_height = 4, + tiles_x = 12, + tiles_y = 12, + spring_k = 18.0, + friction = 8.0, + mass = 12.0, + move_factor = 0.65, + resize_factor = 0.45, + max_warp = 140.0, + }, + }, + }) + end + + local function apply_visual_performance_mode() + if verify_config then + return + end + + local visual_effects_enabled = not hypr_visual_performance_mode + hl.config({ + decoration = { + blur = { + enabled = visual_effects_enabled, + }, + }, + animations = { + enabled = visual_effects_enabled, + }, + }) + + if enable_hyprwobbly then + hl.config({ + plugin = { + hyprwobbly = { + enabled = visual_effects_enabled and 1 or 0, + }, + }, + }) + end + end + + local function toggle_visual_performance_mode() + hypr_visual_performance_mode = not hypr_visual_performance_mode + apply_visual_performance_mode() + hl.notification.create({ + text = "Hyprland performance mode: " .. (hypr_visual_performance_mode and "on" or "off"), + duration = 1800, + icon = hypr_visual_performance_mode and notification_icons.warning or notification_icons.ok, + color = hypr_visual_performance_mode and "rgba(edb443ff)" or "rgba(33ccffee)", + font_size = 13, + }) + end + local function apply_rules() if verify_config then return @@ -243,6 +315,9 @@ function M.setup(ctx) ctx.apply_rules = apply_rules ctx.apply_hyprglass_config = apply_hyprglass_config + ctx.apply_hyprwobbly_config = apply_hyprwobbly_config + ctx.apply_visual_performance_mode = apply_visual_performance_mode + ctx.toggle_visual_performance_mode = toggle_visual_performance_mode end return M diff --git a/dotfiles/config/hypr/hyprland/state.lua b/dotfiles/config/hypr/hyprland/state.lua index c42850e9..cd83779a 100644 --- a/dotfiles/config/hypr/hyprland/state.lua +++ b/dotfiles/config/hypr/hyprland/state.lua @@ -1,3 +1,20 @@ +local function env_bool(name, default) + local value = os.getenv(name) + if value == nil or value == "" then + return default + end + + value = value:lower() + if value == "1" or value == "true" or value == "yes" or value == "on" then + return true + end + if value == "0" or value == "false" or value == "no" or value == "off" then + return false + end + + return default +end + local shell_ui_command = "hypr_shell_ui" local columns_layout = "nStack" local large_main_layout = "master" @@ -46,10 +63,12 @@ return { current_layout = columns_layout, enable_nstack = true, enable_hyprexpo = true, - enable_hyprspace = false, + enable_hyprspace = env_bool("HYPRLAND_ENABLE_HYPRSPACE", false), enable_hyprwinview = true, enable_workspace_history = true, + enable_hyprwobbly = true, enable_hyprglass = false, + hypr_visual_performance_mode = false, configure_nstack_plugin_from_lua = false, workspace_layouts = {}, minimized_windows = {}, diff --git a/dotfiles/config/taffybar/taffybar b/dotfiles/config/taffybar/taffybar index b4b98bf8..368f6a40 160000 --- a/dotfiles/config/taffybar/taffybar +++ b/dotfiles/config/taffybar/taffybar @@ -1 +1 @@ -Subproject commit b4b98bf8a20c5f5dfcd510b19768fd127542b69e +Subproject commit 368f6a40a2bea44ada4819749220a506c75073c1 diff --git a/dotfiles/lib/bin/hypr_perf_snapshot b/dotfiles/lib/bin/hypr_perf_snapshot new file mode 100755 index 00000000..4b5fe15c --- /dev/null +++ b/dotfiles/lib/bin/hypr_perf_snapshot @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +set -u + +runtime_dir="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" +timestamp="$(date +%Y%m%d-%H%M%S)" +out="${1:-$runtime_dir/hypr-perf-snapshot-$timestamp.log}" + +mkdir -p "$(dirname "$out")" + +latest_hypr_instance() { + local hypr_dir="$runtime_dir/hypr" + [[ -d "$hypr_dir" ]] || return 1 + find "$hypr_dir" -mindepth 2 -maxdepth 2 -name .socket.sock -printf '%T@ %h\n' 2>/dev/null | + sort -nr | + awk 'NR == 1 { n = split($2, parts, "/"); print parts[n] }' +} + +if [[ -z "${HYPRLAND_INSTANCE_SIGNATURE:-}" ]]; then + signature="$(latest_hypr_instance || true)" + if [[ -n "$signature" ]]; then + export HYPRLAND_INSTANCE_SIGNATURE="$signature" + fi +fi + +section() { + printf '\n## %s\n' "$1" +} + +run_optional() { + local label="$1" + shift + section "$label" + "$@" 2>&1 || true +} + +{ + section "Snapshot" + date --iso-8601=seconds + printf 'host=%s\n' "$(hostname)" + printf 'runtime_dir=%s\n' "$runtime_dir" + printf 'hyprland_instance=%s\n' "${HYPRLAND_INSTANCE_SIGNATURE:-}" + uptime + printf 'loadavg=' + cat /proc/loadavg 2>/dev/null || true + + if command -v hyprctl >/dev/null 2>&1; then + run_optional "Hyprland Version" hyprctl version + run_optional "Hyprland Monitors" hyprctl monitors + plugins="$(hyprctl plugin list 2>&1 || true)" + section "Hyprland Plugins" + printf '%s\n' "$plugins" + section "Hyprland Runtime Options" + for option in \ + animations:enabled \ + decoration:blur:enabled \ + render:direct_scanout; do + printf '\n### %s\n' "$option" + hyprctl getoption "$option" 2>&1 || true + done + if printf '%s\n' "$plugins" | grep -q 'Plugin hyprwobbly'; then + printf '\n### plugin:hyprwobbly:enabled\n' + hyprctl getoption plugin:hyprwobbly:enabled 2>&1 || true + fi + else + section "Hyprland" + printf 'hyprctl not found\n' + fi + + run_optional "Top Processes By CPU" ps -eo pid,ppid,comm,pcpu,pmem,args --sort=-pcpu + run_optional "Top Threads By CPU" ps -eLo pid,tid,comm,pcpu,pmem --sort=-pcpu + + if command -v nvidia-smi >/dev/null 2>&1; then + run_optional "NVIDIA GPU" nvidia-smi + fi + + section "DRM GPU Busy" + for busy in /sys/class/drm/card*/device/gpu_busy_percent; do + [[ -e "$busy" ]] || continue + printf '%s=' "$busy" + cat "$busy" 2>/dev/null || true + done + + section "Recent User Unit Failures" + systemctl --user --no-pager --failed 2>&1 || true +} >"$out" + +printf '%s\n' "$out" diff --git a/dotfiles/lib/bin/hypr_plugin_dev_session b/dotfiles/lib/bin/hypr_plugin_dev_session new file mode 100755 index 00000000..c64a97e9 --- /dev/null +++ b/dotfiles/lib/bin/hypr_plugin_dev_session @@ -0,0 +1,416 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +usage() { + cat <<'EOF' +Usage: hypr_plugin_dev_session [options] + +Launch a disposable nested Hyprland compositor for plugin development, then +build/load a plugin only into that nested instance. + +Options: + --repo DIR Plugin repo to build. Defaults to $PWD if it has a flake, + otherwise /home/imalison/Projects/hyprwobbly when present. + --attr ATTR Nix build attr. Default: .#hyprwobbly + --plugin PATH Load an already-built plugin .so instead of building. + --hyprland-bin PATH Hyprland binary to run. Defaults to the plugin repo's + Hyprland flake input when building, otherwise PATH. + --system-hyprland Force /run/current-system/PATH Hyprland instead of the + plugin repo's Hyprland flake input. + --config PATH Run Hyprland with this config instead of a generated + minimal config. + --user-config Run the normal user Lua config. + --enable-hyprspace Set HYPRLAND_ENABLE_HYPRSPACE=1 for the nested config. + --no-load Start the nested compositor but do not load a plugin. + --detach Leave the nested compositor running and exit after setup. + --keep-tmp Do not delete the generated config/log directory. + --size WxH Nested monitor size. Default: 1280x720 + --terminal COMMAND Command to open in the nested compositor. + --exit-after-ready Exit the nested compositor after startup checks. + -h, --help Show this help. + +Examples: + hypr_plugin_dev_session + hypr_plugin_dev_session --repo ~/Projects/hyprwobbly --attr .#hyprwobbly + hypr_plugin_dev_session --plugin "$PWD/result/lib/libhyprwobbly.so" + hypr_plugin_dev_session --user-config --enable-hyprspace --no-load +EOF +} + +die() { + printf 'hypr_plugin_dev_session: %s\n' "$*" >&2 + exit 1 +} + +runtime_dir="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" +repo="" +attr=".#hyprwobbly" +plugin_path="" +plugin_path_from_arg=0 +hyprland_bin="" +force_system_hyprland=0 +load_plugin=1 +config_arg="" +enable_hyprspace=0 +detach=0 +keep_tmp=0 +size="1280x720" +terminal_cmd="" +exit_after_ready=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + --repo) + repo="${2:-}" + [[ -n "$repo" ]] || die "--repo requires a directory" + shift 2 + ;; + --attr) + attr="${2:-}" + [[ -n "$attr" ]] || die "--attr requires a Nix attr" + shift 2 + ;; + --plugin) + plugin_path="${2:-}" + [[ -n "$plugin_path" ]] || die "--plugin requires a path" + plugin_path_from_arg=1 + shift 2 + ;; + --hyprland-bin) + hyprland_bin="${2:-}" + [[ -n "$hyprland_bin" ]] || die "--hyprland-bin requires a path" + shift 2 + ;; + --system-hyprland) + force_system_hyprland=1 + shift + ;; + --config) + config_arg="${2:-}" + [[ -n "$config_arg" ]] || die "--config requires a path" + shift 2 + ;; + --user-config) + config_arg="${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprland.lua" + shift + ;; + --enable-hyprspace) + enable_hyprspace=1 + shift + ;; + --no-load) + load_plugin=0 + shift + ;; + --detach) + detach=1 + shift + ;; + --keep-tmp) + keep_tmp=1 + shift + ;; + --size) + size="${2:-}" + [[ "$size" =~ ^[0-9]+x[0-9]+$ ]] || die "--size must look like 1280x720" + shift 2 + ;; + --terminal) + terminal_cmd="${2:-}" + [[ -n "$terminal_cmd" ]] || die "--terminal requires a command" + shift 2 + ;; + --exit-after-ready) + exit_after_ready=1 + shift + ;; + -h | --help) + usage + exit 0 + ;; + *) + die "unknown argument: $1" + ;; + esac +done + +command -v hyprctl >/dev/null 2>&1 || die "hyprctl is not on PATH" +command -v jq >/dev/null 2>&1 || die "jq is not on PATH" + +parent_wayland_display() { + if [[ -n "${WAYLAND_DISPLAY:-}" && -S "$runtime_dir/$WAYLAND_DISPLAY" ]]; then + printf '%s\n' "$WAYLAND_DISPLAY" + return 0 + fi + + local from_instances + from_instances="$(hyprctl instances -j 2>/dev/null | jq -r 'max_by(.time) | .wl_socket // empty' 2>/dev/null || true)" + if [[ -n "$from_instances" && -S "$runtime_dir/$from_instances" ]]; then + printf '%s\n' "$from_instances" + return 0 + fi + + local lock + lock="$(find "$runtime_dir/hypr" -mindepth 2 -maxdepth 2 -name hyprland.lock -printf '%T@ %p\n' 2>/dev/null | sort -nr | awk 'NR == 1 { print $2 }')" + if [[ -n "$lock" ]]; then + local from_lock + from_lock="$(sed -n '2p' "$lock" 2>/dev/null || true)" + if [[ -n "$from_lock" && -S "$runtime_dir/$from_lock" ]]; then + printf '%s\n' "$from_lock" + return 0 + fi + fi + + return 1 +} + +if [[ -z "$repo" ]]; then + if [[ -f "$PWD/flake.nix" ]]; then + repo="$PWD" + elif [[ -d /home/imalison/Projects/hyprwobbly ]]; then + repo="/home/imalison/Projects/hyprwobbly" + else + repo="$PWD" + fi +fi +repo="$(realpath "$repo")" + +if [[ -n "$plugin_path" ]]; then + plugin_path="$(realpath "$plugin_path")" + [[ -f "$plugin_path" ]] || die "plugin does not exist: $plugin_path" +fi + +choose_terminal() { + if [[ -n "$terminal_cmd" ]]; then + printf '%s\n' "$terminal_cmd" + return + fi + + for candidate in ghostty foot kitty alacritty wezterm xterm; do + if command -v "$candidate" >/dev/null 2>&1; then + printf '%s\n' "$candidate" + return + fi + done + + printf '%s\n' 'sh -lc "sleep infinity"' +} + +find_built_plugin() { + local result_dir="$repo/result" + [[ -e "$result_dir" ]] || die "build result missing: $result_dir" + + local preferred="$result_dir/lib/libhyprwobbly.so" + if [[ -f "$preferred" ]]; then + printf '%s\n' "$(realpath "$preferred")" + return + fi + + local count + count="$(find -L "$result_dir" -type f -name '*.so' | wc -l)" + [[ "$count" -gt 0 ]] || die "no .so files found under $result_dir" + [[ "$count" -eq 1 ]] || die "multiple .so files found under $result_dir; pass --plugin explicitly" + find -L "$result_dir" -type f -name '*.so' -print -quit +} + +repo_hyprland_bin() { + local flake_ref expr out + flake_ref="$(jq -Rn --arg s "path:$repo" '$s')" + expr="let flake = builtins.getFlake ${flake_ref}; system = builtins.currentSystem; in flake.inputs.hyprland.packages.\${system}.hyprland" + + printf 'Building matching nested Hyprland from %s input\n' "$repo" >&2 + mapfile -t hyprland_outputs < <(nix build --impure --no-link --print-out-paths --expr "$expr") + for out in "${hyprland_outputs[@]}"; do + if [[ -x "$out/bin/Hyprland" ]]; then + printf '%s\n' "$out/bin/Hyprland" + return 0 + fi + done + + return 1 +} + +pid="" +tail_pid="" +tmpdir="$(mktemp -d "$runtime_dir/hypr-plugin-dev.XXXXXX")" +config="$tmpdir/hyprland.conf" +stdout_log="$tmpdir/hyprland.stdout.log" + +if [[ -n "$config_arg" ]]; then + config="$(realpath "$config_arg")" + [[ -f "$config" ]] || die "config does not exist: $config" +fi + +cleanup() { + local status=$? + + if [[ -n "$tail_pid" ]] && kill -0 "$tail_pid" >/dev/null 2>&1; then + kill "$tail_pid" >/dev/null 2>&1 || true + fi + + if [[ "$detach" -eq 0 && -n "$pid" ]] && kill -0 "$pid" >/dev/null 2>&1; then + if [[ -n "${nested_sig:-}" ]]; then + HYPRLAND_INSTANCE_SIGNATURE="$nested_sig" hyprctl dispatch exit >/dev/null 2>&1 || true + sleep 0.5 + fi + kill -- "-$pid" >/dev/null 2>&1 || true + kill "$pid" >/dev/null 2>&1 || true + fi + + if [[ "$keep_tmp" -eq 0 && "$detach" -eq 0 ]]; then + rm -rf "$tmpdir" + fi + + exit "$status" +} +trap cleanup EXIT INT TERM + +if [[ -z "$config_arg" ]]; then + term_for_config="$(choose_terminal)" + cat >"$config" <"$stdout_log" 2>&1 & +pid=$! + +nested_sig="" +for _ in $(seq 1 100); do + if ! kill -0 "$pid" >/dev/null 2>&1; then + printf 'Nested Hyprland exited before becoming ready. Log follows:\n' >&2 + sed -n '1,220p' "$stdout_log" >&2 || true + exit 1 + fi + + nested_sig="$(hyprctl instances -j 2>/dev/null | jq -r --argjson pid "$pid" '.[] | select(.pid == $pid) | .instance' | head -n1)" + if [[ -n "$nested_sig" && "$nested_sig" != "null" ]]; then + break + fi + sleep 0.1 +done + +[[ -n "$nested_sig" && "$nested_sig" != "null" ]] || die "timed out waiting for nested Hyprland instance for pid $pid" + +printf 'Nested Hyprland pid: %s\n' "$pid" +printf 'Nested Hyprland signature: %s\n' "$nested_sig" +printf 'Nested Hyprland log: %s\n' "$stdout_log" +printf 'Target it with:\n HYPRLAND_INSTANCE_SIGNATURE=%q hyprctl ...\n' "$nested_sig" + +HYPRLAND_INSTANCE_SIGNATURE="$nested_sig" hyprctl monitors >/dev/null +HYPRLAND_INSTANCE_SIGNATURE="$nested_sig" hyprctl configerrors || true + +if [[ "$load_plugin" -eq 1 ]]; then + printf 'Loading plugin into nested Hyprland: %s\n' "$plugin_path" + load_output="$(HYPRLAND_INSTANCE_SIGNATURE="$nested_sig" hyprctl plugin load "$plugin_path" 2>&1)" + load_status=$? + printf '%s\n' "$load_output" + sleep 0.5 + + if ! kill -0 "$pid" >/dev/null 2>&1; then + printf 'Nested Hyprland crashed while loading plugin. Log follows:\n' >&2 + sed -n '1,260p' "$stdout_log" >&2 || true + exit 1 + fi + + plugin_list="$(HYPRLAND_INSTANCE_SIGNATURE="$nested_sig" hyprctl plugin list 2>&1)" + printf '%s\n' "$plugin_list" + if [[ "$load_status" -ne 0 || "$load_output" == *"could not be loaded"* || "$plugin_list" == *"no plugins loaded"* ]]; then + die "plugin did not load into nested Hyprland" + fi +fi + +if [[ "$exit_after_ready" -eq 1 ]]; then + printf 'Startup check complete; exiting nested Hyprland.\n' + exit 0 +fi + +if [[ "$detach" -eq 1 ]]; then + trap - EXIT INT TERM + disown "$pid" >/dev/null 2>&1 || true + printf 'Leaving nested Hyprland running. Stop it with:\n HYPRLAND_INSTANCE_SIGNATURE=%q hyprctl dispatch exit\n' "$nested_sig" + printf 'If the Nix wrapper process remains, clean up its process group with:\n kill -- -%q\n' "$pid" + printf 'Generated files remain in %s\n' "$tmpdir" + exit 0 +fi + +printf 'Nested session is running. Press Ctrl-C here to stop it.\n' +tail -n +1 -F "$stdout_log" & +tail_pid=$! +wait "$pid" diff --git a/nixos/flake.lock b/nixos/flake.lock index 1aaf1c72..8b4f5f05 100644 --- a/nixos/flake.lock +++ b/nixos/flake.lock @@ -10,11 +10,11 @@ ] }, "locked": { - "lastModified": 1778647860, - "narHash": "sha256-WIHQcCdzY/2MYYtVZyGkctjsISTsjHLVuunJXt5g92c=", + "lastModified": 1778761768, + "narHash": "sha256-1i9S0IRjUuz98ab9/V2+5ax5YxIDhvl7NuMlBVXJ328=", "owner": "colonelpanic8", "repo": "Hyprspace", - "rev": "a9d7685ef610d5a45c7c4f9f974bdf78443c9829", + "rev": "2ca4f1a47cf2dc569e6e7034e53c4cd9b25e36a8", "type": "github" }, "original": { @@ -161,16 +161,16 @@ ] }, "locked": { - "lastModified": 1778574539, - "narHash": "sha256-4wOF2LWLxWCzE2B7Nud9GZuXGEAWJwvp8XrWx+3p5aY=", + "lastModified": 1778822718, + "narHash": "sha256-2CTjJUnLhYJIu8hW/Gmdhbhvp7RbBYY1mBFNYvzBpxE=", "owner": "colonelpanic8", "repo": "codex-desktop-linux", - "rev": "39671f3a944cdc68d216bd3fcf1f53a5de5c39a9", + "rev": "31c94dd2edfd9a86df263baaec33779123a120f3", "type": "github" }, "original": { "owner": "colonelpanic8", - "ref": "codex/nixos-browser-cache-fixes", + "ref": "reenable-computer-use-in-nix", "repo": "codex-desktop-linux", "type": "github" } @@ -817,28 +817,19 @@ } }, "hyprexpo": { - "inputs": { - "hyprland": [ - "hyprland" - ], - "nixpkgs": [ - "nixpkgs" - ], - "systems": [ - "systems" - ] - }, + "flake": false, "locked": { - "lastModified": 1778658902, - "narHash": "sha256-k2RpS0zA76KZBMR6Uy+WSRJBR6uDsa5m0FxfVKH01PE=", + "lastModified": 1778816761, + "narHash": "sha256-+riuSr1jdxdhfYNtt6S5a6uUUDhYb67gDRKHpk7l1pg=", "owner": "colonelpanic8", - "repo": "hyprexpo", - "rev": "d2786e7c21f412e0f60cc27d6763e9939ef284b0", + "repo": "hyprexpo-plus", + "rev": "4d753224148c45153dbea95b3d6b1897bd71dfbb", "type": "github" }, "original": { "owner": "colonelpanic8", - "repo": "hyprexpo", + "ref": "codex/lua-config-api", + "repo": "hyprexpo-plus", "type": "github" } }, @@ -1202,6 +1193,32 @@ "type": "github" } }, + "hyprwobbly": { + "inputs": { + "hyprland": [ + "hyprland" + ], + "nixpkgs": [ + "nixpkgs" + ], + "systems": [ + "systems" + ] + }, + "locked": { + "lastModified": 1778742689, + "narHash": "sha256-j0/j9/9Pd5nC3jXsLrq9GKshcSg1Ci55E1gg7Qj84Y8=", + "owner": "colonelpanic8", + "repo": "hyprwobbly", + "rev": "d531d3f0b68d6d450bf3038571043c3f39c1fd6e", + "type": "github" + }, + "original": { + "owner": "colonelpanic8", + "repo": "hyprwobbly", + "type": "github" + } + }, "imalison-taffybar": { "inputs": { "flake-utils": [ @@ -1801,6 +1818,7 @@ "hyprutils" ], "hyprwinview": "hyprwinview", + "hyprwobbly": "hyprwobbly", "imalison-taffybar": "imalison-taffybar", "kanshi-sni": "kanshi-sni", "keepbook": "keepbook", diff --git a/nixos/flake.nix b/nixos/flake.nix index c65cbd88..3213c3d5 100644 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -116,12 +116,8 @@ }; hyprexpo = { - url = "github:colonelpanic8/hyprexpo"; - inputs = { - hyprland.follows = "hyprland"; - nixpkgs.follows = "nixpkgs"; - systems.follows = "systems"; - }; + url = "github:colonelpanic8/hyprexpo-plus/codex/lua-config-api"; + flake = false; }; Hyprspace = { @@ -143,6 +139,15 @@ inputs.nixpkgs.follows = "nixpkgs"; }; + hyprwobbly = { + url = "github:colonelpanic8/hyprwobbly"; + inputs = { + hyprland.follows = "hyprland"; + nixpkgs.follows = "nixpkgs"; + systems.follows = "systems"; + }; + }; + hyprglass = { url = "github:colonelpanic8/hyprglass?ref=codex/hyprland-0.55-compat"; flake = false; @@ -238,7 +243,7 @@ }; codex-desktop-linux = { - url = "github:colonelpanic8/codex-desktop-linux?ref=codex/nixos-browser-cache-fixes"; + url = "github:colonelpanic8/codex-desktop-linux?ref=reenable-computer-use-in-nix"; inputs = { nixpkgs.follows = "nixpkgs"; flake-utils.follows = "flake-utils"; diff --git a/nixos/flake/per-system.nix b/nixos/flake/per-system.nix index 7261b87e..f24d04ad 100644 --- a/nixos/flake/per-system.nix +++ b/nixos/flake/per-system.nix @@ -26,6 +26,15 @@ hyprlang = inputs.hyprlang.packages.${system}.hyprlang; hyprutils = inputs.hyprutils.packages.${system}.hyprutils; }; + hyprexpo = pkgs.callPackage ../packages/hyprexpo-plus { + src = inputs.hyprexpo; + hyprland = inputs.hyprland.packages.${system}.hyprland; + aquamarine = inputs.aquamarine.packages.${system}.aquamarine; + hyprcursor = inputs.hyprcursor.packages.${system}.hyprcursor; + hyprgraphics = inputs.hyprgraphics.packages.${system}.hyprgraphics; + hyprlang = inputs.hyprlang.packages.${system}.hyprlang; + hyprutils = inputs.hyprutils.packages.${system}.hyprutils; + }; tangledConfig = dotfilesOrgApi.org-agenda-custom-config; # Import container build logic @@ -42,7 +51,7 @@ in { } // lib.optionalAttrs pkgs.stdenv.isLinux { hyprNStack = inputs.hyprNStack.packages.${system}.hyprNStack; - hyprexpo-lua = inputs.hyprexpo.packages.${system}.hyprexpo; + hyprexpo-lua = hyprexpo; hyprwinview = inputs.hyprwinview.packages.${system}.hyprwinview; hypr-workspace-history = inputs.hypr-workspace-history.packages.${system}.hypr-workspace-history; inherit hyprglass; @@ -60,7 +69,7 @@ in { } // lib.optionalAttrs pkgs.stdenv.isLinux { hyprNStack = inputs.hyprNStack.packages.${system}.hyprNStack; - hyprexpo-lua = inputs.hyprexpo.packages.${system}.hyprexpo; + hyprexpo-lua = hyprexpo; hyprwinview = inputs.hyprwinview.packages.${system}.hyprwinview; hypr-workspace-history = inputs.hypr-workspace-history.packages.${system}.hypr-workspace-history; inherit hyprglass; diff --git a/nixos/hyprland.nix b/nixos/hyprland.nix index 7bfd2aa6..3104ed6e 100644 --- a/nixos/hyprland.nix +++ b/nixos/hyprland.nix @@ -108,13 +108,30 @@ hyprlang = inputs.hyprlang.packages.${system}.hyprlang; hyprutils = inputs.hyprutils.packages.${system}.hyprutils; }; + hyprwobbly = inputs.hyprwobbly.packages.${system}.hyprwobbly.overrideAttrs (old: { + patches = + (old.patches or []) + ++ [ + ./packages/hyprwobbly-safe-geometry-and-idle-timer.patch + ]; + }); + hyprexpo = pkgs.callPackage ./packages/hyprexpo-plus { + src = inputs.hyprexpo; + hyprland = baseHyprlandPackage; + aquamarine = inputs.aquamarine.packages.${system}.aquamarine; + hyprcursor = inputs.hyprcursor.packages.${system}.hyprcursor; + hyprgraphics = inputs.hyprgraphics.packages.${system}.hyprgraphics; + hyprlang = inputs.hyprlang.packages.${system}.hyprlang; + hyprutils = inputs.hyprutils.packages.${system}.hyprutils; + }; hyprlandPluginPackages = [ inputs.hyprNStack.packages.${system}.hyprNStack - inputs.hyprexpo.packages.${system}.hyprexpo + hyprexpo hyprspace inputs.hyprwinview.packages.${system}.hyprwinview inputs.hypr-workspace-history.packages.${system}.hypr-workspace-history + hyprwobbly ] ++ lib.optionals enableHyprglass [hyprglass]; hyprRofiWindow = pkgs.writeShellApplication { diff --git a/nixos/packages/hyprexpo-plus/default.nix b/nixos/packages/hyprexpo-plus/default.nix new file mode 100644 index 00000000..6b940ef5 --- /dev/null +++ b/nixos/packages/hyprexpo-plus/default.nix @@ -0,0 +1,75 @@ +{ + stdenv, + lib, + cmake, + pkg-config, + aquamarine, + cairo, + glslang, + hyprland, + hyprcursor, + hyprgraphics, + hyprlang, + hyprutils, + libdrm, + libGL, + libinput, + libxcb, + libxcb-errors, + libxcb-wm, + libxkbcommon, + lua, + pango, + pixman, + systemd, + wayland, + src, +}: +stdenv.mkDerivation { + pname = "hyprexpo-plus"; + version = "unstable-${src.shortRev or "unknown"}"; + inherit src; + + nativeBuildInputs = [ + cmake + pkg-config + ]; + + buildInputs = [ + aquamarine + cairo + glslang + hyprland + hyprcursor + hyprgraphics + hyprlang + hyprutils + libdrm + libGL + libinput + libxcb + libxcb-errors + libxcb-wm + libxkbcommon + lua + pango + pixman + systemd + wayland + ]; + + installPhase = '' + runHook preInstall + + install -Dm755 libhyprexpo.so "$out/lib/libhyprexpo.so" + + runHook postInstall + ''; + + meta = { + description = "Enhanced Hyprland workspace overview plugin"; + homepage = "https://github.com/sandwichfarm/hyprexpo-plus"; + license = lib.licenses.bsd3; + platforms = lib.platforms.linux; + }; +} diff --git a/nixos/packages/hyprspace-lua-api.patch b/nixos/packages/hyprspace-lua-api.patch index a300d4a4..e1264e01 100644 --- a/nixos/packages/hyprspace-lua-api.patch +++ b/nixos/packages/hyprspace-lua-api.patch @@ -1,16 +1,16 @@ diff --git a/src/main.cpp b/src/main.cpp -index 9b75f8a..b69b459 100644 +index 9dd0286..51fd8d7 100644 --- a/src/main.cpp +++ b/src/main.cpp -@@ -5,6 +5,7 @@ +@@ -6,6 +6,7 @@ #include #include #include +#include #include + #include #include "Overview.hpp" - #include "Globals.hpp" -@@ -377,6 +378,65 @@ static SDispatchResult dispatchCloseOverview(std::string arg) { +@@ -380,6 +381,65 @@ static SDispatchResult dispatchCloseOverview(std::string arg) { return SDispatchResult{}; } @@ -76,7 +76,7 @@ index 9b75f8a..b69b459 100644 void* findFunctionBySymbol(HANDLE inHandle, const std::string func, const std::string sym) { // should return all functions auto funcSearch = HyprlandAPI::findFunctionsByName(inHandle, func); -@@ -516,6 +566,8 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE inHandle) { +@@ -545,6 +605,8 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE inHandle) { HyprlandAPI::addDispatcherV2(pHandle, "overview:open", ::dispatchOpenOverview); HyprlandAPI::addDispatcherV2(pHandle, "overview:close", ::dispatchCloseOverview); diff --git a/nixos/packages/hyprwobbly-safe-geometry-and-idle-timer.patch b/nixos/packages/hyprwobbly-safe-geometry-and-idle-timer.patch new file mode 100644 index 00000000..5e6661e7 --- /dev/null +++ b/nixos/packages/hyprwobbly-safe-geometry-and-idle-timer.patch @@ -0,0 +1,270 @@ +diff --git a/Wobbly.cpp b/Wobbly.cpp +index d21594e..d8e1534 100644 +--- a/Wobbly.cpp ++++ b/Wobbly.cpp +@@ -35,6 +35,18 @@ namespace { + Vector2D lerp(const Vector2D& a, const Vector2D& b, float t) { + return a + (b - a) * t; + } ++ ++ bool finite(double value) { ++ return std::isfinite(value) && std::abs(value) < 1000000.0; ++ } ++ ++ bool finite(const Vector2D& vec) { ++ return finite(vec.x) && finite(vec.y); ++ } ++ ++ bool usableBox(const CBox& box) { ++ return finite(box.x) && finite(box.y) && finite(box.w) && finite(box.h) && box.w > 0.0 && box.h > 0.0; ++ } + } + + CWobblyTransformer::CWobblyTransformer(PHLWINDOW pWindow) : m_window(pWindow) { +@@ -50,6 +62,10 @@ bool CWobblyTransformer::belongsTo(PHLWINDOW pWindow) const { + return m_window.lock() == pWindow; + } + ++bool CWobblyTransformer::active() const { ++ return m_active; ++} ++ + int CWobblyTransformer::gridWidth() const { + return std::clamp(cfgInt(g_pGridWidth, 4), 2, 16); + } +@@ -126,7 +142,10 @@ size_t CWobblyTransformer::index(int x, int y) const { + } + + void CWobblyTransformer::resetModel(const Vector2D& size) { +- m_sizePx = {std::max(1.0, size.x), std::max(1.0, size.y)}; ++ if (!finite(size)) ++ return; ++ ++ m_sizePx = {std::clamp(size.x, 1.0, 100000.0), std::clamp(size.y, 1.0, 100000.0)}; + m_points.clear(); + m_points.resize(sc(gridWidth() * gridHeight())); + +@@ -159,6 +178,9 @@ Vector2D CWobblyTransformer::currentPointerLocalPx(const CBox& boxLayout) const + } + + void CWobblyTransformer::applyMoveImpulse(const Vector2D& delta, const Vector2D& anchor) { ++ if (!finite(delta) || !finite(anchor)) ++ return; ++ + const auto DIAG = std::max(1.F, sc(length(m_sizePx))); + + for (auto& point : m_points) { +@@ -171,6 +193,9 @@ void CWobblyTransformer::applyMoveImpulse(const Vector2D& delta, const Vector2D& + } + + void CWobblyTransformer::applyResizeImpulse(const Vector2D& deltaSize) { ++ if (!finite(deltaSize)) ++ return; ++ + const auto oldSize = m_sizePx; + const auto oldPts = m_points; + +@@ -216,12 +241,15 @@ void CWobblyTransformer::preWindowRender(CSurfacePassElement::SRenderData* pRend + pRenderData->h + topLeftExtents.y + bottomRightExtents.y, + }; + +- if (fullBox.empty()) ++ if (!usableBox(fullBox) || fullBox.empty()) + fullBox = {pRenderData->pos.x, pRenderData->pos.y, pRenderData->w, pRenderData->h}; + ++ if (!usableBox(fullBox)) ++ return; ++ + CBox fullBoxPx = fullBox.copy().translate(-PMONITOR->m_position).scale(PMONITOR->m_scale).round(); + +- if (fullBoxPx.w <= 1 || fullBoxPx.h <= 1) ++ if (!usableBox(fullBoxPx) || fullBoxPx.w <= 1 || fullBoxPx.h <= 1) + return; + + const Vector2D newSize = fullBoxPx.size(); +@@ -251,10 +279,10 @@ void CWobblyTransformer::preWindowRender(CSurfacePassElement::SRenderData* pRend + + damage(); + +- if (std::abs(deltaSize.x) > 0.5 || std::abs(deltaSize.y) > 0.5) ++ if (finite(deltaSize) && (std::abs(deltaSize.x) > 0.5 || std::abs(deltaSize.y) > 0.5)) + applyResizeImpulse(deltaSize); + +- if (std::abs(deltaPos.x) > 0.5 || std::abs(deltaPos.y) > 0.5) { ++ if (finite(deltaPos) && (std::abs(deltaPos.x) > 0.5 || std::abs(deltaPos.y) > 0.5)) { + Vector2D anchor = m_sizePx / 2.0; + if (m_wasDragging) + anchor = currentPointerLocalPx(fullBox); +@@ -296,6 +324,11 @@ void CWobblyTransformer::stepSimulation(float dt) { + point.force += (point.rest - point.pos) * (K * 0.18F); + point.velocity += (point.force / MASS) * STEP; + point.pos += point.velocity * STEP; ++ ++ if (!finite(point.pos) || !finite(point.velocity)) { ++ point.pos = point.rest; ++ point.velocity = {}; ++ } + } + } + +@@ -313,6 +346,12 @@ void CWobblyTransformer::stepSimulation(float dt) { + void CWobblyTransformer::constrainWarp() { + const auto LIMIT = maxWarp(); + for (auto& point : m_points) { ++ if (!finite(point.pos)) { ++ point.pos = point.rest; ++ point.velocity = {}; ++ continue; ++ } ++ + auto diff = point.pos - point.rest; + auto len = length(diff); + if (len > LIMIT) +@@ -356,6 +395,9 @@ CBox CWobblyTransformer::deformedBoundsLayout() const { + Vector2D max = m_points.front().pos; + + for (const auto& point : m_points) { ++ if (!finite(point.pos)) ++ return {}; ++ + min.x = std::min(min.x, point.pos.x); + min.y = std::min(min.y, point.pos.y); + max.x = std::max(max.x, point.pos.x); +@@ -363,7 +405,8 @@ CBox CWobblyTransformer::deformedBoundsLayout() const { + } + + const double SCALE = std::max(0.01, m_sourceBoxPx.w / std::max(1.0, m_sourceBoxLayout.w)); +- return {m_sourceBoxLayout.x + min.x / SCALE, m_sourceBoxLayout.y + min.y / SCALE, (max.x - min.x) / SCALE, (max.y - min.y) / SCALE}; ++ const CBox BOX = {m_sourceBoxLayout.x + min.x / SCALE, m_sourceBoxLayout.y + min.y / SCALE, (max.x - min.x) / SCALE, (max.y - min.y) / SCALE}; ++ return usableBox(BOX) ? BOX : CBox{}; + } + + void CWobblyTransformer::damage() { +@@ -372,13 +415,13 @@ void CWobblyTransformer::damage() { + return; + + const auto NOW = deformedBoundsLayout().expand(8); +- if (!NOW.empty()) ++ if (usableBox(NOW) && !NOW.empty()) + g_pHyprRenderer->damageBox(NOW); + +- if (!m_lastDamage.empty()) ++ if (usableBox(m_lastDamage) && !m_lastDamage.empty()) + g_pHyprRenderer->damageBox(m_lastDamage); + +- m_lastDamage = NOW; ++ m_lastDamage = usableBox(NOW) ? NOW : CBox{}; + } + + void CWobblyTransformer::tick(float dt) { +@@ -402,6 +445,8 @@ SP CWobblyTransformer::transform(SP + return in; + + const Vector2D MONSIZE = PMONITOR->m_transformedSize; ++ if (!finite(MONSIZE) || MONSIZE.x <= 1.0 || MONSIZE.y <= 1.0 || !usableBox(m_sourceBoxPx)) ++ return in; + + if (!m_outputFB) + m_outputFB = g_pHyprRenderer->createFB("hyprwobbly"); +diff --git a/Wobbly.hpp b/Wobbly.hpp +index b627304..8ebb12f 100644 +--- a/Wobbly.hpp ++++ b/Wobbly.hpp +@@ -19,6 +19,7 @@ class CWobblyTransformer : public IWindowTransformer { + void tick(float dt); + void damage(); + bool belongsTo(PHLWINDOW pWindow) const; ++ bool active() const; + + private: + struct SPoint { +diff --git a/globals.hpp b/globals.hpp +index 38ffff4..2196124 100644 +--- a/globals.hpp ++++ b/globals.hpp +@@ -13,8 +13,7 @@ class CWobblyTransformer; + + inline HANDLE PHANDLE = nullptr; + inline CHyprSignalListener g_openWindowListener; +-inline CFunctionHook* g_pStyleValidHook = nullptr; +-inline wl_event_source* g_pTick = nullptr; ++inline wl_event_source* g_pTick = nullptr; + inline SP g_pWobblyShader; + inline bool g_shaderReady = false; + inline std::vector g_transformers; +diff --git a/main.cpp b/main.cpp +index f86901e..1ea5249 100644 +--- a/main.cpp ++++ b/main.cpp +@@ -19,17 +19,10 @@ + + using Render::GL::g_pHyprOpenGL; + +-typedef std::string (*origStyleValid)(CHyprAnimationManager*, const std::string&, const std::string&); +- + APICALL EXPORT std::string PLUGIN_API_VERSION() { + return HYPRLAND_API_VERSION; + } + +-static bool isWobblyStyle(const std::string& config, std::string style) { +- std::ranges::transform(style, style.begin(), [](unsigned char c) { return std::tolower(c); }); +- return config == "windowsMove" && (style == "wobbly" || style.starts_with("wobbly ")); +-} +- + template + static void addConfigValue(SP& storage, SP value) { + storage = std::move(value); +@@ -51,13 +44,6 @@ static void registerConfigValues() { + addConfigValue(g_pMaxWarp, makeShared("plugin:hyprwobbly:max_warp", "maximum warp", 140.F)); + } + +-static std::string hkStyleValidInConfigVar(CHyprAnimationManager* thisptr, const std::string& config, const std::string& style) { +- if (isWobblyStyle(config, style)) +- return ""; +- +- return (*(origStyleValid)g_pStyleValidHook->m_original)(thisptr, config, style); +-} +- + static bool hasWobbly(PHLWINDOW pWindow) { + return std::ranges::any_of(g_transformers, [pWindow](const auto* wobbly) { return wobbly && wobbly->belongsTo(pWindow); }); + } +@@ -79,13 +65,16 @@ static int onTick(void*) { + const float DT = std::clamp(std::chrono::duration(NOW - LAST).count(), 0.001F, 0.05F); + LAST = NOW; + ++ bool anyActive = false; + const auto TRANSFORMERS = g_transformers; + for (auto* const transformer : TRANSFORMERS) { +- if (transformer) ++ if (transformer) { + transformer->tick(DT); ++ anyActive = anyActive || transformer->active(); ++ } + } + +- const int TIMEOUT = g_pHyprRenderer->m_mostHzMonitor ? 1000.0 / g_pHyprRenderer->m_mostHzMonitor->m_refreshRate : 16; ++ const int TIMEOUT = anyActive && g_pHyprRenderer->m_mostHzMonitor ? std::max(8, static_cast(1000.0 / g_pHyprRenderer->m_mostHzMonitor->m_refreshRate)) : 100; + wl_event_source_timer_update(g_pTick, TIMEOUT); + return 0; + } +@@ -104,20 +93,6 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { + + registerConfigValues(); + +- auto FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "styleValidInConfigVar"); +- for (auto& fn : FNS) { +- if (!fn.demangled.contains("CHyprAnimationManager")) +- continue; +- +- g_pStyleValidHook = HyprlandAPI::createFunctionHook(PHANDLE, fn.address, (void*)::hkStyleValidInConfigVar); +- break; +- } +- +- if (!g_pStyleValidHook || !g_pStyleValidHook->hook()) { +- HyprlandAPI::addNotification(PHANDLE, "[hyprwobbly] Failure in initialization: failed to hook animation style validator", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); +- throw std::runtime_error("[hyprwobbly] failed to hook animation style validator"); +- } +- + g_pHyprOpenGL->makeEGLCurrent(); + g_pWobblyShader = makeShared(); + g_shaderReady = g_pWobblyShader->createProgram(WOBBLY_VERTEX_SHADER, WOBBLY_FRAGMENT_SHADER); diff --git a/nixos/taffybar.nix b/nixos/taffybar.nix index a264cd88..b905db83 100644 --- a/nixos/taffybar.nix +++ b/nixos/taffybar.nix @@ -7,10 +7,45 @@ ... }: let system = pkgs.stdenv.hostPlatform.system; + hyprlandPackage = config.programs.hyprland.package; taffybarPackage = inputs.imalison-taffybar.defaultPackage.${system}; taffybarStart = pkgs.writeShellScript "taffybar-start" '' runtime_dir="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}" + find_hyprland_instance() { + instances_json="$(${hyprlandPackage}/bin/hyprctl instances -j 2>/dev/null || true)" + [ -n "$instances_json" ] || return 1 + + if [ -n "''${WAYLAND_DISPLAY:-}" ]; then + inst_row="$( + printf '%s\n' "$instances_json" | + ${pkgs.jq}/bin/jq -r --arg sock "$WAYLAND_DISPLAY" \ + '.[] | select(.instance and .wl_socket and .wl_socket == $sock) | [.time, .instance, .wl_socket] | @tsv' 2>/dev/null | + ${pkgs.coreutils}/bin/sort -n | + ${pkgs.coreutils}/bin/tail -n 1 + )" + else + inst_row="" + fi + + if [ -z "''${inst_row:-}" ]; then + inst_row="$( + printf '%s\n' "$instances_json" | + ${pkgs.jq}/bin/jq -r \ + '.[] | select(.instance and .wl_socket) | [.time, .instance, .wl_socket] | @tsv' 2>/dev/null | + ${pkgs.coreutils}/bin/sort -n | + ${pkgs.coreutils}/bin/tail -n 1 + )" + fi + + [ -n "''${inst_row:-}" ] || return 1 + set -- $inst_row + signature="$2" + socket_name="$3" + [ -n "$signature" ] && [ -n "$socket_name" ] || return 1 + printf '%s\t%s\n' "$signature" "$socket_name" + } + find_wayland_socket() { if [ -n "''${WAYLAND_DISPLAY:-}" ] && [ -S "$runtime_dir/$WAYLAND_DISPLAY" ]; then printf '%s\n' "$WAYLAND_DISPLAY" @@ -49,6 +84,22 @@ export XDG_SESSION_TYPE=wayland fi + if [ "$is_hyprland" = 1 ]; then + if inst_row="$(find_hyprland_instance)"; then + set -- $inst_row + signature="$1" + socket_name="$2" + if [ "''${HYPRLAND_INSTANCE_SIGNATURE:-}" != "$signature" ]; then + echo "taffybar-start: correcting HYPRLAND_INSTANCE_SIGNATURE=''${HYPRLAND_INSTANCE_SIGNATURE:-} to $signature" >&2 + export HYPRLAND_INSTANCE_SIGNATURE="$signature" + fi + if [ "''${WAYLAND_DISPLAY:-}" != "$socket_name" ]; then + echo "taffybar-start: correcting WAYLAND_DISPLAY=''${WAYLAND_DISPLAY:-} to $socket_name" >&2 + export WAYLAND_DISPLAY="$socket_name" + fi + fi + fi + if [ "''${XDG_SESSION_TYPE:-}" = "wayland" ] || [ -n "''${WAYLAND_DISPLAY:-}" ] || [ "$is_hyprland" = 1 ]; then if [ -z "''${WAYLAND_DISPLAY:-}" ] || [ ! -S "$runtime_dir/$WAYLAND_DISPLAY" ]; then if socket_name="$(find_wayland_socket)"; then