mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-07 14:58:56 +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
|
||||
|
||||
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
|
||||
sudo apt-get install xclip
|
||||
sudo apt install xclip
|
||||
```
|
||||
|
||||
After installing `xclip`, restart SeedPass to enable clipboard support.
|
||||
|
||||
## Quick Start
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
SeedPass does not install these tools automatically. To use clipboard features
|
||||
such as secret mode, install **xclip** manually:
|
||||
|
||||
```bash
|
||||
sudo apt-get install xclip
|
||||
sudo apt install xclip
|
||||
```
|
||||
|
||||
After installing `xclip`, restart SeedPass to enable clipboard support.
|
||||
|
||||
## Quick Start
|
||||
|
||||
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() {
|
||||
print_info "Installing system packages required for Gtk bindings..."
|
||||
if command -v apt-get &>/dev/null; then
|
||||
sudo apt-get update && sudo apt-get install -y \
|
||||
build-essential pkg-config libcairo2 libcairo2-dev \
|
||||
libgirepository1.0-dev gobject-introspection \
|
||||
gir1.2-gtk-3.0 python3-dev libffi-dev libssl-dev xclip
|
||||
sudo apt-get update && sudo apt-get install -y \\
|
||||
build-essential pkg-config libcairo2 libcairo2-dev \\
|
||||
libgirepository1.0-dev gobject-introspection \\
|
||||
gir1.2-gtk-3.0 python3-dev libffi-dev libssl-dev
|
||||
elif command -v yum &>/dev/null; then
|
||||
sudo yum install -y @'Development Tools' cairo cairo-devel \
|
||||
gobject-introspection-devel gtk3-devel python3-devel \
|
||||
libffi-devel openssl-devel xclip
|
||||
sudo yum install -y @'Development Tools' cairo cairo-devel \\
|
||||
gobject-introspection-devel gtk3-devel python3-devel \\
|
||||
libffi-devel openssl-devel
|
||||
elif command -v dnf &>/dev/null; then
|
||||
sudo dnf groupinstall -y "Development Tools" && sudo dnf install -y \
|
||||
cairo cairo-devel gobject-introspection-devel gtk3-devel \
|
||||
python3-devel libffi-devel openssl-devel xclip
|
||||
sudo dnf groupinstall -y "Development Tools" && sudo dnf install -y \\
|
||||
cairo cairo-devel gobject-introspection-devel gtk3-devel \\
|
||||
python3-devel libffi-devel openssl-devel
|
||||
elif command -v pacman &>/dev/null; then
|
||||
sudo pacman -Syu --noconfirm base-devel pkgconf cairo \
|
||||
gobject-introspection gtk3 python xclip
|
||||
sudo pacman -Syu --noconfirm base-devel pkgconf cairo \\
|
||||
gobject-introspection gtk3 python
|
||||
elif command -v brew &>/dev/null; then
|
||||
brew install pkg-config cairo gobject-introspection gtk+3
|
||||
else
|
||||
print_warning "Unsupported package manager. Please install Gtk/GObject dependencies manually."
|
||||
fi
|
||||
print_warning "Install 'xclip' manually to enable clipboard features in secret mode."
|
||||
}
|
||||
usage() {
|
||||
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
|
||||
)
|
||||
print(code)
|
||||
try:
|
||||
copy_to_clipboard(code, password_manager.clipboard_clear_delay)
|
||||
if copy_to_clipboard(code, password_manager.clipboard_clear_delay):
|
||||
print(colored("Code copied to clipboard", "green"))
|
||||
except Exception as exc:
|
||||
logging.warning(f"Clipboard copy failed: {exc}")
|
||||
return 0
|
||||
|
||||
def signal_handler(sig, _frame):
|
||||
|
@@ -1504,13 +1504,13 @@ class PasswordManager:
|
||||
)
|
||||
)
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(password, self.clipboard_clear_delay)
|
||||
print(
|
||||
colored(
|
||||
f"[+] Password copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
if copy_to_clipboard(password, self.clipboard_clear_delay):
|
||||
print(
|
||||
colored(
|
||||
f"[+] Password copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
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"npub: {npub}", "cyan"))
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(nsec, self.clipboard_clear_delay)
|
||||
print(
|
||||
colored(
|
||||
f"[+] nsec copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
if copy_to_clipboard(nsec, self.clipboard_clear_delay):
|
||||
print(
|
||||
colored(
|
||||
f"[+] nsec copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(color_text(f"nsec: {nsec}", "deterministic"))
|
||||
if confirm_action("Show QR code for npub? (Y/N): "):
|
||||
@@ -2104,13 +2104,13 @@ class PasswordManager:
|
||||
if notes:
|
||||
print(colored(f"Notes: {notes}", "cyan"))
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(value, self.clipboard_clear_delay)
|
||||
print(
|
||||
colored(
|
||||
f"[+] Value copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
if copy_to_clipboard(value, self.clipboard_clear_delay):
|
||||
print(
|
||||
colored(
|
||||
f"[+] Value copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(color_text(f"Value: {value}", "deterministic"))
|
||||
try:
|
||||
@@ -2162,13 +2162,13 @@ class PasswordManager:
|
||||
)
|
||||
if confirm_action("Reveal seed now? (y/N): "):
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(seed, self.clipboard_clear_delay)
|
||||
print(
|
||||
colored(
|
||||
f"[+] Seed copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
if copy_to_clipboard(seed, self.clipboard_clear_delay):
|
||||
print(
|
||||
colored(
|
||||
f"[+] Seed copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(color_text(seed, "deterministic"))
|
||||
if confirm_action("Show Compact Seed QR? (Y/N): "):
|
||||
@@ -2521,13 +2521,13 @@ class PasswordManager:
|
||||
while True:
|
||||
code = self.entry_manager.get_totp_code(index, self.parent_seed)
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(code, self.clipboard_clear_delay)
|
||||
print(
|
||||
colored(
|
||||
f"[+] 2FA code for '{label}' copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
if copy_to_clipboard(code, self.clipboard_clear_delay):
|
||||
print(
|
||||
colored(
|
||||
f"[+] 2FA code for '{label}' copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(colored("\n[+] Retrieved 2FA Code:\n", "green"))
|
||||
print(colored(f"Label: {label}", "cyan"))
|
||||
@@ -2593,13 +2593,13 @@ class PasswordManager:
|
||||
print(colored("Public Key:", "cyan"))
|
||||
print(color_text(pub_pem, "default"))
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(priv_pem, self.clipboard_clear_delay)
|
||||
print(
|
||||
colored(
|
||||
f"[+] SSH private key copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
if copy_to_clipboard(priv_pem, self.clipboard_clear_delay):
|
||||
print(
|
||||
colored(
|
||||
f"[+] SSH private key copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(colored("Private Key:", "cyan"))
|
||||
print(color_text(priv_pem, "deterministic"))
|
||||
@@ -2628,13 +2628,13 @@ class PasswordManager:
|
||||
if tags:
|
||||
print(colored(f"Tags: {', '.join(tags)}", "cyan"))
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(phrase, self.clipboard_clear_delay)
|
||||
print(
|
||||
colored(
|
||||
f"[+] Seed phrase copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
if copy_to_clipboard(phrase, self.clipboard_clear_delay):
|
||||
print(
|
||||
colored(
|
||||
f"[+] Seed phrase copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(color_text(phrase, "deterministic"))
|
||||
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"Fingerprint: {fingerprint}", "cyan"))
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(priv_key, self.clipboard_clear_delay)
|
||||
print(
|
||||
colored(
|
||||
f"[+] PGP key copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
if copy_to_clipboard(priv_key, self.clipboard_clear_delay):
|
||||
print(
|
||||
colored(
|
||||
f"[+] PGP key copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(color_text(priv_key, "deterministic"))
|
||||
except Exception as e: # pragma: no cover - best effort
|
||||
@@ -2704,13 +2704,13 @@ class PasswordManager:
|
||||
print(colored(f"Label: {label}", "cyan"))
|
||||
print(colored(f"npub: {npub}", "cyan"))
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(nsec, self.clipboard_clear_delay)
|
||||
print(
|
||||
colored(
|
||||
f"[+] nsec copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
if copy_to_clipboard(nsec, self.clipboard_clear_delay):
|
||||
print(
|
||||
colored(
|
||||
f"[+] nsec copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(color_text(f"nsec: {nsec}", "deterministic"))
|
||||
if notes:
|
||||
@@ -2740,13 +2740,13 @@ class PasswordManager:
|
||||
)
|
||||
)
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(value, self.clipboard_clear_delay)
|
||||
print(
|
||||
colored(
|
||||
f"[+] Value copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
if copy_to_clipboard(value, self.clipboard_clear_delay):
|
||||
print(
|
||||
colored(
|
||||
f"[+] Value copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(color_text(f"Value: {value}", "deterministic"))
|
||||
|
||||
@@ -2767,13 +2767,15 @@ class PasswordManager:
|
||||
if show == "y":
|
||||
for f_label, f_value in hidden_fields:
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(f_value, self.clipboard_clear_delay)
|
||||
print(
|
||||
colored(
|
||||
f"[+] {f_label} copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
if copy_to_clipboard(
|
||||
f_value, self.clipboard_clear_delay
|
||||
):
|
||||
print(
|
||||
colored(
|
||||
f"[+] {f_label} copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(colored(f" {f_label}: {f_value}", "cyan"))
|
||||
return
|
||||
@@ -2808,13 +2810,13 @@ class PasswordManager:
|
||||
index, self.parent_seed
|
||||
)
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(seed, self.clipboard_clear_delay)
|
||||
print(
|
||||
colored(
|
||||
f"[+] Seed phrase copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
if copy_to_clipboard(seed, self.clipboard_clear_delay):
|
||||
print(
|
||||
colored(
|
||||
f"[+] Seed phrase copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(color_text(seed, "deterministic"))
|
||||
return
|
||||
@@ -2852,13 +2854,13 @@ class PasswordManager:
|
||||
|
||||
if password:
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(password, self.clipboard_clear_delay)
|
||||
print(
|
||||
colored(
|
||||
f"[+] Password for '{website_name}' copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
if copy_to_clipboard(password, self.clipboard_clear_delay):
|
||||
print(
|
||||
colored(
|
||||
f"[+] Password for '{website_name}' copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(
|
||||
colored(
|
||||
@@ -2897,13 +2899,15 @@ class PasswordManager:
|
||||
if show == "y":
|
||||
for label, value in hidden_fields:
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(value, self.clipboard_clear_delay)
|
||||
print(
|
||||
colored(
|
||||
f"[+] {label} copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
if copy_to_clipboard(
|
||||
value, self.clipboard_clear_delay
|
||||
):
|
||||
print(
|
||||
colored(
|
||||
f"[+] {label} copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.",
|
||||
"green",
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(colored(f" {label}: {value}", "cyan"))
|
||||
else:
|
||||
@@ -3847,10 +3851,10 @@ class PasswordManager:
|
||||
filled = int(20 * (period - remaining) / period)
|
||||
bar = "[" + "#" * filled + "-" * (20 - filled) + "]"
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(code, self.clipboard_clear_delay)
|
||||
print(
|
||||
f"[{idx}] {label}: [HIDDEN] {bar} {remaining:2d}s - copied to clipboard"
|
||||
)
|
||||
if copy_to_clipboard(code, self.clipboard_clear_delay):
|
||||
print(
|
||||
f"[{idx}] {label}: [HIDDEN] {bar} {remaining:2d}s - copied to clipboard"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"[{idx}] {label}: {color_text(code, 'deterministic')} {bar} {remaining:2d}s"
|
||||
@@ -3863,10 +3867,10 @@ class PasswordManager:
|
||||
filled = int(20 * (period - remaining) / period)
|
||||
bar = "[" + "#" * filled + "-" * (20 - filled) + "]"
|
||||
if self.secret_mode_enabled:
|
||||
copy_to_clipboard(code, self.clipboard_clear_delay)
|
||||
print(
|
||||
f"[{idx}] {label}: [HIDDEN] {bar} {remaining:2d}s - copied to clipboard"
|
||||
)
|
||||
if copy_to_clipboard(code, self.clipboard_clear_delay):
|
||||
print(
|
||||
f"[{idx}] {label}: [HIDDEN] {bar} {remaining:2d}s - copied to clipboard"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
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.signal, "signal", lambda *a, **k: None)
|
||||
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"])
|
||||
assert rc == 0
|
||||
|
@@ -1,6 +1,7 @@
|
||||
from pathlib import Path
|
||||
import pyperclip
|
||||
import threading
|
||||
import shutil
|
||||
|
||||
import sys
|
||||
|
||||
@@ -66,3 +67,16 @@ def test_copy_to_clipboard_does_not_clear_if_changed(monkeypatch):
|
||||
fake_copy("other")
|
||||
callbacks["func"]()
|
||||
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 = []
|
||||
monkeypatch.setattr(
|
||||
"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()
|
||||
|
@@ -301,7 +301,7 @@ def test_show_entry_details_sensitive(monkeypatch, capsys, entry_type):
|
||||
"seedpass.core.manager.confirm_action", lambda *a, **k: True
|
||||
)
|
||||
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.time.sleep", lambda *a, **k: None)
|
||||
|
@@ -46,7 +46,7 @@ def test_password_retrieve_secret_mode(monkeypatch, capsys):
|
||||
called = []
|
||||
monkeypatch.setattr(
|
||||
"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()
|
||||
@@ -73,7 +73,7 @@ def test_totp_display_secret_mode(monkeypatch, capsys):
|
||||
called = []
|
||||
monkeypatch.setattr(
|
||||
"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()
|
||||
@@ -95,7 +95,7 @@ def test_password_retrieve_no_secret_mode(monkeypatch, capsys):
|
||||
called = []
|
||||
monkeypatch.setattr(
|
||||
"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()
|
||||
@@ -123,7 +123,7 @@ def test_totp_display_no_secret_mode(monkeypatch, capsys):
|
||||
called = []
|
||||
monkeypatch.setattr(
|
||||
"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()
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import threading
|
||||
import logging
|
||||
import subprocess
|
||||
import shutil
|
||||
import sys
|
||||
import threading
|
||||
|
||||
import pyperclip
|
||||
|
||||
@@ -10,31 +9,38 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _ensure_clipboard() -> None:
|
||||
"""Attempt to ensure a clipboard mechanism is available."""
|
||||
"""Ensure a clipboard mechanism is available or raise an informative error."""
|
||||
try:
|
||||
pyperclip.copy("")
|
||||
except pyperclip.PyperclipException as exc:
|
||||
if sys.platform.startswith("linux"):
|
||||
if shutil.which("xclip") is None and shutil.which("xsel") is None:
|
||||
apt = shutil.which("apt-get") or shutil.which("apt")
|
||||
if apt:
|
||||
try:
|
||||
subprocess.run(
|
||||
["sudo", apt, "install", "-y", "xclip"], check=True
|
||||
)
|
||||
pyperclip.copy("")
|
||||
return
|
||||
except Exception as install_exc: # pragma: no cover - system dep
|
||||
logger.warning(
|
||||
"Automatic xclip installation failed: %s", install_exc
|
||||
)
|
||||
raise exc
|
||||
raise pyperclip.PyperclipException(
|
||||
"Clipboard support requires the 'xclip' package. "
|
||||
"Install it with 'sudo apt install xclip' and restart SeedPass."
|
||||
) from exc
|
||||
raise
|
||||
|
||||
|
||||
def copy_to_clipboard(text: str, timeout: int) -> None:
|
||||
"""Copy text to the clipboard and clear after timeout seconds if unchanged."""
|
||||
def copy_to_clipboard(text: str, timeout: int) -> bool:
|
||||
"""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)
|
||||
|
||||
def clear_clipboard() -> None:
|
||||
@@ -44,3 +50,4 @@ def copy_to_clipboard(text: str, timeout: int) -> None:
|
||||
timer = threading.Timer(timeout, clear_clipboard)
|
||||
timer.daemon = True
|
||||
timer.start()
|
||||
return True
|
||||
|
Reference in New Issue
Block a user