diff options
| author | tak0dan <tmtroshko@gmail.com> | 2026-01-22 17:32:45 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-22 11:32:45 -0500 |
| commit | 16ce0ba50456256602019b37df369ac9a7307633 (patch) | |
| tree | f5595e4c076bcf77f5106af7575636e8b8426c40 /config/hypr/scripts | |
| parent | 9dd1f73d832d51972498e775baf992274ef6063c (diff) | |
Refactor Tak0-Autodispatch.sh with improved logic (#929)
Diffstat (limited to 'config/hypr/scripts')
| -rwxr-xr-x | config/hypr/scripts/Tak0-Autodispatch.sh | 361 |
1 files changed, 295 insertions, 66 deletions
diff --git a/config/hypr/scripts/Tak0-Autodispatch.sh b/config/hypr/scripts/Tak0-Autodispatch.sh index 114a3e8e..0f81a6a3 100755 --- a/config/hypr/scripts/Tak0-Autodispatch.sh +++ b/config/hypr/scripts/Tak0-Autodispatch.sh @@ -1,90 +1,319 @@ #!/usr/bin/env bash -# USAGE / ІНСТРУКЦІЯ: -# 1) Run from terminal: -# ./dispatch.sh <application_command> <target_workspace_number> -# Example: -# ./dispatch.sh discord 2 # -# 2) Call from Hyprland config (in hyprland.conf file): -# exec-once = /path/to/dispatch.sh <application_command> <target_workspace_number> +# ───────────────────────────────────────────────────────────────────────────── +# Tak0-Autodispatch.sh +# ───────────────────────────────────────────────────────────────────────────── # -# Logs are saved in dispatch.log file next to the script. -# If the window doesn't appear or is dispatched incorrectly — info will be there. +# 🇬🇧 ENGLISH +# ----------------------------------------------------------------------------- +# This script is an "authoritative spawn dispatcher" for Hyprland. # -# Notes: -# - Script waits about ~9 seconds (30 iterations of 0.3 sec) for window to appear. -# - Uses hyprctl and jq, so these tools must be installed. +# Its purpose is to FORCE all windows belonging to a single application launch +# (main window + helpers + Electron / Steam child processes) +# onto a specific workspace. # -# USAGE / ІНСТРУКЦІЯ: -# 1) Запуск з терміналу: -# ./dispatch.sh <application_command> <target_workspace_number> -# Наприклад: -# ./dispatch.sh discord 2 +# It explicitly ignores: +# • spawn race conditions +# • delayed window creation +# • detached helper processes +# • Electron / Chromium / Steam madness # -# 2) Виклик з конфігурації Hyprland (у файлі hyprland.conf): -# exec-once = /path/to/dispatch.sh <application_command> <target_workspace_number> +# Typical use cases: +# • Launch Steam / Discord / browsers without window leakage +# • Prevent apps from spawning on the currently focused workspace +# • Control applications that completely ignore static windowrules # -# Логи зберігаються у файлі dispatch.log поруч зі скриптом. -# Якщо вікно не з'явилось або неправильно диспатчилось — інформація там. +# Invocation: +# ./Tak0-Autodispatch.sh <workspace> [rule ...] -- <command> +# +# Important notes: +# • All window rules are TEMPORARY +# • No permanent pollution of Hyprland configuration +# +# ----------------------------------------------------------------------------- +# 🇺🇦 УКРАЇНСЬКА +# ----------------------------------------------------------------------------- +# Цей скрипт — це "авторитарний диспетчер запуску" для Hyprland. +# +# Його задача — ГАРАНТОВАНО відправити ВСІ вікна одного запуску програми +# (основне вікно + helper-и + Electron / Steam дочірні процеси) +# на вказаний workspace. +# +# Скрипту байдуже на: +# • race conditions +# • затримки створення вікон +# • відʼєднані helper-процеси +# • Electron / Chromium / Steam безумство +# +# Типові сценарії використання: +# • Запуск Steam / Discord / браузерів без витоку вікон +# • Заборона спавну на поточному workspace +# • Контроль програм, які ігнорують static windowrules +# +# Запуск: +# ./Tak0-Autodispatch.sh <workspace> [rule ...] -- <command> +# +# Важливо: +# • Всі rules — тимчасові +# • Глобальна конфігурація Hyprland НЕ псується +# +# ───────────────────────────────────────────────────────────────────────────── +# +# REQUIREMENTS / ВИМОГИ: +# - hyprctl → runtime control of Hyprland +# - jq → JSON client parsing +# - pgrep/ps → process tree inspection # -# Примітки: -# - Скрипт чекає до ~9 секунд (30 ітерацій по 0.3 сек) поки вікно з'явиться. -# - Використовує hyprctl і jq, тому ці інструменти мають бути встановлені. + +set -u LOGFILE="$(dirname "$0")/dispatch.log" -# Log file path located next to the script. -# Файл логів розташований поруч зі скриптом. -APP=$1 -# The application command or window class to launch or match. -# Команда для запуску аплікації або клас вікна для пошуку. +# ───────────────────────────────────────────────────────────────────────────── +# 0️⃣ ARGUMENT PARSING +# ───────────────────────────────────────────────────────────────────────────── +# +# EN: +# $1 → target workspace +# Next args → optional capture rules (windowrulev2 syntax) +# "--" → argument separator +# After "--" → command to execute (verbatim) +# +# UA: +# $1 → цільовий workspace +# Далі → capture rules (сумісні з windowrulev2) +# "--" → роздільник аргументів +# Після "--" → команда запуску (як є) +# + +TARGET_WS="$1" +shift || true + +CAPTURE_RULES=() +while [[ "${1-}" != "--" && -n "${1-}" ]]; do + CAPTURE_RULES+=("$1") + shift || break +done + +if [[ "${1-}" == "--" ]]; then + shift +fi -TARGET_WORKSPACE=$2 -# The target workspace number where the window should be moved. -# Цільовий номер воркспейсу, куди потрібно перемістити вікно. +CMD="$*" -# Check if required arguments are provided. -# Перевірка наявності необхідних параметрів. -if [[ -z "$APP" || -z "$TARGET_WORKSPACE" ]]; then - echo "Usage: $0 <application_command> <target_workspace_number>" >> "$LOGFILE" 2>&1 +if [[ -z "$TARGET_WS" || -z "$CMD" ]]; then + echo "Usage: $0 <workspace> [rule rule ...] -- <command>" >>"$LOGFILE" exit 1 fi -echo "Starting dispatch of '$APP' to workspace $TARGET_WORKSPACE at $(date)" >> "$LOGFILE" -# Starting the dispatch process and logging the event. -# Початок процесу диспатчу, запис у лог. +echo "=== Deploy '$CMD' → WS $TARGET_WS @ $(date) ===" >>"$LOGFILE" + +# ───────────────────────────────────────────────────────────────────────────── +# 1️⃣ HYPRLAND READINESS GATE +# ───────────────────────────────────────────────────────────────────────────── +# +# EN: +# Hyprland may not be fully initialized during early autostart. +# hyprctl silently fails if called too early. +# +# UA: +# Під час раннього автозапуску Hyprland може бути ще не готовий. +# hyprctl у такому випадку тихо фейлиться. +# + +for _ in {1..50}; do + hyprctl -j monitors >/dev/null 2>&1 && break + sleep 0.1 +done + +# ───────────────────────────────────────────────────────────────────────────── +# 2️⃣ CLEANUP GUARANTEE +# ───────────────────────────────────────────────────────────────────────────── +# +# EN: +# Ensures that ALL temporary rules are removed +# even on crash, SIGTERM, or user interruption. +# +# UA: +# Гарантує прибирання ВСІХ тимчасових правил +# навіть у разі крешу, SIGTERM або Ctrl+C. +# + +cleanup() { + echo "Cleanup: removing temporary capture rules and initialWorkspace at $(date)" >>"$LOGFILE" + + hyprctl keyword windowrulev2 "unset, initialClass:.*" >>"$LOGFILE" 2>&1 || true + + for RULE in "${CAPTURE_RULES[@]}"; do + echo "Cleanup: removing temporary capture rule: $RULE" >>"$LOGFILE" + hyprctl keyword windowrulev2 "unset, $RULE" >>"$LOGFILE" 2>&1 || true + done +} + +trap cleanup EXIT INT TERM ERR + +# ───────────────────────────────────────────────────────────────────────────── +# 3️⃣ ULTRA-EARLY GLOBAL CAPTURE (NUCLEAR OPTION) +# ───────────────────────────────────────────────────────────────────────────── +# +# EN: +# Temporarily forces ALL windows (initialClass:.*) +# onto the target workspace. +# +# Protects against ultra-fast helpers: +# • gpu-process +# • renderer +# • steamwebhelper +# +# UA: +# Тимчасово заганяє АБСОЛЮТНО всі вікна +# на цільовий workspace. +# +# Рятує від ультрашвидких helper-ів. +# + +echo "Applying temporary initialWorkspace capture (initialClass:.*)" >>"$LOGFILE" +hyprctl keyword windowrulev2 \ + "initialWorkspace $TARGET_WS silent, initialClass:.*" \ + >>"$LOGFILE" 2>&1 || true -# Avoid early workspace focus issues by switching workspace first. -# Уникаємо проблем з раннім фокусом, спочатку переключаємо воркспейс. -hyprctl dispatch workspace "$TARGET_WORKSPACE" >> "$LOGFILE" 2>&1 -sleep 0.4 +# ───────────────────────────────────────────────────────────────────────────── +# 3️⃣.1 OPTIONAL CLASS-BASED PRE-CAPTURE +# ───────────────────────────────────────────────────────────────────────────── +# +# EN: +# Additional precision rules. +# Useful for Electron / Steam multi-process hell. +# +# UA: +# Додаткові class-based правила. +# Підвищують точність для Electron / Steam. +# -# Launch the application in the background and disown it. -# Запускаємо аплікацію у фоновому режимі та відв’язуємо від терміналу. -$APP & disown -pid=$! +for RULE in "${CAPTURE_RULES[@]}"; do + echo "Applying temporary capture rule: $RULE" >>"$LOGFILE" + hyprctl keyword windowrulev2 \ + "initialWorkspace $TARGET_WS silent, $RULE" \ + >>"$LOGFILE" 2>&1 || true +done -echo "Launched '$APP' with PID $pid" >> "$LOGFILE" -# Log the launched process ID. -# Лог процесу запуску з PID. +# ───────────────────────────────────────────────────────────────────────────── +# 4️⃣ APPLICATION LAUNCH +# ───────────────────────────────────────────────────────────────────────────── +# +# EN: +# bash -c allows aliases, env vars, wrappers. +# ROOT_PID is the root of process lineage. +# +# UA: +# bash -c дозволяє aliases, env vars та wrappers. +# ROOT_PID — корінь дерева процесів. +# -# Wait for the application window to appear (matching window class). -# Чекаємо появи вікна аплікації (за класом вікна). -for i in {1..30}; do - win=$(hyprctl clients -j | jq -r --arg APP "$APP" ' - .[] | select(.class | test($APP;"i")) | .address' 2>>"$LOGFILE") +bash -c "$CMD" & +ROOT_PID=$! +echo "Root PID: $ROOT_PID" >>"$LOGFILE" - if [[ -n "$win" ]]; then - echo "Found window $win for app '$APP', moving to workspace $TARGET_WORKSPACE" >> "$LOGFILE" - # Move the window to the target workspace. - # Переміщаємо вікно на цільовий воркспейс. - hyprctl dispatch movetoworkspace "$TARGET_WORKSPACE,address:$win" >> "$LOGFILE" 2>&1 - exit 0 +# Resolve canonical process name +APP_NAME="" +for _ in {1..20}; do + if [[ -r "/proc/$ROOT_PID/comm" ]]; then + APP_NAME="$(tr -d '\0' < /proc/$ROOT_PID/comm 2>/dev/null || true)" + break fi - sleep 0.3 + sleep 0.05 +done + +if [[ -z "$APP_NAME" ]]; then + read -r -a __toks <<< "$CMD" + APP_NAME="$(basename "${__toks[0]}")" +fi + +echo "App gate name: $APP_NAME" >>"$LOGFILE" + +sleep 1.5 + +# Release the nuclear option ASAP +echo "Releasing ultra-early wide capture" >>"$LOGFILE" +hyprctl keyword windowrulev2 "unset, initialClass:.*" >>"$LOGFILE" 2>&1 || true + +# ───────────────────────────────────────────────────────────────────────────── +# 5️⃣ SUPERVISION LOOP (AUTHORITATIVE PHASE) +# ───────────────────────────────────────────────────────────────────────────── +# +# EN: +# This loop: +# • scans ALL Hyprland clients +# • matches PID lineage +# • matches detached helpers +# • matches class rules +# +# UA: +# Цей цикл: +# • читає ВСІ клієнти Hyprland +# • звіряє PID дерево +# • ловить відʼєднані helper-и +# • застосовує class fallback +# + +get_descendants() { + local root="$1" + local all=("$root") + local changed=1 + + while (( changed )); do + changed=0 + for p in "${all[@]}"; do + for c in $(pgrep -P "$p" 2>/dev/null || true); do + if [[ ! " ${all[*]} " =~ " $c " ]]; then + all+=("$c") + changed=1 + fi + done + done + done + + echo "${all[@]}" +} + +pid_matches_app() { + local pid="$1" + local comm + comm="$(ps -p "$pid" -o comm= 2>/dev/null)" || return 1 + [[ "$comm" == "$APP_NAME" || "$comm" == "$APP_NAME"* ]] +} + +END_TIME=$((SECONDS + 20)) +declare -A SEEN + +while (( SECONDS < END_TIME )); do + PIDS="$(get_descendants "$ROOT_PID")" + + while IFS=$'\t' read -r PID ADDR CLASS; do + MATCH=0 + + for TPID in $PIDS; do + [[ "$PID" == "$TPID" ]] && MATCH=1 && break + done + + pid_matches_app "$PID" && MATCH=1 + + for RULE in "${CAPTURE_RULES[@]}"; do + if [[ "$RULE" =~ class:\^\((.*)\)\$ ]]; then + [[ "$CLASS" =~ ${BASH_REMATCH[1]} ]] && MATCH=1 + fi + done + + if (( MATCH )) && [[ -z "${SEEN[$ADDR]-}" ]]; then + echo "Placing window $ADDR (pid $PID, class $CLASS) → WS $TARGET_WS" >>"$LOGFILE" + hyprctl dispatch movetoworkspacesilent \ + "$TARGET_WS,address:$ADDR" >>"$LOGFILE" 2>&1 || true + SEEN[$ADDR]=1 + fi + done < <(hyprctl clients -j | jq -r '.[] | [.pid, .address, .class] | @tsv') + + sleep 0.01 done -echo "ERROR: Window for '$APP' was NOT found or dispatched properly to workspace $TARGET_WORKSPACE at $(date)" >> "$LOGFILE" -# Log error if window was not found or dispatched correctly. -# Запис помилки, якщо вікно не знайдено або неправильно диспатчено. -exit 1 +echo "=== Deploy finished: '$CMD' ===" >>"$LOGFILE" +exit 0 |
