feat: Add GUI prompts for OTP secret input and streamline initial configuration setup with automatic config creation and launcher selection.
This commit is contained in:
parent
252a7ed239
commit
95ec73df5c
1 changed files with 95 additions and 68 deletions
87
wrapper.py
87
wrapper.py
|
|
@ -19,9 +19,40 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_KEYRING = False
|
HAS_KEYRING = False
|
||||||
import getpass
|
import getpass
|
||||||
|
import shlex
|
||||||
|
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
|
def prompt_gui_secret():
|
||||||
|
"""Prompts for the OTP secret using a DE-appropriate GUI dialog (kdialog or zenity)."""
|
||||||
|
desktop = os.environ.get('XDG_CURRENT_DESKTOP', '').lower()
|
||||||
|
|
||||||
|
# Check for KDE
|
||||||
|
if 'kde' in desktop and shutil.which('kdialog'):
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['kdialog', '--password', 'Please enter your XIVLauncher TOTP Secret (base32) or otpauth:// URL:'],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
return result.stdout.strip()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: kdialog failed: {e}")
|
||||||
|
|
||||||
|
# Fallback to Zenity for GNOME/others
|
||||||
|
elif shutil.which('zenity'):
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['zenity', '--password', '--title=XIVLauncher Wrapper', '--text=Please enter your TOTP Secret (base32) or otpauth:// URL:'],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
return result.stdout.strip()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: zenity failed: {e}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def parse_secret(secret_input):
|
def parse_secret(secret_input):
|
||||||
"""Clean and extract secret from input (handles raw secret or otpauth:// URL)."""
|
"""Clean and extract secret from input (handles raw secret or otpauth:// URL)."""
|
||||||
if not secret_input:
|
if not secret_input:
|
||||||
|
|
@ -221,6 +252,11 @@ def get_secret(config, config_path):
|
||||||
else:
|
else:
|
||||||
print("\nOTP Secret not found in config.json and keyring module is missing.")
|
print("\nOTP Secret not found in config.json and keyring module is missing.")
|
||||||
|
|
||||||
|
print("Attempting GUI prompt...")
|
||||||
|
secret_input = prompt_gui_secret()
|
||||||
|
|
||||||
|
if not secret_input:
|
||||||
|
print("GUI prompt unavailable or cancelled. Falling back to CLI.")
|
||||||
print("Please enter your TOTP Secret (base32) or otpauth:// URL.")
|
print("Please enter your TOTP Secret (base32) or otpauth:// URL.")
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
|
@ -228,12 +264,17 @@ def get_secret(config, config_path):
|
||||||
if not secret_input:
|
if not secret_input:
|
||||||
print("Secret cannot be empty.")
|
print("Secret cannot be empty.")
|
||||||
continue
|
continue
|
||||||
|
break
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nOperation cancelled.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# Basic validation/parsing check
|
# Basic validation/parsing check
|
||||||
parsed = parse_secret(secret_input)
|
parsed = parse_secret(secret_input)
|
||||||
if not parsed:
|
while not parsed:
|
||||||
print("Invalid secret format. Please try again.")
|
print("Invalid secret format.")
|
||||||
continue
|
secret_input = getpass.getpass("Secret: ").strip()
|
||||||
|
parsed = parse_secret(secret_input)
|
||||||
|
|
||||||
if HAS_KEYRING:
|
if HAS_KEYRING:
|
||||||
try:
|
try:
|
||||||
|
|
@ -256,10 +297,6 @@ def get_secret(config, config_path):
|
||||||
print(f"Error saving to config.json: {e}")
|
print(f"Error saving to config.json: {e}")
|
||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\nOperation cancelled.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Determine config path (XDG standard)
|
# Determine config path (XDG standard)
|
||||||
xdg_config_home = os.environ.get('XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))
|
xdg_config_home = os.environ.get('XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))
|
||||||
|
|
@ -274,11 +311,16 @@ def main():
|
||||||
config_path = local_config
|
config_path = local_config
|
||||||
|
|
||||||
if not os.path.exists(config_path):
|
if not os.path.exists(config_path):
|
||||||
print(f"Config file not found.")
|
print(f"Config file not found. Creating default empty configuration at {config_path}...")
|
||||||
print(f"Expected at: {config_path}")
|
os.makedirs(config_dir, exist_ok=True)
|
||||||
print(f"Please copy config.example.json to {config_path} and fill in your secret.")
|
config = {"launcher_cmd": ""}
|
||||||
|
try:
|
||||||
|
with open(config_path, 'w') as f:
|
||||||
|
json.dump(config, f, indent=4)
|
||||||
|
except OSError as e:
|
||||||
|
print(f"Error creating config file: {e}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
with open(config_path, 'r') as f:
|
with open(config_path, 'r') as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
|
|
@ -303,31 +345,16 @@ def main():
|
||||||
print("Please install XIVLauncher or manually set 'launcher_cmd' in config.json.")
|
print("Please install XIVLauncher or manually set 'launcher_cmd' in config.json.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
elif len(available) == 1:
|
elif len(available) >= 1:
|
||||||
name, command = available[0]
|
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}")
|
print(f"Detected {name}: {command}")
|
||||||
cmd = command
|
cmd = command
|
||||||
config['launcher_cmd'] = cmd
|
config['launcher_cmd'] = cmd
|
||||||
config_dirty = True
|
config_dirty = True
|
||||||
|
|
||||||
else:
|
|
||||||
print("Multiple XIVLauncher installations found:")
|
|
||||||
for i, (name, command) in enumerate(available):
|
|
||||||
print(f"{i+1}) {name} ({command})")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
selection = input("Select which one to launch (number): ").strip()
|
|
||||||
idx = int(selection) - 1
|
|
||||||
if 0 <= idx < len(available):
|
|
||||||
cmd = available[idx][1]
|
|
||||||
config['launcher_cmd'] = cmd
|
|
||||||
config_dirty = True
|
|
||||||
break
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
print("Invalid selection. Please enter a number from the list.")
|
|
||||||
|
|
||||||
# Save config if updated (e.g. launcher_cmd detected)
|
# Save config if updated (e.g. launcher_cmd detected)
|
||||||
if config_dirty:
|
if config_dirty:
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue