run-steam-game 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. #!/usr/bin/env bash
  2. set -u
  3. if [[ $# -lt 2 ]]; then
  4. echo "Usage: $0 <Steam AppID> <process name>" >&2
  5. echo "Example: $0 108800 Crysis2.exe" >&2
  6. exit 2
  7. fi
  8. APPID="$1"
  9. PROCESS_NAME="$2"
  10. START_TIMEOUT="${START_TIMEOUT:-120}"
  11. EXIT_STABLE_TIME="${EXIT_STABLE_TIME:-3}"
  12. LOG_DIR="${XDG_STATE_HOME:-${HOME}/.local/state}/sunshine"
  13. LOG_FILE="${LOG_DIR}/steam-game.log"
  14. mkdir -p "${LOG_DIR}"
  15. WRAPPER_PID="$$"
  16. log() {
  17. printf '[%s] [STEAM:%s] %s\n' \
  18. "$(date '+%Y-%m-%d %H:%M:%S')" \
  19. "${APPID}" \
  20. "$*" >> "${LOG_FILE}"
  21. }
  22. #
  23. # Retourne les PID dont la ligne de commande contient littéralement
  24. # le nom de processus demandé.
  25. #
  26. game_pids() {
  27. local proc
  28. local pid
  29. local cmdline
  30. local -a argv
  31. for proc in /proc/[0-9]*; do
  32. pid="${proc##*/}"
  33. # Ne jamais matcher le wrapper principal.
  34. [[ "${pid}" == "${WRAPPER_PID}" ]] && continue
  35. # game_pids() tourne dans un sous-shell à cause de :
  36. #
  37. # mapfile -t pids < <(game_pids)
  38. #
  39. # Il faut donc également ignorer ce sous-shell.
  40. [[ "${pid}" == "${BASHPID}" ]] && continue
  41. [[ -r "${proc}/cmdline" ]] || continue
  42. argv=()
  43. mapfile -d '' -t argv < "${proc}/cmdline" 2>/dev/null || true
  44. [[ ${#argv[@]} -gt 0 ]] || continue
  45. cmdline="${argv[*]}"
  46. if [[ "${cmdline}" == *"${PROCESS_NAME}"* ]]; then
  47. printf '%s\n' "${pid}"
  48. fi
  49. done
  50. }
  51. #
  52. # Termine le ou les processus correspondant au jeu.
  53. #
  54. terminate_game() {
  55. local -a pids
  56. mapfile -t pids < <(game_pids)
  57. if [[ ${#pids[@]} -eq 0 ]]; then
  58. log "No game process to terminate"
  59. return 0
  60. fi
  61. log "Stopping game processes: ${pids[*]}"
  62. kill -TERM "${pids[@]}" 2>/dev/null || true
  63. sleep 3
  64. mapfile -t pids < <(game_pids)
  65. if [[ ${#pids[@]} -gt 0 ]]; then
  66. log "Force stopping remaining processes: ${pids[*]}"
  67. kill -KILL "${pids[@]}" 2>/dev/null || true
  68. fi
  69. }
  70. #
  71. # Si Sunshine termine le wrapper, terminer également le jeu.
  72. #
  73. on_termination() {
  74. log "Wrapper received termination signal"
  75. terminate_game
  76. exit 143
  77. }
  78. trap on_termination TERM INT HUP
  79. #
  80. # Vérifications.
  81. #
  82. if ! command -v steam >/dev/null 2>&1; then
  83. log "ERROR: steam command not found"
  84. exit 127
  85. fi
  86. log "Wrapper started with PID ${WRAPPER_PID}"
  87. log "Launching Steam AppID ${APPID}"
  88. log "Process name: ${PROCESS_NAME}"
  89. #
  90. # Demander à Steam de lancer le jeu.
  91. #
  92. steam -applaunch "${APPID}" \
  93. >> "${LOG_FILE}" 2>&1 &
  94. #
  95. # Attendre l'apparition du véritable processus du jeu.
  96. #
  97. start_time="${SECONDS}"
  98. while true; do
  99. mapfile -t pids < <(game_pids)
  100. if [[ ${#pids[@]} -gt 0 ]]; then
  101. log "Game detected: ${pids[*]}"
  102. break
  103. fi
  104. if (( SECONDS - start_time >= START_TIMEOUT )); then
  105. log "ERROR: Game did not appear within ${START_TIMEOUT} seconds"
  106. exit 124
  107. fi
  108. sleep 1
  109. done
  110. #
  111. # Rester vivant aussi longtemps que le jeu.
  112. #
  113. missing_since=""
  114. while true; do
  115. mapfile -t pids < <(game_pids)
  116. if [[ ${#pids[@]} -gt 0 ]]; then
  117. missing_since=""
  118. else
  119. if [[ -z "${missing_since}" ]]; then
  120. missing_since="${SECONDS}"
  121. log "Game process disappeared, waiting ${EXIT_STABLE_TIME}s"
  122. fi
  123. if (( SECONDS - missing_since >= EXIT_STABLE_TIME )); then
  124. log "Game exited"
  125. break
  126. fi
  127. fi
  128. sleep 1
  129. done
  130. log "Wrapper exiting normally"
  131. exit 0