Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,38 @@ systemctl --user disable blitztext-linux
```
</details>

## Compatibility matrix (diagnostic, not a support promise)

BlitztextLinux is developed and tested on **Kubuntu (KDE Plasma, Wayland)**.
The systems below share the Ubuntu/Debian package base, so `install.sh` runs on
them, but only Kubuntu is systematically tested. This table documents expected
behavior and known risks — it is **not** an official support statement.

| Target system | Expected session | Main risks | Status |
| :--- | :--- | :--- | :--- |
| Kubuntu 26.04, Plasma | Wayland | ydotool ≥ 1.0 via apt available; lowest risk | tested |
| Ubuntu 24.04, GNOME | Wayland | apt ydotool 0.1.x is client-only → no auto-paste (clipboard mode works); GNOME tray needs the AppIndicator extension | untested |
| Ubuntu 26.04, GNOME | Wayland | GNOME tray needs the AppIndicator extension; Python 3.13/3.14: torch/openai-whisper wheels may lag behind new Python releases | untested |
| Linux Mint 22.x, Cinnamon | X11 (default) | `xclip` is the required clipboard backend, not `wl-copy`; apt ydotool 0.1.x → no auto-paste | untested |
| Lubuntu 24.04, LXQt | X11 (default) | same as Mint: `xclip` required; Qt tray via StatusNotifier usually fine | untested |
| Lubuntu 26.04, LXQt | X11 or Wayland | session type decides the clipboard backend — run `scripts/verify.sh` to see which one applies | untested |

Xfce and MATE are expected to behave like the X11 rows above, but are not
tracked here.

What works everywhere, regardless of session type:

- Transcription and clipboard copy (no `ydotool`, no `input` group needed).
- Window/tray operation without global hotkeys (`install.sh` offers this mode;
`BLITZTEXT_NO_HOTKEY=1 bash scripts/install.sh` selects it non-interactively).

What is environment-dependent:

- **Auto-paste** needs a working `ydotoold` (ydotool ≥ 1.0).
- **Global hotkeys** need evdev access via the `input` group.
- **Clipboard backend**: `wl-copy` on Wayland, `xclip` on plain X11.
`scripts/verify.sh` prints the detected session and which backend is required.

## Desktop session notes

BlitztextLinux is developed for KDE Plasma on Wayland, with X11 fallbacks where the
Expand Down
52 changes: 42 additions & 10 deletions scripts/verify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,41 @@ ydotoold_provider_exists() {
command -v ydotoold >/dev/null 2>&1 || ydotool_user_service_exists
}

# ─── Session-Erkennung ────────────────────────────────────────────────────────
# Die App wählt das Clipboard-Backend nach Umgebung: wl-copy, wenn
# WAYLAND_DISPLAY gesetzt ist oder gar kein DISPLAY existiert; nur in einer
# reinen X11-Session (DISPLAY ohne WAYLAND_DISPLAY) ist xclip das Pflicht-Backend.
if [[ -z "${WAYLAND_DISPLAY:-}" && -n "${DISPLAY:-}" ]]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check Wayland socket before requiring wl-copy

When WAYLAND_DISPLAY is set but points to no socket while DISPLAY is set, the launcher falls back to X11 by unsetting WAYLAND_DISPLAY in app/blitztext_linux.py, and PasteService then needs xclip. This new detection treats any non-empty WAYLAND_DISPLAY as Wayland, so verify.sh can fail on missing wl-copy and only warn on missing xclip even though the app will run in X11; mirror the startup normalization or validate the socket before choosing CLIPBOARD_REQUIRED.

Useful? React with 👍 / 👎.

CLIPBOARD_REQUIRED="xclip"
CLIPBOARD_OPTIONAL="wl-copy"
else
CLIPBOARD_REQUIRED="wl-copy"
CLIPBOARD_OPTIONAL="xclip"
fi

clipboard_install_hint() {
if [[ "$1" == "wl-copy" ]]; then
echo "sudo apt install wl-clipboard"
else
echo "sudo apt install xclip"
fi
}

echo ""
echo -e "${BOLD}══════════════════════════════════════════════════════${RESET}"
echo -e "${BOLD} BlitztextLinux — Abhängigkeits-Check${RESET}"
echo -e "${BOLD} Verzeichnis: ${BLITZTEXT_DIR}${RESET}"
echo -e "${BOLD}══════════════════════════════════════════════════════${RESET}"
echo ""
echo -e "${BOLD}── Session & Desktop ────────────────────────────────${RESET}"

# Rein informativ — keine harten Entscheidungen anhand dieser Werte.
info "XDG_SESSION_TYPE: ${XDG_SESSION_TYPE:-nicht gesetzt}"
info "XDG_CURRENT_DESKTOP: ${XDG_CURRENT_DESKTOP:-nicht gesetzt}"
info "WAYLAND_DISPLAY: ${WAYLAND_DISPLAY:-nicht gesetzt}"
info "DISPLAY: ${DISPLAY:-nicht gesetzt}"
info "Clipboard-Backend für diese Session: ${CLIPBOARD_REQUIRED} (Pflicht), ${CLIPBOARD_OPTIONAL} (Fallback)"

echo ""
echo -e "${BOLD}── Systembinaries ───────────────────────────────────${RESET}"

Expand All @@ -66,25 +96,26 @@ else
fail "parec nicht gefunden — 'sudo apt install pulseaudio-utils'"
fi

# wl-copy (wl-clipboard)
if command -v wl-copy &>/dev/null; then
pass "wl-copy gefunden: $(command -v wl-copy)"
# Clipboard-Backend (Pflicht für die aktuelle Session)
if command -v "${CLIPBOARD_REQUIRED}" &>/dev/null; then
pass "${CLIPBOARD_REQUIRED} gefunden: $(command -v "${CLIPBOARD_REQUIRED}")"
else
fail "wl-copy nicht gefunden — 'sudo apt install wl-clipboard'"
fail "${CLIPBOARD_REQUIRED} nicht gefunden — '$(clipboard_install_hint "${CLIPBOARD_REQUIRED}")'"
fi

# xclip (X11 clipboard fallback)
if command -v xclip &>/dev/null; then
pass "xclip gefunden: $(command -v xclip)"
# Clipboard-Fallback (optional für die aktuelle Session)
if command -v "${CLIPBOARD_OPTIONAL}" &>/dev/null; then
pass "${CLIPBOARD_OPTIONAL} gefunden: $(command -v "${CLIPBOARD_OPTIONAL}")"
else
warn "xclip nicht gefunden — X11-Clipboard-Fallback fehlt ('sudo apt install xclip')"
warn "${CLIPBOARD_OPTIONAL} nicht gefunden — Clipboard-Fallback fehlt ('$(clipboard_install_hint "${CLIPBOARD_OPTIONAL}")')"
fi

# ydotool
# ydotool (optional: nur für Auto-Paste nötig, Clipboard-Kopie geht ohne)
if command -v ydotool &>/dev/null; then
pass "ydotool gefunden: $(command -v ydotool)"
else
fail "ydotool nicht gefunden — 'sudo apt install ydotool'"
warn "ydotool nicht gefunden — Auto-Paste nicht verfügbar ('sudo apt install ydotool')"
warn " Transkription und Clipboard-Kopie funktionieren auch ohne ydotool"
fi

# ffmpeg
Expand Down Expand Up @@ -186,6 +217,7 @@ elif groups "$(whoami)" 2>/dev/null | grep -qw "input"; then
else
warn "Benutzer NICHT in Gruppe 'input' — evdev-Hotkeys funktionieren nicht"
warn " Behebung: sudo usermod -aG input \$USER (dann Re-Login)"
info " Fenster/Tray-Bedienung funktioniert auch ohne die Gruppe 'input'"
fi

echo ""
Expand Down
92 changes: 91 additions & 1 deletion tests/test_verify_script.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
from __future__ import annotations

import os
import re
import shutil
import subprocess
from pathlib import Path

_ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")


def _strip_ansi(text: str) -> str:
return _ANSI_RE.sub("", text)

# NOTE: tests/conftest.py's autouse `_block_real_notifications` fixture patches
# `subprocess.run` process-wide (it patches the shared `subprocess` module via
# `app.notify.subprocess.run`). Resolving real tool paths must therefore use
Expand Down Expand Up @@ -66,7 +73,13 @@ def _prepare_verify_script(
return script_path, stub_bin


def _run_script(script_path: Path, home_dir: Path, runtime_dir: Path, stub_bin: Path) -> subprocess.CompletedProcess[str]:
def _run_script(
script_path: Path,
home_dir: Path,
runtime_dir: Path,
stub_bin: Path,
extra_env: dict[str, str] | None = None,
) -> subprocess.CompletedProcess[str]:
env = os.environ.copy()
env.update(
{
Expand All @@ -77,6 +90,8 @@ def _run_script(script_path: Path, home_dir: Path, runtime_dir: Path, stub_bin:
"DISPLAY": "",
}
)
if extra_env:
env.update(extra_env)
proc = subprocess.Popen(
[BASH, str(script_path)],
cwd=str(script_path.parent),
Expand Down Expand Up @@ -184,3 +199,78 @@ def test_verify_all_checked_binaries_present_removes_their_fail_lines(tmp_path:
for tool in checked_tools:
assert f"{tool} nicht gefunden" not in result.stdout
assert f"{tool} gefunden" in result.stdout


def test_verify_prints_session_and_desktop_info(tmp_path: Path):
repo_root = Path(__file__).resolve().parents[1]
script_path, stub_bin = _prepare_verify_script(tmp_path, repo_root)
home_dir = tmp_path / "home"
runtime_dir = tmp_path / "runtime"
runtime_dir.mkdir(parents=True, exist_ok=True)

result = _run_script(
script_path,
home_dir,
runtime_dir,
stub_bin,
extra_env={"XDG_SESSION_TYPE": "wayland", "XDG_CURRENT_DESKTOP": "KDE"},
)
stdout = _strip_ansi(result.stdout)

assert "XDG_SESSION_TYPE: wayland" in stdout
assert "XDG_CURRENT_DESKTOP: KDE" in stdout


def test_verify_x11_session_requires_xclip_not_wl_copy(tmp_path: Path):
"""In einer reinen X11-Session (Mint/Lubuntu-Default) ist xclip Pflicht."""
repo_root = Path(__file__).resolve().parents[1]
script_path, stub_bin = _prepare_verify_script(tmp_path, repo_root, present_checked_tools=())
home_dir = tmp_path / "home"
runtime_dir = tmp_path / "runtime"
runtime_dir.mkdir(parents=True, exist_ok=True)

result = _run_script(
script_path, home_dir, runtime_dir, stub_bin, extra_env={"DISPLAY": ":0"}
)
stdout = _strip_ansi(result.stdout)

assert "[FAIL] xclip nicht gefunden" in stdout
assert "[WARN] wl-copy nicht gefunden" in stdout
assert "[FAIL] wl-copy nicht gefunden" not in stdout


def test_verify_wayland_session_requires_wl_copy_not_xclip(tmp_path: Path):
repo_root = Path(__file__).resolve().parents[1]
script_path, stub_bin = _prepare_verify_script(tmp_path, repo_root, present_checked_tools=())
home_dir = tmp_path / "home"
runtime_dir = tmp_path / "runtime"
runtime_dir.mkdir(parents=True, exist_ok=True)

result = _run_script(
script_path,
home_dir,
runtime_dir,
stub_bin,
extra_env={"WAYLAND_DISPLAY": "wayland-0", "DISPLAY": ":0"},
)
stdout = _strip_ansi(result.stdout)

assert "[FAIL] wl-copy nicht gefunden" in stdout
assert "[WARN] xclip nicht gefunden" in stdout
assert "[FAIL] xclip nicht gefunden" not in stdout


def test_verify_missing_ydotool_is_warn_not_fail(tmp_path: Path):
"""ydotool ist optional: ohne ydotool fehlt nur Auto-Paste, kein Hard-Fail."""
repo_root = Path(__file__).resolve().parents[1]
script_path, stub_bin = _prepare_verify_script(tmp_path, repo_root, present_checked_tools=())
home_dir = tmp_path / "home"
runtime_dir = tmp_path / "runtime"
runtime_dir.mkdir(parents=True, exist_ok=True)

result = _run_script(script_path, home_dir, runtime_dir, stub_bin)
stdout = _strip_ansi(result.stdout)

assert "[WARN] ydotool nicht gefunden" in stdout
assert "[FAIL] ydotool nicht gefunden" not in stdout
assert "funktionieren auch ohne ydotool" in stdout