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
2 changes: 1 addition & 1 deletion .github/workflows/blitztext-linux-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12"]
python-version: ["3.11", "3.12", "3.14"]
env:
# Qt headless: Offscreen-Plattform, damit GUI-Tests ohne Display laufen.
QT_QPA_PLATFORM: offscreen
Expand Down
37 changes: 30 additions & 7 deletions README.de.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<img src="docs/screenshots/linux/Banner.png" alt="Blitztext Linux Banner" width="860">

<h1>Blitztext Linux</h1>
<p><strong>Dein lokaler KI-Sprachassistent für KDE Plasma & Wayland</strong></p>
<p><strong>Dein lokaler KI-Sprachassistent für Linux-Desktops unter Wayland</strong></p>

<p>
<a href="https://timintech.github.io/blitztextweb/"><img src="https://img.shields.io/badge/🌐_Webseite-blitztextweb-2ea44f?style=for-the-badge" alt="Webseite"></a>
Expand All @@ -11,7 +11,7 @@
<p>
<a href="https://github.com/TimInTech/blitztext-linux/actions/workflows/blitztext-linux-ci.yml"><img src="https://github.com/TimInTech/blitztext-linux/actions/workflows/blitztext-linux-ci.yml/badge.svg" alt="Blitztext Linux CI"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/Lizenz-MIT-yellow.svg" alt="Lizenz: MIT"></a>
<img src="https://img.shields.io/badge/Plattform-Ubuntu%2FKubuntu%20%2B%20KDE%20Plasma-blue" alt="Plattform">
<img src="https://img.shields.io/badge/Plattform-Ubuntu%2FKubuntu%20%2B%20Wayland-blue" alt="Plattform">
</p>
<p><a href="README.md">🇬🇧 English</a> | <strong>🇩🇪 Deutsch</strong></p>
<p><i>Sprache per Hotkey aufnehmen, lokal oder online transkribieren, optional per LLM umschreiben und direkt in die aktive Anwendung einfügen.</i></p>
Expand All @@ -25,7 +25,7 @@
</div>

> [!IMPORTANT]
> **Eigenständiger Linux-Port:** Dieses Repository enthält ausschließlich den Linux-Port von Blitztext – eine eigenständige Python 3/PyQt6-Implementierung optimiert für **Kubuntu/Ubuntu unter KDE Plasma mit Wayland**. Für die originale macOS-Version besuche bitte das [offizielle Haupt-Repository](https://github.com/cmagnussen/blitztext-app).
> **Eigenständiger Linux-Port:** Dieses Repository enthält ausschließlich den Linux-Port von Blitztext – eine eigenständige Python 3/PyQt6-Implementierung, die unter **KDE Plasma mit Wayland** entwickelt und zusätzlich nativ unter **Ubuntu 26.04 GNOME mit Wayland** verifiziert wurde. Für die originale macOS-Version besuche bitte das [offizielle Haupt-Repository](https://github.com/cmagnussen/blitztext-app).

---

Expand Down Expand Up @@ -71,6 +71,16 @@ Die KI-Workflows helfen bei Formulierung, Ton und Emojis. Die passenden Einstell
<br><br>
</div>

> [!IMPORTANT]
> **Das Feld „API-Key-Umgebung“ ist kein Eingabefeld für den geheimen
> API-Key.** Trage dort nur den Namen der Umgebungsvariable ein, für OpenAI
> beispielsweise `OPENAI_API_KEY` und für OpenRouter `OPENROUTER_API_KEY`.
> Der tatsächliche Schlüssel wird beim Speichern der Einstellungen weder in
> `config.json` noch automatisch in eine andere Datei geschrieben. Lege ihn
> separat in `~/.config/blitztext-linux/secrets.env` ab; `./run.sh` und der
> systemd-User-Service laden diese Datei beim nächsten Start. Eine vollständige
> Anleitung mit Dateiformat und Berechtigungen steht unter [Secrets](#secrets).

**LLM-Anbieter.** Blitztext unterstützt drei Anbieter-Modi, wählbar unter **Einstellungen → KI-Workflows → „LLM-Anbieter"**:

| Anbieter | Wann verwenden |
Expand Down Expand Up @@ -238,7 +248,8 @@ Es ist idempotent (mehrfach ausführbar) und erledigt alles vollautomatisch:
2. Installiert fehlende Systempakete (inkl. `pipx`).
3. Fragt den Betriebsmodus ab: globale Hotkeys mit `input`-Gruppe oder nur Fenster/Tray ohne globale Hotkeys.
4. Richtet eine `.venv` Umgebung ein und installiert `openai-whisper`/`faster-whisper`.
5. Bereitet `ydotool.service` und den systemd-User-Service vor.
5. Bereitet `ydotool.service` vor, installiert den systemd-User-Service und
aktiviert ihn für den Autostart, ohne ihn sofort zu starten.

### Nach der Installation

Expand All @@ -251,11 +262,21 @@ Es ist idempotent (mehrfach ausführbar) und erledigt alles vollautomatisch:
./run.sh
```
*(Erscheint das Tray-Symbol und reagieren die Hotkeys? Dann lief alles glatt!)*
3. **Autostart aktivieren:**
3. **Den bereits aktivierten Autostart-Dienst jetzt starten:**
```bash
systemctl --user start blitztext-linux
```

> **Stand Ubuntu 26.04 GNOME / Wayland (verifiziert am 10.08.2026):** Eine
> saubere Installation mit Python 3.14.4 bestand `scripts/verify.sh` vor der
> ersten Benutzerkonfiguration mit 17 PASS, 0 FAIL, 0 WARN und danach mit
> 18 PASS, 0 FAIL, 0 WARN. Hauptfenster, GNOME-AppIndicator-Tray, Audioaufnahme,
> lokale `openai-whisper`-Transkription, globaler Left-Alt-Hotkey,
> Wayland-Zwischenablage, `ydotool`-Auto-Paste und der Autostart als
> systemd-User-Service wurden in der echten Desktop-Sitzung ausgeführt. Diese
> Verifikation gilt für die native Installation, nicht für das experimentelle
> Flatpak-Manifest.

<details>
<summary><b>Autostart wieder deaktivieren</b></summary>

Expand Down Expand Up @@ -300,9 +321,11 @@ pip install PyQt6 evdev openai pytest openai-whisper faster-whisper
```

**4. Whisper-Engine als Alternative via pipx**
Falls du `openai-whisper` losgelöst von der venv installieren möchtest (umgeht Versionskonflikte auf neueren Ubuntu-Setups durch Python 3.11):
Falls du `openai-whisper` losgelöst von der venv installieren möchtest, nutze
den vorhandenen System-Interpreter. Der verifizierte Ubuntu-26.04-Pfad verwendet
die Projekt-venv mit Python 3.14.4; pipx ist dort nicht erforderlich:
```bash
pipx install --python "$(command -v python3.11)" openai-whisper
pipx install --python "$(command -v python3)" openai-whisper
pipx inject openai-whisper faster-whisper # optional, für beschleunigte Ausführung
```

Expand Down
35 changes: 28 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<img src="docs/screenshots/linux/Banner.png" alt="Blitztext Linux Banner" width="860">

<h1>Blitztext Linux</h1>
<p><strong>Your local AI voice assistant for KDE Plasma & Wayland</strong></p>
<p><strong>Your local AI voice assistant for Linux desktops on Wayland</strong></p>

<p>
<a href="https://timintech.github.io/blitztextweb/"><img src="https://img.shields.io/badge/🌐_Website-blitztextweb-2ea44f?style=for-the-badge" alt="Website"></a>
Expand All @@ -11,7 +11,7 @@
<p>
<a href="https://github.com/TimInTech/blitztext-linux/actions/workflows/blitztext-linux-ci.yml"><img src="https://github.com/TimInTech/blitztext-linux/actions/workflows/blitztext-linux-ci.yml/badge.svg" alt="Blitztext Linux CI"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
<img src="https://img.shields.io/badge/Platform-Ubuntu%2FKubuntu%20%2B%20KDE%20Plasma-blue" alt="Platform">
<img src="https://img.shields.io/badge/Platform-Ubuntu%2FKubuntu%20%2B%20Wayland-blue" alt="Platform">
</p>
<p><strong>🇬🇧 English</strong> | <a href="README.de.md">🇩🇪 Deutsch</a></p>
<p><i>Record speech via hotkey, transcribe locally or online, optionally rewrite it with an LLM, and paste it directly into the active application.</i></p>
Expand All @@ -25,7 +25,7 @@
</div>

> [!IMPORTANT]
> **Standalone Linux port:** This repository contains exclusively the Linux port of Blitztext – a standalone Python 3/PyQt6 implementation optimized for **Kubuntu/Ubuntu running KDE Plasma with Wayland**. For the original macOS version, please visit the [official main repository](https://github.com/cmagnussen/blitztext-app).
> **Standalone Linux port:** This repository contains exclusively the Linux port of Blitztext – a standalone Python 3/PyQt6 implementation developed on **KDE Plasma with Wayland** and also verified natively on **Ubuntu 26.04 GNOME with Wayland**. For the original macOS version, please visit the [official main repository](https://github.com/cmagnussen/blitztext-app).

---

Expand Down Expand Up @@ -71,6 +71,15 @@ The AI workflows help with phrasing, tone, and emojis. You'll find the relevant
<br><br>
</div>

> [!IMPORTANT]
> **“API key environment” is not an input field for the secret API key.** Enter
> only the environment-variable name there, such as `OPENAI_API_KEY` for OpenAI
> or `OPENROUTER_API_KEY` for OpenRouter. Saving the settings does not write the
> actual key to `config.json` or automatically create another file for it.
> Store the key separately in `~/.config/blitztext-linux/secrets.env`; `./run.sh`
> and the systemd user service load that file on the next start. See
> [Secrets](#secrets) for the complete file format and permission instructions.

**LLM providers.** Blitztext supports three provider modes, selectable under **Settings → AI Workflows → "LLM provider"**:

| Provider | When to use |
Expand Down Expand Up @@ -238,7 +247,8 @@ It is idempotent (safe to run repeatedly) and handles everything fully automatic
2. Installs missing system packages (incl. `pipx`).
3. Prompts for the operating mode: global hotkeys with `input` group, or window/tray only without global hotkeys.
4. Sets up a `.venv` environment and installs `openai-whisper`/`faster-whisper`.
5. Prepares `ydotool.service` and the systemd user service.
5. Prepares `ydotool.service`, installs the systemd user service, and enables it
for autostart without starting it immediately.

### After installation

Expand All @@ -251,11 +261,20 @@ It is idempotent (safe to run repeatedly) and handles everything fully automatic
./run.sh
```
*(Does the tray icon appear and do the hotkeys respond? Then everything went smoothly!)*
3. **Enable autostart:**
3. **Start the already enabled autostart service now:**
```bash
systemctl --user start blitztext-linux
```

> **Ubuntu 26.04 GNOME / Wayland status (verified 2026-08-10):** A clean
> installation on Python 3.14.4 passed `scripts/verify.sh` with 17 PASS, 0 FAIL,
> 0 WARN before the first user configuration and 18 PASS, 0 FAIL, 0 WARN
> afterwards. The main window, GNOME AppIndicator tray, audio recording, local
> `openai-whisper` transcription, the global Left-Alt hotkey, Wayland clipboard,
> `ydotool` auto-paste, and systemd user-service autostart were all exercised in
> the real desktop session. This verification applies to the native install,
> not the experimental Flatpak manifest.

<details>
<summary><b>Disable autostart again</b></summary>

Expand Down Expand Up @@ -300,9 +319,11 @@ pip install PyQt6 evdev openai pytest openai-whisper faster-whisper
```

**4. Whisper engine as an alternative via pipx**
If you want to install `openai-whisper` decoupled from the venv (avoids version conflicts on newer Ubuntu setups due to Python 3.11):
If you want to install `openai-whisper` decoupled from the venv, use the
available system interpreter. The verified Ubuntu 26.04 path uses the project
venv with Python 3.14.4, so pipx is not required there:
```bash
pipx install --python "$(command -v python3.11)" openai-whisper
pipx install --python "$(command -v python3)" openai-whisper
pipx inject openai-whisper faster-whisper # optional, for accelerated execution
```

Expand Down
36 changes: 30 additions & 6 deletions app/history_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,16 @@ def __init__(self, entry: HistoryEntry, parent: Optional[QWidget] = None) -> Non
"border: 1px solid palette(mid); margin: 1px 0; }"
)

# Diese Timer muessen dem Widget gehoeren. Ein statischer
# QTimer.singleShot mit Lambda kann nach deleteLater() noch feuern und
# dann auf das bereits zerstoerte C++-Objekt zugreifen.
self._copy_reset_timer = QTimer(self)
self._copy_reset_timer.setSingleShot(True)
self._copy_reset_timer.timeout.connect(self._reset_copy_button)
self._highlight_reset_timer = QTimer(self)
self._highlight_reset_timer.setSingleShot(True)
self._highlight_reset_timer.timeout.connect(self._reset_highlight)

layout = QVBoxLayout(self)
layout.setContentsMargins(8, 6, 8, 6)
layout.setSpacing(3)
Expand Down Expand Up @@ -200,7 +210,7 @@ def _copy_to_clipboard(self) -> None:
_clipboard_write(self.entry.text)
self._btn_copy.setText("✓")
self._btn_copy.setStyleSheet("color: #4caf50; font-weight: bold;")
QTimer.singleShot(1500, self._reset_copy_button)
self._copy_reset_timer.start(1500)

def _reset_copy_button(self) -> None:
self._btn_copy.setText("\U0001f4cb")
Expand All @@ -211,10 +221,13 @@ def highlight(self) -> None:
"HistoryEntryWidget { background: #fff3cd; border-radius: 4px; "
"border: 1px solid #ffc107; margin: 1px 0; }"
)
QTimer.singleShot(800, lambda: self.setStyleSheet(
self._highlight_reset_timer.start(800)

def _reset_highlight(self) -> None:
self.setStyleSheet(
"HistoryEntryWidget { background: palette(base); border-radius: 4px; "
"border: 1px solid palette(mid); margin: 1px 0; }"
))
)


class HistoryPanel(QWidget):
Expand All @@ -236,6 +249,17 @@ def __init__(
self._entry_widgets: List[HistoryEntryWidget] = []
self._clear_armed = False
self._setup_ui()
self._scroll_reset_timer = QTimer(self)
self._scroll_reset_timer.setSingleShot(True)
self._scroll_reset_timer.timeout.connect(
lambda: self._scroll.verticalScrollBar().setValue(0)
)
self._clear_disarm_timer = QTimer(self)
self._clear_disarm_timer.setSingleShot(True)
self._clear_disarm_timer.timeout.connect(self._disarm_clear)
self._merge_reset_timer = QTimer(self)
self._merge_reset_timer.setSingleShot(True)
self._merge_reset_timer.timeout.connect(self._reset_merge_button)

def _setup_ui(self) -> None:
layout = QVBoxLayout(self)
Expand Down Expand Up @@ -293,7 +317,7 @@ def add_entry(self, text: str, is_dictation: bool = False, source: Optional[str]
self._list_layout.insertWidget(0, widget)

widget.highlight()
QTimer.singleShot(50, lambda: self._scroll.verticalScrollBar().setValue(0))
self._scroll_reset_timer.start(50)

self._update_header()
self._update_merge_button()
Expand All @@ -320,7 +344,7 @@ def _on_clear_clicked(self) -> None:
self._clear_armed = True
self._btn_clear.setText(t("history.button.confirm_clear"))
self._btn_clear.setStyleSheet("color: #f44336; font-weight: bold;")
QTimer.singleShot(3000, self._disarm_clear)
self._clear_disarm_timer.start(3000)
else:
self.clear_all()

Expand Down Expand Up @@ -353,7 +377,7 @@ def _merge_dictation(self) -> None:

self._btn_merge.setText(t("history.status.saved") if path else t("history.status.copied"))
self._btn_merge.setStyleSheet("color: #4caf50; font-weight: bold;")
QTimer.singleShot(2500, self._reset_merge_button)
self._merge_reset_timer.start(2500)

def _reset_merge_button(self) -> None:
self._btn_merge.setText(t("history.button.merge"))
Expand Down
30 changes: 19 additions & 11 deletions docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ bash scripts/install.sh

## After installation

1. Restart or log out and back in so the `input` group is active.
1. If you selected global hotkeys, restart or log out and back in so the
`input` group is active. Window/tray-only mode does not require this.
2. Run the verification script:

```bash
Expand All @@ -22,7 +23,7 @@ bash scripts/install.sh
./run.sh
```

4. Enable autostart if you want it on every login:
4. Start the autostart service that `install.sh` already enabled:

```bash
systemctl --user start blitztext-linux
Expand All @@ -39,24 +40,26 @@ systemctl --user disable blitztext-linux

## 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.
BlitztextLinux is developed and tested on **Kubuntu (KDE Plasma, Wayland)** and
has also completed a native clean-install and live functional pass on **Ubuntu
26.04 (GNOME, Wayland)**. The other systems below share the Ubuntu/Debian
package base, so `install.sh` runs on them, but they are not systematically
tested. This table documents observed or 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 |
| Ubuntu 26.04, GNOME | Wayland | GNOME tray needs the AppIndicator extension; ydotool 1.0.4 is available via apt | tested 2026-08-10: Python 3.14.4, verify 17/0/0 before first config and 18/0/0 afterwards; window, tray, recording, local Whisper, hotkey, clipboard, auto-paste, autostart |
| 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:
Base functions when the listed session prerequisites are present:

- Transcription and clipboard copy (no `ydotool`, no `input` group needed).
- Window/tray operation without global hotkeys (`install.sh` offers this mode;
Expand All @@ -71,15 +74,19 @@ What is environment-dependent:

## Desktop session notes

BlitztextLinux is developed for KDE Plasma on Wayland, with X11 fallbacks where the
underlying tools support them.
BlitztextLinux is developed for KDE Plasma on Wayland and additionally verified
on Ubuntu 26.04 GNOME/Wayland, with X11 fallbacks where the underlying tools
support them.

- GUI startup needs a real desktop session: either a usable `WAYLAND_DISPLAY` socket
or `DISPLAY` must be available. In headless shells, `scripts/verify.sh` can report
a warning even when the installed dependencies are otherwise correct.
- Qt prefers Wayland when `WAYLAND_DISPLAY` points to an existing socket. If that
variable is stale but `DISPLAY` is set, the launcher falls back to X11.
- Clipboard support uses `wl-copy`/`wl-paste` on Wayland and `xclip` on X11.
- GNOME needs an AppIndicator/StatusNotifier extension for the tray icon. Ubuntu
26.04's default GNOME session provided a working StatusNotifier host in the
verified native-install test.
- Auto-paste uses `ydotool`; terminal windows may need `Ctrl+Shift+V` instead of
`Ctrl+V`, so the app detects known terminal window classes when possible.
- Global hotkeys still use `evdev`/the `input` group. A future desktop-native XDG
Expand Down Expand Up @@ -116,6 +123,7 @@ sudo usermod -aG input $USER
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install --index-url https://download.pytorch.org/whl/cpu torch
pip install PyQt6 evdev openai pytest openai-whisper faster-whisper
```

Expand All @@ -124,7 +132,7 @@ pip install PyQt6 evdev openai pytest openai-whisper faster-whisper
If you want to install `openai-whisper` outside the venv:

```bash
pipx install --python "$(command -v python3.11)" openai-whisper
pipx install --python "$(command -v python3)" openai-whisper
pipx inject openai-whisper faster-whisper # optional
```

Expand Down
Loading