|
|
@@ -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
|
|
|
-
|
|
|
-DEBUG="${DEBUG:-0}"
|
|
|
-
|
|
|
-debug() {
|
|
|
- [ "$DEBUG" = "1" ] && echo "[DEBUG] $*" >&2 || true
|
|
|
-}
|
|
|
+# 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
|
|
|
+
|
|
|
+CLASS=""
|
|
|
+TITLE=""
|
|
|
+ARGS=()
|
|
|
+
|
|
|
+# 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'"
|
|
|
+# 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_class() {
|
|
|
+# Find window by app_id (sway uses app_id), title, or app name
|
|
|
+find_window() {
|
|
|
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
|
|
|
+ local title="$2"
|
|
|
+ local app="$3"
|
|
|
|
|
|
- # 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)
|
|
|
+ # 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
|
|
|
|
|
|
- debug "find_window_by_title (title only) result: '$result'"
|
|
|
- [ -n "$result" ] && [ "$result" != "null" ] && echo "$result" && return
|
|
|
+ # 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
|
|
|
|
|
|
- # 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"
|
|
|
-}
|
|
|
-
|
|
|
-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" '
|
|
|
+ # 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 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")))
|
|
|
- ) |
|
|
|
+ select((.app_id | type) == "string" and .app_id == $app) |
|
|
|
.id
|
|
|
- ' | head -n 1)
|
|
|
- debug "find_window result: '$result'"
|
|
|
- echo "$result"
|
|
|
+ ' | head -n 1
|
|
|
}
|
|
|
|
|
|
-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'"
|
|
|
-
|
|
|
- ([ -z "$WINDOW_ID" ] || [ "$WINDOW_ID" = "null" ]) && WINDOW_ID=$(find_window "${APP_NAME,,}")
|
|
|
- debug "After lowercase app name search: WINDOW_ID='$WINDOW_ID'"
|
|
|
-fi
|
|
|
+# Try to find existing window
|
|
|
+WINDOW_ID=$(find_window "$CLASS" "$TITLE" "$APP_NAME")
|
|
|
|
|
|
-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
|
|
|
+# 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
|
|
|
|
|
|
-debug "No matching window found, launching new instance"
|
|
|
-"$COMMAND" "${ARGS[@]}" >/dev/null 2>&1 &
|
|
|
-disown
|