diff --git a/apps/ghostty/home.nix b/apps/ghostty/home.nix index 2407c1c..1171e6a 100644 --- a/apps/ghostty/home.nix +++ b/apps/ghostty/home.nix @@ -12,6 +12,7 @@ enableFishIntegration = true; settings = { + confirm-close-surface = false; window-padding-x = 15; window-padding-y = 15; window-padding-balance = true; diff --git a/apps/hyprland/hypr/rules.nix b/apps/hyprland/hypr/rules.nix index 99ab9c2..b9bbadc 100644 --- a/apps/hyprland/hypr/rules.nix +++ b/apps/hyprland/hypr/rules.nix @@ -5,10 +5,7 @@ # "center, class:^(org\.hakase\..*)$" # "size 80% 70%, class:^(org\.hakase\..*)$" # Example using percentages # --- POPUP RULES (Wifi, BT, Audio) --- - "size 602 478, class:^(org\.hakase\.popup\..*)$" - "move 1312 32, class:^(org\.hakase\.popup\..*)$" "float, class:^(org\.hakase\.popup\..*)$" - "pin, class:^(org\.hakase\.popup\..*)$" "animation slide right, class:^(org\.hakase\.popup\..*)$" "dimaround, class:^(org\.hakase\.popup\..*)$" ]; diff --git a/apps/waybar/home.nix b/apps/waybar/home.nix index 9160c37..5b59ae6 100644 --- a/apps/waybar/home.nix +++ b/apps/waybar/home.nix @@ -95,7 +95,7 @@ tooltip-format-disconnected = "Disconnected"; interval = 3; spacing = 1; - on-click = "hakase-launch-wifi"; + on-click = "hakase-popup-wifi"; }; "battery" = { @@ -124,12 +124,12 @@ format-connected = "󰂱"; format-no-controller = ""; tooltip-format = "Devices connected: {num_connections}"; - on-click = "hakase-launch-bluetooth"; + on-click = "hakase-popup-bluetooth"; }; "pulseaudio" = { format = "{icon}"; - on-click = "hakase-focus-wrapper wiremix"; + on-click = "hakase-popup-volume"; on-click-right = "pamixer -t"; tooltip-format = "Playing at {volume}%"; scroll-step = 5; diff --git a/scripts/hakase-scripts.nix b/scripts/hakase-scripts.nix index 1109f5d..48ae9ed 100644 --- a/scripts/hakase-scripts.nix +++ b/scripts/hakase-scripts.nix @@ -53,71 +53,105 @@ ''; hakase-launch-popup = pkgs.writeShellScriptBin "hakase-launch-popup" '' - if (($# == 0)); then - echo "Usage: hakase-launch-popup [command]" + if (($# < 3)); then + echo "Usage: hakase-launch-popup [width] [height] [command...]" exit 1 fi + # --- ARGS & CONFIG --- + WIN_WIDTH="$1" + WIN_HEIGHT="$2" + shift 2 + + MARGIN=10 + BAR_HEIGHT=40 + # --------------------- + CMD_NAME=$(basename "$1") - # Define a specific class for popups so Hyprland rules can catch them - CLASS_NAME="org.hakase.popup.$CMD_NAME" + TARGET_CLASS="org.hakase.popup.$CMD_NAME" - # Check if already running; if so, toggle/close it - EXISTING_ADDR=$(${pkgs.hyprland}/bin/hyprctl clients -j | ${pkgs.jq}/bin/jq -r --arg c "$CLASS_NAME" '.[] | select(.class == $c) | .address') + # 1. SINGLETON: Close any existing hakase popups first + # This ensures only one popup is ever open at a time. + EXISTING_JSON=$(${pkgs.hyprland}/bin/hyprctl clients -j | ${pkgs.jq}/bin/jq -r '.[] | select(.class | startswith("org.hakase.popup."))') - if [[ -n "$EXISTING_ADDR" ]]; then - ${pkgs.hyprland}/bin/hyprctl dispatch closewindow "address:$EXISTING_ADDR" - exit 0 - fi + if [[ -n "$EXISTING_JSON" ]]; then + OLD_ADDR=$(echo "$EXISTING_JSON" | ${pkgs.jq}/bin/jq -r '.address') + OLD_CLASS=$(echo "$EXISTING_JSON" | ${pkgs.jq}/bin/jq -r '.class') - # Launch the application - # We use setsid to detach, but keep the PID to kill it later - xdg-terminal-exec --app-id="$CLASS_NAME" -e "$@" & - APP_PID=$! + ${pkgs.hyprland}/bin/hyprctl dispatch closewindow "address:$OLD_ADDR" - # Wait for the window to appear and grab its address - TIMEOUT=0 - WINDOW_ADDR="" - while [[ -z "$WINDOW_ADDR" && $TIMEOUT -lt 20 ]]; do - sleep 0.1 - WINDOW_ADDR=$(${pkgs.hyprland}/bin/hyprctl clients -j | ${pkgs.jq}/bin/jq -r --arg c "$CLASS_NAME" '.[] | select(.class == $c) | .address') - ((TIMEOUT++)) - done - - if [[ -z "$WINDOW_ADDR" ]]; then - echo "Failed to find window for $CLASS_NAME" - exit 1 - fi - - # Monitor Hyprland socket for focus changes - # We use socat to stream events. If activewindow changes to something else, kill the app. - ${pkgs.socat}/bin/socat -U - UNIX-CONNECT:"$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" | while read -r line; do - if [[ "$line" == "activewindow>>"* ]]; then - # Extract the new focused address - NEW_FOCUS="0x$(echo "$line" | cut -d '>' -f3 | cut -d ',' -f1)" - - # If the new focus is NOT our popup window, kill the popup - if [[ "$WINDOW_ADDR" != "$NEW_FOCUS" ]]; then - kill "$APP_PID" 2>/dev/null - # Also force close in Hyprland to be sure - ${pkgs.hyprland}/bin/hyprctl dispatch closewindow "address:$WINDOW_ADDR" - break - fi + # Toggle behavior: If we clicked the same button again, just close and exit. + if [[ "$OLD_CLASS" == "$TARGET_CLASS" ]]; then + exit 0 fi - done - ''; + sleep 0.15 + fi - # 2. Specific Launchers using the Popup logic + # 2. POSITIONING + MONITOR_INFO=$(${pkgs.hyprland}/bin/hyprctl monitors -j | ${pkgs.jq}/bin/jq '.[] | select(.focused == true)') + MON_X=$(echo "$MONITOR_INFO" | ${pkgs.jq}/bin/jq '.x') + MON_Y=$(echo "$MONITOR_INFO" | ${pkgs.jq}/bin/jq '.y') + MON_WIDTH=$(echo "$MONITOR_INFO" | ${pkgs.jq}/bin/jq '.width') + + # Defaults to 0 if jq fails + MON_X=''${MON_X:-0} + MON_Y=''${MON_Y:-0} + MON_WIDTH=''${MON_WIDTH:-1920} + + TARGET_X=$((MON_X + MON_WIDTH - WIN_WIDTH - MARGIN)) + TARGET_Y=$((MON_Y + BAR_HEIGHT + MARGIN)) + + # 3. LAUNCH + RULE="[float;pin;size $WIN_WIDTH $WIN_HEIGHT;move $TARGET_X $TARGET_Y]" + ${pkgs.hyprland}/bin/hyprctl dispatch exec "$RULE xdg-terminal-exec --app-id=$TARGET_CLASS -e $@" + + # 4. BACKGROUND: Close on "Click Outside" (Blur) + ( + # Wait for the window to actually appear + TIMEOUT=0 + WINDOW_ADDR="" + while [[ -z "$WINDOW_ADDR" && $TIMEOUT -lt 20 ]]; do + sleep 0.1 + WINDOW_ADDR=$(${pkgs.hyprland}/bin/hyprctl clients -j | ${pkgs.jq}/bin/jq -r --arg c "$TARGET_CLASS" '.[] | select(.class == $c) | .address') + ((TIMEOUT++)) + done + + if [[ -n "$WINDOW_ADDR" ]]; then + # Listen to socket for focus changes + ${pkgs.socat}/bin/socat -U - UNIX-CONNECT:"$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" | while read -r line; do + if [[ "$line" == "activewindow>>"* ]]; then + # Extract address from event: activewindow>>1234abcd,title + NEW_FOCUS=$(echo "$line" | cut -d '>' -f3 | cut -d ',' -f1) + + # Normalize to 0x format to match hyprctl + if [[ "$NEW_FOCUS" != 0x* ]]; then + NEW_FOCUS="0x$NEW_FOCUS" + fi + + # If the focused window is NOT our popup, kill the popup + if [[ "$WINDOW_ADDR" != "$NEW_FOCUS" ]]; then + ${pkgs.hyprland}/bin/hyprctl dispatch closewindow "address:$WINDOW_ADDR" + break + fi + fi + done + fi + ) & + + exit 0 + ''; # 1. WiFi (Impala) - Requires a larger view hakase-popup-wifi = pkgs.writeShellScriptBin "hakase-popup-wifi" '' - exec hakase-launch-popup impala + exec hakase-launch-popup 800 500 impala ''; + # 2. Bluetooth (Bluetui) - Fits your specific test size hakase-popup-bluetooth = pkgs.writeShellScriptBin "hakase-popup-bluetooth" '' - exec hakase-launch-popup bluetui + exec hakase-launch-popup 602 478 bluetui ''; + # 3. Volume (Wiremix) - Can be smaller hakase-popup-volume = pkgs.writeShellScriptBin "hakase-popup-volume" '' - exec hakase-launch-popup wiremix + exec hakase-launch-popup 800 300 wiremix ''; in { environment.systemPackages = [ @@ -128,6 +162,7 @@ in { pkgs.impala pkgs.wiremix pkgs.pamixer + pkgs.yad hakase-launch-popup hakase-popup-wifi