prep for linux compatibility
This commit is contained in:
parent
66d5032085
commit
6cceb32037
3 changed files with 137 additions and 31 deletions
|
|
@ -6,14 +6,15 @@ import sys
|
|||
import random
|
||||
import re
|
||||
import queue
|
||||
#import readchar
|
||||
import ctypes
|
||||
import tempfile
|
||||
import psutil
|
||||
import subprocess
|
||||
import getpass
|
||||
|
||||
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1"
|
||||
import pygame
|
||||
|
||||
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
||||
#kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
||||
|
||||
# Event to signal shutdown
|
||||
shutdown_event = threading.Event()
|
||||
|
|
@ -21,19 +22,29 @@ shutdown_event = threading.Event()
|
|||
ERROR_ALREADY_EXISTS = 183
|
||||
|
||||
# ========================= CONFIG =========================
|
||||
DEBUG = False # Set True to enable debug overrides
|
||||
DEBUG = True # Set True to enable debug overrides
|
||||
|
||||
# Default paths (used if not in debug mode)
|
||||
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"
|
||||
DEFAULT_WIN_GAME_EXE = r"./qgame.dll"
|
||||
DEFAULT_LIN_GAME_EXE = r"./qgame.so"
|
||||
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"
|
||||
DEBUG_WIN_GAME_EXE = r"D:/GOG Games/Quake III/qgame.dll"
|
||||
DEBUG_LIN_GAME_EXE = r"D:/GOG Games/Quake III/qgame.so"
|
||||
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"
|
||||
|
||||
if (os.name == "nt"):
|
||||
DEFAULT_GAME_EXE = DEFAULT_WIN_GAME_EXE
|
||||
DEBUG_GAME_EXE = DEBUG_WIN_GAME_EXE
|
||||
else:
|
||||
DEFAULT_GAME_EXE = DEFAULT_LIN_GAME_EXE
|
||||
DEBUG_GAME_EXE = DEBUG_LIN_GAME_EXE
|
||||
|
||||
|
||||
# Initial volume
|
||||
VOLUME_STEP = 0.1 # step for W/S volume control
|
||||
|
|
@ -78,6 +89,54 @@ ANSI_ESCAPE_RE = re.compile(
|
|||
# ==========================================================
|
||||
|
||||
# ===================== UTILITY FUNCTIONS =================
|
||||
def acquire_single_instance(lockfile_base: str):
|
||||
"""
|
||||
Ensures only one instance of the program is running per user.
|
||||
Uses a lock file in the temp directory and psutil to check for stale locks.
|
||||
Returns (file descriptor, lockfile path) if acquired, None if another instance is running.
|
||||
"""
|
||||
username = getpass.getuser()
|
||||
lockfile_name = f"{lockfile_base}_{username}.lock"
|
||||
lockfile_path = os.path.join(tempfile.gettempdir(), lockfile_name)
|
||||
|
||||
# If lock file exists, check if PID inside is running
|
||||
if os.path.exists(lockfile_path):
|
||||
try:
|
||||
with open(lockfile_path, "r") as f:
|
||||
pid = int(f.read())
|
||||
if psutil.pid_exists(pid):
|
||||
# Another instance is running
|
||||
return None, lockfile_path
|
||||
else:
|
||||
# Stale lock file; remove it
|
||||
os.unlink(lockfile_path)
|
||||
except Exception:
|
||||
# Could not read PID; remove stale lock
|
||||
try:
|
||||
os.unlink(lockfile_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Create lock file exclusively
|
||||
try:
|
||||
fd = os.open(lockfile_path, os.O_CREAT | os.O_EXCL | os.O_RDWR)
|
||||
os.write(fd, str(os.getpid()).encode())
|
||||
return fd, lockfile_path
|
||||
except FileExistsError:
|
||||
# Race condition: another process created it just now
|
||||
return None, lockfile_path
|
||||
|
||||
def release_single_instance(fd, lockfile_path):
|
||||
"""Closes the lock file and removes it."""
|
||||
try:
|
||||
os.close(fd)
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
os.unlink(lockfile_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
def Check_Arena_Blacklist(arenaline: str) -> bool:
|
||||
global arena0_bl_path
|
||||
arena_bl_local = load_arena0_blacklist(arena0_bl_path)
|
||||
|
|
@ -86,21 +145,21 @@ def Check_Arena_Blacklist(arenaline: str) -> bool:
|
|||
return True
|
||||
return False
|
||||
|
||||
def acquire_single_instance(name: str):
|
||||
handle = kernel32.CreateMutexW(
|
||||
None, # lpMutexAttributes
|
||||
False, # bInitialOwner
|
||||
name # lpName
|
||||
)
|
||||
# def acquire_single_instance(name: str):
|
||||
# handle = kernel32.CreateMutexW(
|
||||
# None, # lpMutexAttributes
|
||||
# False, # bInitialOwner
|
||||
# name # lpName
|
||||
# )
|
||||
|
||||
if not handle:
|
||||
raise ctypes.WinError(ctypes.get_last_error())
|
||||
# if not handle:
|
||||
# raise ctypes.WinError(ctypes.get_last_error())
|
||||
|
||||
if kernel32.GetLastError() == ERROR_ALREADY_EXISTS:
|
||||
kernel32.CloseHandle(handle)
|
||||
return None
|
||||
# if kernel32.GetLastError() == ERROR_ALREADY_EXISTS:
|
||||
# kernel32.CloseHandle(handle)
|
||||
# return None
|
||||
|
||||
return handle
|
||||
# return handle
|
||||
|
||||
def strip_ansi(text: str) -> str:
|
||||
return ANSI_ESCAPE_RE.sub("", text)
|
||||
|
|
@ -465,11 +524,11 @@ def send_command(proc, cmd):
|
|||
|
||||
# ======================== MAIN ===========================
|
||||
def main():
|
||||
mutex = acquire_single_instance("Global\\q3aLauncherAndMp3Playback")
|
||||
# mutex = acquire_single_instance("Global\\q3aLauncherAndMp3Playback")
|
||||
|
||||
if mutex is None:
|
||||
print("Another instance is already running.")
|
||||
sys.exit(1)
|
||||
# if mutex is None:
|
||||
# print("Another instance is already running.")
|
||||
# sys.exit(1)
|
||||
|
||||
global playlist, ra3_maps, pty_proc, playlist_path, ra3_maps_path, arena0_blacklist, arena0_bl_path
|
||||
|
||||
|
|
@ -503,7 +562,7 @@ def main():
|
|||
|
||||
# # Launch quake process via subprocess
|
||||
pty_proc = subprocess.Popen(
|
||||
[game_exe, "--showterminalconsole"] + sys.argv[1:],
|
||||
[game_exe, "+set", "ttycon", "1"] + sys.argv[1:],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
|
|
@ -542,5 +601,13 @@ def main():
|
|||
pygame.mixer.quit()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
lock_fd, lock_path = acquire_single_instance("q3a_launcher.lock")
|
||||
if lock_fd is None:
|
||||
print("Another instance is already running.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
main()
|
||||
finally:
|
||||
release_single_instance(lock_fd, lock_path)
|
||||
|
||||
# ==========================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue