Initial commit: Win Dictation - AI Voice to Text for Windows with automatic model selection
This commit is contained in:
+558
@@ -0,0 +1,558 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
#include <commctrl.h>
|
||||
#include <shellapi.h>
|
||||
#include <dwmapi.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "transcriber.h"
|
||||
#include "whisper.h"
|
||||
#include <cstring>
|
||||
|
||||
#pragma comment(lib, "comctl32.lib")
|
||||
#pragma comment(lib, "dwmapi.lib")
|
||||
|
||||
// Constants
|
||||
#define WM_TRAYICON (WM_USER + 1)
|
||||
#define WM_APPEND_TEXT (WM_USER + 2)
|
||||
#define ID_TRAY_APP_ICON 1001
|
||||
#define ID_TRAY_EXIT 1002
|
||||
#define ID_TRAY_SHOW 1003
|
||||
#define ID_BTN_RECORD 1004
|
||||
#define ID_BTN_CLEAR 1005
|
||||
#define ID_EDIT_TEXT 1006
|
||||
#define ID_COMBO_AUDIO 1007
|
||||
#define ID_PROGRESS_VU 1008
|
||||
#define ID_STATIC_STATUS 1009
|
||||
#define ID_PROGRESS_BUFFER 1010
|
||||
#define ID_TIMER_UPDATE 2
|
||||
#define HOTKEY_ID 1
|
||||
#define IDI_ICON1 101
|
||||
|
||||
// Modern colors (dark theme)
|
||||
#define COLOR_BG RGB(32, 33, 36)
|
||||
#define COLOR_SURFACE RGB(41, 42, 45)
|
||||
#define COLOR_PRIMARY RGB(138, 180, 248)
|
||||
#define COLOR_SUCCESS RGB(129, 201, 149)
|
||||
#define COLOR_TEXT RGB(232, 234, 237)
|
||||
#define COLOR_TEXT_DIM RGB(154, 160, 166)
|
||||
#define COLOR_ACCENT RGB(66, 133, 244)
|
||||
|
||||
// Globals
|
||||
HINSTANCE hInst;
|
||||
HWND hMainWnd;
|
||||
NOTIFYICONDATA nid;
|
||||
Transcriber g_transcriber;
|
||||
WhisperConfig g_config;
|
||||
bool g_isRecording = false;
|
||||
|
||||
// UI Resources
|
||||
HBRUSH g_hBrushBg = NULL;
|
||||
HBRUSH g_hBrushSurface = NULL;
|
||||
HFONT g_hFontNormal = NULL;
|
||||
HFONT g_hFontLarge = NULL;
|
||||
HFONT g_hFontMono = NULL;
|
||||
|
||||
// Forward declarations
|
||||
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
|
||||
void ShowContextMenu(HWND hwnd, POINT pt);
|
||||
void ToggleRecording(HWND hwnd);
|
||||
void RefreshAudioDevices(HWND hwnd);
|
||||
void InitializeUI(HWND hwnd);
|
||||
void UpdateStatus(HWND hwnd);
|
||||
bool DetectGPUAvailability();
|
||||
std::string SelectOptimalModel(bool has_gpu);
|
||||
|
||||
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) {
|
||||
hInst = hInstance;
|
||||
|
||||
// Initialize Common Controls
|
||||
INITCOMMONCONTROLSEX icex;
|
||||
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
|
||||
icex.dwICC = ICC_WIN95_CLASSES | ICC_STANDARD_CLASSES;
|
||||
InitCommonControlsEx(&icex);
|
||||
|
||||
// Create UI resources
|
||||
g_hBrushBg = CreateSolidBrush(COLOR_BG);
|
||||
g_hBrushSurface = CreateSolidBrush(COLOR_SURFACE);
|
||||
|
||||
g_hFontNormal = CreateFont(16, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
|
||||
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Segoe UI");
|
||||
|
||||
g_hFontLarge = CreateFont(20, 0, 0, 0, FW_SEMIBOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
|
||||
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Segoe UI");
|
||||
|
||||
g_hFontMono = CreateFont(16, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
|
||||
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Consolas");
|
||||
|
||||
// Register Window Class
|
||||
WNDCLASSEX wc = {0};
|
||||
wc.cbSize = sizeof(WNDCLASSEX);
|
||||
wc.style = CS_HREDRAW | CS_VREDRAW;
|
||||
wc.lpfnWndProc = WndProc;
|
||||
wc.hInstance = hInstance;
|
||||
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
wc.hbrBackground = g_hBrushBg;
|
||||
wc.lpszClassName = L"WhisperDictationClass";
|
||||
wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
|
||||
RegisterClassEx(&wc);
|
||||
|
||||
// Create Window (modern, larger size)
|
||||
hMainWnd = CreateWindowEx(
|
||||
0,
|
||||
L"WhisperDictationClass",
|
||||
L"Whisper Dictation - AI Voice to Text",
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, 720, 600,
|
||||
NULL, NULL, hInstance, NULL
|
||||
);
|
||||
|
||||
if (!hMainWnd) return FALSE;
|
||||
|
||||
// Enable dark mode for title bar (Windows 10+)
|
||||
BOOL useDarkMode = TRUE;
|
||||
DwmSetWindowAttribute(hMainWnd, 20, &useDarkMode, sizeof(useDarkMode));
|
||||
|
||||
InitializeUI(hMainWnd);
|
||||
RefreshAudioDevices(hMainWnd);
|
||||
|
||||
// Tray Icon
|
||||
nid.cbSize = sizeof(NOTIFYICONDATA);
|
||||
nid.hWnd = hMainWnd;
|
||||
nid.uID = ID_TRAY_APP_ICON;
|
||||
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
|
||||
nid.uCallbackMessage = WM_TRAYICON;
|
||||
nid.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
|
||||
wcscpy_s(nid.szTip, L"Whisper Dictation");
|
||||
Shell_NotifyIcon(NIM_ADD, &nid);
|
||||
|
||||
// Register Hotkey (Ctrl + Shift + R)
|
||||
RegisterHotKey(hMainWnd, HOTKEY_ID, MOD_CONTROL | MOD_SHIFT, 'R');
|
||||
|
||||
// Detect GPU availability and select optimal model
|
||||
bool has_gpu = DetectGPUAvailability();
|
||||
g_config.model_path = SelectOptimalModel(has_gpu);
|
||||
|
||||
// Setup callback for transcribed text
|
||||
g_transcriber.set_callback([](const std::string& text) {
|
||||
std::string* msg = new std::string(text);
|
||||
PostMessage(hMainWnd, WM_APPEND_TEXT, (WPARAM)msg, 0);
|
||||
});
|
||||
|
||||
// Start UI update timer
|
||||
SetTimer(hMainWnd, ID_TIMER_UPDATE, 33, NULL); // ~30 FPS for smooth animations
|
||||
|
||||
ShowWindow(hMainWnd, nCmdShow);
|
||||
UpdateWindow(hMainWnd);
|
||||
|
||||
MSG msg;
|
||||
while (GetMessage(&msg, NULL, 0, 0)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
Shell_NotifyIcon(NIM_DELETE, &nid);
|
||||
DeleteObject(g_hBrushBg);
|
||||
DeleteObject(g_hBrushSurface);
|
||||
DeleteObject(g_hFontNormal);
|
||||
DeleteObject(g_hFontLarge);
|
||||
DeleteObject(g_hFontMono);
|
||||
|
||||
return (int)msg.wParam;
|
||||
}
|
||||
|
||||
void InitializeUI(HWND hwnd) {
|
||||
// Create all controls with modern styling
|
||||
|
||||
// Record button (large, primary)
|
||||
HWND hBtnRecord = CreateWindow(L"BUTTON", L"⬤ Start Recording",
|
||||
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_OWNERDRAW,
|
||||
20, 20, 300, 50, hwnd, (HMENU)ID_BTN_RECORD, hInst, NULL);
|
||||
SendMessage(hBtnRecord, WM_SETFONT, (WPARAM)g_hFontLarge, TRUE);
|
||||
|
||||
// Clear button
|
||||
HWND hBtnClear = CreateWindow(L"BUTTON", L"Clear",
|
||||
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
|
||||
340, 20, 120, 50, hwnd, (HMENU)ID_BTN_CLEAR, hInst, NULL);
|
||||
SendMessage(hBtnClear, WM_SETFONT, (WPARAM)g_hFontNormal, TRUE);
|
||||
|
||||
// Status text
|
||||
HWND hStatus = CreateWindow(L"STATIC", L"Ready • GPU: Detecting...",
|
||||
WS_CHILD | WS_VISIBLE | SS_LEFT,
|
||||
20, 85, 640, 25, hwnd, (HMENU)ID_STATIC_STATUS, hInst, NULL);
|
||||
SendMessage(hStatus, WM_SETFONT, (WPARAM)g_hFontNormal, TRUE);
|
||||
|
||||
// Audio device selector
|
||||
CreateWindow(L"STATIC", L"Microphone:",
|
||||
WS_CHILD | WS_VISIBLE | SS_LEFT,
|
||||
20, 120, 120, 20, hwnd, NULL, hInst, NULL);
|
||||
|
||||
HWND hCombo = CreateWindow(L"COMBOBOX", L"",
|
||||
WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_VSCROLL,
|
||||
140, 118, 360, 200, hwnd, (HMENU)ID_COMBO_AUDIO, hInst, NULL);
|
||||
SendMessage(hCombo, WM_SETFONT, (WPARAM)g_hFontNormal, TRUE);
|
||||
|
||||
// VU Meter label and progress
|
||||
CreateWindow(L"STATIC", L"Level:",
|
||||
WS_CHILD | WS_VISIBLE | SS_LEFT,
|
||||
520, 120, 60, 20, hwnd, NULL, hInst, NULL);
|
||||
|
||||
HWND hVU = CreateWindow(PROGRESS_CLASS, L"",
|
||||
WS_CHILD | WS_VISIBLE | PBS_SMOOTH,
|
||||
580, 118, 100, 22, hwnd, (HMENU)ID_PROGRESS_VU, hInst, NULL);
|
||||
SendMessage(hVU, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
|
||||
SendMessage(hVU, PBM_SETBARCOLOR, 0, (LPARAM)COLOR_SUCCESS);
|
||||
SendMessage(hVU, PBM_SETBKCOLOR, 0, (LPARAM)COLOR_SURFACE);
|
||||
|
||||
// Buffer progress bar
|
||||
CreateWindow(L"STATIC", L"Buffer:",
|
||||
WS_CHILD | WS_VISIBLE | SS_LEFT,
|
||||
20, 155, 60, 20, hwnd, NULL, hInst, NULL);
|
||||
|
||||
HWND hBuffer = CreateWindow(PROGRESS_CLASS, L"",
|
||||
WS_CHILD | WS_VISIBLE | PBS_SMOOTH,
|
||||
85, 153, 595, 22, hwnd, (HMENU)ID_PROGRESS_BUFFER, hInst, NULL);
|
||||
SendMessage(hBuffer, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
|
||||
SendMessage(hBuffer, PBM_SETBARCOLOR, 0, (LPARAM)COLOR_PRIMARY);
|
||||
SendMessage(hBuffer, PBM_SETBKCOLOR, 0, (LPARAM)COLOR_SURFACE);
|
||||
|
||||
// Transcription text box (large, monospaced)
|
||||
HWND hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", L"",
|
||||
WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN,
|
||||
20, 195, 660, 340, hwnd, (HMENU)ID_EDIT_TEXT, hInst, NULL);
|
||||
SendMessage(hEdit, WM_SETFONT, (WPARAM)g_hFontMono, TRUE);
|
||||
SendMessage(hEdit, EM_SETLIMITTEXT, 0, 0); // No limit
|
||||
}
|
||||
|
||||
void RefreshAudioDevices(HWND hwnd) {
|
||||
HWND hCombo = GetDlgItem(hwnd, ID_COMBO_AUDIO);
|
||||
SendMessage(hCombo, CB_RESETCONTENT, 0, 0);
|
||||
|
||||
std::vector<std::string> devices = Transcriber::get_audio_devices();
|
||||
if (devices.empty()) {
|
||||
SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)L"No devices found");
|
||||
SendMessage(hCombo, CB_SETCURSEL, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& device : devices) {
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, device.c_str(), -1, NULL, 0);
|
||||
if (len > 0) {
|
||||
std::vector<wchar_t> wbuf(len);
|
||||
MultiByteToWideChar(CP_UTF8, 0, device.c_str(), -1, wbuf.data(), len);
|
||||
SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)wbuf.data());
|
||||
}
|
||||
}
|
||||
SendMessage(hCombo, CB_SETCURSEL, 0, 0);
|
||||
g_config.capture_id = 0;
|
||||
}
|
||||
|
||||
void UpdateStatus(HWND hwnd) {
|
||||
wchar_t status[256] = {0};
|
||||
|
||||
if (g_isRecording) {
|
||||
bool gpu = g_transcriber.is_using_gpu();
|
||||
float buffer = g_transcriber.get_buffer_fullness() * 100.0f;
|
||||
|
||||
swprintf_s(status, L"⬤ Recording • GPU: %s • Buffer: %.0f%% • Threads: %d",
|
||||
gpu ? L"ON" : L"CPU", buffer, g_config.n_threads);
|
||||
} else {
|
||||
swprintf_s(status, L"Ready • Press Ctrl+Shift+R to start • Threads: %d",
|
||||
g_config.n_threads);
|
||||
}
|
||||
|
||||
SetDlgItemText(hwnd, ID_STATIC_STATUS, status);
|
||||
}
|
||||
|
||||
void ToggleRecording(HWND hwnd) {
|
||||
if (g_isRecording) {
|
||||
// Stop recording
|
||||
g_transcriber.stop();
|
||||
SetDlgItemText(hwnd, ID_BTN_RECORD, L"⬤ Start Recording");
|
||||
g_isRecording = false;
|
||||
|
||||
// Reset progress bars
|
||||
SendMessage(GetDlgItem(hwnd, ID_PROGRESS_VU), PBM_SETPOS, 0, 0);
|
||||
SendMessage(GetDlgItem(hwnd, ID_PROGRESS_BUFFER), PBM_SETPOS, 0, 0);
|
||||
|
||||
UpdateStatus(hwnd);
|
||||
|
||||
} else {
|
||||
// Start recording
|
||||
HWND hCombo = GetDlgItem(hwnd, ID_COMBO_AUDIO);
|
||||
int idx = SendMessage(hCombo, CB_GETCURSEL, 0, 0);
|
||||
if (idx != CB_ERR) {
|
||||
g_config.capture_id = idx;
|
||||
}
|
||||
|
||||
// Initialize if not loaded
|
||||
if (!g_transcriber.is_loaded()) {
|
||||
SetDlgItemText(hwnd, ID_STATIC_STATUS, L"Loading model...");
|
||||
UpdateWindow(hwnd);
|
||||
|
||||
if (!g_transcriber.init(g_config)) {
|
||||
MessageBox(hwnd, L"Failed to initialize Whisper.\n\nPlease check:\n- Model file exists in models/\n- GPU drivers are up to date (if using GPU)",
|
||||
L"Error", MB_OK | MB_ICONERROR);
|
||||
UpdateStatus(hwnd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
g_transcriber.start();
|
||||
SetDlgItemText(hwnd, ID_BTN_RECORD, L"⬛ Stop Recording");
|
||||
g_isRecording = true;
|
||||
UpdateStatus(hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
// Custom button drawing for modern look
|
||||
void DrawButton(LPDRAWITEMSTRUCT pDIS) {
|
||||
HDC hdc = pDIS->hDC;
|
||||
RECT rect = pDIS->rcItem;
|
||||
bool pressed = (pDIS->itemState & ODS_SELECTED) != 0;
|
||||
bool hover = (pDIS->itemState & ODS_HOTLIGHT) != 0;
|
||||
|
||||
// Background
|
||||
COLORREF bgColor = g_isRecording ? RGB(201, 70, 70) : COLOR_ACCENT;
|
||||
if (pressed) {
|
||||
bgColor = RGB(50, 110, 220);
|
||||
} else if (hover) {
|
||||
bgColor = g_isRecording ? RGB(220, 85, 85) : RGB(88, 145, 255);
|
||||
}
|
||||
|
||||
HBRUSH hBrush = CreateSolidBrush(bgColor);
|
||||
FillRect(hdc, &rect, hBrush);
|
||||
DeleteObject(hBrush);
|
||||
|
||||
// Text
|
||||
wchar_t text[128] = {0};
|
||||
GetWindowText(pDIS->hwndItem, text, 128);
|
||||
|
||||
SetBkMode(hdc, TRANSPARENT);
|
||||
SetTextColor(hdc, RGB(255, 255, 255));
|
||||
SelectObject(hdc, g_hFontLarge);
|
||||
|
||||
DrawText(hdc, text, -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
}
|
||||
|
||||
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
switch (message) {
|
||||
case WM_CTLCOLORSTATIC:
|
||||
{
|
||||
HDC hdcStatic = (HDC)wParam;
|
||||
SetTextColor(hdcStatic, COLOR_TEXT);
|
||||
SetBkColor(hdcStatic, COLOR_BG);
|
||||
return (LRESULT)g_hBrushBg;
|
||||
}
|
||||
|
||||
case WM_CTLCOLOREDIT:
|
||||
{
|
||||
HDC hdcEdit = (HDC)wParam;
|
||||
SetTextColor(hdcEdit, COLOR_TEXT);
|
||||
SetBkColor(hdcEdit, COLOR_SURFACE);
|
||||
return (LRESULT)g_hBrushSurface;
|
||||
}
|
||||
|
||||
case WM_DRAWITEM:
|
||||
if (wParam == ID_BTN_RECORD) {
|
||||
DrawButton((LPDRAWITEMSTRUCT)lParam);
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_SIZE:
|
||||
{
|
||||
int width = LOWORD(lParam);
|
||||
int height = HIWORD(lParam);
|
||||
|
||||
// Responsive layout
|
||||
MoveWindow(GetDlgItem(hWnd, ID_BTN_RECORD), 20, 20, 300, 50, TRUE);
|
||||
MoveWindow(GetDlgItem(hWnd, ID_BTN_CLEAR), 340, 20, 120, 50, TRUE);
|
||||
MoveWindow(GetDlgItem(hWnd, ID_STATIC_STATUS), 20, 85, width - 40, 25, TRUE);
|
||||
MoveWindow(GetDlgItem(hWnd, ID_COMBO_AUDIO), 140, 118, width - 280, 22, TRUE);
|
||||
MoveWindow(GetDlgItem(hWnd, ID_PROGRESS_VU), width - 120, 118, 100, 22, TRUE);
|
||||
MoveWindow(GetDlgItem(hWnd, ID_PROGRESS_BUFFER), 85, 153, width - 105, 22, TRUE);
|
||||
MoveWindow(GetDlgItem(hWnd, ID_EDIT_TEXT), 20, 195, width - 40, height - 215, TRUE);
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_COMMAND:
|
||||
switch (LOWORD(wParam)) {
|
||||
case ID_TRAY_EXIT:
|
||||
DestroyWindow(hWnd);
|
||||
break;
|
||||
case ID_TRAY_SHOW:
|
||||
ShowWindow(hWnd, SW_SHOW);
|
||||
SetForegroundWindow(hWnd);
|
||||
break;
|
||||
case ID_BTN_RECORD:
|
||||
ToggleRecording(hWnd);
|
||||
break;
|
||||
case ID_BTN_CLEAR:
|
||||
SetDlgItemText(hWnd, ID_EDIT_TEXT, L"");
|
||||
break;
|
||||
case ID_COMBO_AUDIO:
|
||||
if (HIWORD(wParam) == CBN_SELCHANGE) {
|
||||
if (g_isRecording) {
|
||||
// Stop current recording
|
||||
g_transcriber.stop();
|
||||
SetDlgItemText(hWnd, ID_BTN_RECORD, L"⬤ Start Recording");
|
||||
g_isRecording = false;
|
||||
|
||||
// Wait for complete shutdown
|
||||
Sleep(100);
|
||||
|
||||
// Update config with new device
|
||||
HWND hCombo = GetDlgItem(hWnd, ID_COMBO_AUDIO);
|
||||
int idx = SendMessage(hCombo, CB_GETCURSEL, 0, 0);
|
||||
if (idx != CB_ERR) {
|
||||
g_config.capture_id = idx;
|
||||
}
|
||||
|
||||
// Restart with new device
|
||||
g_transcriber.start();
|
||||
SetDlgItemText(hWnd, ID_BTN_RECORD, L"⬛ Stop Recording");
|
||||
g_isRecording = true;
|
||||
UpdateStatus(hWnd);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_TIMER:
|
||||
if (wParam == ID_TIMER_UPDATE && g_isRecording) {
|
||||
// Update VU meter (smooth animation)
|
||||
float energy = g_transcriber.get_audio_energy();
|
||||
int pos = (int)(energy * 100.0f);
|
||||
SendMessage(GetDlgItem(hWnd, ID_PROGRESS_VU), PBM_SETPOS, pos, 0);
|
||||
|
||||
// Update buffer indicator
|
||||
float buffer = g_transcriber.get_buffer_fullness();
|
||||
int buf_pos = (int)(buffer * 100.0f);
|
||||
SendMessage(GetDlgItem(hWnd, ID_PROGRESS_BUFFER), PBM_SETPOS, buf_pos, 0);
|
||||
|
||||
// Update status text
|
||||
UpdateStatus(hWnd);
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_TRAYICON:
|
||||
if (lParam == WM_RBUTTONUP) {
|
||||
POINT pt;
|
||||
GetCursorPos(&pt);
|
||||
ShowContextMenu(hWnd, pt);
|
||||
} else if (lParam == WM_LBUTTONDBLCLK) {
|
||||
ShowWindow(hWnd, SW_SHOW);
|
||||
SetForegroundWindow(hWnd);
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_HOTKEY:
|
||||
if (wParam == HOTKEY_ID) {
|
||||
ToggleRecording(hWnd);
|
||||
if (g_isRecording) {
|
||||
ShowWindow(hWnd, SW_SHOW);
|
||||
SetForegroundWindow(hWnd);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_APPEND_TEXT:
|
||||
{
|
||||
std::string* s = (std::string*)wParam;
|
||||
if (s) {
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, s->c_str(), -1, NULL, 0);
|
||||
if (len > 0) {
|
||||
std::vector<wchar_t> wbuf(len);
|
||||
MultiByteToWideChar(CP_UTF8, 0, s->c_str(), -1, wbuf.data(), len);
|
||||
|
||||
HWND hEdit = GetDlgItem(hWnd, ID_EDIT_TEXT);
|
||||
int ndx = GetWindowTextLength(hEdit);
|
||||
SendMessage(hEdit, EM_SETSEL, (WPARAM)ndx, (LPARAM)ndx);
|
||||
SendMessage(hEdit, EM_REPLACESEL, 0, (LPARAM)wbuf.data());
|
||||
SendMessage(hEdit, EM_REPLACESEL, 0, (LPARAM)L" ");
|
||||
|
||||
// Auto-scroll to bottom
|
||||
SendMessage(hEdit, EM_SCROLLCARET, 0, 0);
|
||||
}
|
||||
delete s;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_CLOSE:
|
||||
ShowWindow(hWnd, SW_HIDE);
|
||||
return 0;
|
||||
|
||||
case WM_DESTROY:
|
||||
g_transcriber.stop();
|
||||
UnregisterHotKey(hWnd, HOTKEY_ID);
|
||||
KillTimer(hWnd, ID_TIMER_UPDATE);
|
||||
PostQuitMessage(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ShowContextMenu(HWND hwnd, POINT pt) {
|
||||
HMENU hMenu = CreatePopupMenu();
|
||||
InsertMenu(hMenu, 0, MF_BYPOSITION | MF_STRING, ID_TRAY_SHOW, L"Show Window");
|
||||
InsertMenu(hMenu, 1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL);
|
||||
InsertMenu(hMenu, 2, MF_BYPOSITION | MF_STRING, ID_TRAY_EXIT, L"Exit");
|
||||
SetForegroundWindow(hwnd);
|
||||
TrackPopupMenu(hMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, hwnd, NULL);
|
||||
DestroyMenu(hMenu);
|
||||
}
|
||||
|
||||
// Detect GPU availability without loading a model
|
||||
bool DetectGPUAvailability() {
|
||||
// Use whisper_print_system_info to check for GPU backends
|
||||
// This function works without loading a model
|
||||
const char* info = whisper_print_system_info();
|
||||
if (!info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for GPU backends in the system info string
|
||||
return (strstr(info, "CUDA") != nullptr ||
|
||||
strstr(info, "Metal") != nullptr ||
|
||||
strstr(info, "HIP") != nullptr ||
|
||||
strstr(info, "Vulkan") != nullptr);
|
||||
}
|
||||
|
||||
// Select optimal model based on GPU availability
|
||||
// CPU-only systems get tiny.en (faster, smaller), GPU systems get base.en (better accuracy)
|
||||
std::string SelectOptimalModel(bool has_gpu) {
|
||||
if (has_gpu) {
|
||||
// GPU available - use base.en for better accuracy
|
||||
if (GetFileAttributesA("models/ggml-base.en.bin") != INVALID_FILE_ATTRIBUTES) {
|
||||
return "models/ggml-base.en.bin";
|
||||
} else if (GetFileAttributesA("models/ggml-medium.en.bin") != INVALID_FILE_ATTRIBUTES) {
|
||||
return "models/ggml-medium.en.bin";
|
||||
} else if (GetFileAttributesA("models/ggml-large-v3-turbo.bin") != INVALID_FILE_ATTRIBUTES) {
|
||||
return "models/ggml-large-v3-turbo.bin";
|
||||
}
|
||||
// Fallback to tiny if base not available
|
||||
if (GetFileAttributesA("models/ggml-tiny.en.bin") != INVALID_FILE_ATTRIBUTES) {
|
||||
return "models/ggml-tiny.en.bin";
|
||||
}
|
||||
} else {
|
||||
// CPU-only - use tiny.en for better performance on slower machines
|
||||
if (GetFileAttributesA("models/ggml-tiny.en.bin") != INVALID_FILE_ATTRIBUTES) {
|
||||
return "models/ggml-tiny.en.bin";
|
||||
}
|
||||
// Fallback to base if tiny not available
|
||||
if (GetFileAttributesA("models/ggml-base.en.bin") != INVALID_FILE_ATTRIBUTES) {
|
||||
return "models/ggml-base.en.bin";
|
||||
}
|
||||
}
|
||||
|
||||
// Ultimate fallback
|
||||
return "models/ggml-base.en.bin";
|
||||
}
|
||||
Reference in New Issue
Block a user