Replaced winpty with subprocess
This commit is contained in:
parent
ee6aa40093
commit
845ce2c622
9 changed files with 330 additions and 235 deletions
|
|
@ -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__":
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue