docs: clarify manual clipboard dependencies

This commit is contained in:
thePR0M3TH3AN
2025-08-03 08:12:25 -04:00
parent 36061493ac
commit ccca399b09
11 changed files with 166 additions and 139 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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]"

View File

@@ -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):

View File

@@ -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"

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -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)

View File

@@ -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()

View File

@@ -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