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
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,21 @@ jobs:
- name: Run tests
run: ctest --test-dir build --output-on-failure

- name: Validate install tree
run: |
INSTALL_DIR="$(mktemp -d)"
cmake --install build --prefix "$INSTALL_DIR"
test -x "$INSTALL_DIR/bin/mutterkey"
test -x "$INSTALL_DIR/bin/mutterkey-tray"

- name: Validate headless CLI startup
run: QT_QPA_PLATFORM=offscreen ./build/mutterkey --help

- name: Validate headless tray startup
run: |
if timeout 2s env QT_QPA_PLATFORM=offscreen ./build/mutterkey-tray; then
exit 0
else
status=$?
test "$status" -eq 124
fi
16 changes: 16 additions & 0 deletions .github/workflows/release-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,21 @@ jobs:
- name: Run tests
run: ctest --test-dir build --output-on-failure

- name: Validate install tree
run: |
INSTALL_DIR="$(mktemp -d)"
cmake --install build --prefix "$INSTALL_DIR"
test -x "$INSTALL_DIR/bin/mutterkey"
test -x "$INSTALL_DIR/bin/mutterkey-tray"

- name: Validate headless tray startup
run: |
if timeout 2s env QT_QPA_PLATFORM=offscreen ./build/mutterkey-tray; then
exit 0
else
status=$?
test "$status" -eq 124
fi

- name: Run Valgrind Memcheck lane
run: bash scripts/run-valgrind.sh build
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/third_party/whisper.cpp/build/
/third_party/whisper.cpp/build-*/
/Makefile
/next_feature/
*.o
*.obj
*.moc
Expand Down
19 changes: 18 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Current architecture:
- Audio capture uses Qt Multimedia
- Transcription is in-process through vendored `whisper.cpp`
- Clipboard writes prefer `KSystemClipboard` with `QClipboard` fallback
- There is no GUI yet; the entrypoint is the `mutterkey` binary with `daemon`, `once`, and `diagnose` modes
- There is an early Qt Widgets tray shell in `mutterkey-tray`, but the daemon remains the product core
- The recommended day-to-day runtime path is the `systemd --user` service
- The installed desktop entry is intentionally hidden from normal app menus with `NoDisplay=true`
- `daemon` is the default runtime mode; `once` and `diagnose` are validation helpers
Expand All @@ -36,9 +36,13 @@ This repository is intentionally kept minimal:
- `src/transcription/transcriptionworker.*`: worker object hosted on a dedicated `QThread`
- `src/transcription/transcriptiontypes.h`: normalized audio and transcription result value types
- `src/config.*`: JSON config loading and defaults
- `src/app/*`: shared CLI/runtime command helpers used by the main entrypoint
- `src/control/*`: local daemon control transport, typed snapshots, and session/client APIs
- `src/tray/*`: Qt Widgets tray-shell UI scaffolding
- `contrib/mutterkey.service`: example user service
- `contrib/org.mutterkey.mutterkey.desktop`: hidden desktop entry used for desktop identity/integration
- `scripts/check-release-hygiene.sh`: repo hygiene checks for publication-facing content
- `next_feature/`: tracked upcoming feature plans as Markdown; keep only plan `.md` files and the folder-local `.gitignore`
- `docs/Doxyfile.in`: Doxygen config template for repo-owned API docs
- `docs/mainpage.md`: Doxygen landing page used instead of the full README
- `scripts/run-valgrind.sh`: deterministic Valgrind Memcheck runner for release-readiness checks
Expand All @@ -64,6 +68,10 @@ cmake -S . -B "$BUILD_DIR"
cmake --build "$BUILD_DIR" -j"$(nproc)"
```

If a sandboxed build fails with `ccache: error: Read-only file system`, treat
that as an environment limitation rather than a repo regression and rerun the
build with `CCACHE_DISABLE=1`.

If the task affects install layout, licensing, or packaging, also validate a temporary install prefix:

```bash
Expand Down Expand Up @@ -94,6 +102,7 @@ Notes:
- A small `Qt Test` + `CTest` suite exists for config loading and audio normalization, including malformed JSON, wrong-type config inputs, and recording-normalizer edge cases
- Config loading is intentionally forgiving: invalid runtime values fall back to defaults and log warnings
- Use `ctest --test-dir "$BUILD_DIR" --output-on-failure` for changes that affect covered code
- Keep Qt GUI or Widgets tests headless under `CTest`: set `QT_QPA_PLATFORM=offscreen` in the test registration or test properties rather than relying on the caller environment
- Use `bash scripts/run-valgrind.sh "$BUILD_DIR"` or `cmake --build "$BUILD_DIR" --target valgrind` when validating memory behavior for release readiness or after fixing memory-lifetime issues
- On Debian-family systems, install `libc6-dbg` if Valgrind fails at startup with a `ld-linux` / mandatory redirection error
- Use `cmake --build "$BUILD_DIR" --target clang-tidy` after C++ changes when static-analysis noise is likely to matter
Expand All @@ -114,10 +123,12 @@ Notes:
- Keep the Doxygen main page in `docs/mainpage.md` small and API-focused. The release-facing `README.md` may link to files outside the Doxygen input set and should not be used as the Doxygen main page unless the input set is expanded deliberately
- Keep analyzer fixes targeted to `src/` and `tests/`; do not churn `third_party/` or generated Qt autogen output to satisfy tooling
- Reconfigure the build directory after installing new tools so cached `find_program()` results are refreshed
- When validating inside a restricted sandbox, be ready to disable `ccache` with `CCACHE_DISABLE=1` if the cache location is read-only; that is an execution-environment issue, not a Mutterkey build failure
- Prefer fixing the code over weakening `.clang-tidy` or the Clazy check set; only relax tool config when the warning is clearly low-value for this repo
- Do not add broad Valgrind suppressions by default; only add narrow suppressions after reproducing stable third-party noise and keep them clearly scoped
- When adding tests, prefer small `Qt Test` cases that run headlessly under `CTest` and avoid microphone, clipboard, or KDE session dependencies unless the task is specifically integration-focused
- For tool-driven cleanups, preserve the existing design and behavior; do not perform broad rewrites just to satisfy style-oriented recommendations
- Keep forward-looking feature plans under `next_feature/` as tracked Markdown files; do not leave scratch notes, binaries, or generated artifacts there

## Coding Guidelines

Expand All @@ -128,6 +139,8 @@ Notes:
- Avoid introducing optional backends, plugin systems, or cross-platform abstractions unless the task requires them
- Keep the audio path explicit: recorder output may not already match Whisper input requirements, so preserve normalization behavior
- Prefer narrow shared value types across subsystems; for example, consumers that only need captured audio should include `src/audio/recording.h`, not the full recorder class
- Keep JSON and other transport details at subsystem boundaries; prefer typed C++ snapshots/results once data crosses into app-owned control, tray, or service code
- Prefer dependency injection for tray-shell and control-surface code from the first implementation so headless Qt tests stay simple
- Preserve the current product direction: embedded `whisper.cpp`, KDE-first, CLI/service-first

## C++ Core Guidelines Priorities
Expand Down Expand Up @@ -193,12 +206,16 @@ Typical model location:

- Read `README.md` first, especially `Overview`, `Quick Start`, `Run As Service`, and `Development`, then read the touched source files before editing
- Prefer targeted changes over speculative cleanup
- If a change grows daemon, tray, or control-plane behavior, prefer extracting or extending repo-owned libraries under `src/app/`, `src/control/`, or other focused modules instead of piling more orchestration into `src/main.cpp`
- Update `README.md` and `config.example.json` when behavior or setup changes
- Update `contrib/mutterkey.service` and `contrib/org.mutterkey.mutterkey.desktop` when service/desktop behavior changes
- Update `LICENSE`, `THIRD_PARTY_NOTICES.md`, CMake install rules, and `third_party/whisper.cpp.UPSTREAM.md` when packaging, licensing, or vendored dependency behavior changes
- Keep `README.md`, `AGENTS.md`, and any relevant local skills aligned with the current `scripts/update-whisper.sh` workflow when the vendor-update process changes
- Store upcoming feature plans in `next_feature/` as Markdown files, and update the existing plan there when refining the same upcoming feature instead of scattering notes across the repo
- Treat `mutterkey-tray` as a shipped artifact once it is installed or validated in CI; keep install rules, README/setup notes, release checklist items, and workflow checks aligned with that status
- Verify with a fresh CMake build when the change affects compilation or linkage
- Run `ctest` when touching covered code in `src/config.*` or `src/audio/recordingnormalizer.*`, and extend the deterministic headless tests when practical
- When adding or fixing Qt GUI tests, make the `CTest` registration itself headless with `QT_QPA_PLATFORM=offscreen` so CI does not try to load `xcb`
- Prefer expanding tests around pure parsing, value normalization, and other environment-independent logic before adding KDE-session or device-heavy coverage
- Use `-DMUTTERKEY_ENABLE_ASAN=ON` and `-DMUTTERKEY_ENABLE_UBSAN=ON` for fast iteration on memory and UB bugs, and use the repo-owned Valgrind lane as the slower release-focused confirmation step
- Run `clang-tidy` and `clazy` targets for non-trivial C++/Qt changes when the tools are installed in the environment
Expand Down
54 changes: 48 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ option(MUTTERKEY_ENABLE_WHISPER_BLAS "Enable whisper.cpp BLAS CPU acceleration"
set(MUTTERKEY_WHISPER_BLAS_VENDOR "Generic" CACHE STRING "BLAS vendor passed to whisper.cpp when BLAS acceleration is enabled")
set_property(CACHE MUTTERKEY_WHISPER_BLAS_VENDOR PROPERTY STRINGS "Generic;OpenBLAS;FLAME;ATLAS;FlexiBLAS;Intel;NVHPC;Apple")

find_package(Qt6 REQUIRED COMPONENTS Core Gui Multimedia)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Multimedia Network Widgets)
find_package(KF6GlobalAccel CONFIG REQUIRED)
find_package(KF6GuiAddons CONFIG REQUIRED)
find_package(Doxygen QUIET)

set(MUTTERKEY_APP_SOURCES
set(MUTTERKEY_CORE_SOURCES
src/audio/audiorecorder.cpp
src/audio/audiorecorder.h
src/audio/recordingnormalizer.cpp
Expand All @@ -40,7 +40,6 @@ set(MUTTERKEY_APP_SOURCES
src/config.h
src/hotkeymanager.cpp
src/hotkeymanager.h
src/main.cpp
src/service.cpp
src/service.h
src/transcription/transcriptiontypes.h
Expand All @@ -50,14 +49,52 @@ set(MUTTERKEY_APP_SOURCES
src/transcription/whispercpptranscriber.h
)

add_executable(mutterkey ${MUTTERKEY_APP_SOURCES})
set(MUTTERKEY_CONTROL_SOURCES
src/control/daemoncontrolclient.cpp
src/control/daemoncontrolclient.h
src/control/daemoncontrolprotocol.cpp
src/control/daemoncontrolprotocol.h
src/control/daemoncontrolserver.cpp
src/control/daemoncontrolserver.h
src/control/daemoncontroltypes.cpp
src/control/daemoncontroltypes.h
)

add_library(mutterkey_core STATIC ${MUTTERKEY_CORE_SOURCES})
add_library(mutterkey_control STATIC ${MUTTERKEY_CONTROL_SOURCES})
add_library(mutterkey_app STATIC
src/app/applicationcommands.cpp
src/app/applicationcommands.h
)

add_executable(mutterkey
src/main.cpp
)

add_executable(mutterkey-tray
src/tray/traystatuswindow.cpp
src/tray/traystatuswindow.h
src/traymain.cpp
)

target_include_directories(mutterkey_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_include_directories(mutterkey_control PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_include_directories(mutterkey_app PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_include_directories(mutterkey PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(mutterkey PRIVATE Qt6::Core Qt6::Gui Qt6::Multimedia KF6::GlobalAccel KF6::GuiAddons)
target_include_directories(mutterkey-tray PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(mutterkey_core PUBLIC Qt6::Core Qt6::Gui Qt6::Multimedia KF6::GlobalAccel KF6::GuiAddons)
target_link_libraries(mutterkey_control PUBLIC Qt6::Core Qt6::Network mutterkey_core)
target_link_libraries(mutterkey_app PUBLIC Qt6::Core Qt6::Gui mutterkey_control)
target_link_libraries(mutterkey PRIVATE mutterkey_app whisper)
target_link_libraries(mutterkey-tray PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets mutterkey_control)
set_target_properties(mutterkey PROPERTIES
BUILD_RPATH "$ORIGIN/../lib"
INSTALL_RPATH "$ORIGIN/../lib"
)
set_target_properties(mutterkey-tray PROPERTIES
BUILD_RPATH "$ORIGIN/../lib"
INSTALL_RPATH "$ORIGIN/../lib"
)

function(mutterkey_enable_sanitizers target_name)
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
Expand All @@ -84,7 +121,11 @@ function(mutterkey_enable_sanitizers target_name)
endif()
endfunction()

mutterkey_enable_sanitizers(mutterkey_core)
mutterkey_enable_sanitizers(mutterkey_control)
mutterkey_enable_sanitizers(mutterkey_app)
mutterkey_enable_sanitizers(mutterkey)
mutterkey_enable_sanitizers(mutterkey-tray)

set(MUTTERKEY_CLAZY_CHECKS "level0" CACHE STRING "Checks passed to clazy-standalone")

Expand Down Expand Up @@ -163,9 +204,10 @@ add_subdirectory(third_party/whisper.cpp EXCLUDE_FROM_ALL)
# upstream public headers as part of its own package layout.
set_target_properties(whisper ggml PROPERTIES PUBLIC_HEADER "")

target_link_libraries(mutterkey PRIVATE whisper)
target_link_libraries(mutterkey_core PUBLIC whisper)

install(TARGETS mutterkey RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(TARGETS mutterkey-tray RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(TARGETS whisper ggml ggml-base
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Current direction:
- KDE-first
- local-only transcription
- CLI/service-first operation
- no GUI yet
- tray-shell work has started, but the daemon remains the product core
- minimal and developer-oriented rather than a hardened end-user security product

Recommended startup path:
Expand Down Expand Up @@ -46,7 +46,7 @@ Supported environment:

Build requirements:

1. Qt 6 development packages with `Core`, `Gui`, and `Multimedia`
1. Qt 6 development packages with `Core`, `Gui`, `Multimedia`, `Network`, and `Widgets`
2. KDE Frameworks development packages for `KGlobalAccel` and `KGuiAddons`
3. `g++`
4. `cmake`
Expand Down Expand Up @@ -107,6 +107,7 @@ cmake --install "$BUILD_DIR"
This installs:

- `~/.local/bin/mutterkey`
- `~/.local/bin/mutterkey-tray`
- `~/.local/lib/libwhisper.so*` and the required `ggml` libraries
- `~/.local/share/applications/org.mutterkey.mutterkey.desktop`

Expand Down Expand Up @@ -353,6 +354,9 @@ Repository layout:
- `src/transcription/transcriptiontypes.h`: normalized-audio and transcription result value types
- `src/clipboardwriter.*`: clipboard writes with KDE-first fallback behavior
- `src/config.*`: JSON config loading and defaults
- `src/app/*`: shared CLI/runtime command helpers used by the main entrypoint
- `src/control/*`: local daemon control protocol, typed snapshots, and local-socket session/server wiring
- `src/tray/*`: Qt Widgets tray-shell UI scaffolding
- `contrib/mutterkey.service`: example user service

Build and test:
Expand Down
10 changes: 10 additions & 0 deletions RELEASE_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ ctest --test-dir "$SANITIZER_BUILD_DIR" --output-on-failure
QT_QPA_PLATFORM=offscreen "$BUILD_DIR/mutterkey" --help
```

- Validate tray-shell startup in a headless environment:

```bash
timeout 2s env QT_QPA_PLATFORM=offscreen "$BUILD_DIR/mutterkey-tray"
```

- Treat exit code `124` from the tray-shell smoke check as expected when the
process stays alive until `timeout` stops it.

- If the change affects startup, service wiring, or config handling, also run:

```bash
Expand All @@ -122,6 +131,7 @@ cmake --install "$BUILD_DIR" --prefix "$INSTALL_DIR"

- Confirm the installed tree contains:
- `bin/mutterkey`
- `bin/mutterkey-tray`
- required `libwhisper` / `ggml` shared libraries
- the desktop file under `share/applications`
- license files under `share/licenses/mutterkey`
Expand Down
Loading
Loading