feat(flatpak): add Flatpak packaging for universal distribution (#484)
* feat(flatpak): add Flatpak packaging for universal distribution Adds a Flathub-ready Flatpak for the whisper.cpp engine, plus the minimal runtime adaptations Flatpak actually needs — deliberately scoped down from the earlier attempts (#416, #428), which bundled unrelated whisper.cpp GPU, audio, and clipboard changes. Packaging (packaging/flatpak/): - app manifest, AppStream metainfo, and desktop entry (com.vocalinux.Vocalinux) - generated offline Python dependency modules (flatpak-pip-generator) - bundled shared modules: PortAudio, libayatana-appindicator, xdotool, xsel Runtime changes (Flatpak-only, behaviour unchanged outside a sandbox): - utils/paths.py: honour XDG_CONFIG_HOME/XDG_DATA_HOME so config, models and runtime data live under ~/.var/app/<app-id> in the sandbox - autostart uses `flatpak run <app-id>` (the host launcher is always on PATH) - text injection and keyboard backend detect a Wayland host reached via XWayland (no Wayland socket granted) and route through xdotool / pynput-on-X11 - tray indicator references the exported `<app-id>-*` host icon theme names Also: a CI workflow building x86_64 + aarch64 bundles, and README / packaging README / distro-compatibility doc updates. Verified with a full flatpak-builder build + bundle install on aarch64: pywhispercpp loads its native libraries without LD_LIBRARY_PATH, XDG paths redirect correctly, bundled tools are present, and the entrypoint runs. Manifest and builddir lint are clean and AppStream metadata validates. Closes #167 * chore(flatpak): sync metainfo version to 0.14.0-beta Also ignore flatpak-build-dir/ so local builder output stays untracked. * refactor(flatpak): trim docs and comments after ponytail review Drop redundant packaging README command blocks, shrink path/tray/autostart comments, and stop ignoring a non-canonical build-dir name. * fix(flatpak): close readiness gaps for v0.14.0-beta Route log files through XDG data dirs so Flatpak keeps them under ~/.var/app, refresh AppStream screenshots to vocalinux.com, and use the SPDX GPL-3.0-only license id. * fix(flatpak): enable global hotkeys via evdev and --device=input pynput over XWayland only receives keys for the focused X client. Prefer evdev inside Flatpak and grant /dev/input so shortcuts work app-wide. * fix(flatpak): inject text with ydotool/uinput on Wayland xdotool only reaches XWayland clients, so typing appeared to succeed but never hit native apps. Ship ydotool, grant --device=all for uinput, start ydotoold on demand, and type via uinput inside the Flatpak. * fix(flatpak): paste via wl-copy + Ctrl+V instead of typing ydotool type injects character-by-character. Ship wl-clipboard, grant --socket=wayland, and prefer clipboard paste for instant insertion. * test(text-injection): expect ydotool default key-delay of 2 Matches the shipped VOCALINUX_YDOTOOL_KEY_DELAY default used when clipboard paste falls back to character typing.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
name: Flatpak
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
paths:
|
||||
- 'packaging/flatpak/**'
|
||||
- 'src/**'
|
||||
- 'pyproject.toml'
|
||||
- '.github/workflows/flatpak.yml'
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
paths:
|
||||
- 'packaging/flatpak/**'
|
||||
- 'src/**'
|
||||
- 'pyproject.toml'
|
||||
- '.github/workflows/flatpak.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Flatpak (${{ matrix.arch }})
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: x86_64
|
||||
runner: ubuntu-latest
|
||||
- arch: aarch64
|
||||
runner: ubuntu-24.04-arm
|
||||
container:
|
||||
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-50
|
||||
options: --privileged
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build and bundle
|
||||
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
||||
with:
|
||||
bundle: vocalinux-${{ matrix.arch }}.flatpak
|
||||
manifest-path: packaging/flatpak/com.vocalinux.Vocalinux.yml
|
||||
cache-key: flatpak-${{ matrix.arch }}-${{ github.sha }}
|
||||
arch: ${{ matrix.arch }}
|
||||
# Flathub mirrors screenshots at submission time; mirror locally so the
|
||||
# builddir linter does not flag the external screenshot URL.
|
||||
mirror-screenshots-url: https://dl.flathub.org/media
|
||||
|
||||
- name: Upload bundle
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: vocalinux-flatpak-${{ matrix.arch }}
|
||||
path: vocalinux-${{ matrix.arch }}.flatpak
|
||||
if-no-files-found: error
|
||||
@@ -83,6 +83,12 @@ out/
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Flatpak build artifacts
|
||||
.flatpak-builder/
|
||||
build-dir/
|
||||
/repo/
|
||||
*.flatpak
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
|
||||
@@ -195,6 +195,23 @@ yay -S vocalinux
|
||||
|
||||
See [docs/AUR.md](docs/AUR.md).
|
||||
|
||||
### Flatpak (any distro)
|
||||
|
||||
For a sandboxed, distro-independent install (great for NixOS, Fedora Silverblue,
|
||||
Steam Deck, and anywhere else), build the Flatpak from the bundled manifest:
|
||||
|
||||
```bash
|
||||
flatpak install flathub org.gnome.Platform//50 org.gnome.Sdk//50
|
||||
flatpak-builder --user --install --force-clean build-dir \
|
||||
packaging/flatpak/com.vocalinux.Vocalinux.yml
|
||||
flatpak run com.vocalinux.Vocalinux
|
||||
```
|
||||
|
||||
The Flatpak ships the whisper.cpp engine with Vulkan GPU support and runs through
|
||||
XWayland on Wayland sessions. See [`packaging/flatpak/README.md`](packaging/flatpak/README.md)
|
||||
for build details, permissions, and Flathub submission notes. Flathub publishing
|
||||
is in progress.
|
||||
|
||||
### Alternative: Install from Source
|
||||
|
||||
```bash
|
||||
@@ -412,6 +429,7 @@ This script generates all three sounds using the same smooth glide algorithm. Yo
|
||||
- [x] ~~Vulkan GPU support~~ ✅
|
||||
- [ ] In-app update mechanism
|
||||
- [ ] Application-specific commands
|
||||
- [x] ~~Flatpak packaging~~ ✅ (Flathub submission in progress)
|
||||
- [ ] Debian/Ubuntu package (.deb)
|
||||
- [x] ~~Wayland support via IBus~~ ✅
|
||||
- [ ] Voice command customization
|
||||
|
||||
@@ -12,7 +12,7 @@ The cross-distribution compatibility improvements have been implemented in phase
|
||||
| Phase 4 | ✅ Complete | CI test matrix, pkg-config as core dependency |
|
||||
| **Phase 5** | ✅ **Complete** | **Fixed remaining hardcoded GI_TYPELIB_PATH values in install.sh and CI workflow** |
|
||||
| **Phase 6** | ✅ **Complete** | **Added wrapper script verification tests, updated documentation** |
|
||||
| Phase 7 | 📋 Planned | Flatpak packaging for universal distribution support |
|
||||
| **Phase 7** | ✅ **Complete** | **Flatpak packaging (whisper.cpp engine) for universal distribution support — see [`packaging/flatpak/`](../packaging/flatpak/README.md). Flathub submission in progress.** |
|
||||
| Phase 8 | 📋 Planned | Snap packaging for Ubuntu Software Store |
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# Vocalinux Flatpak Packaging
|
||||
|
||||
Manifest and AppStream metadata for building Vocalinux as a Flatpak.
|
||||
|
||||
## Current Scope
|
||||
|
||||
The manifest ships the default `whisper_cpp` engine only. VOSK is omitted because
|
||||
the PyPI package is wheel-only (no sdist), so a Flathub-ready VOSK build would
|
||||
need to compile VOSK and its native deps from source.
|
||||
|
||||
Global keyboard shortcuts use **evdev** (`/dev/input`).
|
||||
Text injection uses **wl-copy** + **ydotool Ctrl+V** (instant paste into native
|
||||
Wayland apps). Character-by-character `ydotool type` is only a fallback.
|
||||
`xdotool` remains for pure X11/XWayland clients.
|
||||
|
||||
Permissions: `--socket=wayland` (clipboard), `--socket=x11` (xdotool fallback),
|
||||
`--device=all` (evdev hotkeys + uinput; Flatpak has no narrower uinput flag).
|
||||
|
||||
## Local Build
|
||||
|
||||
```bash
|
||||
# one-time runtime/SDK
|
||||
flatpak install flathub org.gnome.Platform//50 org.gnome.Sdk//50
|
||||
|
||||
# from repo root — add flags as needed:
|
||||
# --user --install install for the current user
|
||||
# --repo=repo export a local repo (then bundle or lint)
|
||||
# --compose-url-policy=full --mirror-screenshots-url=https://dl.flathub.org/media
|
||||
flatpak-builder --force-clean build-dir packaging/flatpak/com.vocalinux.Vocalinux.yml
|
||||
|
||||
flatpak run com.vocalinux.Vocalinux --debug
|
||||
|
||||
# single-file bundle (after a --repo=repo build)
|
||||
flatpak build-bundle repo vocalinux.flatpak com.vocalinux.Vocalinux
|
||||
# elsewhere: flatpak install --user vocalinux.flatpak
|
||||
```
|
||||
|
||||
Lint (needs `org.flatpak.Builder`):
|
||||
|
||||
```bash
|
||||
flatpak run --command=flatpak-builder-lint org.flatpak.Builder manifest packaging/flatpak/com.vocalinux.Vocalinux.yml
|
||||
flatpak run --command=flatpak-builder-lint org.flatpak.Builder builddir build-dir
|
||||
flatpak run --command=flatpak-builder-lint org.flatpak.Builder repo repo # if you used --repo=repo
|
||||
```
|
||||
|
||||
Prefer `flatpak run` after `--install` for GUI testing. On GNOME runtimes with
|
||||
glycin loaders, `flatpak-builder --run` can fail to load themed SVG icons for an
|
||||
uninstalled stable app ID; it is still fine for non-GUI smokes:
|
||||
|
||||
```bash
|
||||
flatpak-builder --run build-dir packaging/flatpak/com.vocalinux.Vocalinux.yml \
|
||||
python3 -c 'from pywhispercpp.model import Model; print("pywhispercpp ok")'
|
||||
```
|
||||
|
||||
## Python Dependencies
|
||||
|
||||
`python3-dependencies.yaml` is generated with `flatpak-pip-generator` (offline
|
||||
builds). Regenerate when `pyproject.toml` deps change:
|
||||
|
||||
```bash
|
||||
pipx run flatpak-pip-generator \
|
||||
--runtime org.gnome.Sdk//50 \
|
||||
--yaml \
|
||||
--ignore-pkg 'vosk>=0.3.45' "PyGObject; sys_platform == 'linux'" \
|
||||
--output packaging/flatpak/python3-dependencies \
|
||||
pywhispercpp pydub pynput evdev requests tqdm numpy pyaudio python-xlib psutil lxml \
|
||||
meson-python pyproject-metadata
|
||||
```
|
||||
|
||||
`python3-build-dependencies.yaml` is a small hand-maintained helper so
|
||||
`--no-build-isolation` builds can import `mesonpy` before NumPy is built.
|
||||
|
||||
## Flathub Submission Notes
|
||||
|
||||
1. Change the `vocalinux` module source from `type: dir` to a tagged release:
|
||||
|
||||
```yaml
|
||||
sources:
|
||||
- type: git
|
||||
url: https://github.com/jatinkrmalik/vocalinux.git
|
||||
tag: v0.14.0-beta
|
||||
commit: <release-commit-sha>
|
||||
```
|
||||
|
||||
2. Keep build-time network access disabled; only declared sources.
|
||||
3. Re-run AppStream validation and `flatpak-builder` locally.
|
||||
4. Be ready to justify sandbox permissions (IBus and StatusNotifier D-Bus talk names).
|
||||
|
||||
## Manifest Details
|
||||
|
||||
- Runtime / SDK: `org.gnome.Platform//50`, `org.gnome.Sdk//50`
|
||||
- Mic: `--socket=pulseaudio` · GPU: `--device=dri` · models: `--share=network`
|
||||
- Input: `--device=all` (evdev hotkeys + ydotool/`uinput`)
|
||||
- Injection: packaged `wl-copy`, `ydotool`/`ydotoold`, plus `xdotool`/`xsel` fallback
|
||||
- Display: `--socket=wayland` (clipboard) and `--socket=x11` (xdotool fallback)
|
||||
- IBus: `--talk-name=org.freedesktop.IBus`
|
||||
- Tray: `--talk-name=org.kde.StatusNotifierWatcher`
|
||||
|
||||
Config and models use Flatpak XDG dirs under `~/.var/app/com.vocalinux.Vocalinux/`.
|
||||
@@ -0,0 +1,14 @@
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
Name=Vocalinux
|
||||
Comment=Free, offline voice dictation for Linux
|
||||
GenericName=Voice Typing
|
||||
Exec=vocalinux
|
||||
Icon=com.vocalinux.Vocalinux
|
||||
Terminal=false
|
||||
Categories=Utility;Accessibility;AudioVideo;
|
||||
Keywords=voice;typing;dictation;speech;recognition;transcription;accessibility;
|
||||
StartupNotify=true
|
||||
StartupWMClass=com.vocalinux.Vocalinux
|
||||
X-GNOME-UsesNotifications=true
|
||||
@@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>com.vocalinux.Vocalinux</id>
|
||||
|
||||
<name>Vocalinux</name>
|
||||
<summary>Free, offline voice dictation for Linux</summary>
|
||||
<developer id="com.vocalinux">
|
||||
<name>Jatin K Malik</name>
|
||||
</developer>
|
||||
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0-only</project_license>
|
||||
|
||||
<description>
|
||||
<p>
|
||||
Vocalinux is a privacy-first, fully offline voice dictation system for Linux.
|
||||
Speak naturally and your words appear in any application — on X11 or Wayland —
|
||||
using local speech recognition. No cloud, no data leaves your machine.
|
||||
Speech recognition models are downloaded on first run.
|
||||
</p>
|
||||
<p>Features:</p>
|
||||
<ul>
|
||||
<li>Toggle or push-to-talk activation modes</li>
|
||||
<li>Real-time transcription with minimal latency</li>
|
||||
<li>Universal compatibility across all Linux applications</li>
|
||||
<li>100% offline operation for privacy and reliability</li>
|
||||
<li>whisper.cpp by default — high-performance C++ speech recognition</li>
|
||||
<li>Universal GPU support via Vulkan (AMD, Intel, NVIDIA)</li>
|
||||
<li>System tray integration with visual status indicators</li>
|
||||
<li>Start on login support via XDG autostart</li>
|
||||
<li>Pleasant audio feedback with smooth gliding tones</li>
|
||||
<li>Graphical settings dialog for easy configuration</li>
|
||||
</ul>
|
||||
<p>
|
||||
This Flatpak ships the whisper.cpp engine. On a Wayland session, text is
|
||||
injected through XWayland.
|
||||
</p>
|
||||
</description>
|
||||
|
||||
<launchable type="desktop-id">com.vocalinux.Vocalinux.desktop</launchable>
|
||||
|
||||
<categories>
|
||||
<category>Utility</category>
|
||||
<category>Accessibility</category>
|
||||
<category>AudioVideo</category>
|
||||
</categories>
|
||||
|
||||
<keywords>
|
||||
<keyword>voice</keyword>
|
||||
<keyword>typing</keyword>
|
||||
<keyword>dictation</keyword>
|
||||
<keyword>speech</keyword>
|
||||
<keyword>recognition</keyword>
|
||||
<keyword>transcription</keyword>
|
||||
<keyword>accessibility</keyword>
|
||||
</keywords>
|
||||
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Real-time voice-to-text transcription</caption>
|
||||
<image>https://vocalinux.com/screenshots/00-transcription.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Speech engine settings</caption>
|
||||
<image>https://vocalinux.com/screenshots/settings-speech-engine.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>System tray indicator</caption>
|
||||
<image>https://vocalinux.com/screenshots/02-system-tray.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
<url type="homepage">https://vocalinux.com</url>
|
||||
<url type="bugtracker">https://github.com/jatinkrmalik/vocalinux/issues</url>
|
||||
<url type="vcs-browser">https://github.com/jatinkrmalik/vocalinux</url>
|
||||
|
||||
<content_rating type="oars-1.1">
|
||||
<content_attribute id="violence-cartoon">none</content_attribute>
|
||||
<content_attribute id="violence-fantasy">none</content_attribute>
|
||||
<content_attribute id="violence-realistic">none</content_attribute>
|
||||
<content_attribute id="violence-bloodshed">none</content_attribute>
|
||||
<content_attribute id="violence-sexual">none</content_attribute>
|
||||
<content_attribute id="violence-desecration">none</content_attribute>
|
||||
<content_attribute id="violence-slavery">none</content_attribute>
|
||||
<content_attribute id="violence-worship">none</content_attribute>
|
||||
<content_attribute id="drugs-alcohol">none</content_attribute>
|
||||
<content_attribute id="drugs-narcotics">none</content_attribute>
|
||||
<content_attribute id="drugs-tobacco">none</content_attribute>
|
||||
<content_attribute id="sex-nudity">none</content_attribute>
|
||||
<content_attribute id="sex-themes">none</content_attribute>
|
||||
<content_attribute id="sex-homosexuality">none</content_attribute>
|
||||
<content_attribute id="sex-prostitution">none</content_attribute>
|
||||
<content_attribute id="sex-adultery">none</content_attribute>
|
||||
<content_attribute id="sex-appearance">none</content_attribute>
|
||||
<content_attribute id="language-profanity">none</content_attribute>
|
||||
<content_attribute id="language-humor">none</content_attribute>
|
||||
<content_attribute id="language-discrimination">none</content_attribute>
|
||||
<content_attribute id="social-chat">none</content_attribute>
|
||||
<content_attribute id="social-info">none</content_attribute>
|
||||
<content_attribute id="social-audio">none</content_attribute>
|
||||
<content_attribute id="social-location">none</content_attribute>
|
||||
<content_attribute id="social-contacts">none</content_attribute>
|
||||
<content_attribute id="money-purchasing">none</content_attribute>
|
||||
<content_attribute id="money-gambling">none</content_attribute>
|
||||
</content_rating>
|
||||
|
||||
<releases>
|
||||
<release version="0.14.0-beta" date="2026-07-14">
|
||||
<description>
|
||||
<p>Vocalinux 0.14.0-beta as Flatpak: whisper.cpp with Vulkan, XDG config/models/logs, XWayland text injection, and system tray integration.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.12.0-beta" date="2026-06-07">
|
||||
<description>
|
||||
<p>First Flatpak release: whisper.cpp engine with Vulkan GPU support, XDG-compliant data paths, and XWayland text injection.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.11.0-beta" date="2026-05-30">
|
||||
<description>
|
||||
<p>Guided whisper.cpp model variants, hotplug keyboard rescanning, and IBus setup refinements.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.10.2-beta" date="2026-04-08">
|
||||
<description>
|
||||
<p>Non-ASCII text injection, IBus Wayland detection fixes, Pop!_OS dependency support, and code quality improvements.</p>
|
||||
</description>
|
||||
</release>
|
||||
</releases>
|
||||
</component>
|
||||
@@ -0,0 +1,129 @@
|
||||
app-id: com.vocalinux.Vocalinux
|
||||
runtime: org.gnome.Platform
|
||||
runtime-version: "50"
|
||||
sdk: org.gnome.Sdk
|
||||
command: vocalinux
|
||||
|
||||
build-options:
|
||||
env:
|
||||
NO_REPAIR: "1"
|
||||
PYWHISPERCPP_VERSION: "1.4.1"
|
||||
|
||||
finish-args:
|
||||
# Download speech-recognition models on first run.
|
||||
- --share=network
|
||||
# Shared memory for X11 / XWayland.
|
||||
- --share=ipc
|
||||
# X11 for xdotool fallback; Wayland for wl-copy (instant paste into native apps).
|
||||
# Injection itself uses ydotool/uinput (not the Wayland virtual-keyboard protocol).
|
||||
- --socket=x11
|
||||
- --socket=wayland
|
||||
# Microphone capture (works against PulseAudio and PipeWire-pulse).
|
||||
- --socket=pulseaudio
|
||||
# GPU render node for the Vulkan-accelerated whisper.cpp build.
|
||||
- --device=dri
|
||||
# /dev/input for global hotkeys (evdev) and /dev/uinput for ydotool (Ctrl+V /
|
||||
# type). Flatpak has no narrower flag than "all" for uinput.
|
||||
- --device=all
|
||||
# IBus Unicode injection backend.
|
||||
- --talk-name=org.freedesktop.IBus
|
||||
# Desktop notifications.
|
||||
- --talk-name=org.freedesktop.Notifications
|
||||
# System tray / status-notifier icon.
|
||||
- --talk-name=org.kde.StatusNotifierWatcher
|
||||
- --talk-name=org.gnome.Shell
|
||||
# Find the bundled AppIndicator typelib under /app (searched before the runtime).
|
||||
- --env=GI_TYPELIB_PATH=/app/lib/girepository-1.0
|
||||
|
||||
modules:
|
||||
- name: portaudio
|
||||
buildsystem: cmake-ninja
|
||||
config-opts:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DCMAKE_INSTALL_PREFIX=/app
|
||||
- -DCMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
cleanup:
|
||||
- /include
|
||||
- /lib/cmake
|
||||
- /lib/pkgconfig
|
||||
- "*.a"
|
||||
- "*.la"
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://github.com/PortAudio/portaudio/archive/refs/tags/v19.7.0.tar.gz
|
||||
sha256: 5af29ba58bbdbb7bbcefaaecc77ec8fc413f0db6f4c4e286c40c3e1b83174fa0
|
||||
|
||||
- shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json
|
||||
- name: xsel
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- autoreconf -fi
|
||||
- ./configure --prefix=/app
|
||||
- make
|
||||
- make install
|
||||
cleanup:
|
||||
- /share/man
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://github.com/kfish/xsel/archive/refs/tags/1.2.1.tar.gz
|
||||
sha256: 18487761f5ca626a036d65ef2db8ad9923bf61685e06e7533676c56d7d60eb14
|
||||
|
||||
- name: xdotool
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- make
|
||||
- make install PREFIX=/app
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://github.com/jordansissel/xdotool/archive/refs/tags/v3.20211022.1.tar.gz
|
||||
sha256: 82b15a944a5e82fee15e0f6116bd9f642bc3d0bb6989fc0ca5ad9dfe35de0847
|
||||
|
||||
# Wayland clipboard for bulk paste (wl-copy + ydotool Ctrl+V).
|
||||
- name: wl-clipboard
|
||||
buildsystem: meson
|
||||
config-opts:
|
||||
- -Dfishcompletiondir=no
|
||||
- -Dzshcompletiondir=no
|
||||
cleanup:
|
||||
- /share/man
|
||||
- /share/bash-completion
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://github.com/bugaevc/wl-clipboard/archive/refs/tags/v2.2.1.tar.gz
|
||||
sha256: 6eb8081207fb5581d1d82c4bcd9587205a31a3d47bea3ebeb7f41aa1143783eb
|
||||
|
||||
# uinput-based typing / paste chords for native Wayland clients.
|
||||
- name: ydotool
|
||||
buildsystem: cmake-ninja
|
||||
config-opts:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
# CMake 4.x in GNOME SDK rejects projects declaring min < 3.5.
|
||||
- -DCMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
cleanup:
|
||||
- /share/man
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://github.com/ReimuNotMoe/ydotool/archive/refs/tags/v1.0.4.tar.gz
|
||||
sha256: ba075a43aa6ead51940e892ecffa4d0b8b40c241e4e2bc4bd9bd26b61fde23bd
|
||||
- type: shell
|
||||
commands:
|
||||
# scdoc is not in the SDK; man pages are not needed in the sandbox.
|
||||
- sed -i '/add_subdirectory(manpage)/d' CMakeLists.txt
|
||||
- sed -i 's/project(ydotool C)/project(ydotool C)\nadd_definitions(-DVERSION=\\"1.0.4\\")/' CMakeLists.txt
|
||||
|
||||
- python3-build-dependencies.yaml
|
||||
- python3-dependencies.yaml
|
||||
|
||||
- name: vocalinux
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --no-deps --no-build-isolation --prefix=/app .
|
||||
- install -Dm644 packaging/flatpak/com.vocalinux.Vocalinux.desktop -t /app/share/applications/
|
||||
- install -Dm644 resources/icons/scalable/vocalinux.svg /app/share/icons/hicolor/scalable/apps/com.vocalinux.Vocalinux.svg
|
||||
- install -Dm644 resources/icons/scalable/vocalinux-microphone-off.svg /app/share/icons/hicolor/scalable/apps/com.vocalinux.Vocalinux-microphone-off.svg
|
||||
- install -Dm644 resources/icons/scalable/vocalinux-microphone.svg /app/share/icons/hicolor/scalable/apps/com.vocalinux.Vocalinux-microphone.svg
|
||||
- install -Dm644 resources/icons/scalable/vocalinux-microphone-process.svg /app/share/icons/hicolor/scalable/apps/com.vocalinux.Vocalinux-microphone-process.svg
|
||||
- install -Dm644 packaging/flatpak/com.vocalinux.Vocalinux.metainfo.xml -t /app/share/metainfo/
|
||||
sources:
|
||||
- type: dir
|
||||
path: ../..
|
||||
@@ -0,0 +1,11 @@
|
||||
name: python3-build-dependencies
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}" --prefix=${FLATPAK_DEST} meson-python pyproject-metadata
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/16/7f/d1b0c65b267a1463d752b324f11d3470e30889daefc4b9ec83029bfa30b5/meson_python-0.19.0-py3-none-any.whl
|
||||
sha256: 67b5906c37404396d23c195e12c8825506074460d4a2e7083266b845d14f0298
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/1d/0b/da4851b1e2d9c40c9bd74c0abd94510a7d797da9ccde0a90e8953751ed4a/pyproject_metadata-0.11.0-py3-none-any.whl
|
||||
sha256: 85bbecca8694e2c00f63b492c96921d6c228454057c88e7c352b2077fcaa4096
|
||||
@@ -0,0 +1,180 @@
|
||||
# Generated with flatpak-pip-generator --runtime org.gnome.Sdk//50 --yaml --ignore-pkg vosk>=0.3.45 PyGObject; sys_platform == 'linux' --output /tmp/vocalinux-flatpak-gen3/python3-dependencies pywhispercpp pydub pynput evdev requests tqdm numpy pyaudio python-xlib psutil lxml meson-python pyproject-metadata
|
||||
name: python3-dependencies
|
||||
buildsystem: simple
|
||||
build-commands: []
|
||||
modules:
|
||||
- name: python3-pywhispercpp
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
# The PyPI sdist derives its version from git metadata, which is absent in
|
||||
# the offline Flatpak build, so inject the version explicitly. --no-same-owner
|
||||
# stops GNU tar from trying to restore the archive's uid inside the build
|
||||
# sandbox (where chown to an arbitrary uid is not permitted).
|
||||
- tar --no-same-owner -xzf pywhispercpp-1.4.1.tar.gz
|
||||
- python3 -c "from pathlib import Path; p = Path('pywhispercpp-1.4.1/setup.py'); s = p.read_text(); p.write_text(s.replace('setup(\\n name=\"pywhispercpp\",', 'setup(\\n name=\"pywhispercpp\",\\n version=\"1.4.1\",'))"
|
||||
- tar --owner=0 --group=0 -czf pywhispercpp-1.4.1.tar.gz pywhispercpp-1.4.1
|
||||
- PYWHISPERCPP_VERSION=1.4.1 NO_REPAIR=1 GGML_VULKAN=1 pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}"
|
||||
--prefix=${FLATPAK_DEST} "pywhispercpp" --no-build-isolation
|
||||
# whisper.cpp installs its shared libraries next to the extension module.
|
||||
# Symlink them onto the default loader path so the extension resolves them
|
||||
# without a runtime LD_LIBRARY_PATH, regardless of the SDK's Python minor
|
||||
# version or the exact whisper.cpp soname.
|
||||
- |
|
||||
set -eu
|
||||
sitedir="$(find "${FLATPAK_DEST}/lib" -maxdepth 2 -type d -name site-packages | head -n1)"
|
||||
echo "Linking whisper.cpp libraries from: ${sitedir}"
|
||||
find "${sitedir}" -maxdepth 2 \( -name 'libggml*.so*' -o -name 'libwhisper*.so*' \) \
|
||||
-exec ln -sf {} "${FLATPAK_DEST}/lib/" \;
|
||||
ls -l "${FLATPAK_DEST}/lib/" | grep -E 'libggml|libwhisper' || true
|
||||
sources:
|
||||
- &id002
|
||||
type: file
|
||||
url: https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl
|
||||
sha256: 3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a
|
||||
- &id003
|
||||
type: file
|
||||
url: https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl
|
||||
sha256: 3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d
|
||||
- &id004
|
||||
type: file
|
||||
url: https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl
|
||||
sha256: 892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3
|
||||
- &id008
|
||||
type: file
|
||||
url: https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz
|
||||
sha256: 2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl
|
||||
sha256: e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/f6/7d/fe4b2b190d6cdcf0d5e4ca2b946d73bc449a586606a24d25ff50ffec5a5c/pywhispercpp-1.4.1.tar.gz
|
||||
sha256: 520ce9275bb6fc81e0daa2e08144a80ee006b117a18058a77860c5b1c9c122f4
|
||||
- &id005
|
||||
type: file
|
||||
url: https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl
|
||||
sha256: 4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a
|
||||
- &id007
|
||||
type: file
|
||||
url: https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl
|
||||
sha256: ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf
|
||||
- &id006
|
||||
type: file
|
||||
url: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl
|
||||
sha256: bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4
|
||||
- name: python3-pydub
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}"
|
||||
--prefix=${FLATPAK_DEST} "pydub" --no-build-isolation
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl
|
||||
sha256: 65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6
|
||||
- name: python3-pynput
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}"
|
||||
--prefix=${FLATPAK_DEST} "pynput" --no-build-isolation
|
||||
sources:
|
||||
- &id001
|
||||
type: file
|
||||
url: https://files.pythonhosted.org/packages/a5/f5/397b61091120a9ca5001041dd7bf76c385b3bfd67a0e5bcb74b852bd22a4/evdev-1.9.3.tar.gz
|
||||
sha256: 2c140e01ac8437758fa23fe5c871397412461f42d421aa20241dc8fe8cfccbc9
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/59/4f/ac3fa906ae8a375a536b12794128c5efacade9eaa917a35dfd27ce0c7400/pynput-1.8.1-py2.py3-none-any.whl
|
||||
sha256: 42dfcf27404459ca16ca889c8fb8ffe42a9fe54f722fd1a3e130728e59e768d2
|
||||
- &id009
|
||||
type: file
|
||||
url: https://files.pythonhosted.org/packages/fc/b8/ff33610932e0ee81ae7f1269c890f697d56ff74b9f5b2ee5d9b7fa2c5355/python_xlib-0.33-py2.py3-none-any.whl
|
||||
sha256: c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398
|
||||
- &id010
|
||||
type: file
|
||||
url: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl
|
||||
sha256: 4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274
|
||||
- name: python3-evdev
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}"
|
||||
--prefix=${FLATPAK_DEST} "evdev" --no-build-isolation
|
||||
sources:
|
||||
- *id001
|
||||
- name: python3-requests
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}"
|
||||
--prefix=${FLATPAK_DEST} "requests" --no-build-isolation
|
||||
sources:
|
||||
- *id002
|
||||
- *id003
|
||||
- *id004
|
||||
- *id005
|
||||
- *id006
|
||||
- name: python3-tqdm
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}"
|
||||
--prefix=${FLATPAK_DEST} "tqdm" --no-build-isolation
|
||||
sources:
|
||||
- *id007
|
||||
- name: python3-numpy
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}"
|
||||
--prefix=${FLATPAK_DEST} "numpy" --no-build-isolation
|
||||
sources:
|
||||
- *id008
|
||||
- name: python3-pyaudio
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}"
|
||||
--prefix=${FLATPAK_DEST} "pyaudio" --no-build-isolation
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/26/1d/8878c7752febb0f6716a7e1a52cb92ac98871c5aa522cba181878091607c/PyAudio-0.2.14.tar.gz
|
||||
sha256: 78dfff3879b4994d1f4fc6485646a57755c6ee3c19647a491f790a0895bd2f87
|
||||
- name: python3-python-xlib
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}"
|
||||
--prefix=${FLATPAK_DEST} "python-xlib" --no-build-isolation
|
||||
sources:
|
||||
- *id009
|
||||
- *id010
|
||||
- name: python3-psutil
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}"
|
||||
--prefix=${FLATPAK_DEST} "psutil" --no-build-isolation
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz
|
||||
sha256: 0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372
|
||||
- name: python3-lxml
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}"
|
||||
--prefix=${FLATPAK_DEST} "lxml" --no-build-isolation
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz
|
||||
sha256: bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13
|
||||
- name: python3-meson-python
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}"
|
||||
--prefix=${FLATPAK_DEST} "meson-python" --no-build-isolation
|
||||
sources:
|
||||
- type: file
|
||||
url: https://files.pythonhosted.org/packages/16/7f/d1b0c65b267a1463d752b324f11d3470e30889daefc4b9ec83029bfa30b5/meson_python-0.19.0-py3-none-any.whl
|
||||
sha256: 67b5906c37404396d23c195e12c8825506074460d4a2e7083266b845d14f0298
|
||||
- &id011
|
||||
type: file
|
||||
url: https://files.pythonhosted.org/packages/1d/0b/da4851b1e2d9c40c9bd74c0abd94510a7d797da9ccde0a90e8953751ed4a/pyproject_metadata-0.11.0-py3-none-any.whl
|
||||
sha256: 85bbecca8694e2c00f63b492c96921d6c228454057c88e7c352b2077fcaa4096
|
||||
- name: python3-pyproject-metadata
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}"
|
||||
--prefix=${FLATPAK_DEST} "pyproject-metadata" --no-build-isolation
|
||||
sources:
|
||||
- *id011
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "intltool",
|
||||
"cleanup": [ "*" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://launchpad.net/intltool/trunk/0.51.0/+download/intltool-0.51.0.tar.gz",
|
||||
"mirror-urls": [
|
||||
"https://sources.suse.com/SUSE:SLE-15-SP7:Update:BCI/000product/ca527df4d03bf96a67854a8ed2f0c64b/INCLUDED/SUSE:SLE-15-SP6:GA::intltool::45f77273c3f2ef792fa697b444183beb/intltool-0.51.0.tar.gz",
|
||||
"https://mirror-hk.koddos.net/lfs/lfs-packages/12.0/intltool-0.51.0.tar.gz",
|
||||
"https://gstreamer.freedesktop.org/src/mirror/intltool-0.51.0.tar.gz"
|
||||
],
|
||||
"sha256": "67c74d94196b153b774ab9f89b2fa6c6ba79352407037c8c14d5aeb334e959cd"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "intltool-perl5.26-regex-fixes.patch"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
Description: Escape "{", to prevent complaints from perl 5.22 and 5.26
|
||||
Author: Roderich Schupp <roderich.schupp@gmail.com>
|
||||
Author: gregor herrmann <gregoa@debian.org>
|
||||
Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=788705
|
||||
Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=826471
|
||||
Bug-Upstream: https://bugs.launchpad.net/intltool/+bug/1490906
|
||||
|
||||
Index: intltool-0.51.0/intltool-update.in
|
||||
===================================================================
|
||||
--- intltool-0.51.0.orig/intltool-update.in 2017-07-23 17:24:35.113169465 +0200
|
||||
+++ intltool-0.51.0/intltool-update.in 2017-07-23 17:24:35.109169052 +0200
|
||||
@@ -1062,13 +1062,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
- if ($str =~ /^(.*)\${?([A-Z_]+)}?(.*)$/)
|
||||
+ if ($str =~ /^(.*)\$\{?([A-Z_]+)}?(.*)$/)
|
||||
{
|
||||
my $rest = $3;
|
||||
my $untouched = $1;
|
||||
my $sub = "";
|
||||
# Ignore recursive definitions of variables
|
||||
- $sub = $varhash{$2} if defined $varhash{$2} and $varhash{$2} !~ /\${?$2}?/;
|
||||
+ $sub = $varhash{$2} if defined $varhash{$2} and $varhash{$2} !~ /\$\{?$2}?/;
|
||||
|
||||
return SubstituteVariable ("$untouched$sub$rest");
|
||||
}
|
||||
@@ -1190,10 +1190,10 @@
|
||||
$name =~ s/\(+$//g;
|
||||
$version =~ s/\(+$//g;
|
||||
|
||||
- $varhash{"PACKAGE_NAME"} = $name if (not $name =~ /\${?AC_PACKAGE_NAME}?/);
|
||||
- $varhash{"PACKAGE"} = $name if (not $name =~ /\${?PACKAGE}?/);
|
||||
- $varhash{"PACKAGE_VERSION"} = $version if (not $name =~ /\${?AC_PACKAGE_VERSION}?/);
|
||||
- $varhash{"VERSION"} = $version if (not $name =~ /\${?VERSION}?/);
|
||||
+ $varhash{"PACKAGE_NAME"} = $name if (not $name =~ /\$\{?AC_PACKAGE_NAME}?/);
|
||||
+ $varhash{"PACKAGE"} = $name if (not $name =~ /\$\{?PACKAGE}?/);
|
||||
+ $varhash{"PACKAGE_VERSION"} = $version if (not $name =~ /\$\{?AC_PACKAGE_VERSION}?/);
|
||||
+ $varhash{"VERSION"} = $version if (not $name =~ /\$\{?VERSION}?/);
|
||||
}
|
||||
|
||||
if ($conf_source =~ /^AC_INIT\(([^,\)]+),([^,\)]+)[,]?([^,\)]+)?/m)
|
||||
@@ -1219,11 +1219,11 @@
|
||||
$version =~ s/\(+$//g;
|
||||
$bugurl =~ s/\(+$//g if (defined $bugurl);
|
||||
|
||||
- $varhash{"PACKAGE_NAME"} = $name if (not $name =~ /\${?AC_PACKAGE_NAME}?/);
|
||||
- $varhash{"PACKAGE"} = $name if (not $name =~ /\${?PACKAGE}?/);
|
||||
- $varhash{"PACKAGE_VERSION"} = $version if (not $name =~ /\${?AC_PACKAGE_VERSION}?/);
|
||||
- $varhash{"VERSION"} = $version if (not $name =~ /\${?VERSION}?/);
|
||||
- $varhash{"PACKAGE_BUGREPORT"} = $bugurl if (defined $bugurl and not $bugurl =~ /\${?\w+}?/);
|
||||
+ $varhash{"PACKAGE_NAME"} = $name if (not $name =~ /\$\{?AC_PACKAGE_NAME}?/);
|
||||
+ $varhash{"PACKAGE"} = $name if (not $name =~ /\$\{?PACKAGE}?/);
|
||||
+ $varhash{"PACKAGE_VERSION"} = $version if (not $name =~ /\$\{?AC_PACKAGE_VERSION}?/);
|
||||
+ $varhash{"VERSION"} = $version if (not $name =~ /\$\{?VERSION}?/);
|
||||
+ $varhash{"PACKAGE_BUGREPORT"} = $bugurl if (defined $bugurl and not $bugurl =~ /\$\{?\w+}?/);
|
||||
}
|
||||
|
||||
# \s makes this not work, why?
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
From 729546c51806a1b3ea6cb6efb7a115b1baa811f1 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Stefan=20Br=C3=BCns?= <stefan.bruens@rwth-aachen.de>
|
||||
Date: Mon, 18 Nov 2019 19:58:53 +0100
|
||||
Subject: [PATCH 1/1] Fix HAVE_VALGRIND AM_CONDITIONAL
|
||||
|
||||
The AM_CONDITIONAL should also be run with --disable-tests, otherwise
|
||||
HAVE_VALGRIND is undefined.
|
||||
---
|
||||
configure | 4 ++--
|
||||
configure.ac | 2 +-
|
||||
2 files changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/configure b/configure
|
||||
index 831a3bb..8913b9b 100644
|
||||
--- a/configure
|
||||
+++ b/configure
|
||||
@@ -14801,6 +14801,8 @@ else
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||
$as_echo "yes" >&6; }
|
||||
have_valgrind=yes
|
||||
+fi
|
||||
+
|
||||
fi
|
||||
if test "x$have_valgrind" = "xyes"; then
|
||||
HAVE_VALGRIND_TRUE=
|
||||
@@ -14811,8 +14813,6 @@ else
|
||||
fi
|
||||
|
||||
|
||||
-fi
|
||||
-
|
||||
|
||||
|
||||
|
||||
diff --git a/configure.ac b/configure.ac
|
||||
index ace54d1..cbd38a6 100644
|
||||
--- a/configure.ac
|
||||
+++ b/configure.ac
|
||||
@@ -120,8 +120,8 @@ PKG_CHECK_MODULES(DBUSMENUTESTS, json-glib-1.0 >= $JSON_GLIB_REQUIRED_VERSION
|
||||
[have_tests=yes]
|
||||
)
|
||||
PKG_CHECK_MODULES(DBUSMENUTESTSVALGRIND, valgrind, have_valgrind=yes, have_valgrind=no)
|
||||
-AM_CONDITIONAL([HAVE_VALGRIND], [test "x$have_valgrind" = "xyes"])
|
||||
])
|
||||
+AM_CONDITIONAL([HAVE_VALGRIND], [test "x$have_valgrind" = "xyes"])
|
||||
|
||||
AC_SUBST(DBUSMENUTESTS_CFLAGS)
|
||||
AC_SUBST(DBUSMENUTESTS_LIBS)
|
||||
--
|
||||
2.46.2
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
From 8a09e6ad33c58c017c0c8fd756da036fc39428ea Mon Sep 17 00:00:00 2001
|
||||
From: Alexander Koskovich <akoskovich@pm.me>
|
||||
Date: Sun, 29 Sep 2024 13:47:54 -0400
|
||||
Subject: [PATCH 1/1] Make introspection configurable
|
||||
|
||||
---
|
||||
CMakeLists.txt | 1 +
|
||||
src/CMakeLists.txt | 4 ++++
|
||||
2 files changed, 5 insertions(+)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 0e13fcd..f3e9ec0 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -12,6 +12,7 @@ endif(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
option(ENABLE_TESTS "Enable all tests and checks" OFF)
|
||||
option(ENABLE_COVERAGE "Enable coverage reports (includes enabling all tests and checks)" OFF)
|
||||
option(ENABLE_WERROR "Treat all build warnings as errors" OFF)
|
||||
+option(ENABLE_INTROSPECTION "Enable introspection" ON)
|
||||
|
||||
if(ENABLE_COVERAGE)
|
||||
set(ENABLE_TESTS ON)
|
||||
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
|
||||
index 5b3638d..aca9481 100644
|
||||
--- a/src/CMakeLists.txt
|
||||
+++ b/src/CMakeLists.txt
|
||||
@@ -108,6 +108,8 @@ install(TARGETS "ayatana-ido3-0.4" LIBRARY DESTINATION "${CMAKE_INSTALL_FULL_LIB
|
||||
|
||||
# AyatanaIdo3-0.4.gir
|
||||
|
||||
+if (ENABLE_INTROSPECTION)
|
||||
+
|
||||
find_package(GObjectIntrospection REQUIRED QUIET)
|
||||
|
||||
if (INTROSPECTION_FOUND)
|
||||
@@ -183,3 +185,5 @@ if (INTROSPECTION_FOUND)
|
||||
endif ()
|
||||
|
||||
endif ()
|
||||
+
|
||||
+endif ()
|
||||
--
|
||||
2.46.2
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"name": "libayatana-appindicator",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"cleanup": [
|
||||
"/include",
|
||||
"/lib/pkgconfig"
|
||||
],
|
||||
"config-opts": [
|
||||
"-DENABLE_BINDINGS_MONO=NO",
|
||||
"-DENABLE_BINDINGS_VALA=NO",
|
||||
"-DENABLE_GTKDOC=NO"
|
||||
],
|
||||
"modules": [
|
||||
"../intltool/intltool-0.51.json",
|
||||
{
|
||||
"name": "libdbusmenu",
|
||||
"buildsystem": "autotools",
|
||||
"build-options": {
|
||||
"cflags": "-Wno-error"
|
||||
},
|
||||
"cleanup": [
|
||||
"*.la",
|
||||
"/include",
|
||||
"/lib/pkgconfig",
|
||||
"/libexec",
|
||||
"/share/doc",
|
||||
"/share/gtk-doc"
|
||||
],
|
||||
"config-opts": [
|
||||
"--with-gtk=3",
|
||||
"--disable-dumper",
|
||||
"--disable-static",
|
||||
"--disable-tests",
|
||||
"--disable-gtk-doc",
|
||||
"--enable-introspection=no",
|
||||
"--disable-vala"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://launchpad.net/libdbusmenu/16.04/16.04.0/+download/libdbusmenu-16.04.0.tar.gz",
|
||||
"mirror-urls": [
|
||||
"https://sources.suse.com/SUSE:SLFO:Main:Build/libdbusmenu/a5b3e4717f09f90f9cd5568a5f933e8d/libdbusmenu-16.04.0.tar.gz",
|
||||
"https://mirror.math.princeton.edu/pub/frugalware/frugalware-2.1/source/xlib/libdbusmenu/libdbusmenu-16.04.0.tar.gz",
|
||||
"https://ftp.fr.openbsd.org/pub/OpenBSD/distfiles/libdbusmenu-16.04.0.tar.gz"
|
||||
],
|
||||
"sha256": "b9cc4a2acd74509435892823607d966d424bd9ad5d0b00938f27240a1bfa878a"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"paths": [
|
||||
"0001-Fix-HAVE_VALGRIND-AM_CONDITIONAL.patch"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ayatana-ido",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"cleanup": [
|
||||
"/include",
|
||||
"/lib/pkgconfig"
|
||||
],
|
||||
"config-opts": [
|
||||
"-DENABLE_INTROSPECTION=OFF"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/AyatanaIndicators/ayatana-ido.git",
|
||||
"tag": "0.10.4",
|
||||
"commit": "f968079b09e2310fefc3fc307359025f1c74b3eb",
|
||||
"x-checker-data": {
|
||||
"type": "git",
|
||||
"tag-pattern": "^([\\d.]+)$"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"paths": [
|
||||
"0001-Make-introspection-configurable.patch"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "libayatana-indicator",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"cleanup": [
|
||||
"/include",
|
||||
"/lib/pkgconfig",
|
||||
"/libexec",
|
||||
"/share"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/AyatanaIndicators/libayatana-indicator.git",
|
||||
"tag": "0.9.4",
|
||||
"commit": "611bb384b73fa6311777ba4c41381a06f5b99dad",
|
||||
"x-checker-data": {
|
||||
"type": "git",
|
||||
"tag-pattern": "^([\\d.]+)$"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/AyatanaIndicators/libayatana-appindicator.git",
|
||||
"tag": "0.5.94",
|
||||
"commit": "31e8bb083b307e1cc96af4874a94707727bd1e79",
|
||||
"x-checker-data": {
|
||||
"type": "git",
|
||||
"tag-pattern": "^([\\d.]+)$"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -12,10 +12,12 @@ import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from .utils.paths import data_dir
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Lock file location: ~/.local/share/vocalinux/
|
||||
LOCK_FILE_DIR = Path.home() / ".local" / "share" / "vocalinux"
|
||||
# Lock file location: $XDG_DATA_HOME/vocalinux/ (defaults to ~/.local/share/vocalinux/)
|
||||
LOCK_FILE_DIR = Path(data_dir())
|
||||
LOCK_FILE_PATH = LOCK_FILE_DIR / "instance.lock"
|
||||
|
||||
# Global lock file handle
|
||||
|
||||
@@ -20,6 +20,7 @@ from typing import Callable, Optional
|
||||
|
||||
from ..common_types import RecognitionState
|
||||
from ..ui.audio_feedback import play_error_sound, play_start_sound, play_stop_sound
|
||||
from ..utils.paths import models_dir
|
||||
from ..utils.vosk_model_info import VOSK_MODEL_INFO
|
||||
from ..utils.whispercpp_model_info import WHISPERCPP_MODEL_INFO, get_model_path, is_model_downloaded
|
||||
from .command_processor import CommandProcessor
|
||||
@@ -704,7 +705,7 @@ def _show_notification(title: str, message: str, icon: str = "dialog-warning"):
|
||||
|
||||
|
||||
# Define constants
|
||||
MODELS_DIR = os.path.expanduser("~/.local/share/vocalinux/models")
|
||||
MODELS_DIR = models_dir()
|
||||
|
||||
|
||||
def _get_system_model_paths() -> list:
|
||||
|
||||
@@ -26,6 +26,8 @@ import time
|
||||
from pathlib import Path
|
||||
from typing import Callable, Optional
|
||||
|
||||
from ..utils.paths import xdg_data_home
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -57,8 +59,8 @@ except (ImportError, ValueError) as e:
|
||||
GObject = None
|
||||
|
||||
|
||||
# File paths for communication
|
||||
VOCALINUX_IBUS_DIR = Path.home() / ".local" / "share" / "vocalinux-ibus"
|
||||
# File paths for communication ($XDG_DATA_HOME, defaults to ~/.local/share)
|
||||
VOCALINUX_IBUS_DIR = Path(xdg_data_home()) / "vocalinux-ibus"
|
||||
SOCKET_PATH = VOCALINUX_IBUS_DIR / "inject.sock"
|
||||
PID_FILE = VOCALINUX_IBUS_DIR / "engine.pid"
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import time
|
||||
from enum import Enum
|
||||
from typing import Optional # noqa: F401
|
||||
|
||||
from ..utils.paths import config_dir
|
||||
from .ibus_engine import (
|
||||
IBusTextInjector,
|
||||
is_ibus_active_input_method,
|
||||
@@ -156,15 +157,31 @@ class TextInjector:
|
||||
The detected desktop environment
|
||||
"""
|
||||
session_type = os.environ.get("XDG_SESSION_TYPE", "").lower()
|
||||
wayland_display = os.environ.get("WAYLAND_DISPLAY")
|
||||
x11_display = os.environ.get("DISPLAY")
|
||||
|
||||
if session_type == "wayland":
|
||||
# Flatpak often has no Wayland socket (injection uses uinput/x11). Prefer
|
||||
# ydotool: xdotool only types into XWayland windows, not native clients.
|
||||
if os.environ.get("FLATPAK_ID") and not wayland_display and x11_display:
|
||||
if shutil.which("ydotool"):
|
||||
logger.info(
|
||||
"Flatpak on Wayland host: using ydotool (uinput) for text injection"
|
||||
)
|
||||
return DesktopEnvironment.WAYLAND
|
||||
logger.info(
|
||||
"Flatpak on Wayland host: no ydotool; falling back to xdotool/XWayland "
|
||||
"(X11 apps only)"
|
||||
)
|
||||
return DesktopEnvironment.WAYLAND_XDOTOOL
|
||||
return DesktopEnvironment.WAYLAND
|
||||
elif session_type == "x11":
|
||||
return DesktopEnvironment.X11
|
||||
else:
|
||||
# Try to detect based on other methods
|
||||
if "WAYLAND_DISPLAY" in os.environ:
|
||||
if wayland_display:
|
||||
return DesktopEnvironment.WAYLAND
|
||||
elif "DISPLAY" in os.environ:
|
||||
elif x11_display:
|
||||
return DesktopEnvironment.X11
|
||||
else:
|
||||
logger.warning("Could not detect desktop environment, defaulting to X11")
|
||||
@@ -236,6 +253,41 @@ class TextInjector:
|
||||
# pgrep missing, timed out, or otherwise unavailable -> assume not running
|
||||
return False
|
||||
|
||||
def _ensure_ydotoold(self) -> bool:
|
||||
"""Start ydotoold if needed so ydotool can inject via uinput.
|
||||
|
||||
ydotool 1.x talks to a daemon that owns /dev/uinput. Inside Flatpak we
|
||||
start the daemon on demand when the app is granted device access.
|
||||
"""
|
||||
if self._is_ydotoold_running():
|
||||
return True
|
||||
ydotoold = shutil.which("ydotoold")
|
||||
if not ydotoold:
|
||||
return False
|
||||
if not os.path.exists("/dev/uinput"):
|
||||
logger.warning(
|
||||
"ydotoold needs /dev/uinput (Flatpak: grant --device=all). "
|
||||
"Text injection into native Wayland apps will fail."
|
||||
)
|
||||
return False
|
||||
try:
|
||||
subprocess.Popen(
|
||||
[ydotoold],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
start_new_session=True,
|
||||
)
|
||||
except OSError as e:
|
||||
logger.warning(f"Could not start ydotoold: {e}")
|
||||
return False
|
||||
for _ in range(40):
|
||||
time.sleep(0.05)
|
||||
if self._is_ydotoold_running():
|
||||
logger.info("Started ydotoold for uinput text injection")
|
||||
return True
|
||||
logger.warning("ydotoold did not become ready in time")
|
||||
return False
|
||||
|
||||
def _check_dependencies(self):
|
||||
"""Check for the required tools for text injection."""
|
||||
ibus_requested = False
|
||||
@@ -312,17 +364,16 @@ class TextInjector:
|
||||
ydotool_available = shutil.which("ydotool") is not None
|
||||
xdotool_available = shutil.which("xdotool") is not None
|
||||
|
||||
if ydotool_available and self._is_ydotoold_running():
|
||||
# ydotoold is running; ydotool can inject via the daemon.
|
||||
# Prefer ydotool when the daemon is (or can be) ready. Flatpak ships
|
||||
# ydotool for native Wayland typing; wtype needs a Wayland socket.
|
||||
if ydotool_available and self._ensure_ydotoold():
|
||||
self.wayland_tool = "ydotool"
|
||||
logger.info(f"Using {self.wayland_tool} for Wayland text injection")
|
||||
logger.info("Using ydotool for Wayland text injection")
|
||||
elif ydotool_available and not wtype_available:
|
||||
# ydotool present but no daemon and no wtype: try ydotool anyway
|
||||
# (direct uinput mode, requires access to /dev/uinput).
|
||||
self.wayland_tool = "ydotool"
|
||||
logger.warning(
|
||||
"ydotoold daemon not running; using ydotool in direct mode "
|
||||
"(may have latency/permission issues)"
|
||||
"ydotoold not ready; using ydotool without daemon "
|
||||
"(may fail or have latency/permission issues)"
|
||||
)
|
||||
elif wtype_available:
|
||||
self.wayland_tool = "wtype"
|
||||
@@ -401,13 +452,19 @@ class TextInjector:
|
||||
|
||||
def _get_clipboard_tools(self):
|
||||
tools = []
|
||||
if self._session_environment == DesktopEnvironment.WAYLAND and shutil.which("wl-copy"):
|
||||
# Prefer wl-copy on Wayland (including Flatpak with --socket=wayland).
|
||||
host_is_wayland = (
|
||||
self._session_environment == DesktopEnvironment.WAYLAND
|
||||
or os.environ.get("XDG_SESSION_TYPE", "").lower() == "wayland"
|
||||
or bool(os.environ.get("WAYLAND_DISPLAY"))
|
||||
)
|
||||
if host_is_wayland and shutil.which("wl-copy"):
|
||||
tools.append("wl-copy")
|
||||
if shutil.which("xclip"):
|
||||
tools.append("xclip")
|
||||
if shutil.which("xsel"):
|
||||
tools.append("xsel")
|
||||
if self._session_environment != DesktopEnvironment.WAYLAND and shutil.which("wl-copy"):
|
||||
if not host_is_wayland and shutil.which("wl-copy"):
|
||||
tools.append("wl-copy")
|
||||
return tools
|
||||
|
||||
@@ -639,7 +696,7 @@ class TextInjector:
|
||||
try:
|
||||
import json
|
||||
|
||||
config_path = os.path.expanduser("~/.config/vocalinux/config.json")
|
||||
config_path = os.path.join(config_dir(), "config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r") as f:
|
||||
config = json.load(f)
|
||||
@@ -1083,30 +1140,26 @@ class TextInjector:
|
||||
# modify typed keys.
|
||||
self._wait_for_modifiers_released()
|
||||
|
||||
# ydotool emits *positional* evdev keycodes, so `ydotool type` is
|
||||
# re-interpreted through the active keyboard layout, which it assumes is
|
||||
# US QWERTY. On any other layout (AZERTY, QWERTZ, Dvorak, ...) the text
|
||||
# comes out scrambled (e.g. "message" -> ",essqge" on AZERTY), and
|
||||
# non-ASCII characters are dropped entirely. Always paste via the
|
||||
# clipboard instead: Ctrl+V is layout-independent and Unicode-safe.
|
||||
# Prefer clipboard + Ctrl+V for ydotool: one paste, layout-independent.
|
||||
# Flatpak ships wl-copy (--socket=wayland) so native Wayland apps get
|
||||
# bulk paste; character-by-character type is only a fallback.
|
||||
if self.wayland_tool == "ydotool":
|
||||
logger.info("Using clipboard paste for ydotool (layout-independent, Unicode-safe)")
|
||||
if not self._ensure_ydotoold():
|
||||
logger.warning("ydotoold not ready before injection")
|
||||
logger.info("Using clipboard paste for ydotool (instant, layout-independent)")
|
||||
if self._inject_via_clipboard_paste(text):
|
||||
return
|
||||
logger.warning(
|
||||
"Clipboard paste failed, falling back to ydotool type "
|
||||
"(text may be scrambled on non-US layouts)"
|
||||
"(character-by-character; text may be scrambled on non-US layouts)"
|
||||
)
|
||||
|
||||
if self.wayland_tool == "wtype":
|
||||
cmd = ["wtype", text]
|
||||
else: # ydotool
|
||||
# Pass explicit timing parameters to speed up injection.
|
||||
# ydotool defaults to --key-delay 12 (or 20 in older versions).
|
||||
# We use a faster default configurable via environment variable.
|
||||
# IMPORTANT: key-delay must stay > 0 to avoid Shift-leak bug where
|
||||
# capital letters corrupt following characters (e.g. "Can you" -> "CAN YOu").
|
||||
key_delay = os.environ.get("VOCALINUX_YDOTOOL_KEY_DELAY", "8")
|
||||
# Keep key-delay > 0 to avoid Shift-leak ("Can you" -> "CAN YOu").
|
||||
# Low delay so fallback typing finishes quickly for long phrases.
|
||||
key_delay = os.environ.get("VOCALINUX_YDOTOOL_KEY_DELAY", "2")
|
||||
cmd = ["ydotool", "type", "--key-delay", key_delay, text]
|
||||
|
||||
try:
|
||||
|
||||
@@ -29,6 +29,11 @@ def get_autostart_file() -> Path:
|
||||
|
||||
|
||||
def get_exec_command() -> str:
|
||||
# Host session manager cannot exec /app/bin; use the flatpak launcher instead.
|
||||
flatpak_id = os.environ.get("FLATPAK_ID")
|
||||
if flatpak_id:
|
||||
return f"flatpak run {flatpak_id} --start-minimized"
|
||||
|
||||
installed_command = shutil.which("vocalinux")
|
||||
if installed_command:
|
||||
return f"{shlex.quote(installed_command)} --start-minimized"
|
||||
|
||||
@@ -10,10 +10,12 @@ import logging
|
||||
import os
|
||||
from typing import Any, Optional
|
||||
|
||||
from ..utils.paths import config_dir
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Define constants
|
||||
CONFIG_DIR = os.path.expanduser("~/.config/vocalinux")
|
||||
CONFIG_DIR = config_dir()
|
||||
CONFIG_FILE = os.path.join(CONFIG_DIR, "config.json")
|
||||
|
||||
# Default configuration
|
||||
|
||||
@@ -58,15 +58,22 @@ class DesktopEnvironment:
|
||||
'x11', 'wayland', or 'unknown'
|
||||
"""
|
||||
session_type = os.environ.get("XDG_SESSION_TYPE", "").lower()
|
||||
wayland_display = os.environ.get("WAYLAND_DISPLAY")
|
||||
x11_display = os.environ.get("DISPLAY")
|
||||
if session_type == "wayland":
|
||||
# Flatpak often has X11 only (for injection). Session is still Wayland
|
||||
# on the host; create_backend prefers evdev for global shortcuts.
|
||||
if os.environ.get("FLATPAK_ID") and not wayland_display and x11_display:
|
||||
logger.info("Flatpak has X11/XWayland access only; treating session as X11")
|
||||
return DesktopEnvironment.X11
|
||||
return DesktopEnvironment.WAYLAND
|
||||
elif session_type == "x11":
|
||||
return DesktopEnvironment.X11
|
||||
|
||||
# Fallback to environment variables
|
||||
if "WAYLAND_DISPLAY" in os.environ:
|
||||
if wayland_display:
|
||||
return DesktopEnvironment.WAYLAND
|
||||
elif "DISPLAY" in os.environ:
|
||||
elif x11_display:
|
||||
return DesktopEnvironment.X11
|
||||
|
||||
logger.warning("Could not detect desktop environment, defaulting to unknown")
|
||||
@@ -116,27 +123,34 @@ def create_backend(
|
||||
else:
|
||||
logger.warning(f"Unknown preferred backend: '{preferred_backend}'")
|
||||
|
||||
# Auto-select based on environment
|
||||
if env == DesktopEnvironment.WAYLAND:
|
||||
# On Wayland, prefer evdev (if available) as pynput doesn't work
|
||||
# Prefer evdev whenever pynput cannot see keys from other apps:
|
||||
# - native Wayland (no X11 grab path)
|
||||
# - Flatpak: even with --socket=x11, XWayland only delivers keys to the
|
||||
# focused X client, so double-tap Ctrl and other globals need /dev/input
|
||||
need_evdev = env == DesktopEnvironment.WAYLAND or bool(os.environ.get("FLATPAK_ID"))
|
||||
if need_evdev:
|
||||
reason = "Flatpak" if os.environ.get("FLATPAK_ID") else "Wayland"
|
||||
if EVDEV_AVAILABLE:
|
||||
logger.info("Using evdev backend for Wayland")
|
||||
logger.info(f"Using evdev backend for global shortcuts ({reason})")
|
||||
backend = EvdevKeyboardBackend(shortcut=shortcut, mode=mode) # type: ignore
|
||||
if backend.is_available():
|
||||
return backend
|
||||
hint = backend.get_permission_hint()
|
||||
if hint:
|
||||
logger.warning(f"Evdev backend not usable on Wayland: {hint}")
|
||||
logger.warning("Keyboard shortcuts will not work on Wayland without evdev access")
|
||||
logger.warning(f"Evdev backend not usable on {reason}: {hint}")
|
||||
logger.warning(
|
||||
"Keyboard shortcuts will not work without access to keyboard input devices"
|
||||
)
|
||||
return None
|
||||
logger.warning(
|
||||
"evdev backend not available on Wayland. "
|
||||
"evdev backend not available (%s). "
|
||||
"Keyboard shortcuts will not work. Install python-evdev and "
|
||||
"add your user to the 'input' group."
|
||||
"add your user to the 'input' group (or grant Flatpak --device=input).",
|
||||
reason,
|
||||
)
|
||||
return None
|
||||
|
||||
# Default to pynput for X11 or unknown
|
||||
# Default to pynput for native X11 (or unknown)
|
||||
if PYNPUT_AVAILABLE:
|
||||
logger.info("Using pynput backend")
|
||||
return PynputKeyboardBackend(shortcut=shortcut, mode=mode) # type: ignore
|
||||
|
||||
@@ -11,6 +11,8 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Callable, Optional
|
||||
|
||||
from ..utils.paths import data_dir
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -62,8 +64,8 @@ class LoggingManager:
|
||||
self.log_callbacks: list[Callable[[LogRecord], None]] = []
|
||||
self.lock = threading.Lock()
|
||||
|
||||
# Create logs directory
|
||||
self.logs_dir = Path.home() / ".local" / "share" / "vocalinux" / "logs"
|
||||
# Create logs directory ($XDG_DATA_HOME/vocalinux/logs; Flatpak-safe)
|
||||
self.logs_dir = Path(data_dir()) / "logs"
|
||||
self.logs_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Set up custom log handler
|
||||
|
||||
@@ -30,6 +30,7 @@ from gi.repository import Gdk, GLib, Gtk, Pango # noqa: E402
|
||||
|
||||
from ..common_types import RecognitionState # noqa: E402
|
||||
from ..speech_recognition.silero_vad import is_silero_available # noqa: E402
|
||||
from ..utils.paths import models_dir # noqa: E402
|
||||
from ..utils.vosk_model_info import SUPPORTED_LANGUAGES, VOSK_MODEL_INFO # noqa: E402
|
||||
from ..utils.whispercpp_model_info import MODEL_SIZES as WHISPERCPP_MODEL_SIZES
|
||||
from ..utils.whispercpp_model_info import (
|
||||
@@ -315,7 +316,7 @@ def get_available_engines():
|
||||
|
||||
|
||||
# Models directory
|
||||
MODELS_DIR = os.path.expanduser("~/.local/share/vocalinux/models")
|
||||
MODELS_DIR = models_dir()
|
||||
SYSTEM_MODELS_DIRS = [
|
||||
"/usr/local/share/vocalinux/models",
|
||||
"/usr/share/vocalinux/models",
|
||||
@@ -597,7 +598,7 @@ def _prevent_scroll_on_hover(widget: Gtk.Widget):
|
||||
|
||||
def _get_whisper_cache_dir() -> str:
|
||||
"""Get the Whisper model cache directory."""
|
||||
return os.path.expanduser("~/.local/share/vocalinux/models/whisper")
|
||||
return os.path.join(MODELS_DIR, "whisper")
|
||||
|
||||
|
||||
def _is_whisper_model_downloaded(model_name: str) -> bool:
|
||||
|
||||
@@ -37,18 +37,35 @@ from .settings_dialog import SettingsDialog
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Define constants
|
||||
APP_ID = "vocalinux"
|
||||
# Flatpak StatusNotifier host resolves exported <app-id>-* icons, not /app paths.
|
||||
FLATPAK_ID = os.environ.get("FLATPAK_ID")
|
||||
APP_ID = FLATPAK_ID or "vocalinux"
|
||||
|
||||
# Initialize resource manager
|
||||
_resource_manager = ResourceManager()
|
||||
ICON_DIR = _resource_manager.icons_dir
|
||||
|
||||
# Icon file names
|
||||
# Bundled icon file names (paths under ICON_DIR).
|
||||
DEFAULT_ICON = "vocalinux-microphone-off"
|
||||
ACTIVE_ICON = "vocalinux-microphone"
|
||||
PROCESSING_ICON = "vocalinux-microphone-process"
|
||||
|
||||
|
||||
def _themed_icon_names() -> dict:
|
||||
"""Icon theme names for the current runtime (host-exported names in Flatpak)."""
|
||||
if FLATPAK_ID:
|
||||
return {
|
||||
"default": f"{FLATPAK_ID}-microphone-off",
|
||||
"active": f"{FLATPAK_ID}-microphone",
|
||||
"processing": f"{FLATPAK_ID}-microphone-process",
|
||||
}
|
||||
return {
|
||||
"default": DEFAULT_ICON,
|
||||
"active": ACTIVE_ICON,
|
||||
"processing": PROCESSING_ICON,
|
||||
}
|
||||
|
||||
|
||||
# /dev/input settle-detection tuning (used after resume)
|
||||
_INPUT_SETTLE_SECONDS = 2
|
||||
_INPUT_MONITOR_CAP_SECONDS = 10
|
||||
@@ -96,11 +113,7 @@ class TrayIndicator:
|
||||
"active": _resource_manager.get_icon_path(ACTIVE_ICON),
|
||||
"processing": _resource_manager.get_icon_path(PROCESSING_ICON),
|
||||
}
|
||||
self.icon_names = {
|
||||
"default": DEFAULT_ICON,
|
||||
"active": ACTIVE_ICON,
|
||||
"processing": PROCESSING_ICON,
|
||||
}
|
||||
self.icon_names = _themed_icon_names()
|
||||
|
||||
# Register for speech recognition state changes
|
||||
self.speech_engine.register_state_callback(self._on_recognition_state_changed)
|
||||
@@ -190,14 +203,22 @@ class TrayIndicator:
|
||||
exists = os.path.exists(path)
|
||||
logger.info(f"Icon '{name}' ({path}): {'exists' if exists else 'missing'}")
|
||||
|
||||
initial_icon = _themed_icon_names()["default"]
|
||||
try:
|
||||
self.indicator = AppIndicator3.Indicator.new_with_path(
|
||||
APP_ID,
|
||||
DEFAULT_ICON,
|
||||
AppIndicator3.IndicatorCategory.APPLICATION_STATUS,
|
||||
ICON_DIR,
|
||||
)
|
||||
self.indicator.set_icon_theme_path(ICON_DIR)
|
||||
if FLATPAK_ID:
|
||||
self.indicator = AppIndicator3.Indicator.new(
|
||||
APP_ID,
|
||||
initial_icon,
|
||||
AppIndicator3.IndicatorCategory.APPLICATION_STATUS,
|
||||
)
|
||||
else:
|
||||
self.indicator = AppIndicator3.Indicator.new_with_path(
|
||||
APP_ID,
|
||||
initial_icon,
|
||||
AppIndicator3.IndicatorCategory.APPLICATION_STATUS,
|
||||
ICON_DIR,
|
||||
)
|
||||
self.indicator.set_icon_theme_path(ICON_DIR)
|
||||
self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create AppIndicator: {e}")
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
"""XDG base-directory helpers (Flatpak-safe via XDG_CONFIG_HOME / XDG_DATA_HOME)."""
|
||||
|
||||
import os
|
||||
|
||||
APP_DIR_NAME = "vocalinux"
|
||||
|
||||
|
||||
def xdg_config_home() -> str:
|
||||
"""Return ``$XDG_CONFIG_HOME`` or ``~/.config`` (empty values treated as unset)."""
|
||||
return os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config")
|
||||
|
||||
|
||||
def xdg_data_home() -> str:
|
||||
"""Return ``$XDG_DATA_HOME`` or ``~/.local/share`` (empty values treated as unset)."""
|
||||
return os.environ.get("XDG_DATA_HOME") or os.path.expanduser("~/.local/share")
|
||||
|
||||
|
||||
def config_dir() -> str:
|
||||
"""Return the Vocalinux configuration directory."""
|
||||
return os.path.join(xdg_config_home(), APP_DIR_NAME)
|
||||
|
||||
|
||||
def data_dir() -> str:
|
||||
"""Return the Vocalinux data directory."""
|
||||
return os.path.join(xdg_data_home(), APP_DIR_NAME)
|
||||
|
||||
|
||||
def models_dir() -> str:
|
||||
"""Return the directory where speech-recognition models are stored."""
|
||||
return os.path.join(data_dir(), "models")
|
||||
@@ -11,6 +11,8 @@ import subprocess
|
||||
from functools import lru_cache
|
||||
from typing import Optional
|
||||
|
||||
from .paths import models_dir
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Whisper.cpp model information
|
||||
@@ -311,14 +313,14 @@ def get_model_path(model_name: str) -> str:
|
||||
Returns:
|
||||
Path to the model file
|
||||
"""
|
||||
models_dir = os.path.expanduser("~/.local/share/vocalinux/models/whispercpp")
|
||||
os.makedirs(models_dir, exist_ok=True)
|
||||
whispercpp_dir = os.path.join(models_dir(), "whispercpp")
|
||||
os.makedirs(whispercpp_dir, exist_ok=True)
|
||||
|
||||
model_info = WHISPERCPP_MODEL_INFO.get(model_name)
|
||||
if model_info and model_info.get("url"):
|
||||
return os.path.join(models_dir, os.path.basename(model_info["url"]))
|
||||
return os.path.join(whispercpp_dir, os.path.basename(model_info["url"]))
|
||||
|
||||
return os.path.join(models_dir, f"ggml-{model_name}.bin")
|
||||
return os.path.join(whispercpp_dir, f"ggml-{model_name}.bin")
|
||||
|
||||
|
||||
def is_model_downloaded(model_name: str) -> bool:
|
||||
|
||||
@@ -29,6 +29,25 @@ class TestAutostartManager(unittest.TestCase):
|
||||
command = autostart_manager.get_exec_command()
|
||||
self.assertEqual(command, "/usr/bin/python3 -m vocalinux.main --start-minimized")
|
||||
|
||||
def test_get_exec_command_flatpak_uses_flatpak_run(self):
|
||||
# Inside a Flatpak the host session manager must use the flatpak launcher,
|
||||
# which is always on PATH, rather than /app/bin/vocalinux.
|
||||
with patch.dict("os.environ", {"FLATPAK_ID": "com.vocalinux.Vocalinux"}, clear=False):
|
||||
command = autostart_manager.get_exec_command()
|
||||
self.assertEqual(command, "flatpak run com.vocalinux.Vocalinux --start-minimized")
|
||||
|
||||
def test_enable_autostart_flatpak_writes_flatpak_run_exec(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
with patch.dict(
|
||||
"os.environ",
|
||||
{"XDG_CONFIG_HOME": tmp_dir, "FLATPAK_ID": "com.vocalinux.Vocalinux"},
|
||||
clear=False,
|
||||
):
|
||||
self.assertTrue(autostart_manager.enable_autostart())
|
||||
desktop_file = Path(tmp_dir) / "autostart" / "vocalinux.desktop"
|
||||
content = desktop_file.read_text(encoding="utf-8")
|
||||
self.assertIn("Exec=flatpak run com.vocalinux.Vocalinux --start-minimized", content)
|
||||
|
||||
def test_enable_disable_autostart_creates_and_removes_entry(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
with (
|
||||
|
||||
@@ -48,6 +48,73 @@ class TestDesktopEnvironmentDetect:
|
||||
result = DesktopEnvironment.detect()
|
||||
assert result == DesktopEnvironment.X11
|
||||
|
||||
def test_detect_flatpak_wayland_without_socket_uses_x11(self):
|
||||
"""Flatpak with only X11/XWayland is labeled X11 (injection path).
|
||||
|
||||
Global shortcuts still prefer evdev via create_backend when FLATPAK_ID
|
||||
is set — pynput over XWayland is not truly global.
|
||||
"""
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"XDG_SESSION_TYPE": "wayland",
|
||||
"DISPLAY": ":0",
|
||||
}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
assert DesktopEnvironment.detect() == DesktopEnvironment.X11
|
||||
|
||||
def test_detect_flatpak_wayland_with_socket_stays_wayland(self):
|
||||
"""With a Wayland socket exposed, detection still reports Wayland."""
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"XDG_SESSION_TYPE": "wayland",
|
||||
"WAYLAND_DISPLAY": "wayland-0",
|
||||
}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
assert DesktopEnvironment.detect() == DesktopEnvironment.WAYLAND
|
||||
|
||||
def test_create_backend_flatpak_prefers_evdev(self):
|
||||
"""Flatpak must use evdev so hotkeys work when another app is focused."""
|
||||
from vocalinux.ui.keyboard_backends import create_backend
|
||||
|
||||
fake = MagicMock()
|
||||
fake.is_available.return_value = True
|
||||
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"XDG_SESSION_TYPE": "wayland",
|
||||
"DISPLAY": ":0",
|
||||
}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
with (
|
||||
patch.object(kb_module, "EVDEV_AVAILABLE", True),
|
||||
patch.object(kb_module, "EvdevKeyboardBackend", return_value=fake) as mock_evdev,
|
||||
patch.object(kb_module, "PYNPUT_AVAILABLE", True),
|
||||
patch.object(kb_module, "PynputKeyboardBackend") as mock_pynput,
|
||||
):
|
||||
backend = create_backend()
|
||||
assert backend is fake
|
||||
mock_evdev.assert_called_once()
|
||||
mock_pynput.assert_not_called()
|
||||
|
||||
def test_create_backend_native_x11_prefers_pynput(self):
|
||||
"""Outside Flatpak on X11, pynput remains the default."""
|
||||
from vocalinux.ui.keyboard_backends import create_backend
|
||||
|
||||
env = {"XDG_SESSION_TYPE": "x11", "DISPLAY": ":0"}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
with (
|
||||
patch.object(kb_module, "EVDEV_AVAILABLE", True),
|
||||
patch.object(kb_module, "EvdevKeyboardBackend") as mock_evdev,
|
||||
patch.object(kb_module, "PYNPUT_AVAILABLE", True),
|
||||
patch.object(
|
||||
kb_module, "PynputKeyboardBackend", return_value=MagicMock()
|
||||
) as mock_pynput,
|
||||
):
|
||||
backend = create_backend()
|
||||
assert backend is mock_pynput.return_value
|
||||
mock_pynput.assert_called_once()
|
||||
mock_evdev.assert_not_called()
|
||||
|
||||
def test_detect_xdg_session_type_case_insensitive(self):
|
||||
"""Test that XDG_SESSION_TYPE detection is case-insensitive."""
|
||||
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "WAYLAND"}, clear=False):
|
||||
|
||||
@@ -117,6 +117,24 @@ class TestLoggingManager:
|
||||
assert logging_manager.log_callbacks == []
|
||||
assert logging_manager.handler is not None
|
||||
|
||||
def test_logs_dir_honours_xdg_data_home(self, tmp_path, monkeypatch):
|
||||
"""Logs must follow XDG_DATA_HOME so Flatpak sandbox paths work."""
|
||||
import vocalinux.ui.logging_manager as lm
|
||||
|
||||
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
|
||||
lm._logging_manager = None
|
||||
root_logger = logging.getLogger()
|
||||
for h in [h for h in root_logger.handlers if isinstance(h, lm.LoggingHandler)]:
|
||||
root_logger.removeHandler(h)
|
||||
|
||||
manager = lm.LoggingManager(max_records=10)
|
||||
try:
|
||||
assert manager.logs_dir == tmp_path / "vocalinux" / "logs"
|
||||
assert manager.logs_dir.is_dir()
|
||||
finally:
|
||||
root_logger.removeHandler(manager.handler)
|
||||
lm._logging_manager = None
|
||||
|
||||
def test_add_log_record(self, logging_manager):
|
||||
"""Test adding a log record."""
|
||||
from vocalinux.ui.logging_manager import LogRecord
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Tests for the XDG base-directory helpers in vocalinux.utils.paths."""
|
||||
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
from vocalinux.utils import paths
|
||||
|
||||
|
||||
class TestXdgHelpers:
|
||||
def test_defaults_when_env_unset(self):
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
assert paths.xdg_config_home() == os.path.expanduser("~/.config")
|
||||
assert paths.xdg_data_home() == os.path.expanduser("~/.local/share")
|
||||
assert paths.config_dir() == os.path.expanduser("~/.config/vocalinux")
|
||||
assert paths.data_dir() == os.path.expanduser("~/.local/share/vocalinux")
|
||||
assert paths.models_dir() == os.path.expanduser("~/.local/share/vocalinux/models")
|
||||
|
||||
def test_honours_xdg_environment(self):
|
||||
env = {
|
||||
"XDG_CONFIG_HOME": "/run/user/1000/cfg",
|
||||
"XDG_DATA_HOME": "/run/user/1000/data",
|
||||
}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
assert paths.config_dir() == "/run/user/1000/cfg/vocalinux"
|
||||
assert paths.data_dir() == "/run/user/1000/data/vocalinux"
|
||||
assert paths.models_dir() == "/run/user/1000/data/vocalinux/models"
|
||||
|
||||
def test_empty_env_falls_back_to_default(self):
|
||||
# The XDG spec says empty values must be treated as unset.
|
||||
with patch.dict(os.environ, {"XDG_CONFIG_HOME": "", "XDG_DATA_HOME": ""}, clear=True):
|
||||
assert paths.xdg_config_home() == os.path.expanduser("~/.config")
|
||||
assert paths.xdg_data_home() == os.path.expanduser("~/.local/share")
|
||||
|
||||
def test_flatpak_style_data_home(self):
|
||||
# Flatpak points XDG_DATA_HOME at ~/.var/app/<app-id>/data.
|
||||
flatpak_data = "/home/user/.var/app/com.vocalinux.Vocalinux/data"
|
||||
with patch.dict(os.environ, {"XDG_DATA_HOME": flatpak_data}, clear=True):
|
||||
assert paths.models_dir() == os.path.join(flatpak_data, "vocalinux", "models")
|
||||
@@ -205,9 +205,10 @@ class TestTextInjector(unittest.TestCase):
|
||||
# Inject text
|
||||
injector.inject_text("Hello world")
|
||||
|
||||
# Verify ydotool was called correctly with timing parameters
|
||||
# Clipboard paste fails (no wl-copy/xclip/xsel in this mock); type fallback.
|
||||
# Default key-delay is 2 (overridable via VOCALINUX_YDOTOOL_KEY_DELAY).
|
||||
self.mock_subprocess.assert_any_call(
|
||||
["ydotool", "type", "--key-delay", "8", "Hello world"],
|
||||
["ydotool", "type", "--key-delay", "2", "Hello world"],
|
||||
check=True,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
|
||||
@@ -100,6 +100,49 @@ class TestDetectEnvironment(unittest.TestCase):
|
||||
result, [DesktopEnvironment.WAYLAND, DesktopEnvironment.WAYLAND_IBUS]
|
||||
)
|
||||
|
||||
def test_detect_flatpak_prefers_ydotool_when_available(self):
|
||||
"""Flatpak prefers ydotool so injection reaches native Wayland apps."""
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment
|
||||
|
||||
obj = _make_injector(DesktopEnvironment.X11)
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"XDG_SESSION_TYPE": "wayland",
|
||||
"DISPLAY": ":0",
|
||||
}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
with patch("vocalinux.text_injection.text_injector.shutil.which") as which:
|
||||
which.side_effect = lambda name: "/app/bin/ydotool" if name == "ydotool" else None
|
||||
self.assertEqual(obj._detect_environment(), DesktopEnvironment.WAYLAND)
|
||||
|
||||
def test_detect_flatpak_without_ydotool_uses_xwayland(self):
|
||||
"""Without ydotool, Flatpak falls back to xdotool/XWayland."""
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment
|
||||
|
||||
obj = _make_injector(DesktopEnvironment.X11)
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"XDG_SESSION_TYPE": "wayland",
|
||||
"DISPLAY": ":0",
|
||||
}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
with patch("vocalinux.text_injection.text_injector.shutil.which", return_value=None):
|
||||
self.assertEqual(obj._detect_environment(), DesktopEnvironment.WAYLAND_XDOTOOL)
|
||||
|
||||
def test_detect_flatpak_with_wayland_socket_stays_wayland(self):
|
||||
"""If the Wayland socket is exposed, normal Wayland detection still applies."""
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment
|
||||
|
||||
obj = _make_injector(DesktopEnvironment.WAYLAND)
|
||||
env = {
|
||||
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
||||
"XDG_SESSION_TYPE": "wayland",
|
||||
"WAYLAND_DISPLAY": "wayland-0",
|
||||
"DISPLAY": ":0",
|
||||
}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
self.assertEqual(obj._detect_environment(), DesktopEnvironment.WAYLAND)
|
||||
|
||||
def test_detect_x11(self):
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment
|
||||
|
||||
|
||||
@@ -547,3 +547,36 @@ class TestTrayIndicator(unittest.TestCase):
|
||||
delattr(self.tray_indicator, "menu")
|
||||
|
||||
self.tray_indicator._set_menu_item_enabled("Start Voice Typing", True)
|
||||
|
||||
|
||||
class TestTrayIndicatorFlatpakIcons(unittest.TestCase):
|
||||
"""Tray icon names must adapt to the Flatpak runtime.
|
||||
|
||||
Inside a Flatpak the StatusNotifier host runs outside the sandbox and resolves
|
||||
icons from the exported ``<app-id>-*`` theme, so the indicator must reference
|
||||
those names rather than the bundled ``vocalinux-*`` names.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
sys.modules["gi"] = mock_gi
|
||||
sys.modules["gi.repository"] = mock_gi_repository
|
||||
for mod in [k for k in list(sys.modules.keys()) if "tray_indicator" in k]:
|
||||
del sys.modules[mod]
|
||||
|
||||
def test_themed_icon_names_outside_flatpak(self):
|
||||
import vocalinux.ui.tray_indicator as tray
|
||||
|
||||
with patch.object(tray, "FLATPAK_ID", None):
|
||||
names = tray._themed_icon_names()
|
||||
self.assertEqual(names["default"], "vocalinux-microphone-off")
|
||||
self.assertEqual(names["active"], "vocalinux-microphone")
|
||||
self.assertEqual(names["processing"], "vocalinux-microphone-process")
|
||||
|
||||
def test_themed_icon_names_inside_flatpak(self):
|
||||
import vocalinux.ui.tray_indicator as tray
|
||||
|
||||
with patch.object(tray, "FLATPAK_ID", "com.vocalinux.Vocalinux"):
|
||||
names = tray._themed_icon_names()
|
||||
self.assertEqual(names["default"], "com.vocalinux.Vocalinux-microphone-off")
|
||||
self.assertEqual(names["active"], "com.vocalinux.Vocalinux-microphone")
|
||||
self.assertEqual(names["processing"], "com.vocalinux.Vocalinux-microphone-process")
|
||||
|
||||
Reference in New Issue
Block a user