run-steam-game 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. #!/usr/bin/env bash
  2. set -u
  3. if [[ $# -lt 2 ]]; then
  4. echo "Usage: $0 <Steam AppID> <process regex>" >&2
  5. exit 2
  6. fi
  7. APPID="$1"
  8. PROCESS_REGEX="$2"
  9. START_TIMEOUT="${START_TIMEOUT:-120}"
  10. EXIT_STABLE_TIME="${EXIT_STABLE_TIME:-3}"
  11. SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
  12. LOG_FILE="${SCRIPT_DIR}/steam-game.log"
  13. log() {
  14. printf '[%s] [STEAM:%s] %s\n' \
  15. "$(date '+%Y-%m-%d %H:%M:%S')" \
  16. "${APPID}" \
  17. "$*" >> "${LOG_FILE}"
  18. }
  19. if ! command -v steam >/dev/null 2>&1; then
  20. log "ERROR: steam command not found"
  21. exit 127
  22. fi
  23. #
  24. # PIDs du jeu.
  25. #
  26. # On inspecte /proc nous-mêmes afin d'éviter que pgrep -f ne matche
  27. # accidentellement le wrapper avec le regex passé en argument.
  28. #
  29. game_pids() {
  30. local proc
  31. local pid
  32. local cmdline
  33. local -a argv
  34. for proc in /proc/[0-9]*; do
  35. pid="${proc##*/}"
  36. # Ne jamais nous matcher nous-mêmes.
  37. [[ "${pid}" == "$$" ]] && continue
  38. [[ -r "${proc}/cmdline" ]] || continue
  39. argv=()
  40. mapfile -d '' -t argv < "${proc}/cmdline" 2>/dev/null || true
  41. [[ ${#argv[@]} -gt 0 ]] || continue
  42. cmdline="${argv[*]}"
  43. if [[ "${cmdline}" =~ ${PROCESS_REGEX} ]]; then
  44. printf '%s\n' "${pid}"
  45. fi
  46. done
  47. }
  48. terminate_game() {
  49. local -a pids
  50. mapfile -t pids < <(game_pids)
  51. if [[ ${#pids[@]} -gt 0 ]]; then
  52. log "Stopping game processes: ${pids[*]}"
  53. kill -TERM "${pids[@]}" 2>/dev/null || true
  54. sleep 3
  55. mapfile -t pids < <(game_pids)
  56. if [[ ${#pids[@]} -gt 0 ]]; then
  57. log "Force stopping remaining processes: ${pids[*]}"
  58. kill -KILL "${pids[@]}" 2>/dev/null || true
  59. fi
  60. fi
  61. }
  62. #
  63. # Important pour la suite :
  64. # si Sunshine arrête ce wrapper, on ferme également le jeu.
  65. #
  66. trap 'log "Wrapper received termination signal"; terminate_game; exit 143' \
  67. TERM INT HUP
  68. log "Launching Steam AppID ${APPID}"
  69. log "Process regex: ${PROCESS_REGEX}"
  70. nohup steam -applaunch "${APPID}" \
  71. >> "${LOG_FILE}" 2>&1 &
  72. #
  73. # Attendre que le vrai processus du jeu apparaisse.
  74. #
  75. start_time="${SECONDS}"
  76. while true; do
  77. mapfile -t pids < <(game_pids)
  78. if [[ ${#pids[@]} -gt 0 ]]; then
  79. log "Game detected: ${pids[*]}"
  80. break
  81. fi
  82. if (( SECONDS - start_time >= START_TIMEOUT )); then
  83. log "ERROR: game did not appear within ${START_TIMEOUT}s"
  84. exit 124
  85. fi
  86. sleep 1
  87. done
  88. #
  89. # Rester vivant aussi longtemps que le jeu.
  90. #
  91. missing_since=""
  92. while true; do
  93. mapfile -t pids < <(game_pids)
  94. if [[ ${#pids[@]} -gt 0 ]]; then
  95. missing_since=""
  96. else
  97. if [[ -z "${missing_since}" ]]; then
  98. missing_since="${SECONDS}"
  99. fi
  100. if (( SECONDS - missing_since >= EXIT_STABLE_TIME )); then
  101. log "Game exited"
  102. break
  103. fi
  104. fi
  105. sleep 1
  106. done
  107. exit 0