// Application class implementation #include "pch.h" #include "Application.h" #include "resource.h" #define WINDOW_WIDTH 450 #define WINDOW_HEIGHT 185 #define WINDOW_STYLE WS_POPUP #define EX_WINDOW_STYLE WS_EX_TOOLWINDOW #define WM_TRAYICON WM_USER // Tray icon window message #define IDM_ABOUT 1001 // "About" menu identifier #define IDM_EXIT 1002 // "Exit" menu identifier #define IDT_UPDATE 1 // Update timer identifier #define WM_UPDATE WM_USER + 1 // Update window message // Enable visual styles #pragma comment(linker, "\"/manifestdependency:type='win32' \ name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \ processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") namespace watchfuleye { Application::Application(const char* appName, const char* appVersion, unsigned int maximumMinutes, const char* driveToDetect) : m_appName(appName) , m_appVersion(appVersion) , m_window(nullptr) , m_running(true) , m_active(false) , m_overTime(false) , m_maxMinutes(maximumMinutes) , m_drive(driveToDetect) , m_startTime() , m_taskbarCreatedMessage(-1) , m_region(nullptr) , m_timeFont(nullptr) , m_lowerFont(nullptr) , m_greenBrush(nullptr) , m_yellowBrush(nullptr) , m_redBrush(nullptr) , m_offscreenDC(nullptr) , m_offscreenBitmap(nullptr) { std::cout << std::format("{} {}\n", m_appName, m_appVersion); } void Application::run() { // Create window SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); // Don't mess with the window size WNDCLASS wc = {}; std::string windowClassName = "WatchfulEyeWindow"; wc.lpszClassName = windowClassName.c_str(); wc.hCursor = LoadCursor(NULL, IDC_SIZEALL); wc.lpfnWndProc = wndProc; RegisterClass(&wc); m_window = CreateWindowEx(EX_WINDOW_STYLE, windowClassName.c_str(), m_appName.c_str(), WINDOW_STYLE, 0, 0, 0, 0, nullptr, nullptr, nullptr, this); // Create tray icon createTrayIcon(); // Create drive query thread std::thread queryThread(&Application::queryThreadFunction, this); ShowWindow(m_window, SW_SHOW); MSG msg = {}; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } m_running = false; queryThread.join(); } LRESULT CALLBACK Application::wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { static Application* app = nullptr; switch (msg) { case WM_CREATE: { app = reinterpret_cast(reinterpret_cast(lParam)->lpCreateParams); app->sizeWindow(hWnd); // Round the window's corners app->m_region = CreateRoundRectRgn(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, 20, 20); SetWindowRgn(hWnd, app->m_region, TRUE); // Spawn in the lower right corner of the virtual screen (might be problematic in some cases) int cornerX = GetSystemMetrics(SM_XVIRTUALSCREEN) + GetSystemMetrics(SM_CXVIRTUALSCREEN); int cornerY = GetSystemMetrics(SM_YVIRTUALSCREEN) + GetSystemMetrics(SM_CYVIRTUALSCREEN); // And set topmost flag SetWindowPos(hWnd, HWND_TOPMOST, cornerX - 500, cornerY - 400, 0, 0, SWP_NOSIZE); app->m_taskbarCreatedMessage = RegisterWindowMessage("TaskbarCreated"); // Set up UI LOGFONT lFont = {}; strcpy(lFont.lfFaceName, "Consolas"); lFont.lfHeight = 35; app->m_lowerFont = CreateFontIndirect(&lFont); lFont.lfHeight = 180; lFont.lfWeight = FW_BOLD; app->m_timeFont = CreateFontIndirect(&lFont); app->m_greenBrush = CreateSolidBrush(RGB(150, 255, 150)); app->m_yellowBrush = CreateSolidBrush(RGB(255, 255, 150)); app->m_redBrush = CreateSolidBrush(RGB(255, 150, 150)); // App will update (repaint) at 5 FPS SendMessage(hWnd, WM_UPDATE, NULL, NULL); SetTimer(hWnd, IDT_UPDATE, 200, nullptr); return 0; } case WM_DPICHANGED: { // Prevents the window from resizing because of DPI changes app->sizeWindow(hWnd); return 0; } case WM_NCHITTEST: { // Allows the window to be moved by dragging from anywhere within it LRESULT result = DefWindowProc(hWnd, msg, wParam, lParam); if (result == HTCLIENT) return HTCAPTION; return result; } case WM_SETCURSOR: { // Allows the cursor to change with the above WM_NCHITTEST code if (LOWORD(lParam) == HTCAPTION) lParam = MAKELPARAM(HTCLIENT, HIWORD(lParam)); break; } case WM_TRAYICON: { if (LOWORD(lParam) == WM_LBUTTONDOWN || LOWORD(lParam) == WM_RBUTTONDOWN) { SetForegroundWindow(hWnd); POINT cursor = {}; GetCursorPos(&cursor); HMENU popupMenu = CreatePopupMenu(); AppendMenu(popupMenu, MF_STRING, IDM_ABOUT, "About"); AppendMenu(popupMenu, MF_STRING, IDM_EXIT, "Exit"); TrackPopupMenu(popupMenu, NULL, cursor.x, cursor.y, NULL, hWnd, nullptr); DestroyMenu(popupMenu); return 0; } break; } case WM_COMMAND: { if (HIWORD(wParam) == 0 && LOWORD(wParam) == IDM_ABOUT) { MessageBox(hWnd, std::format("{} {}\nCopyright Grayson Riffe 2026\ngraysonriffe.com\n\n" "\t{} is a simple countdown application\n" "specifically designed for this electronics testing labratory.\n" "Hopefully, it's working!", app->m_appName, app->m_appVersion, app->m_appName).c_str(), std::format("About {}", app->m_appName).c_str(), MB_OK); return 0; } else if (HIWORD(wParam) == 0 && LOWORD(wParam) == IDM_EXIT) { DestroyWindow(hWnd); return 0; } break; } case WM_TIMER: { if (wParam == IDT_UPDATE) { SendMessage(hWnd, WM_UPDATE, NULL, NULL); return 0; } break; } case WM_UPDATE: { InvalidateRect(hWnd, nullptr, TRUE); UpdateWindow(hWnd); return 0; } case WM_ERASEBKGND: { // Don't erase the background, it is already done in WM_PAINT return 1; } case WM_PAINT: { PAINTSTRUCT ps = {}; HDC hDC = BeginPaint(hWnd, &ps); RECT clientRect = {}; GetClientRect(hWnd, &clientRect); // Create offscreen buffer if not already created if (!app->m_offscreenDC) { app->m_offscreenDC = CreateCompatibleDC(hDC); app->m_offscreenBitmap = CreateCompatibleBitmap(hDC, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); } // Setup RECTs RECT timeRect = clientRect; timeRect.top -= 25, timeRect.bottom -= 25; RECT lowerRect = { .top = WINDOW_HEIGHT - 65, .right = WINDOW_WIDTH, .bottom = WINDOW_HEIGHT }; HANDLE prevBitmap = SelectObject(app->m_offscreenDC, app->m_offscreenBitmap); // Fill background HBRUSH backgroundBrush = nullptr; if (!app->m_active) backgroundBrush = app->m_greenBrush; else if (app->m_overTime) backgroundBrush = app->m_redBrush; else backgroundBrush = app->m_yellowBrush; FillRect(app->m_offscreenDC, &clientRect, backgroundBrush); // Setup text options SetTextColor(app->m_offscreenDC, RGB(0, 0, 0)); SetBkMode(app->m_offscreenDC, TRANSPARENT); // Calculate time Seconds elapsed = Duration(Clock::now() - app->m_startTime).count(); Seconds remaining = app->m_maxMinutes * 60; if (app->m_active) remaining -= elapsed; if (!app->m_active || (app->m_active && !app->m_overTime)) { // Draw time SelectObject(app->m_offscreenDC, app->m_timeFont); unsigned int minutes = remaining % (3600) / 60, seconds = remaining % 60; DrawText(app->m_offscreenDC, std::format("{:02}:{:02}", minutes, seconds).c_str(), -1, &timeRect, DT_SINGLELINE | DT_NOCLIP | DT_CENTER | DT_VCENTER); } if (!app->m_active) { // Ready (green) SelectObject(app->m_offscreenDC, app->m_lowerFont); DrawText(app->m_offscreenDC, "Ready", -1, &lowerRect, DT_SINGLELINE | DT_NOCLIP | DT_CENTER | DT_VCENTER); } else if (!app->m_overTime) { // Counting (yellow) SelectObject(app->m_offscreenDC, app->m_lowerFont); DrawText(app->m_offscreenDC, "Time Remaining on Tester", -1, &lowerRect, DT_SINGLELINE | DT_NOCLIP | DT_CENTER | DT_VCENTER); } else { // Over time (red) SelectObject(app->m_offscreenDC, app->m_lowerFont); DrawText(app->m_offscreenDC, "Please Eject Your USB", -1, &lowerRect, DT_SINGLELINE | DT_NOCLIP | DT_CENTER | DT_VCENTER); SelectObject(app->m_offscreenDC, app->m_timeFont); DrawText(app->m_offscreenDC, "STOP", -1, &timeRect, DT_SINGLELINE | DT_NOCLIP | DT_CENTER | DT_VCENTER); } if (remaining <= 0) app->m_overTime = true; BitBlt(hDC, 0, 0, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top, app->m_offscreenDC, 0, 0, SRCCOPY); SelectObject(app->m_offscreenDC, prevBitmap); EndPaint(hWnd, &ps); return 0; } case WM_CLOSE: { // Do nothing return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } if (app && msg == app->m_taskbarCreatedMessage) { app->createTrayIcon(); return 0; } return DefWindowProc(hWnd, msg, wParam, lParam); } void Application::sizeWindow(const HWND& hWnd) { RECT clientRect = { .right = WINDOW_WIDTH, .bottom = WINDOW_HEIGHT }; AdjustWindowRectExForDpi(&clientRect, WINDOW_STYLE, FALSE, EX_WINDOW_STYLE, GetDpiForWindow(hWnd)); SetWindowPos(hWnd, nullptr, 0, 0, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top, SWP_NOZORDER | SWP_NOMOVE); } void Application::createTrayIcon() { NOTIFYICONDATA iconData = {}; iconData.cbSize = sizeof(iconData); iconData.uVersion = NOTIFYICON_VERSION_4; iconData.hWnd = m_window; iconData.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; iconData.uCallbackMessage = WM_TRAYICON; iconData.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICONMAIN)); strcpy(iconData.szTip, m_appName.c_str()); Shell_NotifyIcon(NIM_ADD, &iconData); } void Application::queryThreadFunction() { char buff[100] = {}; while (m_running) { bool driveDetected = QueryDosDevice(m_drive, buff, 100) > 0; if (driveDetected && !m_active) { m_active = true; m_startTime = Clock::now(); } else if (!driveDetected && m_active) { m_active = false; m_overTime = false; } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } Application::~Application() { DeleteObject(m_region); DeleteObject(m_timeFont); DeleteObject(m_lowerFont); DeleteObject(m_greenBrush); DeleteObject(m_yellowBrush); DeleteObject(m_redBrush); DeleteObject(m_offscreenBitmap); DeleteDC(m_offscreenDC); } }