7 Commits
Author SHA1 Message Date
grayson 862f7fd8e7 Add flashing effect to "STOP" 2026-06-12 21:57:48 -05:00
grayson 8882894f09 Add outline 2026-06-12 21:46:10 -05:00
grayson 81b8ceb3d9 Fix DPI bug 2026-06-12 02:04:22 -05:00
grayson f11b6abeec Add double-buffering - No more flicker! 2026-06-07 21:38:49 -05:00
grayson 1763bf72da 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
2026-06-07 20:29:31 -05:00
grayson 4eae4af911 Finishing touches 2026-06-05 02:26:19 -05:00
grayson 89c76d0a9c Add About dialog and version info 2026-06-05 00:22:45 -05:00
6 changed files with 273 additions and 135 deletions

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

+31 -29
View File
@@ -1,6 +1,7 @@
// Microsoft Visual C++ generated resource script.
//
#include "src/resource.h"
#include "version.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
@@ -45,47 +46,48 @@ END
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
// Version
//
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
VS_VERSION_INFO VERSIONINFO
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x0L
FILESUBTYPE 0x0L
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
BLOCK "StringFileInfo"
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 293
TOPMARGIN, 7
BOTTOMMARGIN, 107
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Grayson Riffe"
VALUE "FileDescription", "Watchful Eye"
VALUE "InternalName", "WatchfulEye"
VALUE "LegalCopyright", "Copyright (C) 2026"
VALUE "OriginalFilename", "WatchfulEye.exe"
VALUE "ProductName", "Watchful Eye"
VALUE "ProductVersion", APPVERSION
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// AFX_DIALOG_LAYOUT
// Icon
//
IDD_DIALOGMAIN AFX_DIALOG_LAYOUT
BEGIN
0
END
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICONMAIN ICON "res\\watchful-eye.ico"
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
+218 -94
View File
@@ -5,31 +5,63 @@
#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_EXIT 1001 // "Exit" menu identifier
#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_paintCounter(0)
, m_startTime()
, m_taskbarCreatedMessage(-1)
, m_region(nullptr)
, m_timeFont(nullptr)
, m_lowerFont(nullptr)
, m_greenBrush(nullptr)
, m_yellowBrush(nullptr)
, m_redBrush(nullptr)
, m_outlinePen(nullptr)
, m_offscreenDC(nullptr)
, m_offscreenBitmap(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<DLGPROC>(mainDlgProc), reinterpret_cast<LPARAM>(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();
@@ -37,188 +69,266 @@ 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<Application*>(lParam);
case WM_CREATE: {
app = reinterpret_cast<Application*>(reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams);
SetWindowText(dlg, app->m_appName.c_str());
SendMessage(dlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION))));
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 = 45;
HFONT upperFont = CreateFontIndirect(&lFont);
SendMessage(GetDlgItem(dlg, IDC_STATICLOWER), WM_SETFONT, reinterpret_cast<WPARAM>(upperFont), NULL);
lFont.lfHeight = 35;
app->m_lowerFont = CreateFontIndirect(&lFont);
lFont.lfHeight = 200;
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->m_outlinePen = CreatePen(PS_SOLID, 5, RGB(0, 0, 0));
// 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
RECT* suggestedRECT = reinterpret_cast<RECT*>(lParam);
app->sizeWindow(hWnd, suggestedRECT->left, suggestedRECT->top);
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);
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, 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_EXIT)
DestroyWindow(dlg);
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 TRUE;
return 0;
}
case WM_ERASEBKGND: {
HDC hDC = reinterpret_cast<HDC>(wParam);
else if (HIWORD(wParam) == 0 && LOWORD(wParam) == IDM_EXIT) {
DestroyWindow(hWnd);
RECT clientRect = {};
GetClientRect(dlg, &clientRect);
HBRUSH backgroundBrush = nullptr;
if (!app->m_active)
backgroundBrush = greenBackgroundBrush;
else if (app->m_overTime)
backgroundBrush = redBackgroundBrush;
else
backgroundBrush = yellowBackgroundBrush;
FillRect(hDC, &clientRect, backgroundBrush);
return TRUE;
return 0;
}
case WM_CTLCOLORSTATIC: {
if (GetWindowLongPtr(reinterpret_cast<HWND>(lParam), GWL_EXSTYLE) & WS_EX_TRANSPARENT) {
SetBkMode(reinterpret_cast<HDC>(wParam), TRANSPARENT);
return reinterpret_cast<INT_PTR>(GetStockObject(NULL_BRUSH));
}
return FALSE;
break;
}
case WM_TIMER: {
if (wParam == IDT_UPDATE) {
SendMessage(dlg, WM_UPDATE, NULL, NULL);
return TRUE;
SendMessage(hWnd, WM_UPDATE, NULL, NULL);
return 0;
}
return FALSE;
break;
}
case WM_UPDATE: {
InvalidateRect(dlg, nullptr, TRUE);
UpdateWindow(dlg);
return TRUE;
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(dlg, &ps);
HDC hDC = BeginPaint(hWnd, &ps);
RECT timeRect = {};
GetClientRect(dlg, &timeRect);
timeRect.top -= 25, timeRect.bottom -= 25;
SetTextColor(hDC, RGB(0, 0, 0));
SetBkMode(hDC, TRANSPARENT);
SelectObject(hDC, timeFont);
RECT clientRect = {};
GetClientRect(hWnd, &clientRect);
Seconds elapsed = Duration(Clock::now() - app->m_startTime).count();
if (!app->m_active)
elapsed = 0;
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);
// 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);
}
else {
SetDlgItemText(dlg, IDC_STATICLOWER, "Time Remaining on Tester");
// 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);
// Draw outline
SelectObject(app->m_offscreenDC, GetStockObject(NULL_BRUSH));
SelectObject(app->m_offscreenDC, app->m_outlinePen);
RoundRect(app->m_offscreenDC, 1, 1, WINDOW_WIDTH - 2, WINDOW_HEIGHT - 2, 20, 20);
// 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(hDC, std::format("{:02}:{:02}", minutes, seconds).c_str(), -1, &timeRect, DT_SINGLELINE | DT_NOCLIP | DT_CENTER | DT_VCENTER);
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);
if (app->m_paintCounter % 5 > 1) {
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;
EndPaint(dlg, &ps);
return 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);
app->m_paintCounter++;
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, int newXPos, int newYPos) {
RECT clientRect = { .right = WINDOW_WIDTH, .bottom = WINDOW_HEIGHT };
AdjustWindowRectExForDpi(&clientRect, WINDOW_STYLE, FALSE, EX_WINDOW_STYLE, GetDpiForWindow(hWnd));
SetWindowPos(hWnd, nullptr, newXPos, newYPos, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top, SWP_NOZORDER);
}
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(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
iconData.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICONMAIN));
strcpy(iconData.szTip, m_appName.c_str());
Shell_NotifyIcon(NIM_ADD, &iconData);
}
@@ -242,4 +352,18 @@ 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);
DeleteObject(m_offscreenBitmap);
DeleteDC(m_offscreenDC);
}
}
+17 -2
View File
@@ -11,15 +11,20 @@ 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, int newXPos = 0, int newYPos = 0);
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?
@@ -28,6 +33,16 @@ namespace watchfuleye {
unsigned int m_maxMinutes;
const char* m_drive;
uint8_t m_paintCounter;
TimePoint m_startTime;
UINT m_taskbarCreatedMessage;
HRGN m_region;
HFONT m_timeFont, m_lowerFont;
HBRUSH m_greenBrush, m_yellowBrush, m_redBrush;
HPEN m_outlinePen;
HDC m_offscreenDC;
HBITMAP m_offscreenBitmap;
};
}
+5 -7
View File
@@ -4,17 +4,15 @@
#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"
#define MAX_MINUTES 1
#define DRIVE_TO_DETECT "E:"
// ======== Configuration ========
#define MAX_MINUTES 15
#define DRIVE_TO_DETECT "D:"
// ===============================
int main(int argc, char* argv[]) {
{
watchfuleye::Application app(APPNAME, APPVERSION, MAX_MINUTES, DRIVE_TO_DETECT);
app.run();
+2 -3
View File
@@ -2,14 +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 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 103
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101