From 1763bf72da980a1c8356206ec778c230d784b251 Mon Sep 17 00:00:00 2001 From: Grayson Riffe Date: Sun, 7 Jun 2026 20:29:31 -0500 Subject: [PATCH] Replace dialog with regular window + other stuff No caption anymore Can drag the window from anywhere Drag cursor Rounded corners Prevent DPI resizing Ready text --- WatchfulEye/resource.rc | 45 ------ WatchfulEye/src/Application.cpp | 250 +++++++++++++++++++++----------- WatchfulEye/src/Application.h | 15 +- WatchfulEye/src/main.cpp | 6 +- WatchfulEye/src/resource.h | 6 +- 5 files changed, 183 insertions(+), 139 deletions(-) diff --git a/WatchfulEye/resource.rc b/WatchfulEye/resource.rc index dcc131a..d5db5ca 100644 --- a/WatchfulEye/resource.rc +++ b/WatchfulEye/resource.rc @@ -44,51 +44,6 @@ END #endif // APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Dialog -// - -IDD_DIALOGMAIN DIALOGEX 0, 0, 300, 114 -STYLE DS_SETFONT | DS_FIXEDSYS | WS_CAPTION -EXSTYLE WS_EX_TOPMOST | WS_EX_TOOLWINDOW -CAPTION "Dialog" -FONT 8, "MS Shell Dlg", 400, 0, 0x1 -BEGIN - CTEXT "Lower",IDC_STATICLOWER,7,85,286,22,SS_CENTERIMAGE,WS_EX_TRANSPARENT -END - - -///////////////////////////////////////////////////////////////////////////// -// -// DESIGNINFO -// - -#ifdef APSTUDIO_INVOKED -GUIDELINES DESIGNINFO -BEGIN - IDD_DIALOGMAIN, DIALOG - BEGIN - LEFTMARGIN, 7 - RIGHTMARGIN, 293 - TOPMARGIN, 7 - BOTTOMMARGIN, 107 - END -END -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// AFX_DIALOG_LAYOUT -// - -IDD_DIALOGMAIN AFX_DIALOG_LAYOUT -BEGIN - 0 -END - - ///////////////////////////////////////////////////////////////////////////// // // Version diff --git a/WatchfulEye/src/Application.cpp b/WatchfulEye/src/Application.cpp index f67320f..9e31d53 100644 --- a/WatchfulEye/src/Application.cpp +++ b/WatchfulEye/src/Application.cpp @@ -5,32 +5,59 @@ #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_mainDlg(nullptr) + , 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) { std::cout << std::format("{} {}\n", m_appName, m_appVersion); } void Application::run() { - // Create dialog - SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + // Create window + SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); // Don't mess with the window size - m_mainDlg = CreateDialogParam(nullptr, MAKEINTRESOURCE(IDD_DIALOGMAIN), nullptr, reinterpret_cast(mainDlgProc), reinterpret_cast(this)); + 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(); @@ -38,60 +65,86 @@ namespace watchfuleye { // Create drive query thread std::thread queryThread(&Application::queryThreadFunction, this); - ShowWindow(m_mainDlg, SW_SHOW); + ShowWindow(m_window, SW_SHOW); MSG msg = {}; - while (GetMessage(&msg, nullptr, 0, 0)) - IsDialogMessage(m_mainDlg, &msg); + while (GetMessage(&msg, nullptr, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } m_running = false; queryThread.join(); } - BOOL CALLBACK Application::mainDlgProc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam) { + LRESULT CALLBACK Application::wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { static Application* app = nullptr; - static UINT taskbarCreatedMessage = 0; - static HFONT timeFont = nullptr; - - static HBRUSH greenBackgroundBrush = CreateSolidBrush(RGB(150, 255, 150)); - static HBRUSH yellowBackgroundBrush = CreateSolidBrush(RGB(255, 255, 150)); - static HBRUSH redBackgroundBrush = CreateSolidBrush(RGB(255, 150, 150)); switch (msg) { - case WM_INITDIALOG: { - app = reinterpret_cast(lParam); + case WM_CREATE: { + app = reinterpret_cast(reinterpret_cast(lParam)->lpCreateParams); - SetWindowText(dlg, app->m_appName.c_str()); + 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); - SetWindowPos(dlg, nullptr, cornerX - 550, cornerY - 540, NULL, NULL, SWP_NOZORDER | SWP_NOSIZE); - taskbarCreatedMessage = RegisterWindowMessage("TaskbarCreated"); + // 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; - HFONT upperFont = CreateFontIndirect(&lFont); - SendMessage(GetDlgItem(dlg, IDC_STATICLOWER), WM_SETFONT, reinterpret_cast(upperFont), NULL); + app->m_lowerFont = CreateFontIndirect(&lFont); lFont.lfHeight = 180; lFont.lfWeight = FW_BOLD; - timeFont = CreateFontIndirect(&lFont); + app->m_timeFont = CreateFontIndirect(&lFont); - SendMessage(dlg, WM_UPDATE, NULL, NULL); - SetTimer(dlg, IDT_UPDATE, 1000, nullptr); + app->m_greenBrush = CreateSolidBrush(RGB(150, 255, 150)); + app->m_yellowBrush = CreateSolidBrush(RGB(255, 255, 150)); + app->m_redBrush = CreateSolidBrush(RGB(255, 150, 150)); - return TRUE; + // App will update (repaint) every second + SendMessage(hWnd, WM_UPDATE, NULL, NULL); + SetTimer(hWnd, IDT_UPDATE, 1000, 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(dlg); + SetForegroundWindow(hWnd); POINT cursor = {}; GetCursorPos(&cursor); @@ -100,130 +153,152 @@ namespace watchfuleye { AppendMenu(popupMenu, MF_STRING, IDM_ABOUT, "About"); AppendMenu(popupMenu, MF_STRING, IDM_EXIT, "Exit"); - TrackPopupMenu(popupMenu, NULL, cursor.x, cursor.y, NULL, dlg, nullptr); // Will block here. Might be problematic. + TrackPopupMenu(popupMenu, NULL, cursor.x, cursor.y, NULL, hWnd, nullptr); DestroyMenu(popupMenu); + + return 0; } - return TRUE; + break; } case WM_COMMAND: { - if (HIWORD(wParam) == 0 && LOWORD(wParam) == IDM_ABOUT) - MessageBox(dlg, std::format("{} {}\nCopyright Grayson Riffe 2026\ngraysonriffe.com\n\n" + 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\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); - else if (HIWORD(wParam) == 0 && LOWORD(wParam) == IDM_EXIT) - DestroyWindow(dlg); + return 0; + } - return TRUE; + 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: { HDC hDC = reinterpret_cast(wParam); RECT clientRect = {}; - GetClientRect(dlg, &clientRect); + GetClientRect(hWnd, &clientRect); HBRUSH backgroundBrush = nullptr; if (!app->m_active) - backgroundBrush = greenBackgroundBrush; + backgroundBrush = app->m_greenBrush; else if (app->m_overTime) - backgroundBrush = redBackgroundBrush; + backgroundBrush = app->m_redBrush; else - backgroundBrush = yellowBackgroundBrush; + backgroundBrush = app->m_yellowBrush; FillRect(hDC, &clientRect, backgroundBrush); - return TRUE; - } - - case WM_CTLCOLORSTATIC: { - if (GetWindowLongPtr(reinterpret_cast(lParam), GWL_EXSTYLE) & WS_EX_TRANSPARENT) { - SetBkMode(reinterpret_cast(wParam), TRANSPARENT); - return reinterpret_cast(GetStockObject(NULL_BRUSH)); - } - - return FALSE; - } - - case WM_TIMER: { - if (wParam == IDT_UPDATE) { - SendMessage(dlg, WM_UPDATE, NULL, NULL); - return TRUE; - } - - return FALSE; - } - - case WM_UPDATE: { - InvalidateRect(dlg, nullptr, TRUE); - UpdateWindow(dlg); - return TRUE; + return 1; } case WM_PAINT: { PAINTSTRUCT ps = {}; - HDC hDC = BeginPaint(dlg, &ps); + HDC hDC = BeginPaint(hWnd, &ps); + // Setup RECTs RECT timeRect = {}; - GetClientRect(dlg, &timeRect); + GetClientRect(hWnd, &timeRect); timeRect.top -= 25, timeRect.bottom -= 25; + + RECT lowerRect = { .top = WINDOW_HEIGHT - 65, .right = WINDOW_WIDTH, .bottom = WINDOW_HEIGHT }; + + // Setup text options SetTextColor(hDC, RGB(0, 0, 0)); SetBkMode(hDC, TRANSPARENT); - SelectObject(hDC, timeFont); + // Calculate time Seconds elapsed = Duration(Clock::now() - app->m_startTime).count(); - if (!app->m_active) - elapsed = 0; + Seconds remaining = app->m_maxMinutes * 60; + if (app->m_active) + remaining -= elapsed; - Seconds remaining = app->m_maxMinutes * 60 - elapsed; - - if (app->m_overTime) { - SetDlgItemText(dlg, IDC_STATICLOWER, "Please Eject Your USB"); - DrawText(hDC, "STOP", -1, &timeRect, DT_SINGLELINE | DT_NOCLIP | DT_CENTER | DT_VCENTER); - } - else { - SetDlgItemText(dlg, IDC_STATICLOWER, "Time Remaining on Tester"); + if (!app->m_active || (app->m_active && !app->m_overTime)) { // Draw time + SelectObject(hDC, app->m_timeFont); unsigned int minutes = remaining % (3600) / 60, seconds = remaining % 60; DrawText(hDC, 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(hDC, app->m_lowerFont); + DrawText(hDC, "Ready", -1, &lowerRect, DT_SINGLELINE | DT_NOCLIP | DT_CENTER | DT_VCENTER); + } + else if (!app->m_overTime) { // Counting (yellow) + SelectObject(hDC, app->m_lowerFont); + DrawText(hDC, "Time Remaining on Tester", -1, &lowerRect, DT_SINGLELINE | DT_NOCLIP | DT_CENTER | DT_VCENTER); + } + else { // Over time (red) + SelectObject(hDC, app->m_lowerFont); + DrawText(hDC, "Please Eject Your USB", -1, &lowerRect, DT_SINGLELINE | DT_NOCLIP | DT_CENTER | DT_VCENTER); + SelectObject(hDC, app->m_timeFont); + DrawText(hDC, "STOP", -1, &timeRect, DT_SINGLELINE | DT_NOCLIP | DT_CENTER | DT_VCENTER); + } + if (remaining <= 0) app->m_overTime = true; - EndPaint(dlg, &ps); - return TRUE; + EndPaint(hWnd, &ps); + return 0; } case WM_CLOSE: { // Do nothing - return TRUE; + return 0; } case WM_DESTROY: { PostQuitMessage(0); - return TRUE; + return 0; } } - if (msg == taskbarCreatedMessage) + if (app && msg == app->m_taskbarCreatedMessage) { app->createTrayIcon(); + return 0; + } - return FALSE; + 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_mainDlg; + iconData.hWnd = m_window; iconData.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; iconData.uCallbackMessage = WM_TRAYICON; iconData.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICONMAIN)); @@ -250,4 +325,15 @@ namespace watchfuleye { 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); + } } diff --git a/WatchfulEye/src/Application.h b/WatchfulEye/src/Application.h index ed79e65..5b27d7b 100644 --- a/WatchfulEye/src/Application.h +++ b/WatchfulEye/src/Application.h @@ -11,23 +11,32 @@ namespace watchfuleye { Application(const char* appName, const char* appVersion, unsigned int maximumMinutes, const char* driveToDetect); void run(); + + ~Application(); private: - static BOOL CALLBACK mainDlgProc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + + void sizeWindow(const HWND& hWnd); void createTrayIcon(); void queryThreadFunction(); std::string m_appName, m_appVersion; - HWND m_mainDlg; + HWND m_window; bool m_running; // Is the application running? bool m_active; // Is the timer ticking? - bool m_overTime; // Are we out of time? + bool m_overTime; // Are we out of time? unsigned int m_maxMinutes; const char* m_drive; TimePoint m_startTime; + + UINT m_taskbarCreatedMessage; + HRGN m_region; + HFONT m_timeFont, m_lowerFont; + HBRUSH m_greenBrush, m_yellowBrush, m_redBrush; }; } diff --git a/WatchfulEye/src/main.cpp b/WatchfulEye/src/main.cpp index a538c83..84162ff 100644 --- a/WatchfulEye/src/main.cpp +++ b/WatchfulEye/src/main.cpp @@ -4,11 +4,6 @@ #include "version.h" #include "Application.h" -// Enable visual styles -#pragma comment(linker, "\"/manifestdependency:type='win32' \ -name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \ -processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") - #define APPNAME "Watchful Eye" // ======== Configuration ======== @@ -17,6 +12,7 @@ processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") // =============================== int main(int argc, char* argv[]) { + { watchfuleye::Application app(APPNAME, APPVERSION, MAX_MINUTES, DRIVE_TO_DETECT); app.run(); diff --git a/WatchfulEye/src/resource.h b/WatchfulEye/src/resource.h index aaaee1b..4274545 100644 --- a/WatchfulEye/src/resource.h +++ b/WatchfulEye/src/resource.h @@ -2,15 +2,13 @@ // Microsoft Visual C++ generated include file. // Used by C:\Users\Grayson\Documents\Visual Studio 18\Solutions\watchful-eye\WatchfulEye\resource.rc // -#define IDD_DIALOGMAIN 101 -#define IDI_ICONMAIN 103 -#define IDC_STATICLOWER 1000 +#define IDI_ICONMAIN 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101