Skip to content

Commit 743a9fe

Browse files
authored
Merge pull request #6 from n-n-code/tray_v1
First UI implementation (tray)
2 parents 91abacf + 3a29589 commit 743a9fe

27 files changed

Lines changed: 1711 additions & 175 deletions

.github/workflows/ci.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,21 @@ jobs:
6161
- name: Run tests
6262
run: ctest --test-dir build --output-on-failure
6363

64+
- name: Validate install tree
65+
run: |
66+
INSTALL_DIR="$(mktemp -d)"
67+
cmake --install build --prefix "$INSTALL_DIR"
68+
test -x "$INSTALL_DIR/bin/mutterkey"
69+
test -x "$INSTALL_DIR/bin/mutterkey-tray"
70+
6471
- name: Validate headless CLI startup
6572
run: QT_QPA_PLATFORM=offscreen ./build/mutterkey --help
73+
74+
- name: Validate headless tray startup
75+
run: |
76+
if timeout 2s env QT_QPA_PLATFORM=offscreen ./build/mutterkey-tray; then
77+
exit 0
78+
else
79+
status=$?
80+
test "$status" -eq 124
81+
fi

.github/workflows/release-checks.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,21 @@ jobs:
5050
- name: Run tests
5151
run: ctest --test-dir build --output-on-failure
5252

53+
- name: Validate install tree
54+
run: |
55+
INSTALL_DIR="$(mktemp -d)"
56+
cmake --install build --prefix "$INSTALL_DIR"
57+
test -x "$INSTALL_DIR/bin/mutterkey"
58+
test -x "$INSTALL_DIR/bin/mutterkey-tray"
59+
60+
- name: Validate headless tray startup
61+
run: |
62+
if timeout 2s env QT_QPA_PLATFORM=offscreen ./build/mutterkey-tray; then
63+
exit 0
64+
else
65+
status=$?
66+
test "$status" -eq 124
67+
fi
68+
5369
- name: Run Valgrind Memcheck lane
5470
run: bash scripts/run-valgrind.sh build

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
/third_party/whisper.cpp/build/
66
/third_party/whisper.cpp/build-*/
77
/Makefile
8+
/next_feature/
89
*.o
910
*.obj
1011
*.moc

AGENTS.md

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

71+
If a sandboxed build fails with `ccache: error: Read-only file system`, treat
72+
that as an environment limitation rather than a repo regression and rerun the
73+
build with `CCACHE_DISABLE=1`.
74+
6775
If the task affects install layout, licensing, or packaging, also validate a temporary install prefix:
6876

6977
```bash
@@ -94,6 +102,7 @@ Notes:
94102
- 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
95103
- Config loading is intentionally forgiving: invalid runtime values fall back to defaults and log warnings
96104
- Use `ctest --test-dir "$BUILD_DIR" --output-on-failure` for changes that affect covered code
105+
- 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
97106
- 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
98107
- On Debian-family systems, install `libc6-dbg` if Valgrind fails at startup with a `ld-linux` / mandatory redirection error
99108
- Use `cmake --build "$BUILD_DIR" --target clang-tidy` after C++ changes when static-analysis noise is likely to matter
@@ -114,10 +123,12 @@ Notes:
114123
- 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
115124
- Keep analyzer fixes targeted to `src/` and `tests/`; do not churn `third_party/` or generated Qt autogen output to satisfy tooling
116125
- Reconfigure the build directory after installing new tools so cached `find_program()` results are refreshed
126+
- 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
117127
- 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
118128
- Do not add broad Valgrind suppressions by default; only add narrow suppressions after reproducing stable third-party noise and keep them clearly scoped
119129
- 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
120130
- For tool-driven cleanups, preserve the existing design and behavior; do not perform broad rewrites just to satisfy style-oriented recommendations
131+
- Keep forward-looking feature plans under `next_feature/` as tracked Markdown files; do not leave scratch notes, binaries, or generated artifacts there
121132

122133
## Coding Guidelines
123134

@@ -128,6 +139,8 @@ Notes:
128139
- Avoid introducing optional backends, plugin systems, or cross-platform abstractions unless the task requires them
129140
- Keep the audio path explicit: recorder output may not already match Whisper input requirements, so preserve normalization behavior
130141
- 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
142+
- 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
143+
- Prefer dependency injection for tray-shell and control-surface code from the first implementation so headless Qt tests stay simple
131144
- Preserve the current product direction: embedded `whisper.cpp`, KDE-first, CLI/service-first
132145

133146
## C++ Core Guidelines Priorities
@@ -193,12 +206,16 @@ Typical model location:
193206

194207
- Read `README.md` first, especially `Overview`, `Quick Start`, `Run As Service`, and `Development`, then read the touched source files before editing
195208
- Prefer targeted changes over speculative cleanup
209+
- 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`
196210
- Update `README.md` and `config.example.json` when behavior or setup changes
197211
- Update `contrib/mutterkey.service` and `contrib/org.mutterkey.mutterkey.desktop` when service/desktop behavior changes
198212
- Update `LICENSE`, `THIRD_PARTY_NOTICES.md`, CMake install rules, and `third_party/whisper.cpp.UPSTREAM.md` when packaging, licensing, or vendored dependency behavior changes
199213
- 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
214+
- 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
215+
- 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
200216
- Verify with a fresh CMake build when the change affects compilation or linkage
201217
- Run `ctest` when touching covered code in `src/config.*` or `src/audio/recordingnormalizer.*`, and extend the deterministic headless tests when practical
218+
- 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`
202219
- Prefer expanding tests around pure parsing, value normalization, and other environment-independent logic before adding KDE-session or device-heavy coverage
203220
- 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
204221
- Run `clang-tidy` and `clazy` targets for non-trivial C++/Qt changes when the tools are installed in the environment

CMakeLists.txt

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ option(MUTTERKEY_ENABLE_WHISPER_BLAS "Enable whisper.cpp BLAS CPU acceleration"
2121
set(MUTTERKEY_WHISPER_BLAS_VENDOR "Generic" CACHE STRING "BLAS vendor passed to whisper.cpp when BLAS acceleration is enabled")
2222
set_property(CACHE MUTTERKEY_WHISPER_BLAS_VENDOR PROPERTY STRINGS "Generic;OpenBLAS;FLAME;ATLAS;FlexiBLAS;Intel;NVHPC;Apple")
2323

24-
find_package(Qt6 REQUIRED COMPONENTS Core Gui Multimedia)
24+
find_package(Qt6 REQUIRED COMPONENTS Core Gui Multimedia Network Widgets)
2525
find_package(KF6GlobalAccel CONFIG REQUIRED)
2626
find_package(KF6GuiAddons CONFIG REQUIRED)
2727
find_package(Doxygen QUIET)
2828

29-
set(MUTTERKEY_APP_SOURCES
29+
set(MUTTERKEY_CORE_SOURCES
3030
src/audio/audiorecorder.cpp
3131
src/audio/audiorecorder.h
3232
src/audio/recordingnormalizer.cpp
@@ -40,7 +40,6 @@ set(MUTTERKEY_APP_SOURCES
4040
src/config.h
4141
src/hotkeymanager.cpp
4242
src/hotkeymanager.h
43-
src/main.cpp
4443
src/service.cpp
4544
src/service.h
4645
src/transcription/transcriptiontypes.h
@@ -50,14 +49,52 @@ set(MUTTERKEY_APP_SOURCES
5049
src/transcription/whispercpptranscriber.h
5150
)
5251

53-
add_executable(mutterkey ${MUTTERKEY_APP_SOURCES})
52+
set(MUTTERKEY_CONTROL_SOURCES
53+
src/control/daemoncontrolclient.cpp
54+
src/control/daemoncontrolclient.h
55+
src/control/daemoncontrolprotocol.cpp
56+
src/control/daemoncontrolprotocol.h
57+
src/control/daemoncontrolserver.cpp
58+
src/control/daemoncontrolserver.h
59+
src/control/daemoncontroltypes.cpp
60+
src/control/daemoncontroltypes.h
61+
)
62+
63+
add_library(mutterkey_core STATIC ${MUTTERKEY_CORE_SOURCES})
64+
add_library(mutterkey_control STATIC ${MUTTERKEY_CONTROL_SOURCES})
65+
add_library(mutterkey_app STATIC
66+
src/app/applicationcommands.cpp
67+
src/app/applicationcommands.h
68+
)
69+
70+
add_executable(mutterkey
71+
src/main.cpp
72+
)
73+
74+
add_executable(mutterkey-tray
75+
src/tray/traystatuswindow.cpp
76+
src/tray/traystatuswindow.h
77+
src/traymain.cpp
78+
)
5479

80+
target_include_directories(mutterkey_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
81+
target_include_directories(mutterkey_control PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
82+
target_include_directories(mutterkey_app PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
5583
target_include_directories(mutterkey PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
56-
target_link_libraries(mutterkey PRIVATE Qt6::Core Qt6::Gui Qt6::Multimedia KF6::GlobalAccel KF6::GuiAddons)
84+
target_include_directories(mutterkey-tray PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
85+
target_link_libraries(mutterkey_core PUBLIC Qt6::Core Qt6::Gui Qt6::Multimedia KF6::GlobalAccel KF6::GuiAddons)
86+
target_link_libraries(mutterkey_control PUBLIC Qt6::Core Qt6::Network mutterkey_core)
87+
target_link_libraries(mutterkey_app PUBLIC Qt6::Core Qt6::Gui mutterkey_control)
88+
target_link_libraries(mutterkey PRIVATE mutterkey_app whisper)
89+
target_link_libraries(mutterkey-tray PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets mutterkey_control)
5790
set_target_properties(mutterkey PROPERTIES
5891
BUILD_RPATH "$ORIGIN/../lib"
5992
INSTALL_RPATH "$ORIGIN/../lib"
6093
)
94+
set_target_properties(mutterkey-tray PROPERTIES
95+
BUILD_RPATH "$ORIGIN/../lib"
96+
INSTALL_RPATH "$ORIGIN/../lib"
97+
)
6198

6299
function(mutterkey_enable_sanitizers target_name)
63100
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
@@ -84,7 +121,11 @@ function(mutterkey_enable_sanitizers target_name)
84121
endif()
85122
endfunction()
86123

124+
mutterkey_enable_sanitizers(mutterkey_core)
125+
mutterkey_enable_sanitizers(mutterkey_control)
126+
mutterkey_enable_sanitizers(mutterkey_app)
87127
mutterkey_enable_sanitizers(mutterkey)
128+
mutterkey_enable_sanitizers(mutterkey-tray)
88129

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

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

166-
target_link_libraries(mutterkey PRIVATE whisper)
207+
target_link_libraries(mutterkey_core PUBLIC whisper)
167208

168209
install(TARGETS mutterkey RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
210+
install(TARGETS mutterkey-tray RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
169211
install(TARGETS whisper ggml ggml-base
170212
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
171213
)

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Current direction:
1919
- KDE-first
2020
- local-only transcription
2121
- CLI/service-first operation
22-
- no GUI yet
22+
- tray-shell work has started, but the daemon remains the product core
2323
- minimal and developer-oriented rather than a hardened end-user security product
2424

2525
Recommended startup path:
@@ -47,7 +47,7 @@ Supported environment:
4747

4848
Build requirements:
4949

50-
1. Qt 6 development packages with `Core`, `Gui`, and `Multimedia`
50+
1. Qt 6 development packages with `Core`, `Gui`, `Multimedia`, `Network`, and `Widgets`
5151
2. KDE Frameworks development packages for `KGlobalAccel` and `KGuiAddons`
5252
3. `g++`
5353
4. `cmake`
@@ -108,6 +108,7 @@ cmake --install "$BUILD_DIR"
108108
This installs:
109109

110110
- `~/.local/bin/mutterkey`
111+
- `~/.local/bin/mutterkey-tray`
111112
- `~/.local/lib/libwhisper.so*` and the required `ggml` libraries
112113
- `~/.local/share/applications/org.mutterkey.mutterkey.desktop`
113114

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

359363
Build and test:

RELEASE_CHECKLIST.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@ ctest --test-dir "$SANITIZER_BUILD_DIR" --output-on-failure
105105
QT_QPA_PLATFORM=offscreen "$BUILD_DIR/mutterkey" --help
106106
```
107107

108+
- Validate tray-shell startup in a headless environment:
109+
110+
```bash
111+
timeout 2s env QT_QPA_PLATFORM=offscreen "$BUILD_DIR/mutterkey-tray"
112+
```
113+
114+
- Treat exit code `124` from the tray-shell smoke check as expected when the
115+
process stays alive until `timeout` stops it.
116+
108117
- If the change affects startup, service wiring, or config handling, also run:
109118

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

123132
- Confirm the installed tree contains:
124133
- `bin/mutterkey`
134+
- `bin/mutterkey-tray`
125135
- required `libwhisper` / `ggml` shared libraries
126136
- the desktop file under `share/applications`
127137
- license files under `share/licenses/mutterkey`

0 commit comments

Comments
 (0)