Replaced winpty with subprocess

This commit is contained in:
edschuy95 2026-01-10 16:43:48 -05:00
parent ee6aa40093
commit 845ce2c622
9 changed files with 330 additions and 235 deletions

View file

@ -7,13 +7,13 @@ import random
import re
import queue
import readchar
import winpty
#import winpty
import ctypes
import subprocess
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1"
import pygame
#user32 = ctypes.WinDLL("user32", use_last_error=True)
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
# Event to signal shutdown
@ -28,11 +28,13 @@ DEBUG = False # Set True to enable debug overrides
DEFAULT_GAME_EXE = r".\qgame.dll"
DEFAULT_PLAYLIST = r".\arena\music\playlist.txt"
DEFAULT_RA3_MAPS = r".\arena\music\ra3_maps.txt"
DEFAULT_ARENA0_BL = r".\arena\music\arena0_bl.txt"
# Debug override paths
DEBUG_GAME_EXE = r"D:\GOG Games\Quake III\qgame.dll"
DEBUG_PLAYLIST = r"D:\GOG Games\Quake III\arena\music\playlist.txt"
DEBUG_RA3_MAPS = r"D:\GOG Games\Quake III\arena\music\ra3_maps.txt"
DEBUG_ARENA0_BL = r"D:\GOG Games\Quake III\arena\music\arena0_bl.txt"
# Initial volume
VOLUME_STEP = 0.1 # step for W/S volume control
@ -47,7 +49,9 @@ is_ra3 = False
is_ra3_map = False
is_in_map = False
ra3_maps_path = "."
arena0_bl_path = "."
playlist = []
arena0_bl = []
playlist_index = 0
current_mode = "shuffle" # sequential, shuffle, loop
volume = 0.5
@ -75,6 +79,14 @@ ANSI_ESCAPE_RE = re.compile(
# ==========================================================
# ===================== UTILITY FUNCTIONS =================
def Check_Arena_Blacklist(arenaline: str) -> bool:
global arena0_bl_path
arena_bl_local = load_arena0_blacklist(arena0_bl_path)
for item in arena_bl_local:
if arenaline.lower().startswith(f"\"arena0\" is:\"{item.lower()}"):
return True
return False
def acquire_single_instance(name: str):
handle = kernel32.CreateMutexW(
None, # lpMutexAttributes
@ -203,6 +215,19 @@ def load_ra3_maps(path):
return set()
return maps
def load_arena0_blacklist(path):
maps = set()
try:
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line:
maps.add(line.lower())
except:
print(f"[DEBUG] Failed to open ra3_maps.txt")
return set()
return maps
def next_track():
global playlist
if not playlist:
@ -291,34 +316,59 @@ def change_mode():
# ==========================================================
# ==================== QUAKE MONITOR ======================
def monitor_game(pty_proc):
# def monitor_game(pty_proc):
# #pty version
# global serverstatus_sent
# buffer = ""
# while not stop_flag.is_set() or shutdown_event.is_set():
# try:
# data = pty_proc.read(1024)
# if not data:
# break
# # Normalize to string
# if isinstance(data, bytes):
# data = data.decode(errors="ignore")
# buffer += data
# while "\n" in buffer:
# line, buffer = buffer.split("\n", 1)
# line = strip_ansi(line).strip()
# if line:
# #print(f"[GAME] {line}")
# #print(f"[GAME RAW] {repr(line)}")
# handle_game_line(line, pty_proc)
# except EOFError or KeyboardInterrupt:
# break
def monitor_game(proc):
#subprocess version
global serverstatus_sent
buffer = b""
buffer = ""
while not stop_flag.is_set() or shutdown_event.is_set():
try:
data = pty_proc.read(1024)
if not data:
break
# Normalize to string
if isinstance(data, bytes):
data = data.decode(errors="ignore")
buffer += data
while "\n" in buffer:
line, buffer = buffer.split("\n", 1)
line = strip_ansi(line).strip()
if line:
#print(f"[GAME] {line}")
#print(f"[GAME RAW] {repr(line)}")
handle_game_line(line, pty_proc)
except EOFError or KeyboardInterrupt:
while not stop_flag.is_set():
data = proc.stdout.read(1024)
if not data:
break
buffer += data
while b"\n" in buffer:
line, buffer = buffer.split(b"\n", 1)
try:
line = line.decode(errors="ignore")
except:
continue
line = strip_ansi(line).strip()
if line:
handle_game_line(line, proc)
def handle_game_line(line, pty_proc):
global volume, current_map, ra3_maps, ra3_maps_path, volumecheck, is_ra3, gametypecheck, is_playing, arenacheck, is_in_map, is_ra3_map, last_arena0
@ -405,9 +455,15 @@ def handle_game_line(line, pty_proc):
threading.Timer(.1, lambda: send_command(pty_proc,f"serverstatus")).start()
else:
if not same_map:
print(f"[DEBUG] RA3 map detected. Advancing track.")
is_ra3_map = True
next_track()
if not Check_Arena_Blacklist(line):
print(f"[DEBUG] RA3 map detected. Advancing track.")
is_ra3_map = True
next_track()
else:
print(f"[DEBUG] RA3 Arena found on blacklist - disabling music.")
is_ra3_map = False
stop_playback()
elif "mapname" in line.lower() and serverstatus_sent:
serverstatus_sent = False
current_map = line.split()[-1].lower()
@ -419,15 +475,26 @@ def handle_game_line(line, pty_proc):
else:
print(f"[DEBUG] Unknown map: {current_map}. Stopping playback.")
is_ra3_map = False
stop_playback()
threading.Timer(.3, lambda: stop_playback()).start()
#stop_playback()
def send_command(pty_proc, cmd):
# def send_command(pty_proc, cmd):
# # winpty version
# try:
# pty_proc.write(cmd + "\r\n")
# pty_proc.flush()
# #print(f"[DEBUG] Sent command: {cmd}")
# except Exception as e:
# print(f"[DEBUG] Failed to send command: {e}")
def send_command(proc, cmd):
#subprocess version
try:
pty_proc.write(cmd + "\r\n")
pty_proc.flush()
#print(f"[DEBUG] Sent command: {cmd}")
proc.stdin.write((cmd + "\r\n").encode())
proc.stdin.flush()
except Exception as e:
print(f"[DEBUG] Failed to send command: {e}")
# ==========================================================
# ==================== KEYBOARD HANDLER ===================
@ -452,7 +519,7 @@ def main():
print("Another instance is already running.")
sys.exit(1)
global playlist, ra3_maps, pty_proc, playlist_path, ra3_maps_path
global playlist, ra3_maps, pty_proc, playlist_path, ra3_maps_path, arena0_blacklist, arena0_bl_path
print(f"Loading Quake 3 Arena...")
@ -460,17 +527,12 @@ def main():
game_exe = DEBUG_GAME_EXE if DEBUG else DEFAULT_GAME_EXE
playlist_path = DEBUG_PLAYLIST if DEBUG else DEFAULT_PLAYLIST
ra3_maps_path = DEBUG_RA3_MAPS if DEBUG else DEFAULT_RA3_MAPS
arena0_bl_path = DEBUG_ARENA0_BL if DEBUG else DEFAULT_ARENA0_BL
# Load playlist and map list
playlist = load_playlist(playlist_path)
ra3_maps = load_ra3_maps(ra3_maps_path)
#if not playlist:
# print("Playlist is empty!")
# return
#if not ra3_maps:
# print("RA3 maps list is empty!")
# return
arena0_bl = load_arena0_blacklist(arena0_bl_path)
# Initialize pygame mixer
pygame.mixer.init()
@ -487,12 +549,23 @@ def main():
watcher_thread = threading.Thread(target=track_watcher, daemon=True)
watcher_thread.start()
# Launch quake process via PTY
try:
pty_proc = winpty.PtyProcess.spawn([game_exe, "--showterminalconsole",] + sys.argv[1:] )
except Exception as e:
print(f"Failed to start game: {e}")
return
# # Launch quake process via PTY
# try:
# pty_proc = winpty.PtyProcess.spawn([game_exe, "--showterminalconsole",] + sys.argv[1:] )
# except Exception as e:
# print(f"Failed to start game: {e}")
# return
# # Launch quake process via subprocess
pty_proc = subprocess.Popen(
[game_exe, "--showterminalconsole"] + sys.argv[1:],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
bufsize=0, # unbuffered
universal_newlines=False
)
# Monitor the game output
try:
@ -502,7 +575,26 @@ def main():
finally:
stop_flag.set()
stop_playback()
pty_proc.close()
if pty_proc:
try:
pty_proc.stdin.close()
except:
pass
try:
pty_proc.stdout.close()
except:
pass
try:
pty_proc.terminate()
except:
pass
try:
pty_proc.wait(timeout=5)
except:
pass
pygame.mixer.quit()
if __name__ == "__main__":