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