Core: have webhost slot name links go through the launcher (#2779)
* Core: have webhost slot name links go through the launcher so that components can use them * fix query handling, remove debug prints, and change mousover text for new behavior * remove a missed debug and unused function * filter room id to suuid since that's what everything else uses * pass args to common client correctly * add GUI to select which client to open * remove args parsing and "require" components to parse it themselves * support for messenger since it was basically already done * use "proper" args argparsing and clean up uri handling * use a timer and auto launch text client if no component is found * change the timer to be a bit more appealing. also found a bug lmao * don't hold 5 hostage and capitalize URI ig --------- Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
This commit is contained in:
parent
a40744e6db
commit
430b71a092
|
@ -994,7 +994,7 @@ def get_base_parser(description: typing.Optional[str] = None):
|
|||
return parser
|
||||
|
||||
|
||||
def run_as_textclient():
|
||||
def run_as_textclient(*args):
|
||||
class TextContext(CommonContext):
|
||||
# Text Mode to use !hint and such with games that have no text entry
|
||||
tags = CommonContext.tags | {"TextOnly"}
|
||||
|
@ -1033,7 +1033,7 @@ def run_as_textclient():
|
|||
parser = get_base_parser(description="Gameless Archipelago Client, for text interfacing.")
|
||||
parser.add_argument('--name', default=None, help="Slot Name to connect as.")
|
||||
parser.add_argument("url", nargs="?", help="Archipelago connection url")
|
||||
args = parser.parse_args()
|
||||
args = parser.parse_args(args if args else None) # this is necessary as long as CommonClient itself is launchable
|
||||
|
||||
if args.url:
|
||||
url = urllib.parse.urlparse(args.url)
|
||||
|
|
98
Launcher.py
98
Launcher.py
|
@ -16,10 +16,11 @@ import multiprocessing
|
|||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.parse
|
||||
import webbrowser
|
||||
from os.path import isfile
|
||||
from shutil import which
|
||||
from typing import Callable, Sequence, Union, Optional
|
||||
from typing import Callable, Optional, Sequence, Tuple, Union
|
||||
|
||||
import Utils
|
||||
import settings
|
||||
|
@ -107,7 +108,81 @@ components.extend([
|
|||
])
|
||||
|
||||
|
||||
def identify(path: Union[None, str]):
|
||||
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
|
||||
|
||||
from kvui import App, Button, BoxLayout, Label, Clock, Window
|
||||
|
||||
class Popup(App):
|
||||
timer_label: Label
|
||||
remaining_time: Optional[int]
|
||||
|
||||
def __init__(self):
|
||||
self.title = "Connect to Multiworld"
|
||||
self.icon = r"data/icon.png"
|
||||
super().__init__()
|
||||
|
||||
def build(self):
|
||||
layout = BoxLayout(orientation="vertical")
|
||||
|
||||
if client_component is None:
|
||||
self.remaining_time = 7
|
||||
label_text = (f"A game client able to parse URIs was not detected for {game}.\n"
|
||||
f"Launching Text Client in 7 seconds...")
|
||||
self.timer_label = Label(text=label_text)
|
||||
layout.add_widget(self.timer_label)
|
||||
Clock.schedule_interval(self.update_label, 1)
|
||||
else:
|
||||
layout.add_widget(Label(text="Select client to open and connect with."))
|
||||
button_row = BoxLayout(orientation="horizontal", size_hint=(1, 0.4))
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
layout.add_widget(button_row)
|
||||
|
||||
return layout
|
||||
|
||||
def update_label(self, dt):
|
||||
if self.remaining_time > 1:
|
||||
# countdown the timer and string replace the number
|
||||
self.remaining_time -= 1
|
||||
self.timer_label.text = self.timer_label.text.replace(
|
||||
str(self.remaining_time + 1), str(self.remaining_time)
|
||||
)
|
||||
else:
|
||||
# our timer is finished so launch text client and close down
|
||||
run_component(text_client_component, *launch_args)
|
||||
Clock.unschedule(self.update_label)
|
||||
App.get_running_app().stop()
|
||||
Window.close()
|
||||
|
||||
Popup().run()
|
||||
|
||||
|
||||
def identify(path: Union[None, str]) -> Tuple[Union[None, str], Union[None, Component]]:
|
||||
if path is None:
|
||||
return None, None
|
||||
for component in components:
|
||||
|
@ -299,20 +374,24 @@ def main(args: Optional[Union[argparse.Namespace, dict]] = None):
|
|||
elif not args:
|
||||
args = {}
|
||||
|
||||
if args.get("Patch|Game|Component", None) is not None:
|
||||
file, component = identify(args["Patch|Game|Component"])
|
||||
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)
|
||||
if file:
|
||||
args['file'] = file
|
||||
if component:
|
||||
args['component'] = component
|
||||
if not component:
|
||||
logging.warning(f"Could not identify Component responsible for {args['Patch|Game|Component']}")
|
||||
logging.warning(f"Could not identify Component responsible for {path}")
|
||||
|
||||
if args["update_settings"]:
|
||||
update_settings()
|
||||
if 'file' in args:
|
||||
if "file" in args:
|
||||
run_component(args["component"], args["file"], *args["args"])
|
||||
elif 'component' in args:
|
||||
elif "component" in args:
|
||||
run_component(args["component"], *args["args"])
|
||||
elif not args["update_settings"]:
|
||||
run_gui()
|
||||
|
@ -326,8 +405,9 @@ if __name__ == '__main__':
|
|||
run_group = parser.add_argument_group("Run")
|
||||
run_group.add_argument("--update_settings", action="store_true",
|
||||
help="Update host.yaml and exit.")
|
||||
run_group.add_argument("Patch|Game|Component", type=str, nargs="?",
|
||||
help="Pass either a patch file, a generated game or the name of a component to run.")
|
||||
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.")
|
||||
run_group.add_argument("args", nargs="*",
|
||||
help="Arguments to pass to component.")
|
||||
main(parser.parse_args())
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
{% for patch in room.seed.slots|list|sort(attribute="player_id") %}
|
||||
<tr>
|
||||
<td>{{ patch.player_id }}</td>
|
||||
<td data-tooltip="Connect via TextClient"><a href="archipelago://{{ patch.player_name | e}}:None@{{ config['HOST_ADDRESS'] }}:{{ room.last_port }}">{{ patch.player_name }}</a></td>
|
||||
<td data-tooltip="Connect via Game Client"><a href="archipelago://{{ patch.player_name | e}}:None@{{ config['HOST_ADDRESS'] }}:{{ room.last_port }}?game={{ patch.game }}&room={{ room.id | suuid }}">{{ patch.player_name }}</a></td>
|
||||
<td>{{ patch.game }}</td>
|
||||
<td>
|
||||
{% if patch.data %}
|
||||
|
|
|
@ -228,8 +228,8 @@ Root: HKCR; Subkey: "{#MyAppName}worlddata\shell\open\command"; ValueData: """{a
|
|||
|
||||
Root: HKCR; Subkey: "archipelago"; ValueType: "string"; ValueData: "Archipegalo Protocol"; Flags: uninsdeletekey;
|
||||
Root: HKCR; Subkey: "archipelago"; ValueType: "string"; ValueName: "URL Protocol"; ValueData: "";
|
||||
Root: HKCR; Subkey: "archipelago\DefaultIcon"; ValueType: "string"; ValueData: "{app}\ArchipelagoTextClient.exe,0";
|
||||
Root: HKCR; Subkey: "archipelago\shell\open\command"; ValueType: "string"; ValueData: """{app}\ArchipelagoTextClient.exe"" ""%1""";
|
||||
Root: HKCR; Subkey: "archipelago\DefaultIcon"; ValueType: "string"; ValueData: "{app}\ArchipelagoLauncher.exe,0";
|
||||
Root: HKCR; Subkey: "archipelago\shell\open\command"; ValueType: "string"; ValueData: """{app}\ArchipelagoLauncher.exe"" ""%1""";
|
||||
|
||||
[Code]
|
||||
// See: https://stackoverflow.com/a/51614652/2287576
|
||||
|
|
|
@ -26,10 +26,13 @@ class Component:
|
|||
cli: bool
|
||||
func: Optional[Callable]
|
||||
file_identifier: Optional[Callable[[str], bool]]
|
||||
game_name: Optional[str]
|
||||
supports_uri: Optional[bool]
|
||||
|
||||
def __init__(self, display_name: str, script_name: Optional[str] = None, frozen_name: Optional[str] = None,
|
||||
cli: bool = False, icon: str = 'icon', component_type: Optional[Type] = None,
|
||||
func: Optional[Callable] = None, file_identifier: Optional[Callable[[str], bool]] = None):
|
||||
func: Optional[Callable] = None, file_identifier: Optional[Callable[[str], bool]] = None,
|
||||
game_name: Optional[str] = None, supports_uri: Optional[bool] = False):
|
||||
self.display_name = display_name
|
||||
self.script_name = script_name
|
||||
self.frozen_name = frozen_name or f'Archipelago{script_name}' if script_name else None
|
||||
|
@ -45,6 +48,8 @@ class Component:
|
|||
Type.ADJUSTER if "Adjuster" in display_name else Type.MISC)
|
||||
self.func = func
|
||||
self.file_identifier = file_identifier
|
||||
self.game_name = game_name
|
||||
self.supports_uri = supports_uri
|
||||
|
||||
def handles_file(self, path: str):
|
||||
return self.file_identifier(path) if self.file_identifier else False
|
||||
|
@ -56,10 +61,10 @@ class Component:
|
|||
processes = weakref.WeakSet()
|
||||
|
||||
|
||||
def launch_subprocess(func: Callable, name: str = None):
|
||||
def launch_subprocess(func: Callable, name: str = None, args: Tuple[str, ...] = ()):
|
||||
global processes
|
||||
import multiprocessing
|
||||
process = multiprocessing.Process(target=func, name=name)
|
||||
process = multiprocessing.Process(target=func, name=name, args=args)
|
||||
process.start()
|
||||
processes.add(process)
|
||||
|
||||
|
@ -78,9 +83,9 @@ class SuffixIdentifier:
|
|||
return False
|
||||
|
||||
|
||||
def launch_textclient():
|
||||
def launch_textclient(*args):
|
||||
import CommonClient
|
||||
launch_subprocess(CommonClient.run_as_textclient, name="TextClient")
|
||||
launch_subprocess(CommonClient.run_as_textclient, "TextClient", args)
|
||||
|
||||
|
||||
def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, pathlib.Path]]:
|
||||
|
|
|
@ -19,7 +19,7 @@ from .shop import FIGURINES, PROG_SHOP_ITEMS, SHOP_ITEMS, USEFUL_SHOP_ITEMS, shu
|
|||
from .subclasses import MessengerEntrance, MessengerItem, MessengerRegion, MessengerShopLocation
|
||||
|
||||
components.append(
|
||||
Component("The Messenger", component_type=Type.CLIENT, func=launch_game)#, game_name="The Messenger", supports_uri=True)
|
||||
Component("The Messenger", component_type=Type.CLIENT, func=launch_game, game_name="The Messenger", supports_uri=True)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import argparse
|
||||
import io
|
||||
import logging
|
||||
import os.path
|
||||
|
@ -17,7 +18,7 @@ from Utils import is_windows, messagebox, tuplize_version
|
|||
MOD_URL = "https://api.github.com/repos/alwaysintreble/TheMessengerRandomizerModAP/releases/latest"
|
||||
|
||||
|
||||
def launch_game(url: Optional[str] = None) -> None:
|
||||
def launch_game(*args) -> None:
|
||||
"""Check the game installation, then launch it"""
|
||||
def courier_installed() -> bool:
|
||||
"""Check if Courier is installed"""
|
||||
|
@ -150,15 +151,19 @@ def launch_game(url: Optional[str] = None) -> None:
|
|||
install_mod()
|
||||
elif should_update is None:
|
||||
return
|
||||
|
||||
parser = argparse.ArgumentParser(description="Messenger Client Launcher")
|
||||
parser.add_argument("url", type=str, nargs="?", help="Archipelago Webhost uri to auto connect to.")
|
||||
args = parser.parse_args(args)
|
||||
if not is_windows:
|
||||
if url:
|
||||
open_file(f"steam://rungameid/764790//{url}/")
|
||||
if args.url:
|
||||
open_file(f"steam://rungameid/764790//{args.url}/")
|
||||
else:
|
||||
open_file("steam://rungameid/764790")
|
||||
else:
|
||||
os.chdir(game_folder)
|
||||
if url:
|
||||
subprocess.Popen([MessengerWorld.settings.game_path, str(url)])
|
||||
if args.url:
|
||||
subprocess.Popen([MessengerWorld.settings.game_path, str(args.url)])
|
||||
else:
|
||||
subprocess.Popen(MessengerWorld.settings.game_path)
|
||||
os.chdir(working_directory)
|
||||
|
|
Loading…
Reference in New Issue