1111import argparse
1212import logging
1313import multiprocessing
14+ import os
1415import shlex
1516import subprocess
1617import sys
@@ -41,13 +42,17 @@ def open_host_yaml():
4142 if is_linux :
4243 exe = which ('sensible-editor' ) or which ('gedit' ) or \
4344 which ('xdg-open' ) or which ('gnome-open' ) or which ('kde-open' )
44- subprocess .Popen ([exe , file ])
4545 elif is_macos :
4646 exe = which ("open" )
47- subprocess .Popen ([exe , file ])
4847 else :
4948 webbrowser .open (file )
49+ return
5050
51+ env = os .environ
52+ if "LD_LIBRARY_PATH" in env :
53+ env = env .copy ()
54+ del env ["LD_LIBRARY_PATH" ] # exe is a system binary, so reset LD_LIBRARY_PATH
55+ subprocess .Popen ([exe , file ], env = env )
5156
5257def open_patch ():
5358 suffixes = []
@@ -92,7 +97,11 @@ def open_folder(folder_path):
9297 return
9398
9499 if exe :
95- subprocess .Popen ([exe , folder_path ])
100+ env = os .environ
101+ if "LD_LIBRARY_PATH" in env :
102+ env = env .copy ()
103+ del env ["LD_LIBRARY_PATH" ] # exe is a system binary, so reset LD_LIBRARY_PATH
104+ subprocess .Popen ([exe , folder_path ], env = env )
96105 else :
97106 logging .warning (f"No file browser available to open { folder_path } " )
98107
@@ -104,45 +113,48 @@ def update_settings():
104113
105114components .extend ([
106115 # Functions
107- Component ("Open host.yaml" , func = open_host_yaml ),
108- Component ("Open Patch" , func = open_patch ),
109- Component ("Generate Template Options" , func = generate_yamls ),
110- Component ("Archipelago Website" , func = lambda : webbrowser .open ("https://archipelago.gg/" )),
111- Component ("Discord Server" , icon = "discord" , func = lambda : webbrowser .open ("https://discord.gg/8Z65BR2" )),
116+ Component ("Open host.yaml" , func = open_host_yaml ,
117+ description = "Open the host.yaml file to change settings for generation, games, and more." ),
118+ Component ("Open Patch" , func = open_patch ,
119+ description = "Open a patch file, downloaded from the room page or provided by the host." ),
120+ Component ("Generate Template Options" , func = generate_yamls ,
121+ description = "Generate template YAMLs for currently installed games." ),
122+ Component ("Archipelago Website" , func = lambda : webbrowser .open ("https://archipelago.gg/" ),
123+ description = "Open archipelago.gg in your browser." ),
124+ Component ("Discord Server" , icon = "discord" , func = lambda : webbrowser .open ("https://discord.gg/8Z65BR2" ),
125+ description = "Join the Discord server to play public multiworlds, report issues, or just chat!" ),
112126 Component ("Unrated/18+ Discord Server" , icon = "discord" ,
113- func = lambda : webbrowser .open ("https://discord.gg/fqvNCCRsu4" )),
114- Component ("Browse Files" , func = browse_files ),
127+ func = lambda : webbrowser .open ("https://discord.gg/fqvNCCRsu4" ),
128+ description = "Find unrated and 18+ games in the After Dark Discord server." ),
129+ Component ("Browse Files" , func = browse_files ,
130+ description = "Open the Archipelago installation folder in your file browser." ),
115131])
116132
117133
118- def handle_uri (path : str , launch_args : tuple [str , ...]) -> None :
134+ def handle_uri (path : str ) -> tuple [list [ Component ], Component ] :
119135 url = urllib .parse .urlparse (path )
120136 queries = urllib .parse .parse_qs (url .query )
121- launch_args = (path , * launch_args )
122- client_component = []
137+ client_components = []
123138 text_client_component = None
124139 game = queries ["game" ][0 ]
125140 for component in components :
126141 if component .supports_uri and component .game_name == game :
127- client_component .append (component )
142+ client_components .append (component )
128143 elif component .display_name == "Text Client" :
129144 text_client_component = component
145+ return client_components , text_client_component
130146
131147
132- if not client_component :
133- run_component (text_client_component , * launch_args )
134- return
135- else :
136- from kvui import ButtonsPrompt
137- component_options = {
138- text_client_component .display_name : text_client_component ,
139- ** {component .display_name : component for component in client_component }
140- }
141- popup = ButtonsPrompt ("Connect to Multiworld" ,
142- "Select client to open and connect with." ,
143- lambda component_name : run_component (component_options [component_name ], * launch_args ),
144- * component_options .keys ())
145- popup .open ()
148+ def build_uri_popup (component_list : list [Component ], launch_args : tuple [str , ...]) -> None :
149+ from kvui import ButtonsPrompt
150+ component_options = {
151+ component .display_name : component for component in component_list
152+ }
153+ popup = ButtonsPrompt ("Connect to Multiworld" ,
154+ "Select client to open and connect with." ,
155+ lambda component_name : run_component (component_options [component_name ], * launch_args ),
156+ * component_options .keys ())
157+ popup .open ()
146158
147159
148160def identify (path : None | str ) -> tuple [None | str , None | Component ]:
@@ -184,7 +196,8 @@ def get_exe(component: str | Component) -> Sequence[str] | None:
184196def launch (exe , in_terminal = False ):
185197 if in_terminal :
186198 if is_windows :
187- subprocess .Popen (['start' , * exe ], shell = True )
199+ # intentionally using a window title with a space so it gets quoted and treated as a title
200+ subprocess .Popen (["start" , "Running Archipelago" , * exe ], shell = True )
188201 return
189202 elif is_linux :
190203 terminal = which ('x-terminal-emulator' ) or which ('gnome-terminal' ) or which ('xterm' )
@@ -212,7 +225,7 @@ def create_shortcut(button: Any, component: Component) -> None:
212225refresh_components : Callable [[], None ] | None = None
213226
214227
215- def run_gui (path : str , args : Any ) -> None :
228+ def run_gui (launch_components : list [ Component ] , args : Any ) -> None :
216229 from kvui import (ThemedApp , MDFloatLayout , MDGridLayout , ScrollBox )
217230 from kivy .properties import ObjectProperty
218231 from kivy .core .window import Window
@@ -245,12 +258,12 @@ class Launcher(ThemedApp):
245258 cards : list [LauncherCard ]
246259 current_filter : Sequence [str | Type ] | None
247260
248- def __init__ (self , ctx = None , path = None , args = None ):
261+ def __init__ (self , ctx = None , components = None , args = None ):
249262 self .title = self .base_title + " " + Utils .__version__
250263 self .ctx = ctx
251264 self .icon = r"data/icon.png"
252265 self .favorites = []
253- self .launch_uri = path
266+ self .launch_components = components
254267 self .launch_args = args
255268 self .cards = []
256269 self .current_filter = (Type .CLIENT , Type .TOOL , Type .ADJUSTER , Type .MISC )
@@ -372,9 +385,9 @@ def build(self):
372385 return self .top_screen
373386
374387 def on_start (self ):
375- if self .launch_uri :
376- handle_uri (self .launch_uri , self .launch_args )
377- self .launch_uri = None
388+ if self .launch_components :
389+ build_uri_popup (self .launch_components , self .launch_args )
390+ self .launch_components = None
378391 self .launch_args = None
379392
380393 @staticmethod
@@ -415,7 +428,7 @@ def on_stop(self):
415428 for filter in self .current_filter ))
416429 super ().on_stop ()
417430
418- Launcher (path = path , args = args ).run ()
431+ Launcher (components = launch_components , args = args ).run ()
419432
420433 # avoiding Launcher reference leak
421434 # and don't try to do something with widgets after window closed
@@ -442,7 +455,15 @@ def main(args: argparse.Namespace | dict | None = None):
442455
443456 path = args .get ("Patch|Game|Component|url" , None )
444457 if path is not None :
445- if not path .startswith ("archipelago://" ):
458+ if path .startswith ("archipelago://" ):
459+ args ["args" ] = (path , * args .get ("args" , ()))
460+ # add the url arg to the passthrough args
461+ components , text_client_component = handle_uri (path )
462+ if not components :
463+ args ["component" ] = text_client_component
464+ else :
465+ args ['launch_components' ] = [text_client_component , * components ]
466+ else :
446467 file , component = identify (path )
447468 if file :
448469 args ['file' ] = file
@@ -458,7 +479,7 @@ def main(args: argparse.Namespace | dict | None = None):
458479 elif "component" in args :
459480 run_component (args ["component" ], * args ["args" ])
460481 elif not args ["update_settings" ]:
461- run_gui (path , args .get ("args" , ()))
482+ run_gui (args . get ( "launch_components" , None ) , args .get ("args" , ()))
462483
463484
464485if __name__ == '__main__' :
0 commit comments