mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-08-04 09:46:16 -07:00
Launcher: Loading Screen (#6115)
This commit is contained in:
+157
-18
@@ -2,19 +2,21 @@ import bisect
|
||||
import logging
|
||||
import pathlib
|
||||
import weakref
|
||||
from enum import Enum, auto
|
||||
from typing import Optional, Callable, List, Iterable, Tuple
|
||||
import sys
|
||||
import webbrowser
|
||||
from enum import Enum
|
||||
from typing import Optional, Callable, Iterable, Sequence
|
||||
|
||||
from Utils import local_path, open_filename, is_frozen, is_kivy_running, open_file, user_path, read_apignore
|
||||
from Utils import local_path, open_filename, is_frozen, is_kivy_running, open_file, user_path, read_apignore, \
|
||||
is_windows
|
||||
|
||||
|
||||
class Type(Enum):
|
||||
TOOL = auto()
|
||||
MISC = auto()
|
||||
CLIENT = auto()
|
||||
ADJUSTER = auto()
|
||||
FUNC = auto() # do not use anymore
|
||||
HIDDEN = auto()
|
||||
class Type(str, Enum):
|
||||
TOOL = "TOOL"
|
||||
MISC = "MISC"
|
||||
CLIENT = "CLIENT"
|
||||
ADJUSTER = "ADJUSTER"
|
||||
HIDDEN = "HIDDEN"
|
||||
|
||||
|
||||
class Component:
|
||||
@@ -67,10 +69,6 @@ class Component:
|
||||
self.frozen_name = frozen_name or f'Archipelago{script_name}' if script_name else None
|
||||
self.icon = icon
|
||||
self.cli = cli
|
||||
if component_type == Type.FUNC:
|
||||
from Utils import deprecate
|
||||
deprecate(f"Launcher Component {self.display_name} is using Type.FUNC Type, which is pending removal.")
|
||||
component_type = Type.MISC
|
||||
|
||||
self.type = component_type or (
|
||||
Type.CLIENT if "Client" in display_name else
|
||||
@@ -86,18 +84,27 @@ class Component:
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.display_name})"
|
||||
|
||||
def run(self, *args) -> bool:
|
||||
if self.func:
|
||||
self.func(*args)
|
||||
elif self.script_name:
|
||||
import subprocess
|
||||
subprocess.run([*get_exe(self.script_name), *args])
|
||||
else:
|
||||
logging.warning(f"Component {self} does not appear to be executable.")
|
||||
|
||||
|
||||
processes = weakref.WeakSet()
|
||||
|
||||
|
||||
def launch_subprocess(func: Callable, name: str | None = None, args: Tuple[str, ...] = ()) -> None:
|
||||
def launch_subprocess(func: Callable, name: str | None = None, args: tuple[str, ...] = ()) -> None:
|
||||
import multiprocessing
|
||||
process = multiprocessing.Process(target=func, name=name, args=args)
|
||||
process.start()
|
||||
processes.add(process)
|
||||
|
||||
|
||||
def launch(func: Callable, name: str | None = None, args: Tuple[str, ...] = ()) -> None:
|
||||
def launch(func: Callable, name: str | None = None, args: tuple[str, ...] = ()) -> None:
|
||||
from Utils import is_kivy_running
|
||||
if is_kivy_running():
|
||||
launch_subprocess(func, name, args)
|
||||
@@ -124,7 +131,7 @@ def launch_textclient(*args):
|
||||
launch(CommonClient.run_as_textclient, name="TextClient", args=args)
|
||||
|
||||
|
||||
def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, pathlib.Path]]:
|
||||
def _install_apworld(apworld_src: str = "") -> Optional[tuple[pathlib.Path, pathlib.Path]]:
|
||||
if not apworld_src:
|
||||
apworld_src = open_filename('Select APWorld file to install', (('APWorld', ('.apworld',)),))
|
||||
if not apworld_src:
|
||||
@@ -215,8 +222,124 @@ def export_datapackage() -> None:
|
||||
|
||||
open_file(path)
|
||||
|
||||
def open_patch():
|
||||
from Utils import messagebox
|
||||
from os.path import isfile
|
||||
suffixes = []
|
||||
for c in components:
|
||||
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
|
||||
try:
|
||||
filename = open_filename("Select patch", (("Patches", suffixes),))
|
||||
except Exception as e:
|
||||
messagebox("Error", str(e), error=True)
|
||||
else:
|
||||
file, component = identify(filename)
|
||||
if file and component:
|
||||
exe = get_exe(component)
|
||||
if exe is None or not isfile(exe[-1]):
|
||||
exe = get_exe("Launcher")
|
||||
|
||||
components: List[Component] = [
|
||||
launch([*exe, file], component.cli)
|
||||
|
||||
|
||||
def get_exe(component: str | Component) -> Sequence[str] | None:
|
||||
if isinstance(component, str):
|
||||
name = component
|
||||
component = None
|
||||
if name.startswith("Archipelago"):
|
||||
name = name[11:]
|
||||
if name.endswith(".exe"):
|
||||
name = name[:-4]
|
||||
if name.endswith(".py"):
|
||||
name = name[:-3]
|
||||
if not name:
|
||||
return None
|
||||
for c in components:
|
||||
if c.script_name == name or c.frozen_name == f"Archipelago{name}":
|
||||
component = c
|
||||
break
|
||||
if not component:
|
||||
return None
|
||||
if is_frozen():
|
||||
suffix = ".exe" if is_windows else ""
|
||||
return [local_path(f"{component.frozen_name}{suffix}")] if component.frozen_name else None
|
||||
else:
|
||||
return [sys.executable, local_path(f"{component.script_name}.py")] if component.script_name else None
|
||||
|
||||
def identify(path: None | str) -> tuple[None | str, None | Component]:
|
||||
if path is None:
|
||||
return None, None
|
||||
for component in components:
|
||||
if component.handles_file(path):
|
||||
return path, component
|
||||
elif path == component.display_name or path == component.script_name:
|
||||
return None, component
|
||||
return None, None
|
||||
|
||||
def open_host_yaml():
|
||||
import settings
|
||||
import subprocess
|
||||
from shutil import which
|
||||
from Utils import is_linux, is_macos, env_cleared_lib_path
|
||||
s = settings.get_settings()
|
||||
file = s.filename
|
||||
s.save()
|
||||
assert file, "host.yaml missing"
|
||||
if is_linux:
|
||||
exe = which('sensible-editor') or which('gedit') or \
|
||||
which('xdg-open') or which('gnome-open') or which('kde-open')
|
||||
elif is_macos:
|
||||
exe = which("open")
|
||||
else:
|
||||
webbrowser.open(file)
|
||||
return
|
||||
|
||||
env = env_cleared_lib_path()
|
||||
subprocess.Popen([exe, file], env=env)
|
||||
|
||||
def generate_yamls(*args):
|
||||
import argparse
|
||||
|
||||
from Options import generate_yaml_templates
|
||||
|
||||
parser = argparse.ArgumentParser(description="Generate Template Options", usage="[-h] [--skip_open_folder]")
|
||||
parser.add_argument("--skip_open_folder", action="store_true")
|
||||
args = parser.parse_args(args)
|
||||
|
||||
target = user_path("Players", "Templates")
|
||||
generate_yaml_templates(target, False)
|
||||
if not args.skip_open_folder:
|
||||
open_folder(target)
|
||||
|
||||
|
||||
def browse_files():
|
||||
open_folder(user_path())
|
||||
|
||||
|
||||
def open_folder(folder_path):
|
||||
import subprocess
|
||||
from shutil import which
|
||||
from Utils import is_linux, is_macos, env_cleared_lib_path
|
||||
|
||||
if is_linux:
|
||||
exe = which('xdg-open') or which('gnome-open') or which('kde-open')
|
||||
elif is_macos:
|
||||
exe = which("open")
|
||||
else:
|
||||
webbrowser.open(folder_path)
|
||||
return
|
||||
|
||||
if exe:
|
||||
env = env_cleared_lib_path()
|
||||
subprocess.Popen([exe, folder_path], env=env)
|
||||
else:
|
||||
logging.warning(f"No file browser available to open {folder_path}")
|
||||
|
||||
|
||||
components: list[Component] = [
|
||||
# Launcher
|
||||
Component('Launcher', 'Launcher', component_type=Type.HIDDEN),
|
||||
# Core
|
||||
@@ -231,6 +354,22 @@ components: List[Component] = [
|
||||
description="Install an APWorld to play games not included with Archipelago by default."),
|
||||
Component('Text Client', 'CommonClient', 'ArchipelagoTextClient', func=launch_textclient,
|
||||
description="Connect to a multiworld using the text client."),
|
||||
# Functions
|
||||
Component("Open host.yaml", func=open_host_yaml,
|
||||
description="Open the host.yaml file to change settings for generation, games, and more."),
|
||||
Component("Open Patch", func=open_patch,
|
||||
description="Open a patch file, downloaded from the room page or provided by the host."),
|
||||
Component("Generate Template Options", func=generate_yamls,
|
||||
description="Generate template YAMLs for currently installed games."),
|
||||
Component("Archipelago Website", func=lambda: webbrowser.open("https://archipelago.gg/"),
|
||||
description="Open archipelago.gg in your browser."),
|
||||
Component("Discord Server", icon="discord", func=lambda: webbrowser.open("https://discord.gg/8Z65BR2"),
|
||||
description="Join the Discord server to play public multiworlds, report issues, or just chat!"),
|
||||
Component("Unrated/18+ Discord Server", icon="discord",
|
||||
func=lambda: webbrowser.open("https://discord.gg/fqvNCCRsu4"),
|
||||
description="Find unrated and 18+ games in the After Dark Discord server."),
|
||||
Component("Browse Files", func=browse_files,
|
||||
description="Open the Archipelago installation folder in your file browser."),
|
||||
Component('LttP Adjuster', 'LttPAdjuster'),
|
||||
# Ocarina of Time
|
||||
Component('OoT Client', 'OoTClient',
|
||||
|
||||
Reference in New Issue
Block a user