dev: automated commit - 2025-11-16 11:58:51

This commit is contained in:
Mariano Z. 2025-11-16 15:13:37 -03:00
parent 7dbe7f4685
commit 9dca029a62
Signed by: marianozunino
GPG key ID: 4C73BAD25156DACE
5 changed files with 102 additions and 178 deletions

View file

@ -1,167 +1,96 @@
#!/usr/bin/env bash
set -euo pipefail
# Script to launch an application or focus it if already running in sway
# Usage: launch-or-focus <command> [args...]
# Set DEBUG=1 to enable debug logging
# Simple script to launch an application or focus it if already running in sway
# Usage: launch-or-focus <command> [--class CLASS] [--title TITLE] [args...]
#
# Examples:
# launch-or-focus firefox
# launch-or-focus code --class Code --title "My Project"
# launch-or-focus kitty --class kitty
DEBUG="${DEBUG:-0}"
CLASS=""
TITLE=""
ARGS=()
debug() {
[ "$DEBUG" = "1" ] && echo "[DEBUG] $*" >&2 || true
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--class)
CLASS="$2"
shift 2
;;
--title)
TITLE="$2"
shift 2
;;
*)
ARGS+=("$1")
shift
;;
esac
done
if [ $# -eq 0 ]; then
echo "Usage: $0 <command> [args...]" >&2
if [ ${#ARGS[@]} -eq 0 ]; then
echo "Usage: $0 <command> [--class CLASS] [--title TITLE] [args...]" >&2
exit 1
fi
COMMAND="$1"
shift
ARGS=("$@")
COMMAND="${ARGS[0]}"
APP_NAME=$(basename "$COMMAND")
debug "Command: $COMMAND"
debug "App name: $APP_NAME"
debug "Args: ${ARGS[*]}"
unset 'ARGS[0]'
ARGS=("${ARGS[@]}")
# Extract --class and --title values from args if present
# Handle both --class VALUE and --class=VALUE formats
CLASS_NAME=""
TITLE_NAME=""
for i in "${!ARGS[@]}"; do
if [[ "${ARGS[$i]}" =~ ^--class=(.+)$ ]]; then
CLASS_NAME="${BASH_REMATCH[1]}"
debug "Found class (format --class=VALUE): $CLASS_NAME"
elif [ "${ARGS[$i]}" = "--class" ] && [ $((i+1)) -lt ${#ARGS[@]} ]; then
CLASS_NAME="${ARGS[$((i+1))]}"
debug "Found class (format --class VALUE): $CLASS_NAME"
elif [[ "${ARGS[$i]}" =~ ^--title=(.+)$ ]]; then
TITLE_NAME="${BASH_REMATCH[1]}"
debug "Found title (format --title=VALUE): $TITLE_NAME"
elif [ "${ARGS[$i]}" = "--title" ] && [ $((i+1)) -lt ${#ARGS[@]} ]; then
TITLE_NAME="${ARGS[$((i+1))]}"
debug "Found title (format --title VALUE): $TITLE_NAME"
fi
done
debug "Extracted CLASS_NAME: '$CLASS_NAME'"
debug "Extracted TITLE_NAME: '$TITLE_NAME'"
find_window_by_class() {
local class="$1"
debug "Searching for window by class: $class"
local result
result=$(swaymsg -t get_tree | jq -r --arg class "$class" '
recurse(.nodes[]?, .floating_nodes[]?) |
select(
((.window_properties.class | type) == "string" and .window_properties.class == $class) or
((.app_id | type) == "string" and .app_id == $class)
) |
.id
' | head -n 1)
debug "find_window_by_class result: '$result'"
echo "$result"
}
find_window_by_title() {
local title="$1"
local app="$2"
local result
debug "Searching for window by title: '$title' and app: '$app'"
# Search for windows matching both app and title (most specific)
result=$(swaymsg -t get_tree | jq -r --arg title "$title" --arg app "$app" '
recurse(.nodes[]?, .floating_nodes[]?) |
select(
(
((.app_id | type) == "string" and (.app_id == $app or (.app_id | test($app; "i")))) or
((.window_properties.class | type) == "string" and (.window_properties.class == $app or (.window_properties.class | test($app; "i"))))
) and
((.name | type) == "string" and .name == $title)
) |
.id
' | head -n 1)
debug "find_window_by_title (app+title) result: '$result'"
[ -n "$result" ] && [ "$result" != "null" ] && echo "$result" && return
# Fall back to title-only exact match
debug "Falling back to title-only exact match"
result=$(swaymsg -t get_tree | jq -r --arg title "$title" '
recurse(.nodes[]?, .floating_nodes[]?) |
select((.name | type) == "string" and .name == $title) |
.id
' | head -n 1)
debug "find_window_by_title (title only) result: '$result'"
[ -n "$result" ] && [ "$result" != "null" ] && echo "$result" && return
# Final fallback: title-only case-insensitive match
debug "Falling back to title-only case-insensitive match"
result=$(swaymsg -t get_tree | jq -r --arg title "$title" '
recurse(.nodes[]?, .floating_nodes[]?) |
select((.name | type) == "string" and (.name | test($title; "i"))) |
.id
' | head -n 1)
debug "find_window_by_title (case-insensitive) result: '$result'"
echo "$result"
}
# If class/title were provided as flags, add them to command args (for apps that support them)
if [ -n "$CLASS" ]; then
ARGS=("--class=$CLASS" "${ARGS[@]}")
fi
if [ -n "$TITLE" ]; then
ARGS=("--title=$TITLE" "${ARGS[@]}")
fi
# Find window by app_id (sway uses app_id), title, or app name
find_window() {
local app_name="$1"
debug "Searching for window by app name: $app_name"
local result
result=$(swaymsg -t get_tree | jq -r --arg app "$app_name" '
recurse(.nodes[]?, .floating_nodes[]?) |
select(
((.app_id | type) == "string" and (.app_id == $app or (.app_id | test($app; "i")))) or
((.window_properties.class | type) == "string" and (.window_properties.class == $app or (.window_properties.class | test($app; "i")))) or
((.name | type) == "string" and (.name | test($app; "i")))
) |
.id
' | head -n 1)
debug "find_window result: '$result'"
echo "$result"
}
focus_window() {
local window_id="$1"
debug "Focusing window ID: $window_id"
swaymsg "[con_id=$window_id]" focus
}
WINDOW_ID=""
debug "Starting window search..."
# Only search by class/title if provided - don't fall back to generic app search
# This prevents matching the wrong window
if [ -n "$CLASS_NAME" ]; then
WINDOW_ID=$(find_window_by_class "$CLASS_NAME")
debug "After class search: WINDOW_ID='$WINDOW_ID'"
fi
if ([ -z "$WINDOW_ID" ] || [ "$WINDOW_ID" = "null" ]) && [ -n "$TITLE_NAME" ]; then
WINDOW_ID=$(find_window_by_title "$TITLE_NAME" "$APP_NAME")
debug "After title search: WINDOW_ID='$WINDOW_ID'"
fi
# Only fall back to generic app search if we don't have class or title
# This means the user wants to focus any instance of the app
if ([ -z "$WINDOW_ID" ] || [ "$WINDOW_ID" = "null" ]) && [ -z "$CLASS_NAME" ] && [ -z "$TITLE_NAME" ]; then
WINDOW_ID=$(find_window "$APP_NAME")
debug "After app name search (no class/title): WINDOW_ID='$WINDOW_ID'"
local class="$1"
local title="$2"
local app="$3"
([ -z "$WINDOW_ID" ] || [ "$WINDOW_ID" = "null" ]) && WINDOW_ID=$(find_window "${APP_NAME,,}")
debug "After lowercase app name search: WINDOW_ID='$WINDOW_ID'"
# If class provided, search by app_id first (sway uses app_id)
if [ -n "$class" ]; then
swaymsg -t get_tree | jq -r --arg class "$class" '
recurse(.nodes[]?, .floating_nodes[]?) |
select((.app_id | type) == "string" and .app_id == $class) |
.id
' | head -n 1
return
fi
# If only title provided, search by title
if [ -n "$title" ]; then
swaymsg -t get_tree | jq -r --arg title "$title" '
recurse(.nodes[]?, .floating_nodes[]?) |
select((.name | type) == "string" and .name == $title) |
.id
' | head -n 1
return
fi
# Fallback to app name (search by app_id)
swaymsg -t get_tree | jq -r --arg app "$app" '
recurse(.nodes[]?, .floating_nodes[]?) |
select((.app_id | type) == "string" and .app_id == $app) |
.id
' | head -n 1
}
# Try to find existing window
WINDOW_ID=$(find_window "$CLASS" "$TITLE" "$APP_NAME")
# Focus if found, otherwise launch
if [ -n "$WINDOW_ID" ] && [ "$WINDOW_ID" != "null" ]; then
swaymsg "[con_id=$WINDOW_ID]" focus
else
"$COMMAND" "${ARGS[@]}" >/dev/null 2>&1 &
disown
fi
if [ -n "$WINDOW_ID" ] && [ "$WINDOW_ID" != "null" ] && [ "$WINDOW_ID" -eq "$WINDOW_ID" ] 2>/dev/null; then
debug "Window found! Focusing window ID: $WINDOW_ID"
focus_window "$WINDOW_ID"
exit 0
fi
debug "No matching window found, launching new instance"
"$COMMAND" "${ARGS[@]}" >/dev/null 2>&1 &
disown