From 40ff55f7003e362c7730825c01d373891dc0781e Mon Sep 17 00:00:00 2001 From: edschuy95 Date: Thu, 22 Jan 2026 15:06:25 -0500 Subject: [PATCH] Threading improvements --- Quake3Arena.spec | 6 +-- RA3MP3Playback.pyw | 98 ++++++++++++++++++++++++++++++---------------- textmenu.py | 28 ++++--------- 3 files changed, 74 insertions(+), 58 deletions(-) diff --git a/Quake3Arena.spec b/Quake3Arena.spec index 3266df8..d7f81b5 100644 --- a/Quake3Arena.spec +++ b/Quake3Arena.spec @@ -2,7 +2,7 @@ a = Analysis( - ['RA3MP3Playback.py'], + ['RA3MP3Playback.pyw'], pathex=[], binaries=[], datas=[], @@ -10,7 +10,7 @@ a = Analysis( hookspath=[], hooksconfig={}, runtime_hooks=[], - excludes=['tkinter', 'unittest', 'http', 'pydoc', 'doctest'], + excludes=['unittest', 'http', 'pydoc', 'doctest'], noarchive=False, optimize=0, ) @@ -29,7 +29,7 @@ exe = EXE( upx=True, upx_exclude=[], runtime_tmpdir=None, - console=True, + console=False, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, diff --git a/RA3MP3Playback.pyw b/RA3MP3Playback.pyw index 441f772..109fb1e 100644 --- a/RA3MP3Playback.pyw +++ b/RA3MP3Playback.pyw @@ -603,11 +603,9 @@ def send_command(proc, cmd): # ========================================================== # ======================== MAIN =========================== -def main(): +def game_launch(gameargs): global playlist, ra3_maps, pty_proc, playlist_path, ra3_maps_path, arena0_blacklist, arena0_bl_path, master_fd - print(f"Loading Quake 3 Arena...") - # Use debug paths if enabled game_exe = DEBUG_GAME_EXE if DEBUG else DEFAULT_GAME_EXE playlist_path = DEBUG_PLAYLIST if DEBUG else DEFAULT_PLAYLIST @@ -633,50 +631,24 @@ def main(): chosen_mod = None run_mod = [] - if "fs_game" not in sys.argv[1:]: - game_path = Path(game_exe) - items = find_pk3_subfolders(game_path.parent) - - menu = TextMenu( - items, - width=500, - height=400, - title="Quake III Arena mod loader menu", - border_fg="red", - border_bg="black", - inside_fg="dark red", - inside_bg="black", - selected_fg="black", - selected_bg="red", - timeout=10 - ) - choice=menu.show() - if choice == None: - sys.exit(0) - if choice == "errmenutimeout": - chosen_mod = "baseq3" - else: - chosen_mod = choice.split("\n", 1)[0] - - run_mod = ["+set", "fs_game", chosen_mod] - - #sys.exit(0) + run_mod = ["+set", "fs_game", chosen_mod] if os.name == "nt": pty_proc = subprocess.Popen( - [game_exe, "+set", "ttycon", "1"] + run_mod + sys.argv[1:], + [game_exe, "+set", "ttycon", "1"] + gameargs, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=0, - universal_newlines=False + universal_newlines=False, + creationflags=subprocess.CREATE_NO_WINDOW ) master_fd = None else: master_fd, slave_fd = pty.openpty() pty_proc = subprocess.Popen( - [game_exe, "+set", "ttycon", "1"] + run_mod + sys.argv[1:], + [game_exe, "+set", "ttycon", "1"] + gameargs, stdin=slave_fd, stdout=slave_fd, stderr=slave_fd, @@ -718,6 +690,64 @@ def main(): pass pygame.mixer.quit() +def main(): + global playlist, ra3_maps, pty_proc, playlist_path, ra3_maps_path, arena0_blacklist, arena0_bl_path, master_fd + + game_exe = DEBUG_GAME_EXE if DEBUG else DEFAULT_GAME_EXE + + chosen_mod = None + run_mod = [] + + if "fs_game" not in sys.argv[1:]: + game_path = Path(game_exe) + items = find_pk3_subfolders(game_path.parent) + + # Create a queue to get the result from the menu thread + menu_result_queue = queue.Queue() + + def run_menu(): + menu = TextMenu( + items, + width=500, + height=400, + title="Quake III Arena mod loader menu", + border_fg="red", + border_bg="black", + inside_fg="dark red", + inside_bg="black", + selected_fg="black", + selected_bg="red", + timeout=10 + ) + result = menu.show() + menu_result_queue.put(result) + + # Run the menu in a separate thread + menu_thread = threading.Thread(target=run_menu) + menu_thread.start() + menu_thread.join() # Wait for menu to complete + + # Get the result from the queue + choice = menu_result_queue.get() + + if choice == None: + sys.exit(0) + if choice == "errmenutimeout": + chosen_mod = "baseq3" + else: + chosen_mod = choice.split("\n", 1)[0] + + run_mod = ["+set", "fs_game", chosen_mod] + + #sys.exit(0) + + game_args = run_mod + sys.argv[1:] + + # Start game launcher thread + game_launch_thread = threading.Thread(target=game_launch,args=(game_args,), daemon=True) + game_launch_thread.start() + game_launch_thread.join() + if __name__ == "__main__": lock_fd, lock_path = acquire_single_instance("q3a_launcher.lock") if lock_fd is None: diff --git a/textmenu.py b/textmenu.py index 1d15e64..3c5224b 100644 --- a/textmenu.py +++ b/textmenu.py @@ -291,26 +291,12 @@ class TextMenu: # Run the GUI event loop self.root.mainloop() - # Clean up - only destroy if root still exists - if self.root and str(self.root) != '.': - try: + # Clean up - destroy the window immediately to prevent hanging + try: + if self.root and str(self.root) != '.': self.root.destroy() - except tk.TclError: - # Window may already be destroyed - pass + except tk.TclError: + # Window may already be destroyed + pass - return self.selected - - -if __name__ == "__main__": - # Example usage - choices = [ - "Option 1\nDescription for option 1", - "Option 2\nDescription for option 2", - "Option 3\nDescription for option 3", - "Option 4\nDescription for option 4" - ] - - menu = TextMenu(choices, title="Test Menu", timeout=10) - result = menu.show() - print(f"Selected: {result}") \ No newline at end of file + return self.selected \ No newline at end of file