# NOTE for future engine variants: the vocalinux-engine virtual provide is the # extension point. Add -engine-vosk / -engine-whisper subpackages later that # also "Provides: vocalinux-engine" and let users pick. See # docs/PACKAGING_NOTES.md (TODO section). %global upstream_tag v0.18.5 # tag without the leading v (tarball top-level dir is repo-tag_version) %global tag_version 0.18.5 %global gh_owner VocaHQ %global gh_repo vocalinux # SELinux policy macros (per fedoraproject.org/wiki/SELinux/IndependentPolicy) %global selinuxtype targeted %global modulename vocalinux Name: vocalinux Version: 0.18.5 Release: 1%{?dist} Summary: Free, offline voice dictation system for Linux # Upstream relicensed GPL-3.0 -> AGPL-3.0 after v0.15.0 (main branch is # AGPL-3.0-only). This field MUST match the packaged release's LICENSE: # revisit on the next upstream release bump. License: GPL-3.0-only URL: https://github.com/%{gh_owner}/%{gh_repo} Source0: https://github.com/%{gh_owner}/%{gh_repo}/archive/refs/tags/%{upstream_tag}.tar.gz#/%{name}-%{version}.tar.gz # SELinux policy sources (kept in this packaging repo under selinux/, staged # into the RPM source dir by CI before rpmbuild -bs) Source1: vocalinux.te Source2: vocalinux.fc Source3: vocalinux.if Source4: vocalinux.rpmlintrc BuildArch: noarch BuildRequires: python3-devel BuildRequires: pyproject-rpm-macros # dependencies for the check section BuildRequires: python3-pytest BuildRequires: python3-pytest-mock BuildRequires: python3-pytest-timeout # desktop/appstream validation in the check section BuildRequires: desktop-file-utils BuildRequires: libappstream-glib # tests/test_{dependency_exports,distro_package_map,installer_cuda_diagnostics, # installer_pinned_dependencies,native_packaging,snap_packaging}.py import yaml BuildRequires: python3-pyyaml # tests/test_reproducible_wheel.py shells out to `git init` BuildRequires: git # test_settings_dialog.py::TestAboutColorSchemeRead spawns a fresh python # subprocess where conftest's gi mock does not apply: it needs the real # Gtk 3.0 typelib (conftest mocks gi only in-process). Importing Gtk 3.0 # also requires the xlib 2.0 typelib, which ships in gobject-introspection # and is not pulled in by gtk3. BuildRequires: gtk3 BuildRequires: gobject-introspection # Non-Python runtime tools used by text_injection and the tray UI Requires: xdotool Requires: wtype Requires: wl-clipboard Requires: xclip Requires: xsel Requires: gtk3 Requires: libayatana-appindicator-gtk3 # Engine selection (virtual provide; see header note) Requires: vocalinux-engine # Optional confined domain; works unconfined without it Recommends: %{name}-selinux = %{version}-%{release} %description VocaLinux is a seamless, offline voice dictation system for Linux. It uses whisper.cpp (via pywhispercpp) for local speech recognition, integrates with the system tray (AppIndicator), and injects dictated text into X11 and Wayland applications. %package engine-whispercpp Summary: Whisper.cpp speech engine for VocaLinux (via pywhispercpp) Provides: vocalinux-engine # v0.18.5 upstream requires pywhispercpp>=1.5. Fedora <= 44 ships 1.4.0 # (code degrades gracefully: no GPU device selection), Fedora >= 45 ships # 1.5.0 — match the packaged release per distro version. %if 0%{?fedora} >= 45 Requires: python3-pywhispercpp >= 1.5 %else Requires: python3-pywhispercpp >= 1.2 %endif %description engine-whispercpp Default speech recognition engine for VocaLinux, using the Fedora-provided python3-pywhispercpp bindings to whisper.cpp. %package selinux Summary: SELinux policy module for VocaLinux (permissive) # selinux_requires (not _min) because the post scriptlet uses semanage; it # also provides BuildRequires: selinux-policy-devel/pkgconfig(systemd) and # the Requires/Requires(post) on selinux-policy packages. %{?selinux_requires} %description selinux SELinux policy module providing the vocalinux_t confined domain for VocaLinux. The domain is installed in PERMISSIVE mode by default so a policy bug can never break dictation; remove the permissive entry with "semanage permissive -d vocalinux_t" to enforce. %prep # v0.15.0 declares vosk as a CORE dependency, but vosk is not in Fedora and # the code imports it lazily (only when the vosk engine is selected). Our # package ships the whispercpp engine, so drop the dep from both the dynamic # BuildRequires and the generated runtime Requires. Future releases (main # branch) have already moved vosk to an optional extra — drop this override # when packaging a release that includes that change. %pyproject_patch_dependency vosk:ignore # pydub is declared in v0.15.0 but never imported anywhere in src/ (dead # dependency); it is also unpackaged in Fedora and broken on Python 3.13+ # (needs an audioop shim). Drop it. %pyproject_patch_dependency pydub:ignore # v0.18.5 declares pywhispercpp>=1.5, but Fedora <= 44 ships 1.4.0 and the # code degrades gracefully below 1.5 (GPU device selection disabled with a # warning). Relax the constraint there so the dynamic BuildRequires resolve; # Fedora >= 45 ships 1.5.0 and keeps the upstream requirement. Drop this # override once the oldest targeted Fedora ships pywhispercpp >= 1.5. %if 0%{?fedora} < 45 %pyproject_patch_dependency pywhispercpp:set_lower:1.2 %endif %autosetup -p1 -n %{gh_repo}-%{tag_version} %generate_buildrequires %pyproject_buildrequires %build %pyproject_wheel # Build the SELinux policy module from the policy sources mkdir -p selinux-build && cd selinux-build cp %{SOURCE1} %{SOURCE2} %{SOURCE3} . make -f %{_datadir}/selinux/devel/Makefile vocalinux.pp bzip2 -9 vocalinux.pp cd .. %install %pyproject_install %pyproject_save_files -l vocalinux # Upstream ships main.py with a shebang but mode 0644; make it executable chmod 0755 %{buildroot}%{python3_sitelib}/vocalinux/main.py # Desktop file and AppStream metainfo (from packaging/flatpak/ in the tarball) install -Dm0644 packaging/flatpak/com.vocalinux.Vocalinux.desktop \ %{buildroot}%{_datadir}/applications/com.vocalinux.Vocalinux.desktop install -Dm0644 packaging/flatpak/com.vocalinux.Vocalinux.metainfo.xml \ %{buildroot}%{_datadir}/metainfo/com.vocalinux.Vocalinux.metainfo.xml # Application icon (hicolor, SVG) install -Dm0644 resources/icons/scalable/vocalinux.svg \ %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/com.vocalinux.Vocalinux.svg # SELinux policy module + interfaces install -Dm0644 selinux-build/vocalinux.pp.bz2 \ %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2 install -Dm0644 %{SOURCE3} \ %{buildroot}%{_datadir}/selinux/devel/include/distributed/%{modulename}.if # TestWhispercppGgmlDeviceSelection (v0.18.5): mocks pywhispercpp but the # code probes the real one for context_params support. On Fedora < 45 the # buildroot has pywhispercpp 1.4.0 (no context_params), so the code takes # the fallback path and the context_params assertions fail. Fedora >= 45 # ships 1.5.0 and the tests pass, so only deselect on older releases. %if 0%{?fedora} < 45 %global pywhispercpp_ggml_deselect --deselect tests/test_recognition_manager_device_config.py::TestWhispercppGgmlDeviceSelection %endif %check desktop-file-validate %{buildroot}%{_datadir}/applications/com.vocalinux.Vocalinux.desktop appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/com.vocalinux.Vocalinux.metainfo.xml # Deselected tests are environment-specific, not product defects: # - test_ibus_engine_core: needs a running IBus daemon (no ibus in mock) # - test_enable_autostart_permission_error: asserts chmod-based permission # failures, which do not apply when the build runs as root # - TestWhispercppGpuDeviceSelection: mocks pywhispercpp but the code probes # the real pywhispercpp for context_params support; pywhispercpp is not # installed in the buildroot, so the fallback path is taken and the # assertions on GPU device selection fail # - TestTextInjectorWithIBus / TestTextInjectorWithIbusActiveInputMethod / # TestIBusRuntimeFallback / TestBackgroundIBusInitialization: need a # running IBus daemon / session bus (none in mock) # - TestPortalBackendSelection: needs a running xdg-desktop-portal; without # it the code falls back to ydotool and the portal assertions fail %pytest -m "not slow and not integration and not audio" \ -k "not test_ibus_engine_core" \ --deselect tests/test_autostart_manager_ext.py::TestAutostartManagerExtra::test_enable_autostart_permission_error \ --deselect tests/test_recognition_manager_device_config.py::TestWhispercppGpuDeviceSelection \ --deselect tests/test_ibus_engine.py::TestTextInjectorWithIBus \ --deselect tests/test_ibus_engine.py::TestTextInjectorWithIbusActiveInputMethod \ --deselect tests/test_text_injector.py::TestIBusRuntimeFallback \ --deselect tests/test_text_injector_ext.py::TestBackgroundIBusInitialization \ --deselect tests/test_remote_desktop_portal.py::TestPortalBackendSelection \ %{?pywhispercpp_ggml_deselect} %files -f %{pyproject_files} %doc README.md %{_bindir}/vocalinux %{_bindir}/vocalinux-gui %{_datadir}/applications/com.vocalinux.Vocalinux.desktop %{_datadir}/metainfo/com.vocalinux.Vocalinux.metainfo.xml %{_datadir}/icons/hicolor/scalable/apps/com.vocalinux.Vocalinux.svg %files engine-whispercpp # Virtual-provide-only subpackage; no files. %post selinux %selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.bz2 # Apply file contexts for the installed entry points if selinuxenabled 2>/dev/null; then restorecon -R %{_bindir}/vocalinux %{_bindir}/vocalinux-gui 2>/dev/null || true # Install the domain as permissive by default so policy bugs never # break dictation; users opt into enforcement explicitly. semanage permissive -a vocalinux_t 2>/dev/null || true fi %postun selinux if [ $1 -eq 0 ]; then semanage permissive -d vocalinux_t 2>/dev/null || true %selinux_modules_uninstall -s %{selinuxtype} %{modulename} fi %files selinux %{_datadir}/selinux/packages/%{selinuxtype}/%{modulename}.pp.* %{_datadir}/selinux/devel/include/distributed/%{modulename}.if %ghost %attr(0644,root,root) %verify(not md5 size mode mtime) %{_sharedstatedir}/selinux/%{selinuxtype}/active/modules/200/%{modulename} %changelog * Mon Nov 02 2026 ferdiu - 0.18.5-1 - Drop whispercpp-context-params-guard patch: v0.18.5 ships the fix upstream - Relax pywhispercpp to >=1.2 on Fedora < 45 (ships 1.4.0; code degrades gracefully below 1.5); Fedora >= 45 keeps upstream >=1.5 - Deselect environment-dependent tests (IBus daemon, xdg-desktop-portal, GGML context_params on pywhispercpp < 1.5) in %check * Thu Aug 20 2026 ferdiu - 0.15.0-1 - Package VocaLinux 0.15.0 for Fedora COPR (engine: whisper.cpp via python3-pywhispercpp); optional permissive vocalinux_t SELinux domain