diff --git a/audiogame-manager.sh b/audiogame-manager.sh index bde45d4..8521805 100755 --- a/audiogame-manager.sh +++ b/audiogame-manager.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# shellcheck disable=SC1091 # Get script directory for relative paths scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -11,6 +12,136 @@ export DIALOGOPTS='--no-lines --visit-items' # Wine32 version for SAPI compatibility wineThirtyTwoVersion="9.0" +# Track nvda2speechd lifecycle for this launcher run. +nvda2speechdPid="" +nvda2speechdStarted="false" +customLaunchHandled="false" +cthulhuTitleReaderEnabled="false" +cthulhuTitleReaderTried="false" + +log_msg() { + local logFile="${scriptDir}/game.log" + local timestamp="" + timestamp="$(date)" + echo "${1} [${timestamp}]" >> "$logFile" +} + +start_nvda2speechd() { + if [[ "$nvda2speechdStarted" == "true" ]]; then + return + fi + if ! ss -ltnp | rg 3457 | grep -q 'cthulhu'; then + if [[ -x "${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/nvda2speechd" ]]; then + local translateSetting="${TRANSLATE:-unset}" + local translateFromSetting="${TRANSLATE_FROM:-unset}" + local translateToSetting="${TRANSLATE_TO:-unset}" + log_msg "Preparing nvda2speechd with TRANSLATE=${translateSetting} TRANSLATE_FROM=${translateFromSetting} TRANSLATE_TO=${translateToSetting}." + if [[ "${translateSetting,,}" == "true" ]] || [[ "$translateSetting" == "1" ]]; then + if ! command -v trans &> /dev/null; then + log_msg "TRANSLATE is enabled but 'trans' is missing; nvda2speechd may fail to start." + fi + fi + "${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/nvda2speechd" &> /dev/null & + nvda2speechdPid=$! + nvda2speechdStarted="true" + log_msg "Started nvda2speechd (pid ${nvda2speechdPid})." + else + log_msg "nvda2speechd binary not found or not executable; skipping start." + fi + else + log_msg "cthulhu is already listening on port 3457; skipping nvda2speechd start." + fi +} + +stop_nvda2speechd() { + if [[ "$nvda2speechdStarted" != "true" ]]; then + return + fi + if [[ -n "$nvda2speechdPid" ]] && kill -0 "$nvda2speechdPid" 2>/dev/null; then + kill "$nvda2speechdPid" 2>/dev/null + wait "$nvda2speechdPid" 2>/dev/null + fi + nvda2speechdPid="" + nvda2speechdStarted="false" +} + +enable_cthulhu_window_title_reader() { + if [[ "$cthulhuTitleReaderEnabled" == "true" ]]; then + return 0 + fi + if [[ "$cthulhuTitleReaderTried" == "true" ]]; then + return 1 + fi + cthulhuTitleReaderTried="true" + + if ! command -v gdbus &> /dev/null; then + log_msg "gdbus not found; cannot enable Cthulhu WindowTitleReader." + return 1 + fi + + local dbusService="org.stormux.Cthulhu.Service" + local modulePath="/org/stormux/Cthulhu/Service/PluginSystemManager" + local titleModulePath="/org/stormux/Cthulhu/Service/Plugin_WindowTitleReader" + local moduleInterface="org.stormux.Cthulhu.Module" + local enableResult="" + local titleEnableResult="" + + gdbus call --session --dest "$dbusService" \ + --object-path "$modulePath" \ + --method "${moduleInterface}.ExecuteCommand" \ + "RescanPlugins" false &> /dev/null || true + + enableResult="$(gdbus call --session --dest "$dbusService" \ + --object-path "$modulePath" \ + --method "${moduleInterface}.ExecuteParameterizedCommand" \ + "SetPluginActive" \ + '{"plugin_name": <"WindowTitleReader">, "active": }' \ + false 2>/dev/null || true)" + + if [[ "$enableResult" != *"true"* ]]; then + log_msg "Failed to activate Cthulhu WindowTitleReader plugin via D-Bus." + return 1 + fi + + titleEnableResult="$(gdbus call --session --dest "$dbusService" \ + --object-path "$titleModulePath" \ + --method "${moduleInterface}.ExecuteParameterizedCommand" \ + "SetEnabled" \ + '{"enabled": }' \ + false 2>/dev/null || true)" + + if [[ "$titleEnableResult" == *"true"* ]]; then + cthulhuTitleReaderEnabled="true" + log_msg "Enabled Cthulhu WindowTitleReader tracking via D-Bus." + return 0 + fi + + log_msg "Failed to enable Cthulhu WindowTitleReader tracking via D-Bus." + return 1 +} + +kill_nvda2speechd_listener() { + local nvdaPids=() + mapfile -t nvdaPids < <(pgrep -u "${USER}" -x nvda2speechd 2>/dev/null) + if [[ ${#nvdaPids[@]} -eq 0 ]]; then + log_msg "No nvda2speechd process found to stop." + return + fi + log_msg "Stopping nvda2speechd process(es): ${nvdaPids[*]}." + for pid in "${nvdaPids[@]}"; do + kill "$pid" 2>/dev/null + done + local remainingPids=() + mapfile -t remainingPids < <(pgrep -u "${USER}" -x nvda2speechd 2>/dev/null) + if [[ ${#remainingPids[@]} -gt 0 ]]; then + log_msg "nvda2speechd still running after stop attempt: ${remainingPids[*]}." + fi +} + +cleanup_and_exit() { + stop_nvda2speechd +} + # Check and manage wine32 installation check_wine32() { local wine32Dir="${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/wine32" @@ -316,27 +447,33 @@ kill_game() { # for games that require custom scripts before launch or custom launch parameters custom_launch_parameters() { if [[ "${game[2]}" == "Dragon Pong" ]]; then - "${scriptDir}/speech/speak_window_title.sh" DragonPong.exe & + if ! enable_cthulhu_window_title_reader; then + "${scriptDir}/speech/speak_window_title.sh" DragonPong.exe & + fi + start_nvda2speechd pushd "$(winepath "$winePath")" || exit 1 wine "$wineExec" popd || exit 1 - exit 0 + customLaunchHandled="true" + return 0 fi if [[ "${game[2]}" == "Dreamland" ]]; then - "$WINE" "${game[1]}" &> /dev/null & - exit 0 + start_nvda2speechd + "$WINE" "${game[1]}" &> /dev/null + customLaunchHandled="true" + return 0 fi # executioner's-rage: DLL replacement now handled by update_nvda_dlls() if [[ "${game[2]}" == "Laser Breakout" ]]; then - "${scriptDir}/speech/speak_window_title.sh" play.exe & + if ! enable_cthulhu_window_title_reader; then + "${scriptDir}/speech/speak_window_title.sh" play.exe & + fi fi if [[ "${game[2]}" == "Bokurano Daibouken 2" ]]; then - find "${WINEPREFIX}/drive_c/nyanchangame/bk2" -type f -name 'nvdaControllerClient.dll' -exec rm -v "{}" \; - "${scriptDir}/speech/clipboard_translator.sh" "play.exe" bokurano-daibouken2 & + export TRANSLATE=true fi if [[ "${game[2]}" == "Bokurano Daibouken" ]]; then - find "${WINEPREFIX}/drive_c/nyanchangame/bk" -type f -name 'nvdaControllerClient.dll' -exec rm -v "{}" \; - "${scriptDir}/speech/clipboard_translator.sh" "play.exe" bokurano-daibouken & + export TRANSLATE=true fi if [[ "${game[2]}" == "Bokurano Daibouken 3" ]]; then dictPath="$(winepath "${winePath}")" @@ -358,13 +495,19 @@ custom_launch_parameters() { fi fi if [[ "${game[2]}" == "Bop It Emulator" ]]; then - "${scriptDir}/speech/speak_window_title.sh" bop.exe & + if ! enable_cthulhu_window_title_reader; then + "${scriptDir}/speech/speak_window_title.sh" bop.exe & + fi fi if [[ "${game[2]}" == "Road to Rage" ]]; then - "${scriptDir}/speech/speak_window_title.sh" trtr.exe & + if ! enable_cthulhu_window_title_reader; then + "${scriptDir}/speech/speak_window_title.sh" trtr.exe & + fi fi if [[ "${game[2]}" == "Axel Pong" ]]; then - "${scriptDir}/speech/speak_window_title.sh" axel_pong.exe & + if ! enable_cthulhu_window_title_reader; then + "${scriptDir}/speech/speak_window_title.sh" axel_pong.exe & + fi fi if [[ "${game[2]}" == "Sequence Storm" ]]; then "${scriptDir}/speech/clipboard_reader.sh" SequenceStorm & @@ -375,30 +518,40 @@ custom_launch_parameters() { #fi # sketchbook: DLL replacement now handled by update_nvda_dlls() if [[ "${game[2]}" == "Audiodisc" ]]; then + start_nvda2speechd wine "$winePath\\$wineExec" - exit 0 + customLaunchHandled="true" + return 0 fi if [[ "${game[2]}" == "Audioquake" ]]; then + start_nvda2speechd wine "$winePath\\$wineExec" - exit 0 + customLaunchHandled="true" + return 0 fi if [[ "${game[2]}" == "Screaming Strike 2" ]]; then + start_nvda2speechd pushd "$(winepath "$winePath")" || exit 1 wine "$wineExec" popd || exit 1 - exit 0 + customLaunchHandled="true" + return 0 fi if [[ "${game[2]}" == "Warsim" ]]; then + start_nvda2speechd pushd "$(winepath "${game[1]%\\*}")" || exit 1 wine "${game[1]##*\\}" popd || exit 1 - exit 0 + customLaunchHandled="true" + return 0 fi if [[ "${game[2]}" == "Interceptor" ]]; then + start_nvda2speechd pushd "$(winepath "$winePath")" || exit 1 wine "$wineExec" popd || exit 1 - exit 0 + customLaunchHandled="true" + return 0 fi } @@ -474,14 +627,8 @@ game_launcher() { [[ "$agmNoLaunch" == "true" ]] && return # shellcheck source=.includes/bottle.sh source "${scriptDir}/.includes/bottle.sh" - - # Start nvda2speechd if available - if ! ss -ltnp | rg 3457 | grep -q 'cthulhu'; then - if [[ -x "${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/nvda2speechd" ]]; then - "${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/nvda2speechd" &> /dev/null & - fi - fi - + # Ensure a clean nvda2speechd state before launching a new game. + kill_nvda2speechd_listener # Replace NVDA controller client DLLs in wine64 bottle update_nvda_dlls mapfile -t lines < <(sed -e '/^$/d' -e '/^ *#/d' "${configFile}" 2> /dev/null | sort -t '|' -k3,3f) @@ -568,15 +715,22 @@ game_launcher() { fi fi process_launcher_flags + customLaunchHandled="false" custom_launch_parameters - if [[ "$debugGdb" == "1" ]]; then - # Change to game directory before launching - pushd "$(winepath "${game[1]%\\*}")" > /dev/null || exit 1 - winedbg --gdb "${game[1]##*\\}" - popd > /dev/null || exit 1 - else - wine start /d "${game[1]%\\*}" "${game[1]##*\\}" /realtime + if [[ "$customLaunchHandled" == "true" ]]; then + exit 0 fi + start_nvda2speechd + # Change to game directory before launching (required for proper game operation) + pushd "$(winepath "${game[1]%\\*}")" > /dev/null || exit 1 + if [[ "$debugGdb" == "1" ]]; then + winedbg --gdb "${game[1]##*\\}" + else + # Use direct wine execution instead of 'wine start' to ensure clipboard works + # See: https://bugs.winehq.org/show_bug.cgi?id=50598 + wine "${game[1]##*\\}" + fi + popd > /dev/null || exit 1 fi exit 0 } @@ -584,10 +738,12 @@ game_launcher() { # main script -trap "exit 0" SIGINT +trap cleanup_and_exit EXIT +trap "cleanup_and_exit; exit 0" SIGINT SIGTERM # Detect dialog interface type BEFORE potentially setting DISPLAY # This must happen before we modify DISPLAY to preserve console detection +# shellcheck disable=SC2034 if [[ -z "$DISPLAY" ]]; then dialogType="dialog" elif command -v yad &> /dev/null; then