|
|
@@ -0,0 +1,79 @@
|
|
|
+"""在没有终端的环境里,用操作系统的密码对话框读取密码。
|
|
|
+
|
|
|
+为什么需要它:Claude Desktop、IDE 集成、agent 代跑等场景都没有 TTY,`getpass`
|
|
|
+用不了。而让用户把密码打进对话、或放进命令行参数,都会让密码进入会话上下文或
|
|
|
+`ps` / shell history——那是真正的泄漏。
|
|
|
+
|
|
|
+系统对话框解决了这个矛盾:**密码由用户直接输给操作系统,不经过终端、不经过 argv、
|
|
|
+调用方(包括 agent)全程看不到**,只从子进程的 stdout 拿回来,留在本进程内存里。
|
|
|
+
|
|
|
+支持 zenity / kdialog(Linux)、osascript(macOS)、PowerShell(Windows)。
|
|
|
+没有图形界面时返回 None,由调用方给出可操作的提示。
|
|
|
+"""
|
|
|
+
|
|
|
+import os
|
|
|
+import shutil
|
|
|
+import subprocess
|
|
|
+import sys
|
|
|
+
|
|
|
+DIALOG_TIMEOUT = 180
|
|
|
+
|
|
|
+
|
|
|
+def gui_available():
|
|
|
+ """当前环境有没有可用的图形界面。"""
|
|
|
+ if sys.platform == 'darwin':
|
|
|
+ return shutil.which('osascript') is not None
|
|
|
+ if os.name == 'nt':
|
|
|
+ return True
|
|
|
+ if not (os.environ.get('DISPLAY') or os.environ.get('WAYLAND_DISPLAY')):
|
|
|
+ return False
|
|
|
+ return shutil.which('zenity') is not None or shutil.which('kdialog') is not None
|
|
|
+
|
|
|
+
|
|
|
+def _run(cmd):
|
|
|
+ """跑对话框命令,返回用户输入;取消或失败返回 None。
|
|
|
+
|
|
|
+ stdout 只在本函数内流转,绝不打印——它装着密码。
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ proc = subprocess.run(cmd, capture_output=True, text=True, timeout=DIALOG_TIMEOUT)
|
|
|
+ except (OSError, subprocess.TimeoutExpired):
|
|
|
+ return None
|
|
|
+ if proc.returncode != 0:
|
|
|
+ return None
|
|
|
+ return proc.stdout.rstrip('\n')
|
|
|
+
|
|
|
+
|
|
|
+def ask(prompt, title='WikiPali', secret=True):
|
|
|
+ """弹一个对话框问用户要一行输入。secret=True 时输入被遮蔽。
|
|
|
+
|
|
|
+ 返回字符串;用户取消、超时或没有图形界面时返回 None。
|
|
|
+ """
|
|
|
+ if sys.platform == 'darwin':
|
|
|
+ hidden = ' with hidden answer' if secret else ''
|
|
|
+ script = (f'display dialog "{prompt}" with title "{title}" '
|
|
|
+ f'default answer ""{hidden}')
|
|
|
+ out = _run(['osascript', '-e', script, '-e',
|
|
|
+ 'text returned of result'])
|
|
|
+ return out
|
|
|
+
|
|
|
+ if os.name == 'nt':
|
|
|
+ if secret:
|
|
|
+ ps = (f'$s = Read-Host -AsSecureString "{prompt}"; '
|
|
|
+ '[Runtime.InteropServices.Marshal]::PtrToStringAuto('
|
|
|
+ '[Runtime.InteropServices.Marshal]::SecureStringToBSTR($s))')
|
|
|
+ else:
|
|
|
+ ps = f'Read-Host "{prompt}"'
|
|
|
+ return _run(['powershell', '-NoProfile', '-Command', ps])
|
|
|
+
|
|
|
+ if shutil.which('zenity'):
|
|
|
+ if secret:
|
|
|
+ # --password 只给密码框;要用户名时用 --entry
|
|
|
+ return _run(['zenity', '--password', '--title', f'{title} — {prompt}'])
|
|
|
+ return _run(['zenity', '--entry', '--title', title, '--text', prompt])
|
|
|
+
|
|
|
+ if shutil.which('kdialog'):
|
|
|
+ flag = '--password' if secret else '--inputbox'
|
|
|
+ return _run(['kdialog', '--title', title, flag, prompt])
|
|
|
+
|
|
|
+ return None
|