#include "pch.h"
#include "CoVinceWindowsOverlay.h"
//extra test log file
#include <ShlObj.h> // For SHGetKnownFolderPath
//extra test log file
#pragma comment(lib, "Shell32.lib") // Link against Shell32.lib (often needed for SH functions)
using namespace Gdiplus; // Optional: use namespace
#pragma comment (lib,"Gdiplus.lib") // Link against Gdiplus library
// --- Forward Declarations ---
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// --- Global variables ---
std::vector<HWND> g_overlayWindows; // Use g_ prefix for global convention
std::wstring g_overlayText = L"";
std::atomic<bool> g_showOverlayText = false; // Use atomic for potential thread safety
int g_borderWidth = 0;
COLORREF g_borderColor = RGB(0, 0, 0);
int g_fontSize = 20;
std::atomic<CoVinceOverlayTextAlignment> g_textAlignment = COVINCE_OVERLAY_TEXT_ALIGN_VCENTER;
std::atomic<bool> g_overlayShown = false; // Use atomic for thread safety
const wchar_t* WND_CLASS_NAME = L"CoVinceOverlayWindowClass";
const int POINTER_NAME_FONT_SIZE = 12; // Example font size for names
// --- Add GDI+ Globals ---
ULONG_PTR g_gdiplusToken;
GdiplusStartupInput g_gdiplusStartupInput;
// --- Remote Pointer Definitions & State ---
struct RemotePointer
{
std::wstring userName;
POINT position = { 0, 0 }; // Absolute screen coordinates (x, y)
std::wstring imagePath; // Path to the image file
int displayWidth = 0; // Desired display width
int displayHeight = 0; // Desired display height
// No need for cached Bitmap pointer here, we use the central cache
};
std::map<std::wstring, RemotePointer> g_remotePointers; // Key: userId (wstring)
CRITICAL_SECTION g_pointersCritSec;
// --- Image Cache ---
std::map<std::wstring, Gdiplus::Bitmap*> g_imageCache; // Key: imagePath (wstring)
CRITICAL_SECTION g_imageCacheCritSec;
const size_t MAX_CACHE_ITEMS = 100;
// --- Logging Globals ---
std::ofstream g_logFileStream;
CRITICAL_SECTION g_logCritSec;
bool g_logInitialized = false;
wchar_t g_logFilePath[MAX_PATH] = { 0 }; // Store the determined log file path
// Bool for switching log to file or log to debug
std::atomic<bool> g_logToDebugView = false; // True uses DebugView logging and False Default to file logging
// Extra safety net to make sure DLL is initialized correctly before starting any functions
static std::atomic<bool> g_dllInitializedOk = false;
// --- Logging Implementation ---
// Helper to initialize logging (opens file, etc.) - Not thread-safe itself, call within lock
bool InitializeLoggingInternal()
{
OutputDebugStringA("InitializeLoggingInternal: START \n");
if (g_logInitialized) return true; // Already done
// 1. Get Temp Path
DWORD pathLen = GetTempPathW(MAX_PATH, g_logFilePath);
if (pathLen == 0 || pathLen >= MAX_PATH) {
OutputDebugStringA("InitializeLoggingInternal: OverlayLog Error: Failed to get temp path.\n");
return false;
}
// 2. Generate Unique Filename Part (Timestamp + PID)
// Get current time
auto now = std::chrono::system_clock::now();
auto now_c = std::chrono::system_clock::to_time_t(now);
std::tm now_tm;
localtime_s(&now_tm, &now_c); // Use localtime_s for safety
// Format timestamp (YYYYMMDD_HHMMSS) into a wide string buffer
wchar_t timestampStr[80];
wcsftime(timestampStr, sizeof(timestampStr) / sizeof(wchar_t), L"%Y%m%d_%H%M%S", &now_tm);
// Get Process ID
DWORD processId = GetCurrentProcessId();
// Create the unique filename
wchar_t uniqueFilename[MAX_PATH];
swprintf_s(uniqueFilename, MAX_PATH, L"\\CoVinceOverlayLog_%s_%lu.txt", timestampStr, processId);
// 3. Append Unique Filename to Temp Path
// Ensure we don't exceed MAX_PATH when concatenating
if (wcslen(g_logFilePath) + wcslen(uniqueFilename) < MAX_PATH) {
wcscat_s(g_logFilePath, MAX_PATH, uniqueFilename);
}
else {
OutputDebugStringA("OverlayLog Error: Calculated log file path exceeds MAX_PATH.\n");
// Fallback: Use a simpler name directly in temp? Or just fail?
wcscpy_s(g_logFilePath, MAX_PATH, L"CoVinceOverlayLog_ErrorPath.txt"); // Fallback name
}
// 4. Open file stream in WRITE mode (std::ios::out) - truncates if exists
g_logFileStream.open(g_logFilePath, std::ios::out); // REMOVED std::ios::app
if (!g_logFileStream.is_open()) {
OutputDebugStringA("OverlayLog Error: Failed to open log file.\n");
wchar_t errorMsg[512];
swprintf_s(errorMsg, L"OverlayLog Error: Failed to open %ls\n", g_logFilePath);
OutputDebugStringW(errorMsg);
return false;
}
g_logInitialized = true;
// --- Log header information ---
g_logFileStream << "----------------------------------------" << std::endl;
g_logFileStream << "Logging Initialized: " << std::put_time(&now_tm, "%Y-%m-%d %H:%M:%S") << std::endl;
// Convert wide path to multibyte for logging to the narrow stream
char multiBytePath[MAX_PATH * 2];
int bytesWritten = WideCharToMultiByte(CP_UTF8, 0, g_logFilePath, -1, multiBytePath, sizeof(multiBytePath), NULL, NULL);
if (bytesWritten > 0) {
g_logFileStream << "Log File Path: " << multiBytePath << std::endl;
}
else {
g_logFileStream << "Log File Path: [Error converting path to UTF-8]" << std::endl;
}
g_logFileStream << "Process ID: " << processId << std::endl;
g_logFileStream << "----------------------------------------" << std::endl;
g_logFileStream.flush(); // Ensure header is written
OutputDebugStringA("OverlayLog Initialized.\n");
OutputDebugStringW(g_logFilePath); // Show the exact path in DebugView
OutputDebugStringA("\n");
return true;
}
// Main logging function (Thread-Safe)
void LogToFile(const char* format, ...) // Or rename LogMessage
{
OutputDebugStringA("LogToFile: START \n");
// --- Common Timestamp and Message Formatting ---
std::string timestampStr = "[TimeERR] "; // Default timestamp in case of error
std::string messageStr = "Error formatting log message."; // Default message
EnterCriticalSection(&g_logCritSec);
try // Outer try for exception safety during lock
{
// Try to format timestamp
try
{
OutputDebugStringA("LogToFile: Try to format timestamp. \n");
auto now = std::chrono::system_clock::now();
auto now_c = std::chrono::system_clock::to_time_t(now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
std::tm now_tm;
if (localtime_s(&now_tm, &now_c) == 0)
{ // Check return value of localtime_s
std::ostringstream timeSs;
timeSs << std::put_time(&now_tm, "[%H:%M:%S.") << std::setfill('0') << std::setw(3) << ms.count() << "] ";
timestampStr = timeSs.str(); // Assign if successful
}
}
catch (const std::exception& e)
{
OutputDebugStringA("!!! EXCEPTION during DebugView logging: ");
OutputDebugStringA(e.what());
OutputDebugStringA("\n");
}
// Try to format user message
try
{
OutputDebugStringA("LogToFile: Try to format user message. \n");
char messageBuffer[2048]; // Keep buffer local
va_list args;
va_start(args, format);
int result = vsnprintf_s(messageBuffer, sizeof(messageBuffer), _TRUNCATE, format, args);
va_end(args);
if (result >= 0)
{ // Check formatting success
messageStr = messageBuffer; // Assign if successful
}
}
catch (...)
{
OutputDebugStringA("LogToFile: potential va_list or vsnprintf_s issues. \n");
}
// --- Conditional Output ---
if (g_logToDebugView.load()) // Check the flag
{
// --- Output to DebugView ---
OutputDebugStringA(timestampStr.c_str());
OutputDebugStringA(messageStr.c_str());
OutputDebugStringA("\n");
}
else // --- Output to File ---
{
if (!g_logInitialized)
{
if (!InitializeLoggingInternal())
{
LeaveCriticalSection(&g_logCritSec); // Need to LEAVE CS before returning!
return; // Init failed
}
}
if (!g_logFileStream.is_open())
{
OutputDebugStringA("LogToFile: Log failure? OutputDebugStringA? Cannot write to file. \n");
LeaveCriticalSection(&g_logCritSec); // Need to LEAVE CS before returning!
return; // File not open
}
// Write the pre-formatted strings to file
g_logFileStream << timestampStr << messageStr << std::endl;
}
}
catch (const std::exception& e) // Catch known C++ exceptions
{
OutputDebugStringA("!!! LogToFile: EXCEPTION caught in outer try: ");
OutputDebugStringA(e.what());
OutputDebugStringA("\n");
LeaveCriticalSection(&g_logCritSec); // Ensure unlock before returning/rethrowing
return; // Exit after logging exception
}
catch (...) // Catch ANY other exception (e.g., structured exceptions)
{
OutputDebugStringA("!!! LogToFile: UNKNOWN EXCEPTION caught in outer try block!\n");
LeaveCriticalSection(&g_logCritSec); // Ensure unlock before returning/rethrowing
return; // Exit after logging exception
}
LeaveCriticalSection(&g_logCritSec); // Unlock on normal exit
// Mutex automatically unlocked when lock_guard goes out of scope
}
// --- Make sure helper assumes caller holds lock, or make helper use lock ---
// Modified version that uses the lock INTERNALLY for safety when called from DllMain DETACH
void ShutdownLoggingInternal()
{
// If file logging was active (g_logInitialized is true)
if (!g_logToDebugView.load() && g_logInitialized) // Check if file logging was intended and initialized
{
EnterCriticalSection(&g_logCritSec); // Lock here for safety during file close
try {
// Re-check inside lock to be absolutely sure state hasn't changed
if (g_logInitialized && g_logFileStream.is_open()) {
auto now = std::chrono::system_clock::now();
auto now_c = std::chrono::system_clock::to_time_t(now);
std::tm now_tm;
localtime_s(&now_tm, &now_c);
g_logFileStream << "----------------------------------------" << std::endl;
g_logFileStream << "Logging Stopped: " << std::put_time(&now_tm, "%Y-%m-%d %H:%M:%S") << std::endl;
g_logFileStream << "----------------------------------------" << std::endl;
g_logFileStream.flush();
g_logFileStream.close();
OutputDebugStringA("OverlayLog File Closed.\n");
}
g_logInitialized = false; // Reset flag inside lock after closing/checking
}
catch (const std::exception& e) {
OutputDebugStringA("!!! EXCEPTION in ShutdownLoggingInternal closing file: ");
OutputDebugStringA(e.what());
OutputDebugStringA("\n");
// Ensure leave happens even on exception
}
catch (...) {
OutputDebugStringA("!!! UNKNOWN EXCEPTION in ShutdownLoggingInternal closing file!!!\n");
}
LeaveCriticalSection(&g_logCritSec); // Leave critical section
}
else {
// File logging wasn't active or needed closing
OutputDebugStringA("OverlayLog Shutdown (File logging was not active/open).\n");
g_logInitialized = false; // Ensure flag is false anyway
}
}
// --- Helper Functions ---
// Registers the window class if it doesn't exist
HRESULT RegisterOverlayWindowClass(HINSTANCE hInstance)
{
//LogToFile("RegisterOverlayWindowClass START");
WNDCLASSEXW wcCheck = { sizeof(WNDCLASSEXW) };
if (GetClassInfoExW(hInstance, WND_CLASS_NAME, &wcCheck))
{
//LogToFile("Window class '%ls' already registered.", WND_CLASS_NAME);
return S_OK; // Already registered
}
//LogToFile("Registering window class '%ls'.", WND_CLASS_NAME);
WNDCLASSEXW wc = { 0 };
wc.cbSize = sizeof(WNDCLASSEXW);
wc.style = CS_HREDRAW | CS_VREDRAW; // Redraw on size change
wc.lpfnWndProc = WindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL; // Transparent background handled by layering
wc.lpszMenuName = NULL;
wc.lpszClassName = WND_CLASS_NAME;
wc.hIconSm = NULL;
if (!RegisterClassExW(&wc))
{
DWORD error = GetLastError();
//LogToFile("RegisterClassExW failed for '%ls'. Error: %lu", WND_CLASS_NAME, error);
return HRESULT_FROM_WIN32(error);
}
return S_OK;
}
// --- Cleanup Helper for Image Cache ---
void ClearImageCacheInternal()
{
size_t count = g_imageCache.size(); // Get count before clear
if (count == 0) return; // Nothing to clear
//LogToFile("ClearImageCacheInternal: Clearing %zu items...", count);
// Use iterator loop for safety and C++14 compatibility if needed
for (auto it = g_imageCache.begin(); it != g_imageCache.end(); ++it) {
delete it->second; // Delete the GDI+ Bitmap object
}
// Alternative C++17 loop (requires /std:c++17 or later)
// for (auto const& [path, bitmap] : g_imageCache) {
// delete bitmap;
// }
g_imageCache.clear();
//LogToFile("Image cache cleared (%zu items).", count);
}
// --- Window Procedure Including mouse pointer receive ---
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// LogToFile("WindowProc START"); // Keep this commented unless essential; it's very frequent.
switch (uMsg)
{
case WM_PAINT:
{
// LogToFile("WM_PAINT START"); // Also potentially very frequent
// --- Setup Code (GetClientRect, width, height checks, Create Memory DC/Bitmap) ---
RECT rcClient;
if (!GetClientRect(hwnd, &rcClient)) return 0;
int width = rcClient.right - rcClient.left;
int height = rcClient.bottom - rcClient.top;
if (width <= 0 || height <= 0) { PAINTSTRUCT ps; BeginPaint(hwnd, &ps); EndPaint(hwnd, &ps); return 0; }
HDC hdcScreen = GetDC(hwnd); if (!hdcScreen) return 0;
HDC memHDC = CreateCompatibleDC(hdcScreen); if (!memHDC) { ReleaseDC(hwnd, hdcScreen); return 0; }
BITMAPINFO bmi; ZeroMemory(&bmi, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = width; bmi.bmiHeader.biHeight = -height; // Top-down DIB
bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
HBITMAP memBitmap = CreateDIBSection(memHDC, &bmi, DIB_RGB_COLORS, nullptr, NULL, 0);
if (!memBitmap) { DeleteDC(memHDC); ReleaseDC(hwnd, hdcScreen); return 0; }
HBITMAP hOldBitmap = (HBITMAP)SelectObject(memHDC, memBitmap);
// ------------------------------------------------------------------------------
// --- GDI+ Drawing Starts ---
{ // Scope for Graphics object
//LogToFile("GDI+ Drawing START");
Graphics graphics(memHDC);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
graphics.Clear(Color(0, 0, 0, 0)); // Clear transparent
// --- Draw Border (No locking needed for reading globals here) ---
if (g_borderWidth > 0) {
Color borderGdiplusColor(255, GetRValue(g_borderColor), GetGValue(g_borderColor), GetBValue(g_borderColor));
Pen borderPen(borderGdiplusColor, (REAL)g_borderWidth);
REAL penOffset = (REAL)g_borderWidth / 2.0f;
graphics.DrawRectangle(&borderPen, penOffset, penOffset, (REAL)width - g_borderWidth, (REAL)height - g_borderWidth);
}
// --- Draw Overlay Text ---
if (g_showOverlayText.load())
{
//LogToFile("Draw Text START");
FontFamily fontFamily(L"Arial");
Font font(&fontFamily, (REAL)g_fontSize, FontStyleRegular, UnitPixel);
SolidBrush textBrush(Color(255, 255, 255, 255));
// --- GDI+ Alignment Logic ---
StringFormat format;
CoVinceOverlayTextAlignment currentAlignment = g_textAlignment.load(); // Use .load() for atomics
// Horizontal Alignment (GDI+ StringAlignment enum)
if ((currentAlignment & COVINCE_OVERLAY_TEXT_ALIGN_CENTER) != 0) {
format.SetAlignment(StringAlignmentCenter); // GDI+ Center
}
else if ((currentAlignment & COVINCE_OVERLAY_TEXT_ALIGN_RIGHT) != 0) {
format.SetAlignment(StringAlignmentFar); // GDI+ Right (Far)
}
else { // Default Left
format.SetAlignment(StringAlignmentNear); // GDI+ Left (Near)
}
// Vertical Alignment (GDI+ StringAlignment enum)
if ((currentAlignment & COVINCE_OVERLAY_TEXT_ALIGN_VCENTER) != 0) {
format.SetLineAlignment(StringAlignmentCenter); // GDI+ Middle (Center)
}
else if ((currentAlignment & COVINCE_OVERLAY_TEXT_ALIGN_BOTTOM) != 0) {
format.SetLineAlignment(StringAlignmentFar); // GDI+ Bottom (Far)
}
else { // Default Top
format.SetLineAlignment(StringAlignmentNear); // GDI+ Top (Near)
}
// --- End GDI+ Alignment Logic ---
// Calculate Layout Rectangle (with padding)
RectF layoutRectF(
(REAL)rcClient.left + g_borderWidth + 2,
(REAL)rcClient.top + g_borderWidth + 2,
(REAL)(rcClient.right - rcClient.left) - 2.0f * (g_borderWidth + 2),
(REAL)(rcClient.bottom - rcClient.top) - 2.0f * (g_borderWidth + 2)
);
// Ensure width and height are not negative if padding is too large
if (layoutRectF.Width < 0) layoutRectF.Width = 0;
if (layoutRectF.Height < 0) layoutRectF.Height = 0;
// Now use layoutRectF in DrawString
graphics.DrawString(g_overlayText.c_str(), -1, &font, layoutRectF, &format, &textBrush);
}
// --- REVISED: Remote Pointer Drawing (using CRITICAL_SECTION) ---
{ // Scope for lock and GDI+ resources for pointers
//LogToFile("Remote Pointer Drawing START");
std::vector<RemotePointer> pointersToDraw; // Copy data to draw outside lock
// --- Lock Pointer Map ---
EnterCriticalSection(&g_pointersCritSec); // *** USE CRITICAL_SECTION ***
try {
if (!g_remotePointers.empty()) {
pointersToDraw.reserve(g_remotePointers.size());
for (const auto& pair : g_remotePointers) {
pointersToDraw.push_back(pair.second);
}
}
}
catch (...) {
LeaveCriticalSection(&g_pointersCritSec); // *** LEAVE CS on exception ***
//LogToFile("WM_PAINT: Exception copying pointer data!");
goto PaintCleanup; // Skip drawing pointers if copy failed
}
LeaveCriticalSection(&g_pointersCritSec); // *** LEAVE CS on normal path ***
// --- Unlocked Pointer Map ---
if (!pointersToDraw.empty())
{
RECT windowRect;
BOOL gotWindowRect = GetWindowRect(hwnd, &windowRect);
if (gotWindowRect)
{
// Create GDI+ resources needed for the loop ONCE
FontFamily nameFontFamily(L"Arial"); // Potential crash point?
Font nameFont(&nameFontFamily, (REAL)POINTER_NAME_FONT_SIZE, FontStyleRegular, UnitPixel);
SolidBrush nameBrush(Color(255, 255, 255, 255));
StringFormat nameFormat;
for (const auto& pointer : pointersToDraw)
{
int relativeX = pointer.position.x - windowRect.left;
int relativeY = pointer.position.y - windowRect.top;
int displayWidth = pointer.displayWidth;
int displayHeight = pointer.displayHeight;
// Basic bounds check (check if top-left corner is within reasonable bounds)
if (relativeX < width && relativeX + displayWidth > 0 &&
relativeY < height && relativeY + displayHeight > 0)
{
Gdiplus::Bitmap* cachedBitmap = nullptr;
// --- Lock Image Cache for Lookup ---
EnterCriticalSection(&g_imageCacheCritSec); // *** USE CRITICAL_SECTION ***
try {
auto it = g_imageCache.find(pointer.imagePath);
if (it != g_imageCache.end()) {
cachedBitmap = it->second;
}
}
catch (...) {
LeaveCriticalSection(&g_imageCacheCritSec); // *** LEAVE CS on exception ***
//LogToFile("WM_PAINT: Exception looking up image cache for '%ls'!", pointer.imagePath.c_str());
continue; // Skip this pointer
}
LeaveCriticalSection(&g_imageCacheCritSec); // *** LEAVE CS on normal path ***
// --- Unlocked Image Cache ---
if (cachedBitmap)
{
// --- Draw Rounded Image ---
GraphicsPath path;
path.AddEllipse(relativeX, relativeY, displayWidth, displayHeight);
GraphicsState state = graphics.Save(); // Save state is good practice with clipping
graphics.SetClip(&path, CombineModeReplace);
graphics.DrawImage(cachedBitmap, relativeX, relativeY, displayWidth, displayHeight);
graphics.Restore(state); // Restore state (resets clip)
}
else
{
//LogToFile("WM_PAINT: Image not in cache for path: %ls", pointer.imagePath.c_str());
// Optional: Draw fallback dot here if needed
}
// --- Draw Name ---
if (!pointer.userName.empty())
{
PointF textOrigin((REAL)relativeX + displayWidth + 3,
(REAL)relativeY + (REAL)displayHeight / 2.0f - (REAL)POINTER_NAME_FONT_SIZE / 2.0f);
graphics.DrawString(pointer.userName.c_str(), -1, &nameFont, textOrigin, &nameFormat, &nameBrush);
}
} // End bounds check
} // End for loop
} // End if gotWindowRect
} // End if pointersToDraw not empty
} // End scope for pointer drawing
PaintCleanup: // Label for cleanup, especially after exceptions before UpdateLayeredWindow
// --- Update Layered Window ---
POINT ptSrc = { 0, 0 };
POINT ptDst = { rcClient.left, rcClient.top };
RECT windowRectULW; // Use different variable name
if (GetWindowRect(hwnd, &windowRectULW)) { ptDst.x = windowRectULW.left; ptDst.y = windowRectULW.top; }
SIZE sizeWnd = { width, height };
BLENDFUNCTION blend = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
BOOL success = UpdateLayeredWindow(hwnd, hdcScreen, &ptDst, &sizeWnd, memHDC, &ptSrc, 0, &blend, ULW_ALPHA);
// if (!success) { LogToFile("UpdateLayeredWindow failed: %lu", GetLastError()); }
// --- Cleanup ---
SelectObject(memHDC, hOldBitmap);
DeleteObject(memBitmap);
DeleteDC(memHDC);
ReleaseDC(hwnd, hdcScreen);
ValidateRect(hwnd, NULL);
return 0;
} // End WM_PAINT
}
case WM_DESTROY:
//LogToFile("WM_DESTROY received for HWND %p", hwnd);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
//LogToFile("MonitorEnumProc START");
MONITORINFO mi = { sizeof(MONITORINFO) };
if (!GetMonitorInfo(hMonitor, &mi))
{
//LogToFile("MonitorEnumProc: GetMonitorInfo failed for HMONITOR %p. Error: %lu", hMonitor, GetLastError());
return TRUE; // Continue enumeration
}
HINSTANCE hInstance = GetModuleHandle(NULL); // Get current module handle
if (!hInstance)
{
//LogToFile("MonitorEnumProc: GetModuleHandle(NULL) failed. Error: %lu", GetLastError());
return TRUE; // Cannot create window without instance
}
// Create the overlay window for this monitor
//LogToFile("MonitorEnumProc: Create the overlay window START");
HWND hwnd = CreateWindowExW(
WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_NOACTIVATE, // Added NOACTIVATE
WND_CLASS_NAME,
L"Overlay", // Window title (not visible)
WS_POPUP, // Style for borderless window
mi.rcMonitor.left,
mi.rcMonitor.top,
mi.rcMonitor.right - mi.rcMonitor.left,
mi.rcMonitor.bottom - mi.rcMonitor.top,
NULL, NULL, hInstance, NULL
);
if (!hwnd)
{
//LogToFile("MonitorEnumProc: CreateWindowExW failed for monitor %p. Error: %lu", hMonitor, GetLastError());
return TRUE; // Continue enumeration even if one window fails
}
// Show the window without activating it
ShowWindow(hwnd, SW_SHOWNA);
//UpdateWindow(hwnd); // Force initial paint
InvalidateRect(hwnd, NULL, TRUE); // Request a repaint immediately
// Safely add HWND to the vector (passed via dwData)
auto* pWindowsVec = reinterpret_cast<std::vector<HWND>*>(dwData);
if (pWindowsVec)
{
try
{
pWindowsVec->push_back(hwnd);
}
catch (const std::bad_alloc&)
{
//LogToFile("MonitorEnumProc: Failed to allocate memory to store HWND %p.", hwnd);
DestroyWindow(hwnd); // Clean up
// Decide whether to stop or continue enumeration on memory error
return FALSE; // Stop enumeration on critical error
}
}
return TRUE; // Continue enumeration
}
#ifdef __cplusplus
extern "C" {
#endif
// --- Overlay Functions Implementation ---
COVINCEWINDOWSOVERLAY_API HRESULT __cdecl ShowOverlay(const wchar_t* text, bool showText, int borderW, int r, int g, int b, int fSize, CoVinceOverlayTextAlignment alignment)
{
// --- ADD INITIALIZATION CHECK AT THE VERY START ---
if (!g_dllInitializedOk.load(std::memory_order_acquire)) // Use acquire load for visibility
{
OutputDebugStringA("ShowOverlay called but DLL not initialized!\n"); // Safer log here
return COVINCE_E_NOT_INITIALIZED;
}
// --- END INITIALIZATION CHECK ---
LogToFile("ShowOverlay START");
LogToFile("ShowOverlay called. Border width: %d, Font Size: %d", borderW, fSize);
HRESULT hr = S_OK;
// --- Cleanup previous instance ---
hr = HideOverlay(); // Call HideOverlay first to ensure clean state
if (FAILED(hr))
{
LogToFile("ShowOverlay: HideOverlay failed with HRESULT: 0x%X", hr);
// Decide if this is fatal. Maybe continue? For now, return error.
return hr;
}
LogToFile("ShowOverlay: Previous overlay hidden successfully.");
// --- Input Validation ---
if (!text)
{
LogToFile("ShowOverlay error: text pointer is NULL.");
return E_POINTER; // Standard HRESULT for null pointer
}
if (borderW < 0) borderW = 0; // Basic validation
if (fSize <= 0) fSize = 1; // Basic validation
// --- Update Global State ---
try
{
g_overlayText = text; // Assign C-style string to std::wstring
}
catch (const std::exception& e)
{
LogToFile("ShowOverlay: EXCEPTION assigning text: %s", e.what());
return E_FAIL; // Indicate failure
}
catch (...)
{
LogToFile("ShowOverlay: UNKNOWN EXCEPTION assigning text!");
return E_FAIL;
}
// --- End Text Assignment Change ---
g_showOverlayText = showText;
g_borderWidth = borderW;
g_borderColor = RGB(r, g, b);
g_fontSize = fSize;
g_textAlignment = alignment;
// --- Register Window Class ---
HINSTANCE hInstance = GetModuleHandle(NULL);
if (!hInstance)
{
DWORD error = GetLastError();
LogToFile("ShowOverlay: GetModuleHandle(NULL) failed. Error: %lu", error);
return HRESULT_FROM_WIN32(error);
}
hr = RegisterOverlayWindowClass(hInstance);
if (FAILED(hr))
{
LogToFile("ShowOverlay: RegisterOverlayWindowClass failed with HRESULT: 0x%X", hr);
return COVINCE_E_CLASS_REGISTRATION_FAILED; // Or return hr directly
}
// --- Create Windows on All Monitors ---
std::vector<HWND> tempWindows; // Create windows into a temporary vector
LPARAM lParam = reinterpret_cast<LPARAM>(&tempWindows);
if (!EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, lParam))
{
// If MonitorEnumProc returned FALSE, it might be due to allocation failure
// Otherwise, EnumDisplayMonitors itself failed.
DWORD error = GetLastError();
LogToFile("ShowOverlay: EnumDisplayMonitors failed. Error: %lu", error);
// Clean up any windows that might have been created in tempWindows before failure
for (HWND hwnd : tempWindows)
{
if (IsWindow(hwnd)) DestroyWindow(hwnd);
}
return HRESULT_FROM_WIN32(error);
}
// --- Finalize State ---
if (tempWindows.empty())
{
LogToFile("ShowOverlay: No monitors found or window creation failed on all monitors.");
// Technically not an error based on EnumDisplayMonitors, but maybe return a specific code?
// Or just S_OK as no API failed? Let's return OK but log it.
return S_OK;
}
g_overlayWindows = std::move(tempWindows); // Move ownership to global vector
g_overlayShown = true;
LogToFile("ShowOverlay created %zu windows. Returning S_OK.", g_overlayWindows.size());
return S_OK;
}
COVINCEWINDOWSOVERLAY_API HRESULT __cdecl ShowOverlayOnMonitor(int monitorIndex, const wchar_t* text, bool showText, int borderW, int r, int g, int b, int fSize, CoVinceOverlayTextAlignment alignment)
{
// --- ADD INITIALIZATION CHECK AT THE VERY START ---
if (!g_dllInitializedOk.load(std::memory_order_acquire)) // Use acquire load for visibility
{
OutputDebugStringA("ShowOverlayOnMonitor called but DLL not initialized!\n"); // Safer log here
return COVINCE_E_NOT_INITIALIZED;
}
// --- END INITIALIZATION CHECK ---
LogToFile("ShowOverlayOnMonitor START");
LogToFile("ShowOverlayOnMonitor called for index %d", monitorIndex);
HRESULT hr = S_OK;
// --- Cleanup previous instance ---
hr = HideOverlay();
if (FAILED(hr))
{
LogToFile("ShowOverlayOnMonitor: HideOverlay failed with HRESULT: 0x%X", hr);
return hr;
}
// --- Input Validation ---
if (!text)
{
LogToFile("ShowOverlayOnMonitor error: text pointer is NULL.");
return E_POINTER;
}
if (borderW < 0) borderW = 0;
if (fSize <= 0) fSize = 1;
// --- Update Global State ---
try
{
g_overlayText = text; // Assign C-style string to std::wstring
}
catch (const std::exception& e) {
LogToFile("ShowOverlayOnMonitor: EXCEPTION assigning text: %s", e.what());
return E_FAIL; // Indicate failure
}
catch (...) {
LogToFile("ShowOverlayOnMonitor: UNKNOWN EXCEPTION assigning text!");
return E_FAIL;
}
// --- End Text Assignment Change ---
g_showOverlayText = showText;
g_borderWidth = borderW;
g_borderColor = RGB(r, g, b);
g_fontSize = fSize;
g_textAlignment = alignment;
// --- Register Window Class ---
HINSTANCE hInstance = GetModuleHandle(NULL);
if (!hInstance)
{
DWORD error = GetLastError();
LogToFile("ShowOverlayOnMonitor: GetModuleHandle(NULL) failed. Error: %lu", error);
return HRESULT_FROM_WIN32(error);
}
hr = RegisterOverlayWindowClass(hInstance);
if (FAILED(hr))
{
LogToFile("ShowOverlayOnMonitor: RegisterOverlayWindowClass failed with HRESULT: 0x%X", hr);
return COVINCE_E_CLASS_REGISTRATION_FAILED;
}
// --- Find Specific Monitor ---
std::vector<HMONITOR> monitors;
LPARAM lParamMon = reinterpret_cast<LPARAM>(&monitors);
EnumDisplayMonitors(NULL, NULL, [](HMONITOR hMon, HDC, LPRECT, LPARAM dwData) -> BOOL {
auto* pMons = reinterpret_cast<std::vector<HMONITOR>*>(dwData);
pMons->push_back(hMon);
return TRUE;
}, lParamMon);
if (monitorIndex < 0 || monitorIndex >= static_cast<int>(monitors.size()))
{
LogToFile("ShowOverlayOnMonitor: Invalid monitor index %d (found %zu monitors).", monitorIndex, monitors.size());
return COVINCE_E_INVALID_MONITOR_INDEX;
}
HMONITOR hMonitor = monitors[monitorIndex];
MONITORINFO mi = { sizeof(MONITORINFO) };
if (!GetMonitorInfo(hMonitor, &mi))
{
DWORD error = GetLastError();
LogToFile("ShowOverlayOnMonitor: GetMonitorInfo failed for index %d. Error: %lu", monitorIndex, error);
return HRESULT_FROM_WIN32(error);
}
// --- Create Single Window ---
HWND hwnd = CreateWindowExW(
WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_NOACTIVATE, // Added NOACTIVATE
WND_CLASS_NAME, L"Overlay", WS_POPUP,
mi.rcMonitor.left, mi.rcMonitor.top,
mi.rcMonitor.right - mi.rcMonitor.left,
mi.rcMonitor.bottom - mi.rcMonitor.top,
NULL, NULL, hInstance, NULL
);
if (!hwnd)
{
DWORD error = GetLastError();
LogToFile("ShowOverlayOnMonitor: CreateWindowExW failed. Error: %lu", error);
return COVINCE_E_WINDOW_CREATION_FAILED; // Or HRESULT_FROM_WIN32(error)
}
ShowWindow(hwnd, SW_SHOWNA);
InvalidateRect(hwnd, NULL, TRUE); // Request a repaint immediately
// --- Finalize State ---
g_overlayWindows.push_back(hwnd); // Add the single window
g_overlayShown = true;
LogToFile("ShowOverlayOnMonitor created window %p on monitor %d. Returning S_OK.", hwnd, monitorIndex);
return S_OK;
}
COVINCEWINDOWSOVERLAY_API HRESULT __cdecl HideOverlay()
{
// --- ADD INITIALIZATION CHECK AT THE VERY START ---
if (!g_dllInitializedOk.load(std::memory_order_acquire)) // Use acquire load for visibility
{
OutputDebugStringA("HideOverlay called but DLL not initialized!\n"); // Safer log here
return COVINCE_E_NOT_INITIALIZED;
}
// --- END INITIALIZATION CHECK ---
LogToFile("HideOverlay START");
// --- Handle "Already Hidden" State ---
// Check the atomic flag first for quick exit
if (!g_overlayShown.load()) {
LogToFile("HideOverlay: Overlay already hidden or not initialized.");
// Optional: You could add checks/cleanup here for consistency if pointers/cache
// somehow got populated without g_overlayShown being true, but it might
// indicate logic errors elsewhere. Let's keep it simple for now.
// If you need it:
/*
EnterCriticalSection(&g_pointersCritSec);
try { if (!g_remotePointers.empty()) g_remotePointers.clear(); } catch(...) {}
LeaveCriticalSection(&g_pointersCritSec);
EnterCriticalSection(&g_imageCacheCritSec);
try { if (!g_imageCache.empty()) ClearImageCacheInternal(); } catch(...) {}
LeaveCriticalSection(&g_imageCacheCritSec);
*/
return S_OK; // Nothing to do
}
// --- Destroy Windows (No lock needed for this part) ---
LogToFile("HideOverlay: Destroying %zu windows.", g_overlayWindows.size());
for (HWND hwnd : g_overlayWindows)
{
if (IsWindow(hwnd))
{
DestroyWindow(hwnd);
}
}
g_overlayWindows.clear(); // Clear window list AFTER destroying
// --- Cleanup Pointer Map ---
bool pointersWereCleared = false;
EnterCriticalSection(&g_pointersCritSec);
try {
if (!g_remotePointers.empty()) {
g_remotePointers.clear();
pointersWereCleared = true;
// Logging moved after LeaveCriticalSection
}
}
catch (const std::exception& e) {
// Log error *after* leaving critical section if possible
LogToFile("HideOverlay: EXCEPTION clearing pointer map: %s", e.what());
}
catch (...) {
LogToFile("HideOverlay: UNKNOWN EXCEPTION clearing pointer map!");
}
LeaveCriticalSection(&g_pointersCritSec); // Ensure Leave is always called
if (pointersWereCleared) {
LogToFile("HideOverlay: Cleared pointer map.");
}
// --- Cleanup Image Cache ---
bool cacheWasCleared = false;
EnterCriticalSection(&g_imageCacheCritSec);
try {
if (!g_imageCache.empty()) {
ClearImageCacheInternal(); // Helper deletes bitmaps AND logs clearing now
cacheWasCleared = true;
}
}
catch (const std::exception& e) {
LogToFile("HideOverlay: EXCEPTION clearing image cache: %s", e.what());
}
catch (...) {
LogToFile("HideOverlay: UNKNOWN EXCEPTION clearing image cache!");
}
LeaveCriticalSection(&g_imageCacheCritSec); // Ensure Leave is always called
// --- ------------------------------------ ---
g_overlayShown = false; // Set flag AFTER cleanup attempts
LogToFile("HideOverlay completed.");
return S_OK;
}
// --- Helper for update functions ---
HRESULT UpdateOverlayProperty(std::function<void()> updateAction)
{
LogToFile("UpdateOverlayProperty: START");
if (!g_overlayShown.load() || g_overlayWindows.empty()) {
LogToFile("UpdateOverlayProperty: Overlay not initialized.");
return COVINCE_E_NOT_INITIALIZED;
}
updateAction(); // Perform the specific state update
// Invalidate all windows
for (HWND hwnd : g_overlayWindows) {
if (IsWindow(hwnd)) {
InvalidateRect(hwnd, NULL, TRUE);
}
}
LogToFile("UpdateOverlayProperty completed and invalidated windows. Returning S_OK.");
return S_OK;
}
// --- Update Functions Implementation ---
COVINCEWINDOWSOVERLAY_API HRESULT __cdecl ShowOverlayText(bool show)
{
// --- ADD INITIALIZATION CHECK AT THE VERY START ---
if (!g_dllInitializedOk.load(std::memory_order_acquire)) // Use acquire load for visibility
{
OutputDebugStringA("ShowOverlayText called but DLL not initialized!\n"); // Safer log here
return COVINCE_E_NOT_INITIALIZED;
}
// --- END INITIALIZATION CHECK ---
LogToFile("ShowOverlayText called with show = %d", show);
return UpdateOverlayProperty([&]() {
g_showOverlayText = show;
});
}
COVINCEWINDOWSOVERLAY_API HRESULT __cdecl UpdateOverlayText(const wchar_t* text)
{
// --- ADD INITIALIZATION CHECK AT THE VERY START ---
if (!g_dllInitializedOk.load(std::memory_order_acquire)) // Use acquire load for visibility
{
OutputDebugStringA("UpdateOverlayText called but DLL not initialized!\n"); // Safer log here
return COVINCE_E_NOT_INITIALIZED;
}
// --- END INITIALIZATION CHECK ---
LogToFile("UpdateOverlayText START");
if (!text) {
LogToFile("UpdateOverlayText error: text pointer is NULL.");
return E_POINTER;
}
// Optional: You might still want a general sanity check on length,
// but it's not tied to a fixed buffer size anymore.
/*
size_t textLen = wcslen(text);
if (textLen > 4096) { // Example: Arbitrary large limit
LogToFile("UpdateOverlayText error: text length (%zu) exceeds arbitrary limit.", textLen);
return E_INVALIDARG; // Or a custom HRESULT
}
*/
// Use UpdateOverlayProperty, passing a lambda that assigns to the std::wstring
return UpdateOverlayProperty([&]() {
try {
// Use the std::wstring assignment operator
g_overlayText = text;
// Optional logging inside lambda if needed, using LogToFile carefully
// LogToFile("Updated g_overlayText");
}
catch (const std::bad_alloc& e) {
// Handle potential memory allocation failure during wstring assignment
LogToFile("UpdateOverlayText: EXCEPTION assigning text (bad_alloc): %s", e.what());
// Cannot easily return error HRESULT from lambda within UpdateOverlayProperty
// Maybe set a global error flag if needed? Or just log.
}
catch (const std::exception& e) {
LogToFile("UpdateOverlayText: EXCEPTION assigning text: %s", e.what());
}
catch (...) {
LogToFile("UpdateOverlayText: UNKNOWN EXCEPTION assigning text!");
}
});
}
COVINCEWINDOWSOVERLAY_API HRESULT __cdecl UpdateOverlayFontSize(int fSize)
{
// --- ADD INITIALIZATION CHECK AT THE VERY START ---
if (!g_dllInitializedOk.load(std::memory_order_acquire)) // Use acquire load for visibility
{
OutputDebugStringA("UpdateOverlayFontSize called but DLL not initialized!\n"); // Safer log here
return COVINCE_E_NOT_INITIALIZED;
}
// --- END INITIALIZATION CHECK ---
LogToFile("UpdateOverlayFontSize called with fSize = %d", fSize);
if (fSize <= 0) fSize = 1; // Basic validation
return UpdateOverlayProperty([&]() {
g_fontSize = fSize;
});
}
COVINCEWINDOWSOVERLAY_API HRESULT __cdecl UpdateOverlayWidth(int bWidth)
{
// --- ADD INITIALIZATION CHECK AT THE VERY START ---
if (!g_dllInitializedOk.load(std::memory_order_acquire)) // Use acquire load for visibility
{
OutputDebugStringA("UpdateOverlayWidth called but DLL not initialized!\n"); // Safer log here
return COVINCE_E_NOT_INITIALIZED;
}
// --- END INITIALIZATION CHECK ---
LogToFile("UpdateOverlayWidth called with bWidth = %d", bWidth);
if (bWidth < 0) bWidth = 0; // Basic validation
return UpdateOverlayProperty([&]() {
g_borderWidth = bWidth;
});
}
COVINCEWINDOWSOVERLAY_API HRESULT __cdecl UpdateOverlayColor(int r, int g, int b)
{
// --- ADD INITIALIZATION CHECK AT THE VERY START ---
if (!g_dllInitializedOk.load(std::memory_order_acquire)) // Use acquire load for visibility
{
OutputDebugStringA("UpdateOverlayColor called but DLL not initialized!\n"); // Safer log here
return COVINCE_E_NOT_INITIALIZED;
}
// --- END INITIALIZATION CHECK ---
LogToFile("UpdateOverlayColor called with RGB(%d, %d, %d)", r, g, b);
return UpdateOverlayProperty([&]() {
g_borderColor = RGB(r, g, b);
});
}
COVINCEWINDOWSOVERLAY_API HRESULT __cdecl UpdateOverlayAlignment(CoVinceOverlayTextAlignment alignment)
{
// --- ADD INITIALIZATION CHECK AT THE VERY START ---
if (!g_dllInitializedOk.load(std::memory_order_acquire)) // Use acquire load for visibility
{
OutputDebugStringA("UpdateOverlayAlignment called but DLL not initialized!\n"); // Safer log here
return COVINCE_E_NOT_INITIALIZED;
}
// --- END INITIALIZATION CHECK ---
LogToFile("UpdateOverlayAlignment called with alignment = %d", alignment);
return UpdateOverlayProperty([&]() {
g_textAlignment = alignment; // This automatically uses atomic store
});
}
// --- Pointer API Functions (Modified for Image Path) ---
COVINCEWINDOWSOVERLAY_API HRESULT __cdecl AddOrUpdatePointer(const wchar_t* userId, const wchar_t* userName, int x, int y, const wchar_t* imagePath, int desiredWidth, int desiredHeight) // Desired Size
{
// --- ADD INITIALIZATION CHECK AT THE VERY START ---
if (!g_dllInitializedOk.load(std::memory_order_acquire)) // Use acquire load for visibility
{
OutputDebugStringA("AddOrUpdatePointer called but DLL not initialized!\n"); // Safer log here
return COVINCE_E_NOT_INITIALIZED;
}
// --- END INITIALIZATION CHECK ---
// --- Input Validation ---
if (!userId || !userName || !imagePath) {
LogToFile("AddOrUpdatePointer ERROR: Null input pointer provided.");
return E_POINTER;
}
if (desiredWidth <= 0 || desiredHeight <= 0) {
LogToFile("AddOrUpdatePointer ERROR: Invalid desired dimensions (%d x %d) for UserID '%ls'.", desiredWidth, desiredHeight, userId);
return E_INVALIDARG;
}
LogToFile("AddOrUpdatePointer START - UserID: '%ls', Img: '%ls', Pos: (%d,%d), Size: (%dx%d)",
userId, imagePath, x, y, desiredWidth, desiredHeight);
HRESULT hr = S_OK;
bool imageLoadedOrExisted = false;
std::wstring wsImagePath = imagePath; // Convert path for map key/comparison
// --- Load/Cache Image (Protected by Image Cache Critical Section) ---
EnterCriticalSection(&g_imageCacheCritSec);
try
{
auto it = g_imageCache.find(wsImagePath);
if (it == g_imageCache.end()) { // Not in cache
LogToFile("AddOrUpdatePointer: Image '%ls' not in cache. Loading...", wsImagePath.c_str());
Bitmap* newBitmap = nullptr;
try {
// Use Bitmap constructor that takes path and useEmbeddedColorManagement flag
newBitmap = new Bitmap(wsImagePath.c_str(), FALSE); // Create on heap
}
catch (const std::exception& e) {
LogToFile("AddOrUpdatePointer: EXCEPTION during Bitmap creation for '%ls': %s", wsImagePath.c_str(), e.what());
hr = E_FAIL;
}
catch (...) {
LogToFile("AddOrUpdatePointer: UNKNOWN EXCEPTION during Bitmap creation for '%ls'", wsImagePath.c_str());
hr = E_FAIL;
}
// Check GDI+ status *after* potential constructor completion/exception
if (SUCCEEDED(hr)) {
if (newBitmap && newBitmap->GetLastStatus() == Ok) {
g_imageCache[wsImagePath] = newBitmap; // Store pointer in cache
imageLoadedOrExisted = true;
LogToFile("Loaded image '%ls' into cache.", wsImagePath.c_str());
if (g_imageCache.size() > MAX_CACHE_ITEMS) {
LogToFile("Image cache size (%zu) exceeds limit (%zu). Trimming...", g_imageCache.size(), MAX_CACHE_ITEMS);
size_t itemsToDelete = g_imageCache.size() / 2; // Or just trim down to MAX_CACHE_ITEMS
auto it = g_imageCache.begin();
for (size_t i = 0; i < itemsToDelete && it != g_imageCache.end(); ++i) {
LogToFile("Trimming image cache: Deleting '%ls'", it->first.c_str());
delete it->second; // Delete the bitmap
it = g_imageCache.erase(it); // Erase element and advance iterator correctly
}
LogToFile("Image cache trimmed. New size: %zu", g_imageCache.size());
}
}
else {
Status loadStatus = newBitmap ? newBitmap->GetLastStatus() : GenericError; // Get status or error code
LogToFile("Failed to load image '%ls'. GDI+ Status: %d", wsImagePath.c_str(), loadStatus);
delete newBitmap; // Important: delete pointer if constructor succeeded but status is bad, or if constructor returned null but allocated something partially? Safer to delete.
hr = E_FAIL;
}
}
else {
// Exception occurred during Bitmap creation, ensure pointer is deleted if somehow non-null
delete newBitmap;
}
}
else {
imageLoadedOrExisted = true; // Image already exists in cache
// LogToFile("AddOrUpdatePointer: Image '%ls' found in cache.", wsImagePath.c_str());
}
}
catch (...) { // Catch potential exceptions from std::map or std::wstring itself
LeaveCriticalSection(&g_imageCacheCritSec); // Ensure leave on exception
LogToFile("AddOrUpdatePointer: *** CRITICAL EXCEPTION in Image Cache block! ***");
return E_FAIL; // Return failure
}
LeaveCriticalSection(&g_imageCacheCritSec); // Leave normally
// --- Image Cache Unlocked ---
if (FAILED(hr)) {
LogToFile("AddOrUpdatePointer: Bailing out due to image load/cache failure.");
return hr;
}
// --- Update Pointer Map (Protected by Pointers Critical Section) ---
EnterCriticalSection(&g_pointersCritSec);
try
{
// Only proceed if image is ready
// No need for imageLoadedOrExisted check here due to FAILED(hr) check above
RemotePointer& rp = g_remotePointers[userId]; // Get reference or create new entry
rp.userName = userName;
rp.position = { x, y };
rp.imagePath = wsImagePath; // Store the path (key to the image cache)
rp.displayWidth = desiredWidth;
rp.displayHeight = desiredHeight;
// Log moved outside lock
}
catch (const std::exception& e) {
LeaveCriticalSection(&g_pointersCritSec); // Ensure leave on exception
LogToFile("AddOrUpdatePointer: EXCEPTION Failed to update pointer map for '%ls': %s", userId, e.what());
return E_FAIL; // Return failure
}
catch (...) {
LeaveCriticalSection(&g_pointersCritSec); // Ensure leave on exception
LogToFile("AddOrUpdatePointer: UNKNOWN EXCEPTION updating pointer map for '%ls'", userId);
return E_FAIL; // Return failure
}
LeaveCriticalSection(&g_pointersCritSec); // Leave normally
LogToFile("Updated pointer map for '%ls'.", userId); // Log success outside lock
// --- Pointers Unlocked ---
// --- Trigger Repaint ---
if (g_overlayShown.load()) {
LogToFile("AddOrUpdatePointer: Triggering InvalidateRect.");
for (HWND hwnd : g_overlayWindows) {
if (IsWindow(hwnd)) { InvalidateRect(hwnd, NULL, TRUE); }
}
}
else {
LogToFile("AddOrUpdatePointer: Overlay not shown, repaint not triggered.");
}
return S_OK; // Return success
}
COVINCEWINDOWSOVERLAY_API HRESULT __cdecl RemovePointer(const wchar_t* userId)
{
// --- ADD INITIALIZATION CHECK AT THE VERY START ---
if (!g_dllInitializedOk.load(std::memory_order_acquire)) // Use acquire load for visibility
{
OutputDebugStringA("RemovePointer called but DLL not initialized!\n"); // Safer log here
return COVINCE_E_NOT_INITIALIZED;
}
// --- END INITIALIZATION CHECK ---
LogToFile("RemovePointer START for UserID: '%ls'", userId ? userId : L"NULL");
if (!userId) {
LogToFile("RemovePointer ERROR: Null userId provided.");
return E_POINTER;
}
size_t erasedCount = 0;
// --- Remove from Pointer Map (Protected by Critical Section) ---
EnterCriticalSection(&g_pointersCritSec); // *** ENTER CS ***
try
{
// Attempt to erase the element from the map
erasedCount = g_remotePointers.erase(userId);
// Logging about success/failure moved after Leave
}
catch (const std::exception& e)
{
// Log error if std::map::erase throws (unlikely for standard types unless memory issue)
LeaveCriticalSection(&g_pointersCritSec); // *** LEAVE CS on exception ***
LogToFile("RemovePointer: EXCEPTION during map erase for '%ls': %s", userId, e.what());
// Decide on return code - maybe still try to repaint? Returning error seems appropriate.
return E_FAIL;
}
catch (...)
{
LeaveCriticalSection(&g_pointersCritSec); // *** LEAVE CS on exception ***
LogToFile("RemovePointer: UNKNOWN EXCEPTION during map erase for '%ls'", userId);
return E_FAIL;
}
LeaveCriticalSection(&g_pointersCritSec); // *** LEAVE CS on normal path ***
// --- Pointer Map Unlocked ---
// Log result after lock is released
LogToFile(erasedCount > 0 ? "Removed pointer '%ls' from map." : "Pointer '%ls' not found in map.", userId);
// --- Trigger Repaint ---
// We don't automatically remove from the image cache when a single pointer is removed
// using the simple caching strategy (only clear cache in HideOverlay/ClearAllPointers).
if (erasedCount > 0 && g_overlayShown.load()) {
LogToFile("RemovePointer: Triggering InvalidateRect.");
for (HWND hwnd : g_overlayWindows) {
if (IsWindow(hwnd)) { InvalidateRect(hwnd, NULL, TRUE); }
}
}
// It's generally fine to return S_OK even if the pointer wasn't found (idempotent delete)
return S_OK;
}
COVINCEWINDOWSOVERLAY_API HRESULT __cdecl ClearAllPointers()
{
// --- ADD INITIALIZATION CHECK AT THE VERY START ---
if (!g_dllInitializedOk.load(std::memory_order_acquire)) // Use acquire load for visibility
{
OutputDebugStringA("ClearAllPointers called but DLL not initialized!\n"); // Safer log here
return COVINCE_E_NOT_INITIALIZED;
}
// --- END INITIALIZATION CHECK ---
LogToFile("ClearAllPointers START");
bool hadPointers = false;
HRESULT pointerClearHr = S_OK;
HRESULT cacheClearHr = S_OK;
// --- Clear Pointer Map ---
EnterCriticalSection(&g_pointersCritSec);
try {
hadPointers = !g_remotePointers.empty();
if (hadPointers) g_remotePointers.clear();
}
catch (const std::exception& e) {
LogToFile("ClearAllPointers: EXCEPTION clearing pointer map: %s", e.what());
pointerClearHr = E_FAIL; // Mark error but continue
}
catch (...) {
LogToFile("ClearAllPointers: UNKNOWN EXCEPTION clearing pointer map!");
pointerClearHr = E_FAIL; // Mark error but continue
}
LeaveCriticalSection(&g_pointersCritSec); // Leave AFTER try/catch
if (SUCCEEDED(pointerClearHr) && hadPointers) LogToFile("Cleared pointer map.");
// --- Clear Image Cache ---
EnterCriticalSection(&g_imageCacheCritSec);
try {
if (!g_imageCache.empty()) {
ClearImageCacheInternal(); // Helper deletes bitmaps and logs
}
else {
// LogToFile("ClearAllPointers: Image cache already empty.");
}
}
catch (const std::exception& e) {
LogToFile("ClearAllPointers: EXCEPTION clearing image cache: %s", e.what());
cacheClearHr = E_FAIL; // Mark error
}
catch (...) {
LogToFile("ClearAllPointers: UNKNOWN EXCEPTION clearing image cache!");
cacheClearHr = E_FAIL; // Mark error
}
LeaveCriticalSection(&g_imageCacheCritSec); // Leave AFTER try/catch
// --- Trigger Repaint ---
if (hadPointers && g_overlayShown.load()) {
LogToFile("ClearAllPointers: Triggering InvalidateRect.");
for (HWND hwnd : g_overlayWindows) {
if (IsWindow(hwnd)) { InvalidateRect(hwnd, NULL, TRUE); }
}
}
// Return success unless something failed during cleanup
return FAILED(pointerClearHr) || FAILED(cacheClearHr) ? E_FAIL : S_OK;
}
#ifdef __cplusplus
}
#endif
static BOOL InitializeCriticalSections()
{
OutputDebugStringA("===> Initializing Critical Sections...\n");
// InitializeCriticalSectionAndSpinCount returns BOOL (non-zero for success, zero for failure)
// We want to return TRUE only if ALL succeed.
BOOL cs1_ok = InitializeCriticalSectionAndSpinCount(&g_pointersCritSec, 0x00000400);
BOOL cs2_ok = InitializeCriticalSectionAndSpinCount(&g_imageCacheCritSec, 0x00000400);
BOOL cs3_ok = InitializeCriticalSectionAndSpinCount(&g_logCritSec, 0x00000400);
if (cs1_ok && cs2_ok && cs3_ok)
{
OutputDebugStringA("===> Critical Sections Initialized OK.\n");
return TRUE; // All succeeded
}
else
{
// Log which one(s) failed if possible/needed for more detail
if (!cs1_ok) OutputDebugStringA("===> g_pointersCritSec FAILED to Initialize!\n");
if (!cs2_ok) OutputDebugStringA("===> g_imageCacheCritSec FAILED to Initialize!\n");
if (!cs3_ok) OutputDebugStringA("===> g_logCritSec FAILED to Initialize!\n");
OutputDebugStringA("===> Critical Sections FAILED to Initialize!\n");
return FALSE; // At least one failed
}
}
static void DeleteCriticalSections()
{
OutputDebugStringA("===> Deleting Critical Sections...\n");
DeleteCriticalSection(&g_pointersCritSec);
DeleteCriticalSection(&g_imageCacheCritSec);
DeleteCriticalSection(&g_logCritSec);
OutputDebugStringA("===> Critical Sections Deleted.\n");
}
//Maak de log in dezelfde forlder als waar de images komen te staan, via de global constants
// Refined DllMain using only OutputDebugStringA for its own tracing
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
// This *should* appear now based on the minimal test
OutputDebugStringA("===> 1 DLL_PROCESS_ATTACH Entry\n");
// --- Debug GDI+ Initialization ---
OutputDebugStringA("===> DBG: Attempting GdiplusStartup...\n");
Status gdiplusStatus = GdiplusStartup(&g_gdiplusToken, &g_gdiplusStartupInput, NULL);
if (gdiplusStatus == Ok)
{
OutputDebugStringA("===> DBG: GdiplusStartup SUCCEEDED.\n");
}
else
{
// Log the specific error before returning FALSE
char errMsg[128];
sprintf_s(errMsg, sizeof(errMsg), "===> DBG: GdiplusStartup FAILED! Status Code: %d. Returning FALSE from DllMain.\n", gdiplusStatus);
OutputDebugStringA(errMsg);
return FALSE; // Exit DLL load on GDI+ failure
}
// --- Debug Critical Section Initialization ---
OutputDebugStringA("===> DBG: Attempting InitializeCriticalSections...\n");
BOOL csInitOk = InitializeCriticalSections(); // Ensure this function exists and is correct
if (csInitOk)
{
OutputDebugStringA("===> DBG: InitializeCriticalSections SUCCEEDED.\n");
}
else
{
// Log the failure before returning FALSE
OutputDebugStringA("===> DBG: InitializeCriticalSections FAILED! Returning FALSE from DllMain.\n");
return FALSE; // Exit DLL load on CS failure
}
// If both succeeded, we should reach here
OutputDebugStringA("===> DBG: All initializations passed. Setting g_dllInitializedOk.\n");
g_dllInitializedOk = true; // Set flag only if everything succeeded
OutputDebugStringA("===> 5 DLL_PROCESS_ATTACH Exit (Success)\n"); // Original log message
break; // Break from case DLL_PROCESS_ATTACH
/*
OutputDebugStringA("===> 1 DLL_PROCESS_ATTACH Entry\n");
Status gdiplusStatus;
OutputDebugStringA("===> 2 Calling GdiplusStartup...\n");
gdiplusStatus = GdiplusStartup(&g_gdiplusToken, &g_gdiplusStartupInput, NULL);
OutputDebugStringA("===> 3 GdiplusStartup Returned\n");
if (gdiplusStatus == Ok) {
OutputDebugStringA("===> 4A GdiplusStartup Status: OK\n");
}
else {
char errMsg[100];
sprintf_s(errMsg, "===> 4B GdiplusStartup Status: FAILED! Code: %d\n", gdiplusStatus);
OutputDebugStringA(errMsg);
// return FALSE; // Optional: Fail DLL load if GDI+ failed
}
// Call the helper function
if (!InitializeCriticalSections())
{
OutputDebugStringA("!!! DllMain returning FALSE due to Critical Section init failure!\n");
return FALSE; // Return FALSE from DllMain to indicate DLL load failure
// This prevents the DLL from being used if essential sync primitives fail
}
OutputDebugStringA("===> 5 DLL_PROCESS_ATTACH Exit\n");
g_dllInitializedOk = true;
break; // Break remains inside the block
*/
} // <-- ADD closing brace HERE
case DLL_THREAD_ATTACH:
// Avoid complex operations here
break;
case DLL_THREAD_DETACH:
// Avoid complex operations here
break;
case DLL_PROCESS_DETACH:
{
OutputDebugStringA("===> DBG: DLL_PROCESS_DETACH received.\n");
HideOverlay(); // Clean up windows and associated maps/cache
// Shutdown Logging - Calls the helper which now uses CS internally
ShutdownLoggingInternal();
DeleteCriticalSections(); // Call the cleanup helper
OutputDebugStringA("===> B Calling GdiplusShutdown...\n");
GdiplusShutdown(g_gdiplusToken);
OutputDebugStringA("===> C GdiplusShutdown Returned\n");
OutputDebugStringA("===> D DLL_PROCESS_DETACH Exit\n");
break;
} // <-- Add closing brace HERE
} // End switch
return TRUE; // Must return TRUE for successful attach
}