launch-or-focus 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. # Script to launch an application or focus it if already running in sway
  4. # Usage: launch-or-focus <command> [args...]
  5. if [ $# -eq 0 ]; then
  6. echo "Usage: $0 <command> [args...]" >&2
  7. exit 1
  8. fi
  9. COMMAND="$1"
  10. shift
  11. ARGS=("$@")
  12. APP_NAME=$(basename "$COMMAND")
  13. # Extract --class and --title values from args if present
  14. CLASS_NAME=""
  15. TITLE_NAME=""
  16. for i in "${!ARGS[@]}"; do
  17. if [ "${ARGS[$i]}" = "--class" ] && [ $((i+1)) -lt ${#ARGS[@]} ]; then
  18. CLASS_NAME="${ARGS[$((i+1))]}"
  19. elif [ "${ARGS[$i]}" = "--title" ] && [ $((i+1)) -lt ${#ARGS[@]} ]; then
  20. TITLE_NAME="${ARGS[$((i+1))]}"
  21. fi
  22. done
  23. find_window_by_class() {
  24. local class="$1"
  25. swaymsg -t get_tree | jq -r --arg class "$class" '
  26. recurse(.nodes[]?, .floating_nodes[]?) |
  27. select(
  28. ((.window_properties.class | type) == "string" and .window_properties.class == $class) or
  29. ((.app_id | type) == "string" and .app_id == $class)
  30. ) |
  31. .id
  32. ' | head -n 1
  33. }
  34. find_window_by_title() {
  35. local title="$1"
  36. local app="$2"
  37. local result
  38. # Search for windows matching both app and title (most specific)
  39. result=$(swaymsg -t get_tree | jq -r --arg title "$title" --arg app "$app" '
  40. recurse(.nodes[]?, .floating_nodes[]?) |
  41. select(
  42. (
  43. ((.app_id | type) == "string" and (.app_id == $app or (.app_id | test($app; "i")))) or
  44. ((.window_properties.class | type) == "string" and (.window_properties.class == $app or (.window_properties.class | test($app; "i"))))
  45. ) and
  46. ((.name | type) == "string" and .name == $title)
  47. ) |
  48. .id
  49. ' | head -n 1)
  50. [ -n "$result" ] && [ "$result" != "null" ] && echo "$result" && return
  51. # Fall back to title-only exact match
  52. result=$(swaymsg -t get_tree | jq -r --arg title "$title" '
  53. recurse(.nodes[]?, .floating_nodes[]?) |
  54. select((.name | type) == "string" and .name == $title) |
  55. .id
  56. ' | head -n 1)
  57. [ -n "$result" ] && [ "$result" != "null" ] && echo "$result" && return
  58. # Final fallback: title-only case-insensitive match
  59. swaymsg -t get_tree | jq -r --arg title "$title" '
  60. recurse(.nodes[]?, .floating_nodes[]?) |
  61. select((.name | type) == "string" and (.name | test($title; "i"))) |
  62. .id
  63. ' | head -n 1
  64. }
  65. find_window() {
  66. local app_name="$1"
  67. swaymsg -t get_tree | jq -r --arg app "$app_name" '
  68. recurse(.nodes[]?, .floating_nodes[]?) |
  69. select(
  70. ((.app_id | type) == "string" and (.app_id == $app or (.app_id | test($app; "i")))) or
  71. ((.window_properties.class | type) == "string" and (.window_properties.class == $app or (.window_properties.class | test($app; "i")))) or
  72. ((.name | type) == "string" and (.name | test($app; "i")))
  73. ) |
  74. .id
  75. ' | head -n 1
  76. }
  77. focus_window() {
  78. local window_id="$1"
  79. swaymsg "[con_id=$window_id]" focus
  80. }
  81. WINDOW_ID=""
  82. [ -n "$CLASS_NAME" ] && WINDOW_ID=$(find_window_by_class "$CLASS_NAME")
  83. ([ -z "$WINDOW_ID" ] || [ "$WINDOW_ID" = "null" ]) && [ -n "$TITLE_NAME" ] && WINDOW_ID=$(find_window_by_title "$TITLE_NAME" "$APP_NAME")
  84. ([ -z "$WINDOW_ID" ] || [ "$WINDOW_ID" = "null" ]) && WINDOW_ID=$(find_window "$APP_NAME")
  85. ([ -z "$WINDOW_ID" ] || [ "$WINDOW_ID" = "null" ]) && WINDOW_ID=$(find_window "${APP_NAME,,}")
  86. [ -n "$WINDOW_ID" ] && [ "$WINDOW_ID" != "null" ] && [ "$WINDOW_ID" -eq "$WINDOW_ID" ] 2>/dev/null && focus_window "$WINDOW_ID" && exit 0
  87. "$COMMAND" "${ARGS[@]}" >/dev/null 2>&1 &
  88. disown