From 4d3f9d51cd2be3780b72bfcd1d81a2f42a7d2d34 Mon Sep 17 00:00:00 2001 From: Nicola <73220426+Zerodya@users.noreply.github.com> Date: Fri, 13 Oct 2023 12:40:12 +0200 Subject: [PATCH] v0.1.0 release --- README.md | 74 ++++++++++++++++++---- hyprfreeze | 179 +++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 229 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 4079443..0026e05 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,66 @@ # hyprfreeze -Simple bash script to "freeze" a game process in Hyprland, allowing you to resume it later. -Useful to: -- Pause games during unpausable cutscenes -- Save system resources (CPU and GPU are free, the process is saved in RAM) +Hyprfreeze is an utility to suspend a game process (and other programs) in Hyprland. -(Also works for any other program; freezes the current active window) -### Usage -Make the script executable and add a bind in your Hyprland config: +Useful to: +- Pause single-player games that can't be paused (like Dark Souls) +- Pause during cutscenes to read the subtitles or when something urgent comes up +- Save system resources (excluding RAM) if you need them for another computer task, or if the game's pause menu uses too many + +## Installation +### Arch Linux +Coming soonTM in AUR. + +### Dependencies +- hyprland (to get the pid of the active window with hyprctl) +- jq (to parse json) +- hyprprop (to get the pid of a window by selecting it with your mouse) + +### Manual +Clone this repo and symlink the `hyprfreeze` script to a directory in your `PATH`: +```bash +$ git clone https://github.com/Zerodya/hyprfreeze.git Hyprfreeze +$ ln -s $(pwd)/Hyprfreeze/hyprfreeze $HOME/.local/bin +$ chmod +x Hyprfreeze/hyprfreeze ``` -bind = $mainMod, PAUSE, exec, ~/scripts/hyprfreeze + +## Usage +Add a bind in your Hyprland config to pause the current active window: ``` -### Known issues -- Mouse input doesn't work inside Xorg/XWayland windows while a Wine/Proton game is paused - - Workaround: Run the game in gamescope -- Suspending the computer while a game is paused and then resuming it later may cause the game to stutter heavily +# ~/.config/hypr/hyprland.conf + +... + +# Toggle freeze on active window +bind = $mainMod, PAUSE, exec, hyprfreeze -a +``` +### Available flags +``` +-h, --help show help message +-a, --active pause/resume active window +-p, --pid pause/resume by process id +-n, --name pause/resume by process name/command +-r, --prop pause/resume by clicking on window +--info show information about the process +--dry-run doesn't actually pause/resume a process, useful with --info +``` +### Examples: +``` +# Pause game by process name +hyprfreeze -n eldenring.exe +``` +``` +# Get info about a process by clicking on its window, without suspending it +hyprfreeze -r --info --dry-run +``` +## Disclaimer +There is always the risk, although slim, that an application may crash. + +This is intrinsically related to modifying running processes and is not something that Hyprfreeze can prevent. + +Please make sure to **save your data** before using hyprfreeze. + +## Known issues +- Pausing Wine/Proton games will cause mouse input to not work inside XWayland windows [#1](https://github.com/Zerodya/hyprfreeze/issues/2) + - Workaround 1: Run the game in `gamescope` + - Workaround 2: Pause the game from a terminal `hyprfreeze -n eldenring.exe` +- Pausing Linux native games (e.g. Minecraft) may cause sound to stop in other apps [#2](https://github.com/Zerodya/hyprfreeze/issues/2) \ No newline at end of file diff --git a/hyprfreeze b/hyprfreeze index 86e4234..d467c70 100644 --- a/hyprfreeze +++ b/hyprfreeze @@ -1,16 +1,171 @@ #!/usr/bin/env bash -PID=$(hyprctl activewindow | grep -i pid | awk -F "pid: " '{ print $2 }' | awk -F "," '{ print $1 }') -PIDS=$(pstree $PID -npl | grep -oP '(?<=\()[0-9]+(?=\))') +function printHelp() { + cat </dev/null && + echo "Resumed $(ps -p $PID -o comm= 2>/dev/null) ($PID)" || exit 1 + else + kill -STOP $PIDS 2>/dev/null && + echo "Stopped $(ps -p $PID -o comm= 2>/dev/null) ($PID)" || exit 1 + fi +} + +function freezeActive() { + PID=$(hyprctl activewindow -j | jq '.pid') + + # Prevent pausing itself + script_pid=$$ + if echo "$(pstree -p $PID | grep -oP '\(\K[^\)]+')" | grep -q "$script_pid"; then + echo "You are trying to suspend the hyprfreeze process." + echo "This option is meant to be used via keybind to pause the active window, running it in terminal would suspend hyprfreeze itself." + exit 1 + fi + + hyprfreeze +} + +function freezePid() { + # Check if process pid exists + if ! ps -p $1 &>/dev/null; then + echo "Process ID $1 not found" + exit 1 + fi + + PID=$1 + hyprfreeze +} + +function freezeName() { + # Check if process name exists + if ! pidof -x "$1" >/dev/null; then + echo "Process name $1 not found" + exit 1 + fi + + # Get last process if there are multiple + PID=$(pidof $1 | awk '{print $NF}') + hyprfreeze +} + +function freezeProp() { + PID=$(hyprprop | jq '.pid') + hyprfreeze +} + +function info() { + echo -e "$(tput bold)Process tree:$(tput sgr0)" + ps -p $PID 2>/dev/null && pstree -p $PID + + echo -e "\n$(tput bold)Process threads:$(tput sgr0)" + ps -eLo pid,tid,comm | grep $PID 2>/dev/null + + echo -e "\n$(tput bold)Process ID$(tput sgr0) = $PID \ + \n$(tput bold)Process name$(tput sgr0) = $(ps -p $PID -o comm= 2>/dev/null) \ + \n$(tput bold)Process state$(tput sgr0) = $(ps -o state= -p $PID 2>/dev/null)" +} + +function args() { + # Check if no options were passed + if [ -z "$1" ]; then + printHelp + exit 1 + fi + + # Track valid flags + local valid_flag_count=0 + + # Parse options + local options="hap:n:r" + local long_options="help,active,pid:,name:,prop,info,dry-run" + local parsed_args=$(getopt -o $options --long $long_options -n "$(basename "$0")" -- "$@") + eval set -- "$parsed_args" + while true; do + case $1 in + -h|--help) + printHelp + exit 0 + ;; + -a|--active) + ((valid_flag_count++)) + FLAG_ACTIVE=true + ;; + -p|--pid) + ((valid_flag_count++)) + shift; + FLAG_PID="$1" + ;; + -n|--name) + ((valid_flag_count++)) + shift; + NAME_FLAG="$1" + ;; + -r|--prop) + ((valid_flag_count++)) + FLAG_PROP=true + ;; + --info) + INFO=1 + ;; + --dry-run) + DRYRUN=1 + ;; + --) + shift; # Skip -- argument + COMMAND=${@:2} + break;; + *) + exit 1 + ;; + esac + shift + done + + # Check if more than one valid flag is provided, or if none was provided + if [ $valid_flag_count -ne 1 ]; then + printHelp + exit 1 + fi +} + +function main() { + # Handle the chosen valid flag + if [ "$FLAG_ACTIVE" = true ]; then freezeActive; + elif [ -n "$FLAG_PID" ]; then freezePid "$FLAG_PID"; + elif [ -n "$NAME_FLAG" ]; then freezeName "$NAME_FLAG"; + elif [ "$FLAG_PROP" = true ]; then freezeProp; + fi + + # Run info function after PID is obtained + if [ $INFO -eq 1 ]; then info; fi +} + +INFO=0 +DRYRUN=0 + +args "$@" + +main