add support for XLM pre-launch script integration and non-interactive configuration modes
This commit is contained in:
parent
72c301be78
commit
6f7d8c9d7c
4 changed files with 188 additions and 66 deletions
163
wrapper.py
163
wrapper.py
|
|
@ -13,6 +13,7 @@ import hashlib
|
|||
import os
|
||||
import shutil
|
||||
import configparser
|
||||
import argparse
|
||||
try:
|
||||
import keyring
|
||||
HAS_KEYRING = True
|
||||
|
|
@ -142,7 +143,11 @@ def check_and_update_launcher_ini():
|
|||
print(f"\nConfiguration Check: '{ini_path}' not found.")
|
||||
print("Please open XIVLauncher, go to Settings, and enable 'Start internal OTP server'.")
|
||||
print("This is required for the wrapper to function.")
|
||||
input("Press Enter to continue once you have done this (or to try anyway)...")
|
||||
if sys.stdin.isatty():
|
||||
try:
|
||||
input("Press Enter to continue once you have done this (or to try anyway)...")
|
||||
except EOFError:
|
||||
pass
|
||||
return
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
|
|
@ -164,7 +169,16 @@ def check_and_update_launcher_ini():
|
|||
print(f"\nConfiguration Check: '{ini_path}'")
|
||||
print("XIVLauncher requires 'IsOtpServer' to be enabled to accept OTP codes from this wrapper.")
|
||||
|
||||
choice = input("Enable 'IsOtpServer' now? [Y/n]: ").strip().lower()
|
||||
choice = 'n'
|
||||
if sys.stdin.isatty():
|
||||
try:
|
||||
choice = input("Enable 'IsOtpServer' now? [Y/n]: ").strip().lower()
|
||||
except EOFError:
|
||||
choice = 'y'
|
||||
else:
|
||||
print("Non-interactive mode detected. Auto-enabling 'IsOtpServer'.")
|
||||
choice = 'y'
|
||||
|
||||
if choice in ('', 'y', 'yes'):
|
||||
config.set('Main', 'IsOtpServer', 'true')
|
||||
try:
|
||||
|
|
@ -257,6 +271,10 @@ def get_secret(config, config_path):
|
|||
|
||||
if not secret_input:
|
||||
print("GUI prompt unavailable or cancelled. Falling back to CLI.")
|
||||
if not sys.stdin.isatty():
|
||||
print("Error: No interactive terminal available and GUI prompt failed. Cannot prompt for secret.")
|
||||
return None
|
||||
|
||||
print("Please enter your TOTP Secret (base32) or otpauth:// URL.")
|
||||
while True:
|
||||
try:
|
||||
|
|
@ -268,6 +286,9 @@ def get_secret(config, config_path):
|
|||
except KeyboardInterrupt:
|
||||
print("\nOperation cancelled.")
|
||||
sys.exit(1)
|
||||
except EOFError:
|
||||
print("\nError: EOF reading secret.")
|
||||
return None
|
||||
|
||||
# Basic validation/parsing check
|
||||
parsed = parse_secret(secret_input)
|
||||
|
|
@ -298,6 +319,16 @@ def get_secret(config, config_path):
|
|||
return parsed
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="XIVLauncher Wrapper")
|
||||
parser.add_argument('--prelaunch', action='store_true', help="Run as a prelaunch script (spawns injector and exits)")
|
||||
parser.add_argument('--inject-only', action='store_true', help=argparse.SUPPRESS)
|
||||
args, unknown_args = parser.parse_known_args()
|
||||
|
||||
# Auto-detect if running from a prelaunch.d directory
|
||||
script_path = os.path.abspath(sys.argv[0])
|
||||
if 'prelaunch.d' in script_path.split(os.sep):
|
||||
args.prelaunch = True
|
||||
|
||||
# Determine config path (XDG standard)
|
||||
xdg_config_home = os.environ.get('XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))
|
||||
config_dir = os.path.join(xdg_config_home, 'xivlauncher-wrapper')
|
||||
|
|
@ -332,52 +363,62 @@ def main():
|
|||
check_and_update_launcher_ini()
|
||||
|
||||
cmd = config.get("launcher_cmd")
|
||||
|
||||
config_dirty = False
|
||||
cmd_args = None
|
||||
|
||||
# 2. Auto-detect command if missing
|
||||
if not cmd:
|
||||
print("\n'launcher_cmd' not set in config. detecting installed XIVLauncher versions...")
|
||||
available = detect_launch_command()
|
||||
|
||||
if not available:
|
||||
print("Error: No XIVLauncher installation found (Flatpak or Native).")
|
||||
print("Please install XIVLauncher or manually set 'launcher_cmd' in config.json.")
|
||||
sys.exit(1)
|
||||
|
||||
elif len(available) >= 1:
|
||||
name, command = available[0]
|
||||
if len(available) > 1:
|
||||
print(f"Multiple XIVLauncher installations found. Automatically selecting first available: {name}")
|
||||
else:
|
||||
print(f"Detected {name}: {command}")
|
||||
cmd = command
|
||||
config['launcher_cmd'] = cmd
|
||||
config_dirty = True
|
||||
|
||||
# 2.5 Auto-detect gamemode/game-performance
|
||||
if "use_gamemode" not in config:
|
||||
gamemode_cmd = None
|
||||
if shutil.which("game-performance"):
|
||||
gamemode_cmd = "game-performance"
|
||||
elif shutil.which("gamemoderun"):
|
||||
gamemode_cmd = "gamemoderun"
|
||||
|
||||
if gamemode_cmd:
|
||||
print(f"\n'{gamemode_cmd}' was detected on your system.")
|
||||
print("Would you like to enable it for XIVLauncher? This can improve game performance.")
|
||||
try:
|
||||
choice = input(f"Enable {gamemode_cmd}? [Y/n]: ").strip().lower()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
choice = 'n'
|
||||
print("\nPrompt interrupted, defaulting to No.")
|
||||
# Determine command to launch (if not prelaunch/inject-only)
|
||||
if not args.prelaunch and not args.inject_only:
|
||||
if unknown_args:
|
||||
# Command passed via args (Steam %command% mode)
|
||||
cmd_args = unknown_args
|
||||
cmd = " ".join(shlex.quote(a) for a in cmd_args)
|
||||
else:
|
||||
# 2. Auto-detect command if missing
|
||||
if not cmd:
|
||||
print("\n'launcher_cmd' not set in config. detecting installed XIVLauncher versions...")
|
||||
available = detect_launch_command()
|
||||
|
||||
if choice in ('', 'y', 'yes'):
|
||||
config["use_gamemode"] = True
|
||||
config["gamemode_cmd"] = gamemode_cmd
|
||||
else:
|
||||
config["use_gamemode"] = False
|
||||
config_dirty = True
|
||||
if not available:
|
||||
print("Error: No XIVLauncher installation found (Flatpak or Native).")
|
||||
print("Please install XIVLauncher or manually set 'launcher_cmd' in config.json.")
|
||||
sys.exit(1)
|
||||
|
||||
elif len(available) >= 1:
|
||||
name, command = available[0]
|
||||
if len(available) > 1:
|
||||
print(f"Multiple XIVLauncher installations found. Automatically selecting first available: {name}")
|
||||
else:
|
||||
print(f"Detected {name}: {command}")
|
||||
cmd = command
|
||||
config['launcher_cmd'] = cmd
|
||||
config_dirty = True
|
||||
|
||||
# 2.5 Auto-detect gamemode/game-performance
|
||||
if "use_gamemode" not in config:
|
||||
gamemode_cmd = None
|
||||
if shutil.which("game-performance"):
|
||||
gamemode_cmd = "game-performance"
|
||||
elif shutil.which("gamemoderun"):
|
||||
gamemode_cmd = "gamemoderun"
|
||||
|
||||
if gamemode_cmd:
|
||||
print(f"\n'{gamemode_cmd}' was detected on your system.")
|
||||
print("Would you like to enable it for XIVLauncher? This can improve game performance.")
|
||||
try:
|
||||
if sys.stdin.isatty():
|
||||
choice = input(f"Enable {gamemode_cmd}? [Y/n]: ").strip().lower()
|
||||
else:
|
||||
choice = 'y' # auto-enable if not interactive
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
choice = 'n'
|
||||
print("\nPrompt interrupted, defaulting to No.")
|
||||
|
||||
if choice in ('', 'y', 'yes'):
|
||||
config["use_gamemode"] = True
|
||||
config["gamemode_cmd"] = gamemode_cmd
|
||||
else:
|
||||
config["use_gamemode"] = False
|
||||
config_dirty = True
|
||||
|
||||
# Save config if updated (e.g. launcher_cmd detected)
|
||||
if config_dirty:
|
||||
|
|
@ -395,18 +436,25 @@ def main():
|
|||
print("Error: No secret available.")
|
||||
sys.exit(1)
|
||||
|
||||
if args.inject_only:
|
||||
# Only run the OTP injection loop and exit
|
||||
send_otp(secret)
|
||||
sys.exit(0)
|
||||
|
||||
if args.prelaunch:
|
||||
print("Running in prelaunch mode. Spawning OTP injector in background...")
|
||||
# Spawn ourselves in background with --inject-only
|
||||
subprocess.Popen([sys.executable, os.path.realpath(__file__), '--inject-only'],
|
||||
start_new_session=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL)
|
||||
sys.exit(0)
|
||||
|
||||
print(f"Launch Command: {cmd}")
|
||||
|
||||
# 3. Launch Logic
|
||||
try:
|
||||
# Launch the process
|
||||
# We use shell=True if the command contains arguments like 'flatpak run ...'
|
||||
# But split() and direct arg list is safer and cleaner if possible.
|
||||
# However, 'flatpak run ...' is multiple args.
|
||||
# If the user put a complex command string in config, we should respect it.
|
||||
# Simple split logic:
|
||||
import shlex
|
||||
args = shlex.split(cmd)
|
||||
if not cmd_args:
|
||||
cmd_args = shlex.split(cmd)
|
||||
|
||||
# Prepend gamemode wrapper if enabled
|
||||
if config.get("use_gamemode"):
|
||||
|
|
@ -417,16 +465,17 @@ def main():
|
|||
elif shutil.which("gamemoderun"):
|
||||
g_cmd = "gamemoderun"
|
||||
if g_cmd and shutil.which(g_cmd):
|
||||
args.insert(0, g_cmd)
|
||||
cmd_args.insert(0, g_cmd)
|
||||
print(f"Using game performance wrapper: {g_cmd}")
|
||||
|
||||
try:
|
||||
# Inject XL_SECRET_PROVIDER=file environment variable
|
||||
env = os.environ.copy()
|
||||
env['XL_SECRET_PROVIDER'] = 'file'
|
||||
|
||||
proc = subprocess.Popen(args, env=env)
|
||||
proc = subprocess.Popen(cmd_args, env=env)
|
||||
except FileNotFoundError:
|
||||
print(f"Error: Command executable not found: {cmd}")
|
||||
print(f"Error: Command executable not found: {cmd_args[0] if cmd_args else cmd}")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error launching command '{cmd}': {e}")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue