Skip to content

Commit 91abacf

Browse files
authored
Merge pull request #5 from n-n-code/configurator_tool
Added a configurator tool and CUDA and VULKAN GPU support
2 parents 5f90599 + cedb687 commit 91abacf

14 files changed

Lines changed: 1427 additions & 25 deletions

CMakeLists.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ set(CMAKE_CXX_EXTENSIONS OFF)
1515

1616
option(MUTTERKEY_ENABLE_ASAN "Enable AddressSanitizer for repo-owned code and vendored whisper.cpp" OFF)
1717
option(MUTTERKEY_ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer for repo-owned code and vendored whisper.cpp" OFF)
18+
option(MUTTERKEY_ENABLE_WHISPER_CUDA "Enable whisper.cpp CUDA backend support (NVIDIA)" OFF)
19+
option(MUTTERKEY_ENABLE_WHISPER_VULKAN "Enable whisper.cpp Vulkan backend support" OFF)
20+
option(MUTTERKEY_ENABLE_WHISPER_BLAS "Enable whisper.cpp BLAS CPU acceleration" OFF)
21+
set(MUTTERKEY_WHISPER_BLAS_VENDOR "Generic" CACHE STRING "BLAS vendor passed to whisper.cpp when BLAS acceleration is enabled")
22+
set_property(CACHE MUTTERKEY_WHISPER_BLAS_VENDOR PROPERTY STRINGS "Generic;OpenBLAS;FLAME;ATLAS;FlexiBLAS;Intel;NVHPC;Apple")
1823

1924
find_package(Qt6 REQUIRED COMPONENTS Core Gui Multimedia)
2025
find_package(KF6GlobalAccel CONFIG REQUIRED)
@@ -29,6 +34,8 @@ set(MUTTERKEY_APP_SOURCES
2934
src/audio/recording.h
3035
src/clipboardwriter.cpp
3136
src/clipboardwriter.h
37+
src/commanddispatch.cpp
38+
src/commanddispatch.h
3239
src/config.cpp
3340
src/config.h
3441
src/hotkeymanager.cpp
@@ -146,6 +153,10 @@ set(WHISPER_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
146153
set(WHISPER_BUILD_SERVER OFF CACHE BOOL "" FORCE)
147154
set(WHISPER_SANITIZE_ADDRESS ${MUTTERKEY_ENABLE_ASAN} CACHE BOOL "" FORCE)
148155
set(WHISPER_SANITIZE_UNDEFINED ${MUTTERKEY_ENABLE_UBSAN} CACHE BOOL "" FORCE)
156+
set(GGML_CUDA ${MUTTERKEY_ENABLE_WHISPER_CUDA} CACHE BOOL "" FORCE)
157+
set(GGML_VULKAN ${MUTTERKEY_ENABLE_WHISPER_VULKAN} CACHE BOOL "" FORCE)
158+
set(GGML_BLAS ${MUTTERKEY_ENABLE_WHISPER_BLAS} CACHE BOOL "" FORCE)
159+
set(GGML_BLAS_VENDOR ${MUTTERKEY_WHISPER_BLAS_VENDOR} CACHE STRING "" FORCE)
149160
add_subdirectory(third_party/whisper.cpp EXCLUDE_FROM_ALL)
150161

151162
# Mutterkey ships the vendored shared libraries, but it does not install their
@@ -161,6 +172,15 @@ install(TARGETS whisper ggml ggml-base
161172
if(TARGET ggml-cpu)
162173
install(TARGETS ggml-cpu LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
163174
endif()
175+
if(TARGET ggml-cuda)
176+
install(TARGETS ggml-cuda LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
177+
endif()
178+
if(TARGET ggml-vulkan)
179+
install(TARGETS ggml-vulkan LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
180+
endif()
181+
if(TARGET ggml-blas)
182+
install(TARGETS ggml-blas LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
183+
endif()
164184
install(FILES contrib/org.mutterkey.mutterkey.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
165185
install(FILES LICENSE THIRD_PARTY_NOTICES.md DESTINATION ${MUTTERKEY_LICENSE_INSTALL_DIR})
166186
install(FILES third_party/whisper.cpp/LICENSE DESTINATION ${MUTTERKEY_LICENSE_INSTALL_DIR}/third_party/whisper.cpp)

README.md

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,37 @@ This installs:
111111
- `~/.local/lib/libwhisper.so*` and the required `ggml` libraries
112112
- `~/.local/share/applications/org.mutterkey.mutterkey.desktop`
113113

114+
Optional acceleration flags:
115+
116+
```bash
117+
cmake -S . -B "$BUILD_DIR" \
118+
-DCMAKE_BUILD_TYPE=Release \
119+
-DCMAKE_INSTALL_PREFIX="$HOME/.local" \
120+
-DMUTTERKEY_ENABLE_WHISPER_CUDA=ON
121+
```
122+
123+
```bash
124+
cmake -S . -B "$BUILD_DIR" \
125+
-DCMAKE_BUILD_TYPE=Release \
126+
-DCMAKE_INSTALL_PREFIX="$HOME/.local" \
127+
-DMUTTERKEY_ENABLE_WHISPER_VULKAN=ON
128+
```
129+
130+
```bash
131+
cmake -S . -B "$BUILD_DIR" \
132+
-DCMAKE_BUILD_TYPE=Release \
133+
-DCMAKE_INSTALL_PREFIX="$HOME/.local" \
134+
-DMUTTERKEY_ENABLE_WHISPER_BLAS=ON \
135+
-DMUTTERKEY_WHISPER_BLAS_VENDOR=OpenBLAS
136+
```
137+
138+
Notes:
139+
140+
- `MUTTERKEY_ENABLE_WHISPER_CUDA=ON` is for NVIDIA GPUs and requires a working CUDA toolchain
141+
- `MUTTERKEY_ENABLE_WHISPER_VULKAN=ON` is for Vulkan-capable GPUs and requires Vulkan development headers and loader libraries
142+
- `MUTTERKEY_ENABLE_WHISPER_BLAS=ON` improves CPU inference speed rather than enabling GPU execution
143+
- these options are forwarded to the vendored `whisper.cpp` / `ggml` build and install any resulting backend libraries alongside Mutterkey
144+
114145
### 2. Put a Whisper model on disk
115146

116147
Example location:
@@ -122,11 +153,24 @@ Example location:
122153
### 3. Create the config file
123154

124155
```bash
125-
mkdir -p ~/.config/mutterkey
126-
cp config.example.json ~/.config/mutterkey/config.json
156+
mutterkey config init --model-path ~/.local/share/mutterkey/models/ggml-base.en.bin
127157
```
128158

129-
Edit `~/.config/mutterkey/config.json` and set at least:
159+
`mutterkey config init` writes the Linux config file to:
160+
161+
```text
162+
~/.config/mutterkey/config.json
163+
```
164+
165+
When run from a terminal, Mutterkey can also create this file automatically on
166+
first launch if it does not exist yet. The interactive bootstrap asks for:
167+
168+
- `transcriber.model_path`
169+
- `shortcut.sequence`
170+
171+
You can update saved values later with `mutterkey config set <key> <value>`.
172+
173+
Set at least:
130174

131175
- `shortcut.sequence`
132176
- `transcriber.model_path`
@@ -160,8 +204,11 @@ See [config.example.json](config.example.json) for the full config.
160204
Config notes:
161205

162206
- `transcriber.threads: 0` means auto-detect based on the local machine
207+
- `transcriber.language` accepts a Whisper language code such as `en` or `fi`, or `auto` for language detection
163208
- invalid numeric values fall back to safe defaults and log a warning
209+
- invalid `transcriber.language` values fall back to the default and log a warning
164210
- empty `shortcut.sequence` or `transcriber.model_path` values fall back to defaults and log a warning
211+
- runtime flags such as `--model-path`, `--shortcut`, `--language`, `--translate`, `--threads`, and `--warmup-on-start` override the saved config for the current process only
165212

166213
### 4. Sanity-check the installed binary
167214

@@ -186,7 +233,8 @@ The default service file assumes:
186233
- config file at `%h/.config/mutterkey/config.json`
187234

188235
If your paths differ, edit [contrib/mutterkey.service](contrib/mutterkey.service)
189-
before enabling it.
236+
before enabling it. If the config file does not exist, the service will fail
237+
fast and instruct you to run `mutterkey config init` from a terminal first.
190238

191239
### 6. Use the hotkey
192240

@@ -232,6 +280,14 @@ installed setup looks like:
232280
%h/.local/bin/mutterkey daemon --config %h/.config/mutterkey/config.json
233281
```
234282

283+
Useful config commands:
284+
285+
```bash
286+
~/.local/bin/mutterkey config init --model-path ~/.local/share/mutterkey/models/ggml-base.en.bin
287+
~/.local/bin/mutterkey config set shortcut.sequence Meta+F8
288+
~/.local/bin/mutterkey config set transcriber.language fi
289+
```
290+
235291
The desktop entry
236292
[contrib/org.mutterkey.mutterkey.desktop](contrib/org.mutterkey.mutterkey.desktop)
237293
is intentionally hidden from normal app menus because the project currently

RELEASE_CHECKLIST.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,27 @@ BUILD_DIR="$(mktemp -d /tmp/mutterkey-build-XXXXXX)"
3333
cmake -S . -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Debug
3434
```
3535

36+
- If the release is intended to ship an accelerated Whisper backend, configure
37+
the build with the relevant Mutterkey options:
38+
39+
```bash
40+
cmake -S . -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Debug -DMUTTERKEY_ENABLE_WHISPER_CUDA=ON
41+
```
42+
43+
```bash
44+
cmake -S . -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Debug -DMUTTERKEY_ENABLE_WHISPER_VULKAN=ON
45+
```
46+
47+
```bash
48+
cmake -S . -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Debug -DMUTTERKEY_ENABLE_WHISPER_BLAS=ON -DMUTTERKEY_WHISPER_BLAS_VENDOR=OpenBLAS
49+
```
50+
51+
- Acceleration option notes:
52+
- `MUTTERKEY_ENABLE_WHISPER_CUDA=ON`: NVIDIA GPU build through vendored `ggml`
53+
- `MUTTERKEY_ENABLE_WHISPER_VULKAN=ON`: Vulkan GPU build through vendored `ggml`
54+
- `MUTTERKEY_ENABLE_WHISPER_BLAS=ON`: faster CPU inference, not GPU execution
55+
- choose the backend intentionally for the release artifact and record that choice in release notes or packaging docs when relevant
56+
3657
- Build:
3758

3859
```bash
@@ -104,6 +125,11 @@ cmake --install "$BUILD_DIR" --prefix "$INSTALL_DIR"
104125
- required `libwhisper` / `ggml` shared libraries
105126
- the desktop file under `share/applications`
106127
- license files under `share/licenses/mutterkey`
128+
- If acceleration was enabled for the release, also confirm the installed tree
129+
contains the expected backend library:
130+
- `libggml-cuda.so*` for `MUTTERKEY_ENABLE_WHISPER_CUDA=ON`
131+
- `libggml-vulkan.so*` for `MUTTERKEY_ENABLE_WHISPER_VULKAN=ON`
132+
- `libggml-blas.so*` for `MUTTERKEY_ENABLE_WHISPER_BLAS=ON`
107133
- Do not expect vendored upstream public headers to be installed; Mutterkey's
108134
install rules ship the runtime libraries but intentionally clear vendored
109135
`PUBLIC_HEADER` metadata to avoid upstream header-install warnings.
@@ -118,6 +144,12 @@ cmake --install "$BUILD_DIR" --prefix "$INSTALL_DIR"
118144
recommended installed-binary setup.
119145
- Confirm [contrib/org.mutterkey.mutterkey.desktop](contrib/org.mutterkey.mutterkey.desktop)
120146
still reflects the intended desktop behavior, including `NoDisplay=true`.
147+
- If the release is intended to use accelerated Whisper inference, verify the
148+
runtime logs on a representative machine show the expected backend instead of
149+
CPU-only fallback. For example:
150+
- CUDA/Vulkan releases should not log only `registered backend CPU`
151+
- CPU-accelerated BLAS releases may still be CPU-only, but should be tested
152+
against a representative Whisper model and expected performance target
121153

122154
## Vendored whisper.cpp Updates
123155

config.example.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"device_id": ""
1414
},
1515
"transcriber": {
16-
"model_path": "/absolute/path/to/ggml-base.en.bin",
16+
"model_path": "/path/to/ggml-base.en.bin",
1717
"language": "en",
1818
"translate": false,
1919
"threads": 0,

src/commanddispatch.cpp

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#include "commanddispatch.h"
2+
3+
#include "config.h"
4+
5+
#include <QString>
6+
#include <QTextStream>
7+
8+
namespace {
9+
10+
bool optionConsumesValue(const QString &argument)
11+
{
12+
static const QStringList kOptionsWithValues{
13+
QStringLiteral("--config"),
14+
QStringLiteral("--log-level"),
15+
QStringLiteral("--model-path"),
16+
QStringLiteral("--shortcut"),
17+
QStringLiteral("--language"),
18+
QStringLiteral("--threads"),
19+
};
20+
21+
return kOptionsWithValues.contains(argument);
22+
}
23+
24+
bool isHelpArgument(const QString &argument)
25+
{
26+
return argument == QStringLiteral("--help") || argument == QStringLiteral("-h") || argument == QStringLiteral("--help-all");
27+
}
28+
29+
} // namespace
30+
31+
QStringList rawArguments(std::span<char *const> arguments)
32+
{
33+
QStringList parsedArguments;
34+
parsedArguments.reserve(static_cast<qsizetype>(arguments.size()));
35+
for (char *argument : arguments) {
36+
parsedArguments.append(QString::fromLocal8Bit(argument));
37+
}
38+
return parsedArguments;
39+
}
40+
41+
int commandIndexFromArguments(const QStringList &arguments)
42+
{
43+
for (int index = 1; index < arguments.size(); ++index) {
44+
const QString &argument = arguments.at(index);
45+
if (argument == QStringLiteral("--")) {
46+
return index + 1 < arguments.size() ? index + 1 : -1;
47+
}
48+
49+
if (optionConsumesValue(argument)) {
50+
++index;
51+
continue;
52+
}
53+
54+
if (argument.startsWith(QLatin1String("--")) && argument.contains(QLatin1Char('='))) {
55+
continue;
56+
}
57+
58+
if (argument.startsWith(QLatin1Char('-'))) {
59+
continue;
60+
}
61+
62+
return index;
63+
}
64+
65+
return -1;
66+
}
67+
68+
bool shouldShowConfigHelp(const QStringList &arguments, int commandIndex)
69+
{
70+
if (commandIndex < 0 || commandIndex >= arguments.size() || arguments.at(commandIndex) != QStringLiteral("config")) {
71+
return false;
72+
}
73+
74+
if (commandIndex == arguments.size() - 1) {
75+
return true;
76+
}
77+
78+
for (int index = commandIndex + 1; index < arguments.size(); ++index) {
79+
if (isHelpArgument(arguments.at(index))) {
80+
return true;
81+
}
82+
}
83+
84+
return false;
85+
}
86+
87+
QString configHelpText()
88+
{
89+
QString helpText;
90+
QTextStream output(&helpText);
91+
output << "Usage: mutterkey [options] config <subcommand> [args]" << Qt::endl;
92+
output << Qt::endl;
93+
output << "Configuration subcommands:" << Qt::endl;
94+
output << " init Create the config file, prompting on a terminal when needed" << Qt::endl;
95+
output << " set <key> <value> Persist one config value into the config file" << Qt::endl;
96+
output << Qt::endl;
97+
output << "Config options:" << Qt::endl;
98+
output << " --config <path> Path to the JSON config file" << Qt::endl;
99+
output << " --model-path <path> Set transcriber.model_path during `config init`" << Qt::endl;
100+
output << " --shortcut <sequence> Set shortcut.sequence during `config init`" << Qt::endl;
101+
output << " --language <code|auto> Set transcriber.language during `config init`" << Qt::endl;
102+
output << " --threads <count> Set transcriber.threads during `config init`" << Qt::endl;
103+
output << " --translate Set transcriber.translate=true during `config init`" << Qt::endl;
104+
output << " --no-translate Set transcriber.translate=false during `config init`" << Qt::endl;
105+
output << " --warmup-on-start Set transcriber.warmup_on_start=true during `config init`" << Qt::endl;
106+
output << " --no-warmup-on-start Set transcriber.warmup_on_start=false during `config init`" << Qt::endl;
107+
output << " --log-level <level> Set log_level during `config init`" << Qt::endl;
108+
output << Qt::endl;
109+
output << "Supported keys for `config set`:" << Qt::endl;
110+
for (const QString &key : supportedConfigKeys()) {
111+
output << " " << key << Qt::endl;
112+
}
113+
return helpText;
114+
}

src/commanddispatch.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#pragma once
2+
3+
#include <span>
4+
#include <QStringList>
5+
6+
/**
7+
* @brief Converts raw argv data into Qt strings.
8+
* @param arguments Raw argv span.
9+
* @return Raw command-line arguments as a QStringList.
10+
*/
11+
QStringList rawArguments(std::span<char *const> arguments);
12+
13+
/**
14+
* @brief Finds the first positional command after known global options.
15+
* @param arguments Raw command-line arguments.
16+
* @return Index of the command token, or `-1` when no command is present.
17+
*/
18+
int commandIndexFromArguments(const QStringList &arguments);
19+
20+
/**
21+
* @brief Returns whether the config command should print dedicated help.
22+
* @param arguments Raw command-line arguments.
23+
* @param commandIndex Index returned by commandIndexFromArguments().
24+
* @return `true` for bare `config` and `config --help` style invocations.
25+
*/
26+
bool shouldShowConfigHelp(const QStringList &arguments, int commandIndex);
27+
28+
/**
29+
* @brief Returns the dedicated help text for config subcommands.
30+
* @return Human-readable help text.
31+
*/
32+
QString configHelpText();

0 commit comments

Comments
 (0)