| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | """
 | 
					
						
							|  |  |  | Archipelago launcher for bundled app. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | * if run with APBP as argument, launch corresponding client. | 
					
						
							|  |  |  | * if run with executable as argument, run it passing argv[2:] as arguments | 
					
						
							|  |  |  | * if run without arguments, open launcher GUI | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Scroll down to components= to add components to the launcher as well as setup.py | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import argparse | 
					
						
							|  |  |  | import itertools | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  | import logging | 
					
						
							| 
									
										
										
										
											2023-05-05 00:53:57 +02:00
										 |  |  | import multiprocessing | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | import shlex | 
					
						
							| 
									
										
										
										
											2022-08-27 02:28:46 +02:00
										 |  |  | import subprocess | 
					
						
							|  |  |  | import sys | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  | import urllib.parse | 
					
						
							| 
									
										
										
										
											2023-04-16 01:57:52 +02:00
										 |  |  | import webbrowser | 
					
						
							| 
									
										
										
										
											2022-08-27 02:28:46 +02:00
										 |  |  | from os.path import isfile | 
					
						
							|  |  |  | from shutil import which | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  | from typing import Callable, Optional, Sequence, Tuple, Union | 
					
						
							| 
									
										
										
										
											2023-03-20 21:24:47 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-04 23:43:03 +02:00
										 |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     import ModuleUpdate | 
					
						
							|  |  |  |     ModuleUpdate.update() | 
					
						
							| 
									
										
										
										
											2022-08-27 02:28:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-16 03:00:34 +01:00
										 |  |  | import settings | 
					
						
							|  |  |  | import Utils | 
					
						
							|  |  |  | from Utils import (init_logging, is_frozen, is_linux, is_macos, is_windows, local_path, messagebox, open_filename, | 
					
						
							|  |  |  |                    user_path) | 
					
						
							|  |  |  | from worlds.LauncherComponents import Component, components, icon_paths, SuffixIdentifier, Type | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def open_host_yaml(): | 
					
						
							| 
									
										
										
										
											2024-10-16 18:27:50 -04:00
										 |  |  |     s = settings.get_settings() | 
					
						
							|  |  |  |     file = s.filename | 
					
						
							|  |  |  |     s.save() | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  |     assert file, "host.yaml missing" | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |     if is_linux: | 
					
						
							|  |  |  |         exe = which('sensible-editor') or which('gedit') or \ | 
					
						
							|  |  |  |               which('xdg-open') or which('gnome-open') or which('kde-open') | 
					
						
							|  |  |  |         subprocess.Popen([exe, file]) | 
					
						
							|  |  |  |     elif is_macos: | 
					
						
							|  |  |  |         exe = which("open") | 
					
						
							|  |  |  |         subprocess.Popen([exe, file]) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         webbrowser.open(file) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def open_patch(): | 
					
						
							| 
									
										
										
										
											2022-06-04 18:36:50 +02:00
										 |  |  |     suffixes = [] | 
					
						
							|  |  |  |     for c in components: | 
					
						
							| 
									
										
										
										
											2023-10-02 11:52:00 -07:00
										 |  |  |         if c.type == Type.CLIENT and \ | 
					
						
							|  |  |  |                 isinstance(c.file_identifier, SuffixIdentifier) and \ | 
					
						
							|  |  |  |                 (c.script_name is None or isfile(get_exe(c)[-1])): | 
					
						
							|  |  |  |             suffixes += c.file_identifier.suffixes | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |     try: | 
					
						
							| 
									
										
										
										
											2023-10-02 11:52:00 -07:00
										 |  |  |         filename = open_filename("Select patch", (("Patches", suffixes),)) | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |     except Exception as e: | 
					
						
							| 
									
										
										
										
											2023-10-02 11:52:00 -07:00
										 |  |  |         messagebox("Error", str(e), error=True) | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  |         file, component = identify(filename) | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |         if file and component: | 
					
						
							| 
									
										
										
										
											2023-10-02 11:52:00 -07:00
										 |  |  |             exe = get_exe(component) | 
					
						
							|  |  |  |             if exe is None or not isfile(exe[-1]): | 
					
						
							|  |  |  |                 exe = get_exe("Launcher") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             launch([*exe, file], component.cli) | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-16 01:57:52 +02:00
										 |  |  | def generate_yamls(): | 
					
						
							|  |  |  |     from Options import generate_yaml_templates | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     target = Utils.user_path("Players", "Templates") | 
					
						
							|  |  |  |     generate_yaml_templates(target, False) | 
					
						
							|  |  |  |     open_folder(target) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | def browse_files(): | 
					
						
							| 
									
										
										
										
											2023-04-16 01:57:52 +02:00
										 |  |  |     open_folder(user_path()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def open_folder(folder_path): | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |     if is_linux: | 
					
						
							|  |  |  |         exe = which('xdg-open') or which('gnome-open') or which('kde-open') | 
					
						
							| 
									
										
										
										
											2023-04-16 01:57:52 +02:00
										 |  |  |         subprocess.Popen([exe, folder_path]) | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |     elif is_macos: | 
					
						
							|  |  |  |         exe = which("open") | 
					
						
							| 
									
										
										
										
											2023-04-16 01:57:52 +02:00
										 |  |  |         subprocess.Popen([exe, folder_path]) | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2023-04-16 01:57:52 +02:00
										 |  |  |         webbrowser.open(folder_path) | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  | def update_settings(): | 
					
						
							|  |  |  |     from settings import get_settings | 
					
						
							|  |  |  |     get_settings().save() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-20 21:24:47 +01:00
										 |  |  | components.extend([ | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |     # Functions | 
					
						
							| 
									
										
										
										
											2023-04-17 02:35:54 +02:00
										 |  |  |     Component("Open host.yaml", func=open_host_yaml), | 
					
						
							|  |  |  |     Component("Open Patch", func=open_patch), | 
					
						
							| 
									
										
										
										
											2024-03-28 10:00:10 -04:00
										 |  |  |     Component("Generate Template Options", func=generate_yamls), | 
					
						
							| 
									
										
										
										
											2024-11-03 09:48:52 -05:00
										 |  |  |     Component("Archipelago Website", func=lambda: webbrowser.open("https://archipelago.gg/")), | 
					
						
							| 
									
										
										
										
											2023-04-17 02:35:54 +02:00
										 |  |  |     Component("Discord Server", icon="discord", func=lambda: webbrowser.open("https://discord.gg/8Z65BR2")), | 
					
						
							| 
									
										
										
										
											2024-04-20 20:48:14 -04:00
										 |  |  |     Component("Unrated/18+ Discord Server", icon="discord", func=lambda: webbrowser.open("https://discord.gg/fqvNCCRsu4")), | 
					
						
							| 
									
										
										
										
											2023-04-17 02:35:54 +02:00
										 |  |  |     Component("Browse Files", func=browse_files), | 
					
						
							| 
									
										
										
										
											2023-03-20 21:24:47 +01:00
										 |  |  | ]) | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  | def handle_uri(path: str, launch_args: Tuple[str, ...]) -> None: | 
					
						
							|  |  |  |     url = urllib.parse.urlparse(path) | 
					
						
							|  |  |  |     queries = urllib.parse.parse_qs(url.query) | 
					
						
							|  |  |  |     launch_args = (path, *launch_args) | 
					
						
							|  |  |  |     client_component = None | 
					
						
							|  |  |  |     text_client_component = None | 
					
						
							|  |  |  |     if "game" in queries: | 
					
						
							|  |  |  |         game = queries["game"][0] | 
					
						
							|  |  |  |     else:  # TODO around 0.6.0 - this is for pre this change webhost uri's | 
					
						
							|  |  |  |         game = "Archipelago" | 
					
						
							|  |  |  |     for component in components: | 
					
						
							|  |  |  |         if component.supports_uri and component.game_name == game: | 
					
						
							|  |  |  |             client_component = component | 
					
						
							|  |  |  |         elif component.display_name == "Text Client": | 
					
						
							|  |  |  |             text_client_component = component | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-01 08:33:43 +01:00
										 |  |  |     if client_component is None: | 
					
						
							|  |  |  |         run_component(text_client_component, *launch_args) | 
					
						
							|  |  |  |         return | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-01 08:33:43 +01:00
										 |  |  |     from kvui import App, Button, BoxLayout, Label, Window | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-01 08:33:43 +01:00
										 |  |  |     class Popup(App): | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  |         def __init__(self): | 
					
						
							|  |  |  |             self.title = "Connect to Multiworld" | 
					
						
							|  |  |  |             self.icon = r"data/icon.png" | 
					
						
							|  |  |  |             super().__init__() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def build(self): | 
					
						
							|  |  |  |             layout = BoxLayout(orientation="vertical") | 
					
						
							| 
									
										
										
										
											2024-12-01 08:33:43 +01:00
										 |  |  |             layout.add_widget(Label(text="Select client to open and connect with.")) | 
					
						
							|  |  |  |             button_row = BoxLayout(orientation="horizontal", size_hint=(1, 0.4)) | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-01 08:33:43 +01:00
										 |  |  |             text_client_button = Button( | 
					
						
							|  |  |  |                 text=text_client_component.display_name, | 
					
						
							|  |  |  |                 on_release=lambda *args: run_component(text_client_component, *launch_args) | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             button_row.add_widget(text_client_button) | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-01 08:33:43 +01:00
										 |  |  |             game_client_button = Button( | 
					
						
							|  |  |  |                 text=client_component.display_name, | 
					
						
							|  |  |  |                 on_release=lambda *args: run_component(client_component, *launch_args) | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             button_row.add_widget(game_client_button) | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-01 08:33:43 +01:00
										 |  |  |             layout.add_widget(button_row) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return layout | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-21 11:43:01 -05:00
										 |  |  |         def _stop(self, *largs): | 
					
						
							|  |  |  |             # see run_gui Launcher _stop comment for details | 
					
						
							|  |  |  |             self.root_window.close() | 
					
						
							|  |  |  |             super()._stop(*largs) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  |     Popup().run() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def identify(path: Union[None, str]) -> Tuple[Union[None, str], Union[None, Component]]: | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |     if path is None: | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  |         return None, None | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |     for component in components: | 
					
						
							|  |  |  |         if component.handles_file(path): | 
					
						
							| 
									
										
										
										
											2023-10-02 11:52:00 -07:00
										 |  |  |             return path, component | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  |         elif path == component.display_name or path == component.script_name: | 
					
						
							|  |  |  |             return None, component | 
					
						
							|  |  |  |     return None, None | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_exe(component: Union[str, Component]) -> Optional[Sequence[str]]: | 
					
						
							|  |  |  |     if isinstance(component, str): | 
					
						
							|  |  |  |         name = component | 
					
						
							|  |  |  |         component = None | 
					
						
							| 
									
										
										
										
											2023-10-02 11:52:00 -07:00
										 |  |  |         if name.startswith("Archipelago"): | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |             name = name[11:] | 
					
						
							| 
									
										
										
										
											2023-10-02 11:52:00 -07:00
										 |  |  |         if name.endswith(".exe"): | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |             name = name[:-4] | 
					
						
							| 
									
										
										
										
											2023-10-02 11:52:00 -07:00
										 |  |  |         if name.endswith(".py"): | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |             name = name[:-3] | 
					
						
							|  |  |  |         if not name: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |         for c in components: | 
					
						
							| 
									
										
										
										
											2023-10-02 11:52:00 -07:00
										 |  |  |             if c.script_name == name or c.frozen_name == f"Archipelago{name}": | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |                 component = c | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  |         if not component: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |     if is_frozen(): | 
					
						
							| 
									
										
										
										
											2023-10-02 11:52:00 -07:00
										 |  |  |         suffix = ".exe" if is_windows else "" | 
					
						
							|  |  |  |         return [local_path(f"{component.frozen_name}{suffix}")] if component.frozen_name else None | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2023-10-02 11:52:00 -07:00
										 |  |  |         return [sys.executable, local_path(f"{component.script_name}.py")] if component.script_name else None | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def launch(exe, in_terminal=False): | 
					
						
							|  |  |  |     if in_terminal: | 
					
						
							|  |  |  |         if is_windows: | 
					
						
							|  |  |  |             subprocess.Popen(['start', *exe], shell=True) | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         elif is_linux: | 
					
						
							|  |  |  |             terminal = which('x-terminal-emulator') or which('gnome-terminal') or which('xterm') | 
					
						
							|  |  |  |             if terminal: | 
					
						
							|  |  |  |                 subprocess.Popen([terminal, '-e', shlex.join(exe)]) | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |         elif is_macos: | 
					
						
							|  |  |  |             terminal = [which('open'), '-W', '-a', 'Terminal.app'] | 
					
						
							|  |  |  |             subprocess.Popen([*terminal, *exe]) | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |     subprocess.Popen(exe) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-06 11:36:14 -07:00
										 |  |  | refresh_components: Optional[Callable[[], None]] = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | def run_gui(): | 
					
						
							| 
									
										
										
										
											2024-11-29 21:58:52 -05:00
										 |  |  |     from kvui import App, ContainerLayout, GridLayout, Button, Label, ScrollBox, Widget, ApAsyncImage | 
					
						
							| 
									
										
										
										
											2024-06-04 16:54:21 -07:00
										 |  |  |     from kivy.core.window import Window | 
					
						
							| 
									
										
										
										
											2023-04-17 02:35:54 +02:00
										 |  |  |     from kivy.uix.relativelayout import RelativeLayout | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     class Launcher(App): | 
					
						
							|  |  |  |         base_title: str = "Archipelago Launcher" | 
					
						
							|  |  |  |         container: ContainerLayout | 
					
						
							|  |  |  |         grid: GridLayout | 
					
						
							| 
									
										
										
										
											2024-06-06 11:36:14 -07:00
										 |  |  |         _tool_layout: Optional[ScrollBox] = None | 
					
						
							|  |  |  |         _client_layout: Optional[ScrollBox] = None | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def __init__(self, ctx=None): | 
					
						
							| 
									
										
										
										
											2024-10-27 20:53:11 -04:00
										 |  |  |             self.title = self.base_title + " " + Utils.__version__ | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |             self.ctx = ctx | 
					
						
							|  |  |  |             self.icon = r"data/icon.png" | 
					
						
							|  |  |  |             super().__init__() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-06 11:36:14 -07:00
										 |  |  |         def _refresh_components(self) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-02 23:32:58 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |             def build_button(component: Component) -> Widget: | 
					
						
							| 
									
										
										
										
											2023-04-17 02:35:54 +02:00
										 |  |  |                 """
 | 
					
						
							|  |  |  |                 Builds a button widget for a given component. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 Args: | 
					
						
							|  |  |  |                     component (Component): The component associated with the button. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 Returns: | 
					
						
							|  |  |  |                     None. The button is added to the parent grid layout. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 """
 | 
					
						
							| 
									
										
										
										
											2024-03-02 23:32:58 -06:00
										 |  |  |                 button = Button(text=component.display_name, size_hint_y=None, height=40) | 
					
						
							| 
									
										
										
										
											2023-04-17 02:35:54 +02:00
										 |  |  |                 button.component = component | 
					
						
							|  |  |  |                 button.bind(on_release=self.component_action) | 
					
						
							|  |  |  |                 if component.icon != "icon": | 
					
						
							| 
									
										
										
										
											2024-11-29 21:58:52 -05:00
										 |  |  |                     image = ApAsyncImage(source=icon_paths[component.icon], | 
					
						
							|  |  |  |                                          size=(38, 38), size_hint=(None, 1), pos=(5, 0)) | 
					
						
							| 
									
										
										
										
											2024-03-02 23:32:58 -06:00
										 |  |  |                     box_layout = RelativeLayout(size_hint_y=None, height=40) | 
					
						
							| 
									
										
										
										
											2023-04-17 02:35:54 +02:00
										 |  |  |                     box_layout.add_widget(button) | 
					
						
							|  |  |  |                     box_layout.add_widget(image) | 
					
						
							| 
									
										
										
										
											2024-03-02 23:32:58 -06:00
										 |  |  |                     return box_layout | 
					
						
							|  |  |  |                 return button | 
					
						
							| 
									
										
										
										
											2023-04-17 02:35:54 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-06 11:36:14 -07:00
										 |  |  |             # clear before repopulating | 
					
						
							|  |  |  |             assert self._tool_layout and self._client_layout, "must call `build` first" | 
					
						
							|  |  |  |             tool_children = reversed(self._tool_layout.layout.children) | 
					
						
							|  |  |  |             for child in tool_children: | 
					
						
							|  |  |  |                 self._tool_layout.layout.remove_widget(child) | 
					
						
							|  |  |  |             client_children = reversed(self._client_layout.layout.children) | 
					
						
							|  |  |  |             for child in client_children: | 
					
						
							|  |  |  |                 self._client_layout.layout.remove_widget(child) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             _tools = {c.display_name: c for c in components if c.type == Type.TOOL} | 
					
						
							|  |  |  |             _clients = {c.display_name: c for c in components if c.type == Type.CLIENT} | 
					
						
							|  |  |  |             _adjusters = {c.display_name: c for c in components if c.type == Type.ADJUSTER} | 
					
						
							|  |  |  |             _miscs = {c.display_name: c for c in components if c.type == Type.MISC} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |             for (tool, client) in itertools.zip_longest(itertools.chain( | 
					
						
							| 
									
										
										
										
											2024-06-06 11:36:14 -07:00
										 |  |  |                 _tools.items(), _miscs.items(), _adjusters.items() | 
					
						
							|  |  |  |             ), _clients.items()): | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |                 # column 1 | 
					
						
							|  |  |  |                 if tool: | 
					
						
							| 
									
										
										
										
											2024-06-06 11:36:14 -07:00
										 |  |  |                     self._tool_layout.layout.add_widget(build_button(tool[1])) | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |                 # column 2 | 
					
						
							|  |  |  |                 if client: | 
					
						
							| 
									
										
										
										
											2024-06-06 11:36:14 -07:00
										 |  |  |                     self._client_layout.layout.add_widget(build_button(client[1])) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def build(self): | 
					
						
							|  |  |  |             self.container = ContainerLayout() | 
					
						
							|  |  |  |             self.grid = GridLayout(cols=2) | 
					
						
							|  |  |  |             self.container.add_widget(self.grid) | 
					
						
							|  |  |  |             self.grid.add_widget(Label(text="General", size_hint_y=None, height=40)) | 
					
						
							|  |  |  |             self.grid.add_widget(Label(text="Clients", size_hint_y=None, height=40)) | 
					
						
							|  |  |  |             self._tool_layout = ScrollBox() | 
					
						
							|  |  |  |             self._tool_layout.layout.orientation = "vertical" | 
					
						
							|  |  |  |             self.grid.add_widget(self._tool_layout) | 
					
						
							|  |  |  |             self._client_layout = ScrollBox() | 
					
						
							|  |  |  |             self._client_layout.layout.orientation = "vertical" | 
					
						
							|  |  |  |             self.grid.add_widget(self._client_layout) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             self._refresh_components() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             global refresh_components | 
					
						
							|  |  |  |             refresh_components = self._refresh_components | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-04 16:54:21 -07:00
										 |  |  |             Window.bind(on_drop_file=self._on_drop_file) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |             return self.container | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         @staticmethod | 
					
						
							|  |  |  |         def component_action(button): | 
					
						
							| 
									
										
										
										
											2023-06-19 09:57:17 +02:00
										 |  |  |             if button.component.func: | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |                 button.component.func() | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 launch(get_exe(button.component), button.component.cli) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-04 16:54:21 -07:00
										 |  |  |         def _on_drop_file(self, window: Window, filename: bytes, x: int, y: int) -> None: | 
					
						
							|  |  |  |             """ When a patch file is dropped into the window, run the associated component. """ | 
					
						
							|  |  |  |             file, component = identify(filename.decode()) | 
					
						
							|  |  |  |             if file and component: | 
					
						
							|  |  |  |                 run_component(component, file) | 
					
						
							|  |  |  |             else: | 
					
						
							| 
									
										
										
										
											2024-08-13 12:02:09 -04:00
										 |  |  |                 logging.warning(f"unable to identify component for {file}") | 
					
						
							| 
									
										
										
										
											2024-06-04 16:54:21 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-27 09:30:54 +02:00
										 |  |  |         def _stop(self, *largs): | 
					
						
							|  |  |  |             # ran into what appears to be https://groups.google.com/g/kivy-users/c/saWDLoYCSZ4 with PyCharm. | 
					
						
							|  |  |  |             # Closing the window explicitly cleans it up. | 
					
						
							|  |  |  |             self.root_window.close() | 
					
						
							|  |  |  |             super()._stop(*largs) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |     Launcher().run() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-06 11:36:14 -07:00
										 |  |  |     # avoiding Launcher reference leak | 
					
						
							|  |  |  |     # and don't try to do something with widgets after window closed | 
					
						
							|  |  |  |     global refresh_components | 
					
						
							|  |  |  |     refresh_components = None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  | def run_component(component: Component, *args): | 
					
						
							|  |  |  |     if component.func: | 
					
						
							|  |  |  |         component.func(*args) | 
					
						
							| 
									
										
										
										
											2024-06-06 11:36:14 -07:00
										 |  |  |         if refresh_components: | 
					
						
							|  |  |  |             refresh_components() | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  |     elif component.script_name: | 
					
						
							|  |  |  |         subprocess.run([*get_exe(component.script_name), *args]) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         logging.warning(f"Component {component} does not appear to be executable.") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | def main(args: Optional[Union[argparse.Namespace, dict]] = None): | 
					
						
							|  |  |  |     if isinstance(args, argparse.Namespace): | 
					
						
							|  |  |  |         args = {k: v for k, v in args._get_kwargs()} | 
					
						
							|  |  |  |     elif not args: | 
					
						
							|  |  |  |         args = {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  |     path = args.get("Patch|Game|Component|url", None) | 
					
						
							|  |  |  |     if path is not None: | 
					
						
							|  |  |  |         if path.startswith("archipelago://"): | 
					
						
							|  |  |  |             handle_uri(path, args.get("args", ())) | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         file, component = identify(path) | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |         if file: | 
					
						
							|  |  |  |             args['file'] = file | 
					
						
							|  |  |  |         if component: | 
					
						
							|  |  |  |             args['component'] = component | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  |         if not component: | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  |             logging.warning(f"Could not identify Component responsible for {path}") | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  |     if args["update_settings"]: | 
					
						
							|  |  |  |         update_settings() | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  |     if "file" in args: | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  |         run_component(args["component"], args["file"], *args["args"]) | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  |     elif "component" in args: | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  |         run_component(args["component"], *args["args"]) | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  |     elif not args["update_settings"]: | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |         run_gui() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == '__main__': | 
					
						
							|  |  |  |     init_logging('Launcher') | 
					
						
							| 
									
										
										
										
											2023-06-25 02:24:43 +02:00
										 |  |  |     Utils.freeze_support() | 
					
						
							|  |  |  |     multiprocessing.set_start_method("spawn")  # if launched process uses kivy, fork won't work | 
					
						
							| 
									
										
										
										
											2024-09-08 11:13:01 -05:00
										 |  |  |     parser = argparse.ArgumentParser( | 
					
						
							|  |  |  |         description='Archipelago Launcher', | 
					
						
							|  |  |  |         usage="[-h] [--update_settings] [Patch|Game|Component] [-- component args here]" | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  |     run_group = parser.add_argument_group("Run") | 
					
						
							|  |  |  |     run_group.add_argument("--update_settings", action="store_true", | 
					
						
							|  |  |  |                            help="Update host.yaml and exit.") | 
					
						
							| 
									
										
										
										
											2024-09-07 17:03:04 -05:00
										 |  |  |     run_group.add_argument("Patch|Game|Component|url", type=str, nargs="?", | 
					
						
							|  |  |  |                            help="Pass either a patch file, a generated game, the component name to run, or a url to " | 
					
						
							|  |  |  |                                 "connect with.") | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  |     run_group.add_argument("args", nargs="*", | 
					
						
							|  |  |  |                            help="Arguments to pass to component.") | 
					
						
							| 
									
										
										
										
											2022-03-31 05:08:15 +02:00
										 |  |  |     main(parser.parse_args()) | 
					
						
							| 
									
										
										
										
											2023-06-27 09:30:54 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     from worlds.LauncherComponents import processes | 
					
						
							|  |  |  |     for process in processes: | 
					
						
							|  |  |  |         # we await all child processes to close before we tear down the process host | 
					
						
							|  |  |  |         # this makes it feel like each one is its own program, as the Launcher is closed now | 
					
						
							|  |  |  |         process.join() |