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:
|
||||
HAS_KEYRING = False
|
||||
import getpass
|
||||
import shlex
|
||||
|
||||
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):
|
||||
"""Clean and extract secret from input (handles raw secret or otpauth:// URL)."""
|
||||
if not secret_input:
|
||||
|
|
@ -221,6 +252,11 @@ def get_secret(config, config_path):
|
|||
else:
|
||||
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.")
|
||||
while True:
|
||||
try:
|
||||
|
|
@ -228,12 +264,17 @@ def get_secret(config, config_path):
|
|||
if not secret_input:
|
||||
print("Secret cannot be empty.")
|
||||
continue
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
print("\nOperation cancelled.")
|
||||
sys.exit(1)
|
||||
|
||||
# Basic validation/parsing check
|
||||
parsed = parse_secret(secret_input)
|
||||
if not parsed:
|
||||
print("Invalid secret format. Please try again.")
|
||||
continue
|
||||
while not parsed:
|
||||
print("Invalid secret format.")
|
||||
secret_input = getpass.getpass("Secret: ").strip()
|
||||
parsed = parse_secret(secret_input)
|
||||
|
||||
if HAS_KEYRING:
|
||||
try:
|
||||
|
|
@ -256,10 +297,6 @@ def get_secret(config, config_path):
|
|||
print(f"Error saving to config.json: {e}")
|
||||
return parsed
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\nOperation cancelled.")
|
||||
sys.exit(1)
|
||||
|
||||
def main():
|
||||
# Determine config path (XDG standard)
|
||||
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
|
||||
|
||||
if not os.path.exists(config_path):
|
||||
print(f"Config file not found.")
|
||||
print(f"Expected at: {config_path}")
|
||||
print(f"Please copy config.example.json to {config_path} and fill in your secret.")
|
||||
print(f"Config file not found. Creating default empty configuration at {config_path}...")
|
||||
os.makedirs(config_dir, exist_ok=True)
|
||||
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)
|
||||
|
||||
else:
|
||||
try:
|
||||
with open(config_path, 'r') as f:
|
||||
config = json.load(f)
|
||||
|
|
@ -303,31 +345,16 @@ def main():
|
|||
print("Please install XIVLauncher or manually set 'launcher_cmd' in config.json.")
|
||||
sys.exit(1)
|
||||
|
||||
elif len(available) == 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
|
||||
|
||||
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)
|
||||
if config_dirty:
|
||||
try:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue