mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 07:18:47 +00:00
docs: clarify manual clipboard dependencies
This commit is contained in:
@@ -205,12 +205,14 @@ After reinstalling, run `which seedpass` on Linux/macOS or `where seedpass` on W
|
|||||||
|
|
||||||
#### Linux Clipboard Support
|
#### Linux Clipboard Support
|
||||||
|
|
||||||
On Linux, `pyperclip` relies on external utilities like `xclip` or `xsel`. SeedPass will attempt to install **xclip** automatically if neither tool is available. If the automatic installation fails, you can install it manually:
|
On Linux, `pyperclip` relies on external utilities like `xclip` or `xsel`. SeedPass no longer installs these tools automatically. To enable clipboard features such as secret mode, install **xclip** manually:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install xclip
|
sudo apt install xclip
|
||||||
```
|
```
|
||||||
|
|
||||||
|
After installing `xclip`, restart SeedPass to enable clipboard support.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
After installing dependencies and activating your virtual environment, install the package in editable mode so the `seedpass` command is available:
|
After installing dependencies and activating your virtual environment, install the package in editable mode so the `seedpass` command is available:
|
||||||
|
@@ -192,13 +192,15 @@ python -m pip install -e .
|
|||||||
#### Linux Clipboard Support
|
#### Linux Clipboard Support
|
||||||
|
|
||||||
On Linux, `pyperclip` relies on external utilities like `xclip` or `xsel`.
|
On Linux, `pyperclip` relies on external utilities like `xclip` or `xsel`.
|
||||||
SeedPass will attempt to install **xclip** automatically if neither tool is
|
SeedPass does not install these tools automatically. To use clipboard features
|
||||||
available. If the automatic installation fails, you can install it manually:
|
such as secret mode, install **xclip** manually:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install xclip
|
sudo apt install xclip
|
||||||
```
|
```
|
||||||
|
|
||||||
|
After installing `xclip`, restart SeedPass to enable clipboard support.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
After installing dependencies, activate your virtual environment and install
|
After installing dependencies, activate your virtual environment and install
|
||||||
|
@@ -26,26 +26,27 @@ print_error() { echo -e "\033[1;31m[ERROR]\033[0m $1" >&2; exit 1; }
|
|||||||
install_dependencies() {
|
install_dependencies() {
|
||||||
print_info "Installing system packages required for Gtk bindings..."
|
print_info "Installing system packages required for Gtk bindings..."
|
||||||
if command -v apt-get &>/dev/null; then
|
if command -v apt-get &>/dev/null; then
|
||||||
sudo apt-get update && sudo apt-get install -y \
|
sudo apt-get update && sudo apt-get install -y \\
|
||||||
build-essential pkg-config libcairo2 libcairo2-dev \
|
build-essential pkg-config libcairo2 libcairo2-dev \\
|
||||||
libgirepository1.0-dev gobject-introspection \
|
libgirepository1.0-dev gobject-introspection \\
|
||||||
gir1.2-gtk-3.0 python3-dev libffi-dev libssl-dev xclip
|
gir1.2-gtk-3.0 python3-dev libffi-dev libssl-dev
|
||||||
elif command -v yum &>/dev/null; then
|
elif command -v yum &>/dev/null; then
|
||||||
sudo yum install -y @'Development Tools' cairo cairo-devel \
|
sudo yum install -y @'Development Tools' cairo cairo-devel \\
|
||||||
gobject-introspection-devel gtk3-devel python3-devel \
|
gobject-introspection-devel gtk3-devel python3-devel \\
|
||||||
libffi-devel openssl-devel xclip
|
libffi-devel openssl-devel
|
||||||
elif command -v dnf &>/dev/null; then
|
elif command -v dnf &>/dev/null; then
|
||||||
sudo dnf groupinstall -y "Development Tools" && sudo dnf install -y \
|
sudo dnf groupinstall -y "Development Tools" && sudo dnf install -y \\
|
||||||
cairo cairo-devel gobject-introspection-devel gtk3-devel \
|
cairo cairo-devel gobject-introspection-devel gtk3-devel \\
|
||||||
python3-devel libffi-devel openssl-devel xclip
|
python3-devel libffi-devel openssl-devel
|
||||||
elif command -v pacman &>/dev/null; then
|
elif command -v pacman &>/dev/null; then
|
||||||
sudo pacman -Syu --noconfirm base-devel pkgconf cairo \
|
sudo pacman -Syu --noconfirm base-devel pkgconf cairo \\
|
||||||
gobject-introspection gtk3 python xclip
|
gobject-introspection gtk3 python
|
||||||
elif command -v brew &>/dev/null; then
|
elif command -v brew &>/dev/null; then
|
||||||
brew install pkg-config cairo gobject-introspection gtk+3
|
brew install pkg-config cairo gobject-introspection gtk+3
|
||||||
else
|
else
|
||||||
print_warning "Unsupported package manager. Please install Gtk/GObject dependencies manually."
|
print_warning "Unsupported package manager. Please install Gtk/GObject dependencies manually."
|
||||||
fi
|
fi
|
||||||
|
print_warning "Install 'xclip' manually to enable clipboard features in secret mode."
|
||||||
}
|
}
|
||||||
usage() {
|
usage() {
|
||||||
echo "Usage: $0 [-b | --branch <branch_name>] [-h | --help]"
|
echo "Usage: $0 [-b | --branch <branch_name>] [-h | --help]"
|
||||||
|
@@ -1251,11 +1251,8 @@ def main(argv: list[str] | None = None, *, fingerprint: str | None = None) -> in
|
|||||||
idx, password_manager.parent_seed
|
idx, password_manager.parent_seed
|
||||||
)
|
)
|
||||||
print(code)
|
print(code)
|
||||||
try:
|
if copy_to_clipboard(code, password_manager.clipboard_clear_delay):
|
||||||
copy_to_clipboard(code, password_manager.clipboard_clear_delay)
|
|
||||||
print(colored("Code copied to clipboard", "green"))
|
print(colored("Code copied to clipboard", "green"))
|
||||||
except Exception as exc:
|
|
||||||
logging.warning(f"Clipboard copy failed: {exc}")
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def signal_handler(sig, _frame):
|
def signal_handler(sig, _frame):
|
||||||
|
@@ -1504,13 +1504,13 @@ class PasswordManager:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(password, self.clipboard_clear_delay)
|
if copy_to_clipboard(password, self.clipboard_clear_delay):
|
||||||
print(
|
print(
|
||||||
colored(
|
colored(
|
||||||
f"[+] Password copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
f"[+] Password copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||||
"green",
|
"green",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print(colored(f"Password for {label}: {password}\n", "yellow"))
|
print(colored(f"Password for {label}: {password}\n", "yellow"))
|
||||||
|
|
||||||
@@ -2017,13 +2017,13 @@ class PasswordManager:
|
|||||||
print(colored(f"\n[+] Nostr key entry added with ID {index}.\n", "green"))
|
print(colored(f"\n[+] Nostr key entry added with ID {index}.\n", "green"))
|
||||||
print(colored(f"npub: {npub}", "cyan"))
|
print(colored(f"npub: {npub}", "cyan"))
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(nsec, self.clipboard_clear_delay)
|
if copy_to_clipboard(nsec, self.clipboard_clear_delay):
|
||||||
print(
|
print(
|
||||||
colored(
|
colored(
|
||||||
f"[+] nsec copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
f"[+] nsec copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||||
"green",
|
"green",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print(color_text(f"nsec: {nsec}", "deterministic"))
|
print(color_text(f"nsec: {nsec}", "deterministic"))
|
||||||
if confirm_action("Show QR code for npub? (Y/N): "):
|
if confirm_action("Show QR code for npub? (Y/N): "):
|
||||||
@@ -2104,13 +2104,13 @@ class PasswordManager:
|
|||||||
if notes:
|
if notes:
|
||||||
print(colored(f"Notes: {notes}", "cyan"))
|
print(colored(f"Notes: {notes}", "cyan"))
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(value, self.clipboard_clear_delay)
|
if copy_to_clipboard(value, self.clipboard_clear_delay):
|
||||||
print(
|
print(
|
||||||
colored(
|
colored(
|
||||||
f"[+] Value copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
f"[+] Value copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||||
"green",
|
"green",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print(color_text(f"Value: {value}", "deterministic"))
|
print(color_text(f"Value: {value}", "deterministic"))
|
||||||
try:
|
try:
|
||||||
@@ -2162,13 +2162,13 @@ class PasswordManager:
|
|||||||
)
|
)
|
||||||
if confirm_action("Reveal seed now? (y/N): "):
|
if confirm_action("Reveal seed now? (y/N): "):
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(seed, self.clipboard_clear_delay)
|
if copy_to_clipboard(seed, self.clipboard_clear_delay):
|
||||||
print(
|
print(
|
||||||
colored(
|
colored(
|
||||||
f"[+] Seed copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
f"[+] Seed copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||||
"green",
|
"green",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print(color_text(seed, "deterministic"))
|
print(color_text(seed, "deterministic"))
|
||||||
if confirm_action("Show Compact Seed QR? (Y/N): "):
|
if confirm_action("Show Compact Seed QR? (Y/N): "):
|
||||||
@@ -2521,13 +2521,13 @@ class PasswordManager:
|
|||||||
while True:
|
while True:
|
||||||
code = self.entry_manager.get_totp_code(index, self.parent_seed)
|
code = self.entry_manager.get_totp_code(index, self.parent_seed)
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(code, self.clipboard_clear_delay)
|
if copy_to_clipboard(code, self.clipboard_clear_delay):
|
||||||
print(
|
print(
|
||||||
colored(
|
colored(
|
||||||
f"[+] 2FA code for '{label}' copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
f"[+] 2FA code for '{label}' copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||||
"green",
|
"green",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print(colored("\n[+] Retrieved 2FA Code:\n", "green"))
|
print(colored("\n[+] Retrieved 2FA Code:\n", "green"))
|
||||||
print(colored(f"Label: {label}", "cyan"))
|
print(colored(f"Label: {label}", "cyan"))
|
||||||
@@ -2593,13 +2593,13 @@ class PasswordManager:
|
|||||||
print(colored("Public Key:", "cyan"))
|
print(colored("Public Key:", "cyan"))
|
||||||
print(color_text(pub_pem, "default"))
|
print(color_text(pub_pem, "default"))
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(priv_pem, self.clipboard_clear_delay)
|
if copy_to_clipboard(priv_pem, self.clipboard_clear_delay):
|
||||||
print(
|
print(
|
||||||
colored(
|
colored(
|
||||||
f"[+] SSH private key copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
f"[+] SSH private key copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||||
"green",
|
"green",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print(colored("Private Key:", "cyan"))
|
print(colored("Private Key:", "cyan"))
|
||||||
print(color_text(priv_pem, "deterministic"))
|
print(color_text(priv_pem, "deterministic"))
|
||||||
@@ -2628,13 +2628,13 @@ class PasswordManager:
|
|||||||
if tags:
|
if tags:
|
||||||
print(colored(f"Tags: {', '.join(tags)}", "cyan"))
|
print(colored(f"Tags: {', '.join(tags)}", "cyan"))
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(phrase, self.clipboard_clear_delay)
|
if copy_to_clipboard(phrase, self.clipboard_clear_delay):
|
||||||
print(
|
print(
|
||||||
colored(
|
colored(
|
||||||
f"[+] Seed phrase copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
f"[+] Seed phrase copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||||
"green",
|
"green",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print(color_text(phrase, "deterministic"))
|
print(color_text(phrase, "deterministic"))
|
||||||
if confirm_action("Show derived entropy as hex? (Y/N): "):
|
if confirm_action("Show derived entropy as hex? (Y/N): "):
|
||||||
@@ -2679,13 +2679,13 @@ class PasswordManager:
|
|||||||
print(colored(f"Tags: {', '.join(tags)}", "cyan"))
|
print(colored(f"Tags: {', '.join(tags)}", "cyan"))
|
||||||
print(colored(f"Fingerprint: {fingerprint}", "cyan"))
|
print(colored(f"Fingerprint: {fingerprint}", "cyan"))
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(priv_key, self.clipboard_clear_delay)
|
if copy_to_clipboard(priv_key, self.clipboard_clear_delay):
|
||||||
print(
|
print(
|
||||||
colored(
|
colored(
|
||||||
f"[+] PGP key copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
f"[+] PGP key copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||||
"green",
|
"green",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print(color_text(priv_key, "deterministic"))
|
print(color_text(priv_key, "deterministic"))
|
||||||
except Exception as e: # pragma: no cover - best effort
|
except Exception as e: # pragma: no cover - best effort
|
||||||
@@ -2704,13 +2704,13 @@ class PasswordManager:
|
|||||||
print(colored(f"Label: {label}", "cyan"))
|
print(colored(f"Label: {label}", "cyan"))
|
||||||
print(colored(f"npub: {npub}", "cyan"))
|
print(colored(f"npub: {npub}", "cyan"))
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(nsec, self.clipboard_clear_delay)
|
if copy_to_clipboard(nsec, self.clipboard_clear_delay):
|
||||||
print(
|
print(
|
||||||
colored(
|
colored(
|
||||||
f"[+] nsec copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
f"[+] nsec copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||||
"green",
|
"green",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print(color_text(f"nsec: {nsec}", "deterministic"))
|
print(color_text(f"nsec: {nsec}", "deterministic"))
|
||||||
if notes:
|
if notes:
|
||||||
@@ -2740,13 +2740,13 @@ class PasswordManager:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(value, self.clipboard_clear_delay)
|
if copy_to_clipboard(value, self.clipboard_clear_delay):
|
||||||
print(
|
print(
|
||||||
colored(
|
colored(
|
||||||
f"[+] Value copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
f"[+] Value copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||||
"green",
|
"green",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print(color_text(f"Value: {value}", "deterministic"))
|
print(color_text(f"Value: {value}", "deterministic"))
|
||||||
|
|
||||||
@@ -2767,13 +2767,15 @@ class PasswordManager:
|
|||||||
if show == "y":
|
if show == "y":
|
||||||
for f_label, f_value in hidden_fields:
|
for f_label, f_value in hidden_fields:
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(f_value, self.clipboard_clear_delay)
|
if copy_to_clipboard(
|
||||||
print(
|
f_value, self.clipboard_clear_delay
|
||||||
colored(
|
):
|
||||||
f"[+] {f_label} copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
print(
|
||||||
"green",
|
colored(
|
||||||
|
f"[+] {f_label} copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||||
|
"green",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print(colored(f" {f_label}: {f_value}", "cyan"))
|
print(colored(f" {f_label}: {f_value}", "cyan"))
|
||||||
return
|
return
|
||||||
@@ -2808,13 +2810,13 @@ class PasswordManager:
|
|||||||
index, self.parent_seed
|
index, self.parent_seed
|
||||||
)
|
)
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(seed, self.clipboard_clear_delay)
|
if copy_to_clipboard(seed, self.clipboard_clear_delay):
|
||||||
print(
|
print(
|
||||||
colored(
|
colored(
|
||||||
f"[+] Seed phrase copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
f"[+] Seed phrase copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||||
"green",
|
"green",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print(color_text(seed, "deterministic"))
|
print(color_text(seed, "deterministic"))
|
||||||
return
|
return
|
||||||
@@ -2852,13 +2854,13 @@ class PasswordManager:
|
|||||||
|
|
||||||
if password:
|
if password:
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(password, self.clipboard_clear_delay)
|
if copy_to_clipboard(password, self.clipboard_clear_delay):
|
||||||
print(
|
print(
|
||||||
colored(
|
colored(
|
||||||
f"[+] Password for '{website_name}' copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
f"[+] Password for '{website_name}' copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||||
"green",
|
"green",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
colored(
|
colored(
|
||||||
@@ -2897,13 +2899,15 @@ class PasswordManager:
|
|||||||
if show == "y":
|
if show == "y":
|
||||||
for label, value in hidden_fields:
|
for label, value in hidden_fields:
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(value, self.clipboard_clear_delay)
|
if copy_to_clipboard(
|
||||||
print(
|
value, self.clipboard_clear_delay
|
||||||
colored(
|
):
|
||||||
f"[+] {label} copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
print(
|
||||||
"green",
|
colored(
|
||||||
|
f"[+] {label} copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||||
|
"green",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print(colored(f" {label}: {value}", "cyan"))
|
print(colored(f" {label}: {value}", "cyan"))
|
||||||
else:
|
else:
|
||||||
@@ -3847,10 +3851,10 @@ class PasswordManager:
|
|||||||
filled = int(20 * (period - remaining) / period)
|
filled = int(20 * (period - remaining) / period)
|
||||||
bar = "[" + "#" * filled + "-" * (20 - filled) + "]"
|
bar = "[" + "#" * filled + "-" * (20 - filled) + "]"
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(code, self.clipboard_clear_delay)
|
if copy_to_clipboard(code, self.clipboard_clear_delay):
|
||||||
print(
|
print(
|
||||||
f"[{idx}] {label}: [HIDDEN] {bar} {remaining:2d}s - copied to clipboard"
|
f"[{idx}] {label}: [HIDDEN] {bar} {remaining:2d}s - copied to clipboard"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
f"[{idx}] {label}: {color_text(code, 'deterministic')} {bar} {remaining:2d}s"
|
f"[{idx}] {label}: {color_text(code, 'deterministic')} {bar} {remaining:2d}s"
|
||||||
@@ -3863,10 +3867,10 @@ class PasswordManager:
|
|||||||
filled = int(20 * (period - remaining) / period)
|
filled = int(20 * (period - remaining) / period)
|
||||||
bar = "[" + "#" * filled + "-" * (20 - filled) + "]"
|
bar = "[" + "#" * filled + "-" * (20 - filled) + "]"
|
||||||
if self.secret_mode_enabled:
|
if self.secret_mode_enabled:
|
||||||
copy_to_clipboard(code, self.clipboard_clear_delay)
|
if copy_to_clipboard(code, self.clipboard_clear_delay):
|
||||||
print(
|
print(
|
||||||
f"[{idx}] {label}: [HIDDEN] {bar} {remaining:2d}s - copied to clipboard"
|
f"[{idx}] {label}: [HIDDEN] {bar} {remaining:2d}s - copied to clipboard"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
f"[{idx}] {label}: {color_text(code, 'imported')} {bar} {remaining:2d}s"
|
f"[{idx}] {label}: {color_text(code, 'imported')} {bar} {remaining:2d}s"
|
||||||
|
@@ -60,7 +60,7 @@ def test_totp_command(monkeypatch, capsys):
|
|||||||
monkeypatch.setattr(main, "initialize_app", lambda: None)
|
monkeypatch.setattr(main, "initialize_app", lambda: None)
|
||||||
monkeypatch.setattr(main.signal, "signal", lambda *a, **k: None)
|
monkeypatch.setattr(main.signal, "signal", lambda *a, **k: None)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
main, "copy_to_clipboard", lambda v, d: called.setdefault("val", v)
|
main, "copy_to_clipboard", lambda v, d: (called.setdefault("val", v), True)[1]
|
||||||
)
|
)
|
||||||
rc = main.main(["totp", "ex"])
|
rc = main.main(["totp", "ex"])
|
||||||
assert rc == 0
|
assert rc == 0
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import pyperclip
|
import pyperclip
|
||||||
import threading
|
import threading
|
||||||
|
import shutil
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -66,3 +67,16 @@ def test_copy_to_clipboard_does_not_clear_if_changed(monkeypatch):
|
|||||||
fake_copy("other")
|
fake_copy("other")
|
||||||
callbacks["func"]()
|
callbacks["func"]()
|
||||||
assert clipboard["text"] == "other"
|
assert clipboard["text"] == "other"
|
||||||
|
|
||||||
|
|
||||||
|
def test_copy_to_clipboard_missing_dependency(monkeypatch, capsys):
|
||||||
|
def fail_copy(*args, **kwargs):
|
||||||
|
raise pyperclip.PyperclipException("no copy")
|
||||||
|
|
||||||
|
monkeypatch.setattr(pyperclip, "copy", fail_copy)
|
||||||
|
monkeypatch.setattr(pyperclip, "paste", lambda: "")
|
||||||
|
monkeypatch.setattr(shutil, "which", lambda cmd: None)
|
||||||
|
|
||||||
|
copy_to_clipboard("secret", 1)
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "install xclip" in out.lower()
|
||||||
|
@@ -140,7 +140,7 @@ def test_handle_add_password_secret_mode(monkeypatch, dummy_nostr_client, capsys
|
|||||||
called = []
|
called = []
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"seedpass.core.manager.copy_to_clipboard",
|
"seedpass.core.manager.copy_to_clipboard",
|
||||||
lambda text, delay: called.append((text, delay)),
|
lambda text, delay: (called.append((text, delay)), True)[1],
|
||||||
)
|
)
|
||||||
|
|
||||||
pm.handle_add_password()
|
pm.handle_add_password()
|
||||||
|
@@ -301,7 +301,7 @@ def test_show_entry_details_sensitive(monkeypatch, capsys, entry_type):
|
|||||||
"seedpass.core.manager.confirm_action", lambda *a, **k: True
|
"seedpass.core.manager.confirm_action", lambda *a, **k: True
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"seedpass.core.manager.copy_to_clipboard", lambda *a, **k: None
|
"seedpass.core.manager.copy_to_clipboard", lambda *a, **k: True
|
||||||
)
|
)
|
||||||
monkeypatch.setattr("seedpass.core.manager.timed_input", lambda *a, **k: "b")
|
monkeypatch.setattr("seedpass.core.manager.timed_input", lambda *a, **k: "b")
|
||||||
monkeypatch.setattr("seedpass.core.manager.time.sleep", lambda *a, **k: None)
|
monkeypatch.setattr("seedpass.core.manager.time.sleep", lambda *a, **k: None)
|
||||||
|
@@ -46,7 +46,7 @@ def test_password_retrieve_secret_mode(monkeypatch, capsys):
|
|||||||
called = []
|
called = []
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"seedpass.core.manager.copy_to_clipboard",
|
"seedpass.core.manager.copy_to_clipboard",
|
||||||
lambda text, t: called.append((text, t)),
|
lambda text, t: (called.append((text, t)), True)[1],
|
||||||
)
|
)
|
||||||
|
|
||||||
pm.handle_retrieve_entry()
|
pm.handle_retrieve_entry()
|
||||||
@@ -73,7 +73,7 @@ def test_totp_display_secret_mode(monkeypatch, capsys):
|
|||||||
called = []
|
called = []
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"seedpass.core.manager.copy_to_clipboard",
|
"seedpass.core.manager.copy_to_clipboard",
|
||||||
lambda text, t: called.append((text, t)),
|
lambda text, t: (called.append((text, t)), True)[1],
|
||||||
)
|
)
|
||||||
|
|
||||||
pm.handle_display_totp_codes()
|
pm.handle_display_totp_codes()
|
||||||
@@ -95,7 +95,7 @@ def test_password_retrieve_no_secret_mode(monkeypatch, capsys):
|
|||||||
called = []
|
called = []
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"seedpass.core.manager.copy_to_clipboard",
|
"seedpass.core.manager.copy_to_clipboard",
|
||||||
lambda *a, **k: called.append((a, k)),
|
lambda *a, **k: (called.append((a, k)), True)[1],
|
||||||
)
|
)
|
||||||
|
|
||||||
pm.handle_retrieve_entry()
|
pm.handle_retrieve_entry()
|
||||||
@@ -123,7 +123,7 @@ def test_totp_display_no_secret_mode(monkeypatch, capsys):
|
|||||||
called = []
|
called = []
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"seedpass.core.manager.copy_to_clipboard",
|
"seedpass.core.manager.copy_to_clipboard",
|
||||||
lambda *a, **k: called.append((a, k)),
|
lambda *a, **k: (called.append((a, k)), True)[1],
|
||||||
)
|
)
|
||||||
|
|
||||||
pm.handle_display_totp_codes()
|
pm.handle_display_totp_codes()
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
import threading
|
|
||||||
import logging
|
import logging
|
||||||
import subprocess
|
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
|
|
||||||
import pyperclip
|
import pyperclip
|
||||||
|
|
||||||
@@ -10,31 +9,38 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def _ensure_clipboard() -> None:
|
def _ensure_clipboard() -> None:
|
||||||
"""Attempt to ensure a clipboard mechanism is available."""
|
"""Ensure a clipboard mechanism is available or raise an informative error."""
|
||||||
try:
|
try:
|
||||||
pyperclip.copy("")
|
pyperclip.copy("")
|
||||||
except pyperclip.PyperclipException as exc:
|
except pyperclip.PyperclipException as exc:
|
||||||
if sys.platform.startswith("linux"):
|
if sys.platform.startswith("linux"):
|
||||||
if shutil.which("xclip") is None and shutil.which("xsel") is None:
|
if shutil.which("xclip") is None and shutil.which("xsel") is None:
|
||||||
apt = shutil.which("apt-get") or shutil.which("apt")
|
raise pyperclip.PyperclipException(
|
||||||
if apt:
|
"Clipboard support requires the 'xclip' package. "
|
||||||
try:
|
"Install it with 'sudo apt install xclip' and restart SeedPass."
|
||||||
subprocess.run(
|
) from exc
|
||||||
["sudo", apt, "install", "-y", "xclip"], check=True
|
raise
|
||||||
)
|
|
||||||
pyperclip.copy("")
|
|
||||||
return
|
|
||||||
except Exception as install_exc: # pragma: no cover - system dep
|
|
||||||
logger.warning(
|
|
||||||
"Automatic xclip installation failed: %s", install_exc
|
|
||||||
)
|
|
||||||
raise exc
|
|
||||||
|
|
||||||
|
|
||||||
def copy_to_clipboard(text: str, timeout: int) -> None:
|
def copy_to_clipboard(text: str, timeout: int) -> bool:
|
||||||
"""Copy text to the clipboard and clear after timeout seconds if unchanged."""
|
"""Copy text to the clipboard and clear after timeout seconds if unchanged.
|
||||||
|
|
||||||
|
Returns True if the text was successfully copied, False otherwise.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
_ensure_clipboard()
|
||||||
|
except pyperclip.PyperclipException as exc:
|
||||||
|
warning = (
|
||||||
|
"Clipboard unavailable: "
|
||||||
|
+ str(exc)
|
||||||
|
+ "\nSeedPass secret mode requires clipboard support. "
|
||||||
|
"Install xclip or disable secret mode to view secrets."
|
||||||
|
)
|
||||||
|
logger.warning(warning)
|
||||||
|
print(warning)
|
||||||
|
return False
|
||||||
|
|
||||||
_ensure_clipboard()
|
|
||||||
pyperclip.copy(text)
|
pyperclip.copy(text)
|
||||||
|
|
||||||
def clear_clipboard() -> None:
|
def clear_clipboard() -> None:
|
||||||
@@ -44,3 +50,4 @@ def copy_to_clipboard(text: str, timeout: int) -> None:
|
|||||||
timer = threading.Timer(timeout, clear_clipboard)
|
timer = threading.Timer(timeout, clear_clipboard)
|
||||||
timer.daemon = True
|
timer.daemon = True
|
||||||
timer.start()
|
timer.start()
|
||||||
|
return True
|
||||||
|
Reference in New Issue
Block a user