ndrop/ndrop

258 lines
8.1 KiB
Bash
Executable file

#!/usr/bin/env bash
# set -euo pipefail
print_help() {
echo ""
echo "Usage: ndrop [OPTIONS] [COMMAND]"
echo ""
echo "Command:"
echo " The usual command you would run to start the desired program"
echo ""
echo "Options:"
echo " -c, --class"
echo " Set classname of the program to be run. Use this if the"
echo " classname is different from the name of the [COMMAND]"
echo " and ndrop has no hardcoded replacement."
echo ""
echo " -F, --focus"
echo " Changes the default behaviour: focus the specified program's"
echo " window and switch to its present workspace if necessary."
echo " Do not hide it, if it's already on the current workspace."
echo ""
echo " -H, --help"
echo " Print this help message"
echo ""
echo " -i, --insensitive"
echo " Case insensitive partial matching of class names."
echo " You can try this if a running program is not recognized and"
echo " a new instance is launched instead."
echo ""
echo " -o, --online"
echo " Delay initial launch for up to 20 seconds"
echo " until internet connectivity is established."
echo ""
echo " -v, --verbose"
echo " Show notifications regarding the matching process."
echo " Use this to figure out why running programs are not matched."
echo ""
echo " -V, --version"
echo " Print version"
echo ""
echo "Multiple instances:"
echo ""
echo "Multiple instances of the same program can be run concurrently, if"
echo "different class names are assigned to each instance. Presently there is"
echo "support for the following flags in the [COMMAND] string:"
echo ""
echo " -a ('foot' terminal emulator)"
echo " --class (all other programs)"
echo ""
echo "See man page for more information"
}
print_version() {
echo "ndrop version: 0.1.0-dev"
}
notify() {
notify-send "$@"
echo "$@"
}
notify_low() {
notify-send -u low "$@"
echo "$@"
}
partial_match() {
CLASS=$(niri msg --json windows | jq -r ".[] | select((.app_id |match(\"$1\";\"i\"))) | .app_id" | head -1)
}
wait_online() {
wait_online=0
while [[ $wait_online -lt 200 ]]; do
ping -qc 1 github.com && break
sleep 0.1
$wait_online++
done
}
active_workspace() {
active_workspace_name=$(niri msg --json workspaces | jq -r ".[] | select(.is_focused==true) | .name")
if [[ "$active_workspace_name" != "null" ]]; then
echo "$active_workspace_name"
else
niri msg --json workspaces | jq -r ".[] | select(.is_focused==true) | .id"
fi
}
ndrop_flags() {
while true; do
case "$1" in
-c | --class)
shift
CLASS_OVERRIDE="$1"
;;
-F | --focus)
FOCUS=true
;;
-H | --help)
print_help
exit
;;
-i | --insensitive)
INSENSITIVE=true
;;
-o | --online)
ONLINE=true
;;
-v | --verbose)
VERBOSE=true
;;
-V | --version)
print_version
exit
;;
*) break ;;
esac
shift
done
}
FOCUS=false
INSENSITIVE=false
ONLINE=false
VERBOSE=false
NDROP_FLAGS=()
while true; do
case "$1" in
"")
notify "ndrop: Missing Argument" "Run 'ndrop -h' for more information"
print_help
exit 1
;;
-c | --class)
NDROP_FLAGS+=("$1" "$2")
shift
;;
-*)
NDROP_FLAGS+=("$1")
;;
*) break ;;
esac
shift
done
# shellcheck disable=SC2128 # erroneous warning
if [[ -n $NDROP_FLAGS ]]; then
# shellcheck disable=SC2046 # avoids having to run 'eval set -- "$NDROP_FLAGS"' to remove leading whitespace
ndrop_flags $(getopt -u --options c:FHiovV --longoptions class:,focus,help,insensitive,online,verbose,version, -n ndrop -- "${NDROP_FLAGS[@]}")
fi
CLASS="$1"
COMMANDLINE="${*:1}"
ACTIVE_WORKSPACE=$(active_workspace)
ACTIVE_WORKSPACE_ID=$(niri msg --json workspaces | jq -r ".[] | select(.is_focused==true) | .id")
# ACTIVE_OUTPUT=$(niri msg --json focused-output | jq -r ".name")
# NDROP_OUTPUT=$(niri msg --json workspaces | jq -r ".[] | select(.name==\"ndrop\") | .output")
case "$1" in
epiphany)
CLASS="org.gnome.Epiphany"
;;
brave)
CLASS="brave-browser"
;;
foot)
OPT=$(getopt --options a: --longoptions app-id: -n ndrop -- "$@")
;;
godot4)
partial_match "org.godotengine."
;;
logseq)
CLASS="Logseq"
;;
telegram-desktop)
CLASS="org.telegram.desktop"
;;
tor-browser)
CLASS="Tor Browser"
;;
*)
OPT=$(getopt --options "" --longoptions class: -n ndrop -- "$@")
;;
esac
if $VERBOSE && [[ $1 != "$CLASS" ]]; then notify_low "ndrop: Using '$CLASS' as hardcoded classname of '$1' for matching"; fi
if [[ -n $OPT ]]; then
eval set -- "$OPT" # remove leading whitespace
case "$1" in
-a | --class)
CLASS="$2"
if $VERBOSE; then notify_low "ndrop: Extracted '$CLASS' from [COMMAND] for matching"; fi
;;
esac
fi
if $INSENSITIVE && [[ -n $(niri msg --json windows | jq -r ".[] | select((.app_id |test(\"$CLASS\";\"i\")))") ]]; then
if $VERBOSE; then notify_low "ndrop: --insensitive -> Insensitive (partial) match of class '$CLASS' successful"; fi
CLASS=$(niri msg --json windows | jq -r ".[] | select((.app_id |match(\"$CLASS\";\"i\"))) | .app_id" | head -1) || notify "ndrop: Error assigning case insensitive (partial) match to CLASS"
if $VERBOSE; then notify_low "ndrop: --insensitive -> Using class '$CLASS' after insensitive (partial) matching"; fi
fi
if [[ -n $CLASS_OVERRIDE ]]; then
if $VERBOSE; then notify_low "ndrop: --class -> Using given class '$CLASS_OVERRIDE' instead of '$CLASS' for matching"; fi
CLASS="$CLASS_OVERRIDE"
fi
WINDOW_ID=$(niri msg --json windows | jq -r ".[] | select(.app_id==\"$CLASS\") | .id")
FOCUSED=$(niri msg --json windows | jq -r ".[] | select(.app_id==\"$CLASS\") | .is_focused")
if [[ -n ${WINDOW_ID} ]]; then
if [[ "${ACTIVE_WORKSPACE}" == "ndrop" ]]; then
if ${VERBOSE}; then
notify_low "ndrop: CLASS: ${CLASS}, ACTIVE_WORKSPACE_ID: ${ACTIVE_WORKSPACE_ID}, ACTIVE_WORKSPACE: ${ACTIVE_WORKSPACE}, WINDOW_ID: ${WINDOW_ID}"
notify_low "ndrop: Matched we are on ws 'ndrop'; focus previous ws"
fi
niri msg action focus-workspace-previous
exit 0
else
if [[ $FOCUSED == true ]]; then
if $VERBOSE; then
notify_low "ndrop: CLASS: ${CLASS}, ACTIVE_WORKSPACE_ID: ${ACTIVE_WORKSPACE_ID}, ACTIVE_WORKSPACE: ${ACTIVE_WORKSPACE}, WINDOW_ID: ${WINDOW_ID}"
notify_low "ndrop: Matched focused target on active ws '${ACTIVE_WORKSPACE}'; move target to 'ndrop'"
fi
niri msg action move-window-to-workspace --window-id "${WINDOW_ID}" "ndrop"
niri msg action focus-workspace-previous
# For good multi-monitor support, we need something like this in niri.
# if [[ "$ACTIVE_OUTPUT" != "$NDROP_OUTPUT" ]]; then
# niri msg action focus-monitor "${ACTIVE_OUTPUT}"
# fi
niri msg action focus-workspace "${ACTIVE_WORKSPACE}"
exit 0
else
if [[ $VERBOSE == true ]]; then
notify_low "ndrop: CLASS: ${CLASS}, ACTIVE_WORKSPACE_ID: ${ACTIVE_WORKSPACE_ID}, ACTIVE_WORKSPACE: ${ACTIVE_WORKSPACE}, WINDOW_ID: ${WINDOW_ID}"
# notify_low "ndrop: Matched unfocused target on active ws '${ACTIVE_WORKSPACE}'; focu'"
fi
niri msg action move-window-to-workspace --window-id "${WINDOW_ID}" "${ACTIVE_WORKSPACE}"
niri msg action focus-window --id "${WINDOW_ID}"
niri msg action set-window-width 90% && niri msg action set-window-height 70%
exit 0
fi
fi
else
if $ONLINE; then wait_online; fi
# 'foot' always throws an error when its window is closed. Thus we disable the notification.
if [[ $COMMAND == "foot" ]]; then
# shellcheck disable=SC2086 # when quoting COMMANDLINE the execution of the command fails
$COMMANDLINE
else
# shellcheck disable=SC2086 # when quoting COMMANDLINE the execution of the command fails
$COMMANDLINE || notify "ndrop: Error executing given command" "$COMMANDLINE"
niri msg action set-window-width 90% && niri msg action set-window-height 70%
fi
if $VERBOSE; then notify_low "ndrop: No running program matches class '$CLASS'." "Currently active classes are '$(niri msg --json windows | jq -r '.[] | .app_id' | sort | tr '\n' ' ')'. Executed '$COMMANDLINE' in case it was not running already."; fi
fi