Created
February 19, 2026 13:50
-
-
Save dvassos/c6fe84960470c0aae2c3b30835538d4f to your computer and use it in GitHub Desktop.
Omilia Audio Agent - Windows Build Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================================ | |
| # Omilia Audio Agent -- Source Deployment & Build Script | |
| # Run this in PowerShell (as Admin) on the Build Server (3.88.19.13) | |
| # ============================================================================ | |
| $ErrorActionPreference = "Continue" | |
| $ProjectDir = "C:\omilia\audio-agent" | |
| Write-Host "" | |
| Write-Host "============================================" -ForegroundColor Cyan | |
| Write-Host " Omilia Audio Agent -- Windows Build" -ForegroundColor Cyan | |
| Write-Host "============================================" -ForegroundColor Cyan | |
| Write-Host "" | |
| # ---- Step 1: Check Prerequisites ---- | |
| Write-Host "[1/5] Checking prerequisites..." -ForegroundColor Yellow | |
| $vsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" | |
| if (-not (Test-Path $vsWhere)) { | |
| Write-Host " ERROR: vswhere.exe not found. Install VS 2022 Build Tools first." -ForegroundColor Red | |
| exit 1 | |
| } | |
| $vsInstallPath = & $vsWhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null | |
| if (-not $vsInstallPath) { | |
| Write-Host " ERROR: VS C++ tools not found." -ForegroundColor Red | |
| exit 1 | |
| } | |
| Write-Host " VS found: $vsInstallPath" -ForegroundColor Green | |
| if (-not (Get-Command cmake -ErrorAction SilentlyContinue)) { | |
| Write-Host " ERROR: CMake not found. Install via: choco install -y cmake --installargs 'ADD_CMAKE_TO_PATH=System'" -ForegroundColor Red | |
| exit 1 | |
| } | |
| Write-Host " CMake: $(cmake --version 2>&1 | Select-Object -First 1)" -ForegroundColor Green | |
| Write-Host "" | |
| # ---- Step 2: Create Source Files ---- | |
| Write-Host "[2/5] Writing source files..." -ForegroundColor Yellow | |
| if (Test-Path $ProjectDir) { Remove-Item -Recurse -Force $ProjectDir } | |
| New-Item -ItemType Directory -Path "$ProjectDir\src\ui" -Force | Out-Null | |
| New-Item -ItemType Directory -Path "$ProjectDir\src\service" -Force | Out-Null | |
| New-Item -ItemType Directory -Path "$ProjectDir\src\capture" -Force | Out-Null | |
| New-Item -ItemType Directory -Path "$ProjectDir\src\encoding" -Force | Out-Null | |
| New-Item -ItemType Directory -Path "$ProjectDir\src\transport" -Force | Out-Null | |
| New-Item -ItemType Directory -Path "$ProjectDir\src\vad" -Force | Out-Null | |
| # --- CMakeLists.txt --- | |
| @" | |
| cmake_minimum_required(VERSION 3.20) | |
| project(omilia-audio-agent VERSION 0.1.0 LANGUAGES CXX) | |
| set(CMAKE_CXX_STANDARD 20) | |
| set(CMAKE_CXX_STANDARD_REQUIRED ON) | |
| set(CMAKE_EXPORT_COMPILE_COMMANDS ON) | |
| if(WIN32) | |
| set(CMAKE_WIN32_EXECUTABLE ON) | |
| endif() | |
| add_executable(audio-agent WIN32 | |
| src/main.cpp | |
| src/ui/tray_icon.cpp | |
| src/ui/status_window.cpp | |
| src/ui/resource.rc | |
| src/service/agent_state_machine.cpp | |
| ) | |
| target_include_directories(audio-agent PRIVATE src) | |
| if(WIN32) | |
| target_link_libraries(audio-agent PRIVATE | |
| comctl32 | |
| ole32 | |
| user32 | |
| gdi32 | |
| shell32 | |
| ) | |
| endif() | |
| "@ | Out-File -FilePath "$ProjectDir\CMakeLists.txt" -Encoding utf8 | |
| # --- src/ui/resource.h --- | |
| @" | |
| #pragma once | |
| #define IDI_TRAYICON 101 | |
| #define IDI_ICON_DORMANT 102 | |
| #define IDI_ICON_ARMED 103 | |
| #define IDI_ICON_CAPTURING 104 | |
| #define IDM_TRAY_MENU 200 | |
| #define IDM_STATUS 201 | |
| #define IDM_START_DEMO 202 | |
| #define IDM_STOP_DEMO 203 | |
| #define IDM_EXIT 204 | |
| #define WM_TRAYICON (WM_USER + 1) | |
| #define TIMER_STATE_UPDATE 1 | |
| #define TIMER_LEVEL_UPDATE 2 | |
| "@ | Out-File -FilePath "$ProjectDir\src\ui\resource.h" -Encoding utf8 | |
| # --- src/ui/resource.rc --- | |
| @" | |
| #include <windows.h> | |
| #include "resource.h" | |
| VS_VERSION_INFO VERSIONINFO | |
| FILEVERSION 0,1,0,0 | |
| PRODUCTVERSION 0,1,0,0 | |
| BEGIN | |
| BLOCK "StringFileInfo" | |
| BEGIN | |
| BLOCK "040904b0" | |
| BEGIN | |
| VALUE "CompanyName", "Omilia" | |
| VALUE "FileDescription", "Omilia Desktop Intelligence Audio Agent" | |
| VALUE "FileVersion", "0.1.0" | |
| VALUE "ProductName", "Audio Capture Agent" | |
| VALUE "ProductVersion", "0.1.0" | |
| END | |
| END | |
| BLOCK "VarFileInfo" | |
| BEGIN | |
| VALUE "Translation", 0x0409, 0x04B0 | |
| END | |
| END | |
| "@ | Out-File -FilePath "$ProjectDir\src\ui\resource.rc" -Encoding utf8 | |
| # --- src/service/agent_state_machine.h --- | |
| @" | |
| #pragma once | |
| #include <functional> | |
| #include <string> | |
| namespace omilia::service { | |
| enum class AgentState { | |
| DORMANT, | |
| ARMED, | |
| CAPTURING, | |
| }; | |
| inline const char* agent_state_name(AgentState s) { | |
| switch (s) { | |
| case AgentState::DORMANT: return "DORMANT"; | |
| case AgentState::ARMED: return "ARMED"; | |
| case AgentState::CAPTURING: return "CAPTURING"; | |
| default: return "UNKNOWN"; | |
| } | |
| } | |
| using StateChangeCallback = std::function<void(AgentState old_state, AgentState new_state)>; | |
| class AgentStateMachine { | |
| public: | |
| AgentStateMachine(); | |
| AgentState current_state() const { return state_; } | |
| void set_callback(StateChangeCallback cb) { callback_ = cb; } | |
| void transition_to(AgentState new_state); | |
| void advance_demo(); | |
| float simulated_audio_level() const; | |
| private: | |
| AgentState state_ = AgentState::DORMANT; | |
| StateChangeCallback callback_; | |
| mutable int tick_ = 0; | |
| }; | |
| } // namespace omilia::service | |
| "@ | Out-File -FilePath "$ProjectDir\src\service\agent_state_machine.h" -Encoding utf8 | |
| # --- src/service/agent_state_machine.cpp --- | |
| @" | |
| #include "agent_state_machine.h" | |
| #include <cmath> | |
| #include <cstdlib> | |
| namespace omilia::service { | |
| AgentStateMachine::AgentStateMachine() : state_(AgentState::DORMANT) {} | |
| void AgentStateMachine::transition_to(AgentState new_state) { | |
| if (new_state != state_) { | |
| AgentState old = state_; | |
| state_ = new_state; | |
| if (callback_) { | |
| callback_(old, new_state); | |
| } | |
| } | |
| } | |
| void AgentStateMachine::advance_demo() { | |
| switch (state_) { | |
| case AgentState::DORMANT: | |
| transition_to(AgentState::ARMED); | |
| break; | |
| case AgentState::ARMED: | |
| transition_to(AgentState::CAPTURING); | |
| break; | |
| case AgentState::CAPTURING: | |
| transition_to(AgentState::DORMANT); | |
| break; | |
| } | |
| } | |
| float AgentStateMachine::simulated_audio_level() const { | |
| tick_++; | |
| if (state_ != AgentState::CAPTURING) { | |
| return 0.0f; | |
| } | |
| float base = 0.3f + 0.2f * std::sin(tick_ * 0.1f); | |
| float noise = (std::rand() % 100) / 200.0f; | |
| float level = base + noise; | |
| return (level > 1.0f) ? 1.0f : (level < 0.0f ? 0.0f : level); | |
| } | |
| } // namespace omilia::service | |
| "@ | Out-File -FilePath "$ProjectDir\src\service\agent_state_machine.cpp" -Encoding utf8 | |
| # --- src/ui/tray_icon.h --- | |
| @" | |
| #pragma once | |
| #ifndef WIN32_LEAN_AND_MEAN | |
| #define WIN32_LEAN_AND_MEAN | |
| #endif | |
| #include <windows.h> | |
| #include <shellapi.h> | |
| #include "../service/agent_state_machine.h" | |
| namespace omilia::ui { | |
| class TrayIcon { | |
| public: | |
| TrayIcon(HWND owner, UINT callback_msg); | |
| ~TrayIcon(); | |
| void update_state(omilia::service::AgentState state); | |
| void set_tooltip(const wchar_t* text); | |
| void show_balloon(const wchar_t* title, const wchar_t* msg); | |
| void remove(); | |
| private: | |
| HICON create_colored_icon(COLORREF color); | |
| NOTIFYICONDATAW nid_{}; | |
| HWND owner_; | |
| HICON current_icon_ = nullptr; | |
| }; | |
| } // namespace omilia::ui | |
| "@ | Out-File -FilePath "$ProjectDir\src\ui\tray_icon.h" -Encoding utf8 | |
| # --- src/ui/tray_icon.cpp --- | |
| @" | |
| #include "tray_icon.h" | |
| #include "resource.h" | |
| #include <cmath> | |
| namespace omilia::ui { | |
| TrayIcon::TrayIcon(HWND owner, UINT callback_msg) : owner_(owner) { | |
| nid_.cbSize = sizeof(NOTIFYICONDATAW); | |
| nid_.hWnd = owner; | |
| nid_.uID = IDI_TRAYICON; | |
| nid_.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO; | |
| nid_.uCallbackMessage = callback_msg; | |
| current_icon_ = create_colored_icon(RGB(128, 128, 128)); | |
| nid_.hIcon = current_icon_; | |
| wcscpy_s(nid_.szTip, L"Omilia Audio Agent - DORMANT"); | |
| Shell_NotifyIconW(NIM_ADD, &nid_); | |
| show_balloon(L"Omilia Audio Agent", L"Agent started in DORMANT mode"); | |
| } | |
| TrayIcon::~TrayIcon() { | |
| remove(); | |
| if (current_icon_) DestroyIcon(current_icon_); | |
| } | |
| void TrayIcon::update_state(omilia::service::AgentState state) { | |
| COLORREF color; | |
| const wchar_t* tip; | |
| switch (state) { | |
| case omilia::service::AgentState::DORMANT: | |
| color = RGB(128, 128, 128); | |
| tip = L"Omilia Audio Agent - DORMANT"; | |
| break; | |
| case omilia::service::AgentState::ARMED: | |
| color = RGB(30, 144, 255); | |
| tip = L"Omilia Audio Agent - ARMED"; | |
| break; | |
| case omilia::service::AgentState::CAPTURING: | |
| color = RGB(0, 200, 83); | |
| tip = L"Omilia Audio Agent - CAPTURING"; | |
| break; | |
| default: | |
| color = RGB(128, 128, 128); | |
| tip = L"Omilia Audio Agent"; | |
| break; | |
| } | |
| if (current_icon_) DestroyIcon(current_icon_); | |
| current_icon_ = create_colored_icon(color); | |
| nid_.hIcon = current_icon_; | |
| wcscpy_s(nid_.szTip, tip); | |
| nid_.uFlags = NIF_ICON | NIF_TIP; | |
| Shell_NotifyIconW(NIM_MODIFY, &nid_); | |
| } | |
| void TrayIcon::set_tooltip(const wchar_t* text) { | |
| wcscpy_s(nid_.szTip, text); | |
| nid_.uFlags = NIF_TIP; | |
| Shell_NotifyIconW(NIM_MODIFY, &nid_); | |
| } | |
| void TrayIcon::show_balloon(const wchar_t* title, const wchar_t* msg) { | |
| nid_.uFlags = NIF_INFO; | |
| nid_.dwInfoFlags = NIIF_INFO; | |
| wcscpy_s(nid_.szInfoTitle, title); | |
| wcscpy_s(nid_.szInfo, msg); | |
| Shell_NotifyIconW(NIM_MODIFY, &nid_); | |
| } | |
| void TrayIcon::remove() { | |
| Shell_NotifyIconW(NIM_DELETE, &nid_); | |
| } | |
| HICON TrayIcon::create_colored_icon(COLORREF color) { | |
| int size = 32; | |
| HDC screen_dc = GetDC(nullptr); | |
| HDC mem_dc = CreateCompatibleDC(screen_dc); | |
| BITMAPINFO bmi{}; | |
| bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); | |
| bmi.bmiHeader.biWidth = size; | |
| bmi.bmiHeader.biHeight = size; | |
| bmi.bmiHeader.biPlanes = 1; | |
| bmi.bmiHeader.biBitCount = 32; | |
| bmi.bmiHeader.biCompression = BI_RGB; | |
| void* bits = nullptr; | |
| HBITMAP color_bmp = CreateDIBSection(mem_dc, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0); | |
| HBITMAP mask_bmp = CreateBitmap(size, size, 1, 1, nullptr); | |
| auto* pixels = static_cast<DWORD*>(bits); | |
| int cx = size / 2, cy = size / 2, r = size / 2 - 2; | |
| BYTE cr = GetRValue(color), cg = GetGValue(color), cb = GetBValue(color); | |
| for (int y = 0; y < size; y++) { | |
| for (int x = 0; x < size; x++) { | |
| int dx = x - cx, dy = y - cy; | |
| float dist = std::sqrt(float(dx * dx + dy * dy)); | |
| if (dist < r) { | |
| float alpha = (dist > r - 1.5f) ? (r - dist) / 1.5f : 1.0f; | |
| BYTE a = BYTE(alpha * 255); | |
| pixels[y * size + x] = (a << 24) | (cr << 16) | (cg << 8) | cb; | |
| } else { | |
| pixels[y * size + x] = 0; | |
| } | |
| } | |
| } | |
| HDC mask_dc = CreateCompatibleDC(screen_dc); | |
| HBITMAP old_mask = (HBITMAP)SelectObject(mask_dc, mask_bmp); | |
| HBRUSH white = (HBRUSH)GetStockObject(WHITE_BRUSH); | |
| RECT rc = { 0, 0, size, size }; | |
| FillRect(mask_dc, &rc, white); | |
| HBRUSH black = (HBRUSH)GetStockObject(BLACK_BRUSH); | |
| SelectObject(mask_dc, black); | |
| Ellipse(mask_dc, 2, 2, size - 2, size - 2); | |
| SelectObject(mask_dc, old_mask); | |
| DeleteDC(mask_dc); | |
| ICONINFO ii{}; | |
| ii.fIcon = TRUE; | |
| ii.hbmMask = mask_bmp; | |
| ii.hbmColor = color_bmp; | |
| HICON icon = CreateIconIndirect(&ii); | |
| DeleteObject(color_bmp); | |
| DeleteObject(mask_bmp); | |
| DeleteDC(mem_dc); | |
| ReleaseDC(nullptr, screen_dc); | |
| return icon; | |
| } | |
| } // namespace omilia::ui | |
| "@ | Out-File -FilePath "$ProjectDir\src\ui\tray_icon.cpp" -Encoding utf8 | |
| # --- src/ui/status_window.h --- | |
| @" | |
| #pragma once | |
| #ifndef WIN32_LEAN_AND_MEAN | |
| #define WIN32_LEAN_AND_MEAN | |
| #endif | |
| #include <windows.h> | |
| #include <string> | |
| #include "../service/agent_state_machine.h" | |
| namespace omilia::ui { | |
| class StatusWindow { | |
| public: | |
| StatusWindow(HINSTANCE hInstance); | |
| ~StatusWindow(); | |
| bool create(); | |
| void show(); | |
| void hide(); | |
| bool is_visible() const; | |
| HWND hwnd() const { return hwnd_; } | |
| void update_state(omilia::service::AgentState state); | |
| void update_audio_levels(float mic_level, float speaker_level); | |
| void update_connection(const std::wstring& status); | |
| void update_session(const std::wstring& session_id, const std::wstring& duration); | |
| static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); | |
| private: | |
| void paint(HDC hdc); | |
| void draw_level_bar(HDC hdc, int x, int y, int width, int height, | |
| float level, COLORREF color, const wchar_t* label); | |
| void draw_state_indicator(HDC hdc, int x, int y); | |
| HINSTANCE hInstance_; | |
| HWND hwnd_ = nullptr; | |
| omilia::service::AgentState state_ = omilia::service::AgentState::DORMANT; | |
| float mic_level_ = 0.0f; | |
| float speaker_level_ = 0.0f; | |
| std::wstring connection_status_ = L"Disconnected"; | |
| std::wstring session_id_ = L"\x2014"; | |
| std::wstring session_duration_ = L"00:00"; | |
| HFONT title_font_ = nullptr; | |
| HFONT body_font_ = nullptr; | |
| HFONT small_font_ = nullptr; | |
| }; | |
| } // namespace omilia::ui | |
| "@ | Out-File -FilePath "$ProjectDir\src\ui\status_window.h" -Encoding utf8 | |
| # --- src/ui/status_window.cpp --- | |
| @" | |
| #include "status_window.h" | |
| #include "resource.h" | |
| #include <cmath> | |
| #include <cstdio> | |
| #include <commctrl.h> | |
| #pragma comment(lib, "comctl32.lib") | |
| namespace omilia::ui { | |
| static const wchar_t* CLASS_NAME = L"OmiliaStatusWindow"; | |
| static StatusWindow* g_status_window = nullptr; | |
| StatusWindow::StatusWindow(HINSTANCE hInstance) : hInstance_(hInstance) { | |
| g_status_window = this; | |
| title_font_ = CreateFontW(24, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, | |
| DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, | |
| CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Segoe UI"); | |
| body_font_ = CreateFontW(16, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, | |
| DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, | |
| CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Segoe UI"); | |
| small_font_ = CreateFontW(13, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, | |
| DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, | |
| CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Segoe UI"); | |
| } | |
| StatusWindow::~StatusWindow() { | |
| if (title_font_) DeleteObject(title_font_); | |
| if (body_font_) DeleteObject(body_font_); | |
| if (small_font_) DeleteObject(small_font_); | |
| g_status_window = nullptr; | |
| } | |
| bool StatusWindow::create() { | |
| WNDCLASSEXW wc{}; | |
| wc.cbSize = sizeof(WNDCLASSEXW); | |
| wc.style = CS_HREDRAW | CS_VREDRAW; | |
| wc.lpfnWndProc = WndProc; | |
| wc.hInstance = hInstance_; | |
| wc.hCursor = LoadCursor(nullptr, IDC_ARROW); | |
| wc.hbrBackground = CreateSolidBrush(RGB(30, 30, 30)); | |
| wc.lpszClassName = CLASS_NAME; | |
| RegisterClassExW(&wc); | |
| hwnd_ = CreateWindowExW( | |
| WS_EX_TOPMOST | WS_EX_TOOLWINDOW, | |
| CLASS_NAME, L"Omilia Audio Agent", | |
| WS_POPUP | WS_CAPTION | WS_SYSMENU, | |
| CW_USEDEFAULT, CW_USEDEFAULT, 380, 420, | |
| nullptr, nullptr, hInstance_, nullptr); | |
| return hwnd_ != nullptr; | |
| } | |
| void StatusWindow::show() { | |
| if (hwnd_) { ShowWindow(hwnd_, SW_SHOW); UpdateWindow(hwnd_); } | |
| } | |
| void StatusWindow::hide() { | |
| if (hwnd_) ShowWindow(hwnd_, SW_HIDE); | |
| } | |
| bool StatusWindow::is_visible() const { | |
| return hwnd_ && IsWindowVisible(hwnd_); | |
| } | |
| void StatusWindow::update_state(omilia::service::AgentState state) { | |
| state_ = state; | |
| if (hwnd_) InvalidateRect(hwnd_, nullptr, TRUE); | |
| } | |
| void StatusWindow::update_audio_levels(float mic_level, float speaker_level) { | |
| mic_level_ = mic_level; | |
| speaker_level_ = speaker_level; | |
| if (hwnd_) InvalidateRect(hwnd_, nullptr, FALSE); | |
| } | |
| void StatusWindow::update_connection(const std::wstring& status) { | |
| connection_status_ = status; | |
| if (hwnd_) InvalidateRect(hwnd_, nullptr, TRUE); | |
| } | |
| void StatusWindow::update_session(const std::wstring& session_id, const std::wstring& duration) { | |
| session_id_ = session_id; | |
| session_duration_ = duration; | |
| if (hwnd_) InvalidateRect(hwnd_, nullptr, TRUE); | |
| } | |
| LRESULT CALLBACK StatusWindow::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { | |
| switch (msg) { | |
| case WM_PAINT: { | |
| if (!g_status_window) break; | |
| PAINTSTRUCT ps; | |
| HDC hdc = BeginPaint(hwnd, &ps); | |
| RECT rc; | |
| GetClientRect(hwnd, &rc); | |
| HDC mem_dc = CreateCompatibleDC(hdc); | |
| HBITMAP bmp = CreateCompatibleBitmap(hdc, rc.right, rc.bottom); | |
| HBITMAP old_bmp = (HBITMAP)SelectObject(mem_dc, bmp); | |
| HBRUSH bg = CreateSolidBrush(RGB(30, 30, 30)); | |
| FillRect(mem_dc, &rc, bg); | |
| DeleteObject(bg); | |
| g_status_window->paint(mem_dc); | |
| BitBlt(hdc, 0, 0, rc.right, rc.bottom, mem_dc, 0, 0, SRCCOPY); | |
| SelectObject(mem_dc, old_bmp); | |
| DeleteObject(bmp); | |
| DeleteDC(mem_dc); | |
| EndPaint(hwnd, &ps); | |
| return 0; | |
| } | |
| case WM_CLOSE: | |
| ShowWindow(hwnd, SW_HIDE); | |
| return 0; | |
| case WM_ERASEBKGND: | |
| return 1; | |
| } | |
| return DefWindowProcW(hwnd, msg, wParam, lParam); | |
| } | |
| void StatusWindow::paint(HDC hdc) { | |
| SetBkMode(hdc, TRANSPARENT); | |
| int margin = 20; | |
| int y = 15; | |
| SelectObject(hdc, title_font_); | |
| SetTextColor(hdc, RGB(255, 255, 255)); | |
| TextOutW(hdc, margin, y, L"Omilia Audio Agent", 18); | |
| y += 35; | |
| HPEN divider = CreatePen(PS_SOLID, 1, RGB(60, 60, 60)); | |
| HPEN old_pen = (HPEN)SelectObject(hdc, divider); | |
| MoveToEx(hdc, margin, y, nullptr); | |
| LineTo(hdc, 360 - margin, y); | |
| SelectObject(hdc, old_pen); | |
| DeleteObject(divider); | |
| y += 12; | |
| draw_state_indicator(hdc, margin, y); | |
| y += 60; | |
| SelectObject(hdc, body_font_); | |
| SetTextColor(hdc, RGB(180, 180, 180)); | |
| TextOutW(hdc, margin, y, L"Audio Levels", 12); | |
| y += 25; | |
| draw_level_bar(hdc, margin, y, 320, 20, mic_level_, RGB(0, 180, 255), L"MIC"); | |
| y += 35; | |
| draw_level_bar(hdc, margin, y, 320, 20, speaker_level_, RGB(255, 160, 0), L"SPK"); | |
| y += 45; | |
| SelectObject(hdc, body_font_); | |
| SetTextColor(hdc, RGB(180, 180, 180)); | |
| TextOutW(hdc, margin, y, L"Connection", 10); | |
| y += 22; | |
| SelectObject(hdc, small_font_); | |
| SetTextColor(hdc, RGB(220, 220, 220)); | |
| std::wstring conn_line = L"Status: " + connection_status_; | |
| TextOutW(hdc, margin + 8, y, conn_line.c_str(), (int)conn_line.size()); | |
| y += 20; | |
| std::wstring sess_line = L"Session: " + session_id_; | |
| TextOutW(hdc, margin + 8, y, sess_line.c_str(), (int)sess_line.size()); | |
| y += 20; | |
| std::wstring dur_line = L"Duration: " + session_duration_; | |
| TextOutW(hdc, margin + 8, y, dur_line.c_str(), (int)dur_line.size()); | |
| y += 30; | |
| HPEN foot_div = CreatePen(PS_SOLID, 1, RGB(60, 60, 60)); | |
| old_pen = (HPEN)SelectObject(hdc, foot_div); | |
| MoveToEx(hdc, margin, y, nullptr); | |
| LineTo(hdc, 360 - margin, y); | |
| SelectObject(hdc, old_pen); | |
| DeleteObject(foot_div); | |
| y += 8; | |
| SelectObject(hdc, small_font_); | |
| SetTextColor(hdc, RGB(100, 100, 100)); | |
| TextOutW(hdc, margin, y, L"Omilia Desktop Intelligence v0.1.0", 35); | |
| } | |
| void StatusWindow::draw_state_indicator(HDC hdc, int x, int y) { | |
| COLORREF color; | |
| const wchar_t* state_text; | |
| const wchar_t* detail; | |
| switch (state_) { | |
| case omilia::service::AgentState::DORMANT: | |
| color = RGB(128, 128, 128); | |
| state_text = L"DORMANT"; | |
| detail = L"No softphone detected"; | |
| break; | |
| case omilia::service::AgentState::ARMED: | |
| color = RGB(30, 144, 255); | |
| state_text = L"ARMED"; | |
| detail = L"Softphone detected -- waiting for call"; | |
| break; | |
| case omilia::service::AgentState::CAPTURING: | |
| color = RGB(0, 200, 83); | |
| state_text = L"CAPTURING"; | |
| detail = L"Active call -- streaming audio"; | |
| break; | |
| default: | |
| color = RGB(128, 128, 128); | |
| state_text = L"UNKNOWN"; | |
| detail = L""; | |
| break; | |
| } | |
| HBRUSH brush = CreateSolidBrush(color); | |
| HBRUSH old_brush = (HBRUSH)SelectObject(hdc, brush); | |
| HPEN pen = CreatePen(PS_SOLID, 1, color); | |
| HPEN old_pen = (HPEN)SelectObject(hdc, pen); | |
| Ellipse(hdc, x, y + 5, x + 28, y + 33); | |
| SelectObject(hdc, old_brush); | |
| SelectObject(hdc, old_pen); | |
| DeleteObject(brush); | |
| DeleteObject(pen); | |
| SelectObject(hdc, title_font_); | |
| SetTextColor(hdc, color); | |
| TextOutW(hdc, x + 38, y + 2, state_text, (int)wcslen(state_text)); | |
| SelectObject(hdc, small_font_); | |
| SetTextColor(hdc, RGB(160, 160, 160)); | |
| TextOutW(hdc, x + 38, y + 30, detail, (int)wcslen(detail)); | |
| } | |
| void StatusWindow::draw_level_bar(HDC hdc, int x, int y, int width, int height, | |
| float level, COLORREF color, const wchar_t* label) { | |
| SelectObject(hdc, small_font_); | |
| SetTextColor(hdc, RGB(160, 160, 160)); | |
| TextOutW(hdc, x, y + 2, label, (int)wcslen(label)); | |
| int bar_x = x + 35; | |
| int bar_width = width - 35; | |
| HBRUSH bg = CreateSolidBrush(RGB(50, 50, 50)); | |
| RECT bg_rect = { bar_x, y, bar_x + bar_width, y + height }; | |
| FillRect(hdc, &bg_rect, bg); | |
| DeleteObject(bg); | |
| if (level > 0.001f) { | |
| int fill_width = (int)(bar_width * level); | |
| if (fill_width > bar_width) fill_width = bar_width; | |
| BYTE r = GetRValue(color), g = GetGValue(color), b = GetBValue(color); | |
| HBRUSH fill = CreateSolidBrush(RGB(r * 7 / 10, g * 7 / 10, b * 7 / 10)); | |
| RECT fill_rect = { bar_x, y, bar_x + fill_width, y + height }; | |
| FillRect(hdc, &fill_rect, fill); | |
| DeleteObject(fill); | |
| if (fill_width > 3) { | |
| HBRUSH tip = CreateSolidBrush(color); | |
| RECT tip_rect = { bar_x + fill_width - 3, y, bar_x + fill_width, y + height }; | |
| FillRect(hdc, &tip_rect, tip); | |
| DeleteObject(tip); | |
| } | |
| } | |
| HPEN border = CreatePen(PS_SOLID, 1, RGB(80, 80, 80)); | |
| HPEN old_pen = (HPEN)SelectObject(hdc, border); | |
| HBRUSH old_brush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); | |
| Rectangle(hdc, bar_x, y, bar_x + bar_width, y + height); | |
| SelectObject(hdc, old_pen); | |
| SelectObject(hdc, old_brush); | |
| DeleteObject(border); | |
| } | |
| } // namespace omilia::ui | |
| "@ | Out-File -FilePath "$ProjectDir\src\ui\status_window.cpp" -Encoding utf8 | |
| # --- src/main.cpp --- | |
| @" | |
| #ifndef WIN32_LEAN_AND_MEAN | |
| #define WIN32_LEAN_AND_MEAN | |
| #endif | |
| #include <windows.h> | |
| #include <shellapi.h> | |
| #include <cstdlib> | |
| #include <ctime> | |
| #include "ui/resource.h" | |
| #include "ui/tray_icon.h" | |
| #include "ui/status_window.h" | |
| #include "service/agent_state_machine.h" | |
| static const wchar_t* MAIN_CLASS = L"OmiliaAgentMain"; | |
| static omilia::ui::TrayIcon* g_tray = nullptr; | |
| static omilia::ui::StatusWindow* g_status = nullptr; | |
| static omilia::service::AgentStateMachine g_state_machine; | |
| static bool g_demo_running = false; | |
| static int g_demo_seconds = 0; | |
| void show_context_menu(HWND hwnd) { | |
| POINT pt; | |
| GetCursorPos(&pt); | |
| HMENU menu = CreatePopupMenu(); | |
| AppendMenuW(menu, MF_STRING, IDM_STATUS, | |
| g_status && g_status->is_visible() ? L"Hide Status" : L"Show Status"); | |
| AppendMenuW(menu, MF_SEPARATOR, 0, nullptr); | |
| if (g_demo_running) { | |
| AppendMenuW(menu, MF_STRING, IDM_STOP_DEMO, L"Stop Demo"); | |
| } else { | |
| AppendMenuW(menu, MF_STRING, IDM_START_DEMO, L"Start Demo"); | |
| } | |
| AppendMenuW(menu, MF_SEPARATOR, 0, nullptr); | |
| AppendMenuW(menu, MF_STRING, IDM_EXIT, L"Exit"); | |
| SetForegroundWindow(hwnd); | |
| TrackPopupMenu(menu, TPM_RIGHTALIGN | TPM_BOTTOMALIGN, pt.x, pt.y, 0, hwnd, nullptr); | |
| DestroyMenu(menu); | |
| } | |
| void start_demo(HWND hwnd) { | |
| g_demo_running = true; | |
| g_demo_seconds = 0; | |
| g_state_machine.transition_to(omilia::service::AgentState::DORMANT); | |
| SetTimer(hwnd, TIMER_STATE_UPDATE, 5000, nullptr); | |
| SetTimer(hwnd, TIMER_LEVEL_UPDATE, 100, nullptr); | |
| if (g_tray) g_tray->show_balloon(L"Demo Started", L"Cycling through agent states..."); | |
| if (g_status) { | |
| g_status->update_connection(L"Connected (demo)"); | |
| g_status->update_session(L"demo-session-001", L"00:00"); | |
| } | |
| } | |
| void stop_demo(HWND hwnd) { | |
| g_demo_running = false; | |
| KillTimer(hwnd, TIMER_STATE_UPDATE); | |
| KillTimer(hwnd, TIMER_LEVEL_UPDATE); | |
| g_state_machine.transition_to(omilia::service::AgentState::DORMANT); | |
| if (g_status) { | |
| g_status->update_audio_levels(0.0f, 0.0f); | |
| g_status->update_connection(L"Disconnected"); | |
| g_status->update_session(L"\x2014", L"00:00"); | |
| } | |
| if (g_tray) g_tray->show_balloon(L"Demo Stopped", L"Agent returned to DORMANT"); | |
| } | |
| LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { | |
| switch (msg) { | |
| case WM_TRAYICON: | |
| if (LOWORD(lParam) == WM_RBUTTONUP) { | |
| show_context_menu(hwnd); | |
| } else if (LOWORD(lParam) == WM_LBUTTONDBLCLK) { | |
| if (g_status) { | |
| if (g_status->is_visible()) g_status->hide(); | |
| else g_status->show(); | |
| } | |
| } | |
| return 0; | |
| case WM_COMMAND: | |
| switch (LOWORD(wParam)) { | |
| case IDM_STATUS: | |
| if (g_status) { | |
| if (g_status->is_visible()) g_status->hide(); | |
| else g_status->show(); | |
| } | |
| break; | |
| case IDM_START_DEMO: start_demo(hwnd); break; | |
| case IDM_STOP_DEMO: stop_demo(hwnd); break; | |
| case IDM_EXIT: PostQuitMessage(0); break; | |
| } | |
| return 0; | |
| case WM_TIMER: | |
| if (wParam == TIMER_STATE_UPDATE && g_demo_running) { | |
| g_state_machine.advance_demo(); | |
| } | |
| if (wParam == TIMER_LEVEL_UPDATE && g_demo_running) { | |
| g_demo_seconds++; | |
| float mic = g_state_machine.simulated_audio_level(); | |
| float spk = g_state_machine.simulated_audio_level() * 0.7f; | |
| if (g_status) { | |
| g_status->update_audio_levels(mic, spk); | |
| int secs = g_demo_seconds / 10; | |
| wchar_t dur[32]; | |
| swprintf_s(dur, L"%02d:%02d", secs / 60, secs % 60); | |
| g_status->update_session(L"demo-session-001", dur); | |
| } | |
| } | |
| return 0; | |
| case WM_DESTROY: | |
| PostQuitMessage(0); | |
| return 0; | |
| } | |
| return DefWindowProcW(hwnd, msg, wParam, lParam); | |
| } | |
| int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int) { | |
| std::srand((unsigned)std::time(nullptr)); | |
| WNDCLASSEXW wc{}; | |
| wc.cbSize = sizeof(WNDCLASSEXW); | |
| wc.lpfnWndProc = MainWndProc; | |
| wc.hInstance = hInstance; | |
| wc.lpszClassName = MAIN_CLASS; | |
| RegisterClassExW(&wc); | |
| HWND hwnd = CreateWindowExW(0, MAIN_CLASS, L"OmiliaAgent", | |
| 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, hInstance, nullptr); | |
| if (!hwnd) return 1; | |
| omilia::ui::TrayIcon tray(hwnd, WM_TRAYICON); | |
| g_tray = &tray; | |
| omilia::ui::StatusWindow status(hInstance); | |
| status.create(); | |
| g_status = &status; | |
| g_state_machine.set_callback([](omilia::service::AgentState old_s, | |
| omilia::service::AgentState new_s) { | |
| if (g_tray) g_tray->update_state(new_s); | |
| if (g_status) g_status->update_state(new_s); | |
| wchar_t msg[128]; | |
| swprintf_s(msg, L"%hs \x2192 %hs", | |
| omilia::service::agent_state_name(old_s), | |
| omilia::service::agent_state_name(new_s)); | |
| if (g_tray) g_tray->show_balloon(L"State Transition", msg); | |
| }); | |
| status.show(); | |
| MSG m; | |
| while (GetMessage(&m, nullptr, 0, 0)) { | |
| TranslateMessage(&m); | |
| DispatchMessage(&m); | |
| } | |
| g_tray = nullptr; | |
| g_status = nullptr; | |
| return (int)m.wParam; | |
| } | |
| "@ | Out-File -FilePath "$ProjectDir\src\main.cpp" -Encoding utf8 | |
| $fileCount = (Get-ChildItem -Path $ProjectDir -Recurse -File).Count | |
| Write-Host " $fileCount source files written to $ProjectDir" -ForegroundColor Green | |
| Write-Host "" | |
| # ---- Step 3: Configure with CMake ---- | |
| Write-Host "[3/5] Configuring CMake build..." -ForegroundColor Yellow | |
| $buildDir = "$ProjectDir\build" | |
| New-Item -ItemType Directory -Path $buildDir -Force | Out-Null | |
| Push-Location $buildDir | |
| cmake -G "Visual Studio 17 2022" -A x64 $ProjectDir 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray } | |
| $configResult = $LASTEXITCODE | |
| Pop-Location | |
| if ($configResult -eq 0) { | |
| Write-Host " CMake configured successfully" -ForegroundColor Green | |
| } else { | |
| Write-Host " CMake configuration failed" -ForegroundColor Red | |
| exit 1 | |
| } | |
| Write-Host "" | |
| # ---- Step 4: Build ---- | |
| Write-Host "[4/5] Building audio-agent..." -ForegroundColor Yellow | |
| Push-Location $buildDir | |
| cmake --build . --config Release 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray } | |
| $buildResult = $LASTEXITCODE | |
| Pop-Location | |
| $exePath = "$buildDir\Release\audio-agent.exe" | |
| if (($buildResult -eq 0) -and (Test-Path $exePath)) { | |
| $size = (Get-Item $exePath).Length / 1KB | |
| Write-Host " Build successful!" -ForegroundColor Green | |
| Write-Host " Output: $exePath ($([math]::Round($size, 1)) KB)" -ForegroundColor Green | |
| } else { | |
| Write-Host " Build failed" -ForegroundColor Red | |
| exit 1 | |
| } | |
| Write-Host "" | |
| # ---- Step 5: Done ---- | |
| Write-Host "[5/5] Done!" -ForegroundColor Yellow | |
| Write-Host "" | |
| Write-Host "============================================" -ForegroundColor Green | |
| Write-Host " BUILD COMPLETE" -ForegroundColor Green | |
| Write-Host "============================================" -ForegroundColor Green | |
| Write-Host "" | |
| Write-Host " Executable: $exePath" -ForegroundColor Cyan | |
| Write-Host "" | |
| Write-Host " To run on this machine:" -ForegroundColor White | |
| Write-Host " & '$exePath'" -ForegroundColor White | |
| Write-Host "" | |
| Write-Host " To copy to Desktop machine (54.144.93.95):" -ForegroundColor White | |
| Write-Host " Copy-Item '$exePath' 'C:\temp\audio-agent.exe'" -ForegroundColor White | |
| Write-Host " Then RDP to Desktop and copy from \\3.88.19.13\C$\temp\" -ForegroundColor White | |
| Write-Host "" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment