-
-
Notifications
You must be signed in to change notification settings - Fork 7
Refactor thread handling and improve memory management; add configura… #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "permissions": { | ||
| "allow": [ | ||
| "Bash(wc:*)", | ||
| "Bash(cmake:*)", | ||
| "Bash(ctest:*)", | ||
| "Bash(\"build/tests/Release/display-lock-unittests.exe\")", | ||
| "Bash(\"display-lock-unittests.exe\")", | ||
| "Bash(\"./display-lock-unittests.exe\")", | ||
| "Bash(xxd:*)" | ||
| ] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # CLAUDE.md | ||
|
|
||
| This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
|
|
||
| ## Project Overview | ||
|
|
||
| Display-Lock is a lightweight Windows application that locks the cursor to a selected window. Written primarily in C using the Win32 API, it's designed for gamers and multi-monitor users who need to keep their cursor confined to a specific window. | ||
|
|
||
| ## Build Commands | ||
|
|
||
| ```bash | ||
| # Configure and build (Release) | ||
| cmake -B build -DCMAKE_BUILD_TYPE=Release | ||
| cmake --build build --config Release | ||
|
|
||
| # Configure and build (Debug) | ||
| cmake -B build -DCMAKE_BUILD_TYPE=Debug | ||
| cmake --build build --config Debug | ||
|
|
||
| # Run tests (requires Conan for GTest dependency) | ||
| pip install conan==1.59.0 | ||
| ctest --test-dir build -C Release | ||
| ``` | ||
|
|
||
| The build requires Visual Studio 2022 (MSVC) on Windows. Conan package manager is used to fetch Google Test for unit testing. | ||
|
|
||
| ## Architecture | ||
|
|
||
| **Component Library (`src/components/`)**: Core functionality as a static library (`display_lock_components`) | ||
| - `win.c` - Cursor locking logic and window manipulation | ||
| - `settings.c` - Binary config file persistence (`.DLOCK` format in `%APPDATA%/DisplayLock/`) | ||
| - `applications.c` - Application whitelist management and monitoring thread | ||
| - `notify.c` - System tray icon and notifications | ||
| - `update.c` - Version checking via HTTP | ||
| - `menu.c` - Menu UI handling | ||
|
|
||
| **Main Application (`src/`)**: Win32 dialog-based UI | ||
| - `main.c` - Entry point, window class registration, message loop | ||
| - `ui.c` - Tab view rendering (Window Select, Settings, Applications) | ||
| - `procedures/` - Dialog procedure handlers | ||
|
|
||
| **Key Data Structures (`include/common.h`)**: | ||
| - `SETTINGS` - User preferences (minimize on start, fullscreen, borderless, etc.) | ||
| - `WINDOWLIST` - Array of up to 35 windows for selection | ||
| - `APPLICATION_LIST` - Whitelisted applications for automatic cursor locking | ||
|
|
||
| **Threading Model**: Cursor locking and application monitoring run in dedicated threads, synchronized via mutex (`DLockApplicationMutex`). | ||
|
|
||
| ## Testing | ||
|
|
||
| Tests use Google Test (fetched via Conan). Test files are in `tests/` with test resources copied to the build directory. | ||
|
|
||
| ```bash | ||
| # Run a single test | ||
| ctest --test-dir build -C Release -R SettingsTest | ||
| ``` | ||
|
|
||
| ## Contributing Guidelines | ||
|
|
||
| - Work on `develop` branch for features, `master` for hotfixes | ||
| - Avoid external dependencies (keep it lightweight) | ||
| - Do not manually change version numbers or build scripts | ||
| - Update CHANGELOG.md with changes | ||
| - All builds and tests must pass before merge |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,15 +30,42 @@ BOOL readApplicationList(APPLICATION_LIST *applicationList, const wchar_t *path) | |
| return FALSE; | ||
| } | ||
|
|
||
| fread(&applicationList->count, sizeof(int), 1, file); | ||
| applicationList->applications = (APPLICATION_SETTINGS *)malloc(sizeof(APPLICATION_SETTINGS) * applicationList->count); | ||
| for (int i = 0; i < applicationList->count; i++) | ||
| if (fread(&applicationList->count, sizeof(int), 1, file) != 1) | ||
| { | ||
| fread(&applicationList->applications[i], sizeof(APPLICATION_SETTINGS), 1, file); | ||
| applicationList->count = 0; | ||
| applicationList->applications = NULL; | ||
| fclose(file); | ||
| return FALSE; | ||
| } | ||
|
|
||
| if (applicationList->count > 0) | ||
| { | ||
| applicationList->applications = (APPLICATION_SETTINGS *)malloc(sizeof(APPLICATION_SETTINGS) * applicationList->count); | ||
| if (applicationList->applications == NULL) | ||
| { | ||
| applicationList->count = 0; | ||
| fclose(file); | ||
| return FALSE; | ||
| } | ||
|
|
||
| for (int i = 0; i < applicationList->count; i++) | ||
| { | ||
| if (fread(&applicationList->applications[i], sizeof(APPLICATION_SETTINGS), 1, file) != 1) | ||
| { | ||
| free(applicationList->applications); | ||
| applicationList->applications = NULL; | ||
| applicationList->count = 0; | ||
| fclose(file); | ||
| return FALSE; | ||
| } | ||
| } | ||
| } | ||
| else | ||
| { | ||
| applicationList->applications = NULL; | ||
| } | ||
|
|
||
| fclose(file); | ||
| _fcloseall(); | ||
|
|
||
| return TRUE; | ||
| } | ||
|
|
@@ -64,7 +91,6 @@ BOOL writeApplicationList(APPLICATION_LIST *applicationList, const wchar_t *path | |
| } | ||
|
|
||
| fclose(file); | ||
| _fcloseall(); | ||
|
|
||
| return TRUE; | ||
| } | ||
|
|
@@ -76,6 +102,8 @@ BOOL addApplication(APPLICATION_LIST *applicationList, APPLICATION_SETTINGS appl | |
| { | ||
| applicationList->count = 1; | ||
| applicationList->applications = (APPLICATION_SETTINGS *)malloc(sizeof(APPLICATION_SETTINGS)); | ||
| if (applicationList->applications == NULL) | ||
| return FALSE; | ||
| applicationList->applications[0] = application; | ||
| } | ||
| else | ||
|
|
@@ -87,8 +115,11 @@ BOOL addApplication(APPLICATION_LIST *applicationList, APPLICATION_SETTINGS appl | |
| return FALSE; | ||
| } | ||
|
|
||
| APPLICATION_SETTINGS *temp = (APPLICATION_SETTINGS *)realloc(applicationList->applications, sizeof(APPLICATION_SETTINGS) * (applicationList->count + 1)); | ||
| if (temp == NULL) | ||
| return FALSE; | ||
| applicationList->applications = temp; | ||
| applicationList->count++; | ||
| applicationList->applications = (APPLICATION_SETTINGS *)realloc(applicationList->applications, sizeof(APPLICATION_SETTINGS) * applicationList->count); | ||
| applicationList->applications[applicationList->count - 1] = application; | ||
| } | ||
|
|
||
|
|
@@ -119,7 +150,10 @@ BOOL removeApplication(APPLICATION_LIST *applicationList, int index) | |
| } | ||
|
|
||
| applicationList->count--; | ||
| applicationList->applications = (APPLICATION_SETTINGS *)realloc(applicationList->applications, sizeof(APPLICATION_SETTINGS) * applicationList->count); | ||
| APPLICATION_SETTINGS *temp = (APPLICATION_SETTINGS *)realloc(applicationList->applications, sizeof(APPLICATION_SETTINGS) * applicationList->count); | ||
| if (temp != NULL) | ||
| applicationList->applications = temp; | ||
| // If realloc fails, keep the old pointer (memory is still valid, just larger than needed) | ||
| } | ||
|
|
||
| return TRUE; | ||
|
|
@@ -153,7 +187,7 @@ BOOL createApplicationDirectory(wchar_t *outPath) | |
| if (!SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_RoamingAppData, 0, NULL, &path))) | ||
| return FALSE; | ||
|
|
||
| wcscpy(outPath, path); | ||
| wcscpy_s(outPath, MAX_PATH, path); | ||
|
|
||
| // create directory | ||
| PathAppend(outPath, TEXT("DisplayLock")); | ||
|
|
@@ -173,8 +207,8 @@ BOOL createApplicationSettings(const wchar_t *appPath, APPLICATION_SETTINGS *app | |
| if (basename == appPath) | ||
| return FALSE; | ||
|
|
||
| wcscpy(application->application_path, appPath); | ||
| wcscpy(application->application_name, basename); | ||
| wcscpy_s(application->application_path, MAX_PATH, appPath); | ||
| wcscpy_s(application->application_name, MAX_PATH, basename); | ||
| application->borderless = FALSE; | ||
| application->fullscreen = FALSE; | ||
| application->enabled = TRUE; | ||
|
|
@@ -185,7 +219,7 @@ BOOL createApplicationSettings(const wchar_t *appPath, APPLICATION_SETTINGS *app | |
| BOOL startApplicationThread(HANDLE *thread, int (*callback)(void *parameters), void *args) | ||
| { | ||
| // TODO: check better error checking | ||
| *thread = (HANDLE)_beginthreadex(NULL, 0, callback, args, 0, NULL); | ||
| *thread = (HANDLE)(uintptr_t)_beginthreadex(NULL, 0, callback, args, 0, NULL); | ||
|
||
|
|
||
| if (*thread == NULL) | ||
| return FALSE; | ||
|
|
@@ -194,15 +228,15 @@ BOOL startApplicationThread(HANDLE *thread, int (*callback)(void *parameters), v | |
| } | ||
|
|
||
| // safely closes the thread | ||
| void closeApplicationThread(HANDLE thread, BOOL *status) | ||
| void closeApplicationThread(HANDLE *thread, volatile BOOL *status) | ||
| { | ||
| // check to see if thread is running | ||
| if (thread != NULL) | ||
| if (*thread != NULL) | ||
| { | ||
| *status = FALSE; | ||
| WaitForSingleObject(thread, INFINITE); | ||
| CloseHandle(thread); | ||
| thread = NULL; | ||
| WaitForSingleObject(*thread, INFINITE); | ||
| CloseHandle(*thread); | ||
| *thread = NULL; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -251,62 +285,61 @@ int CALLBACK cursorLockApplications(void *parameters) | |
|
|
||
| while (*(args->clipRunning)) | ||
| { | ||
| HANDLE mutex = CreateMutex(NULL, FALSE, APPLICATION_MUTEX_NAME); | ||
| WaitForSingleObject(mutex, INFINITE); | ||
| WaitForSingleObject(args->mutex, INFINITE); | ||
|
||
|
|
||
| for (int i = 0; i < args->applicationList->count; i++) | ||
| { | ||
| APPLICATION_SETTINGS application = args->applicationList->applications[i]; | ||
|
|
||
| if (application.enabled) | ||
| { | ||
| EnumWindowsProcPIDArgs args; | ||
| args.pid = getPidFromName(application.application_name); | ||
| args.hwnd = NULL; | ||
| EnumWindowsProcPIDArgs enumArgs; | ||
| enumArgs.pid = getPidFromName(application.application_name); | ||
| enumArgs.hwnd = NULL; | ||
|
|
||
| if (args.pid != 0) | ||
| EnumWindows(EnumWindowsProcPID, (LPARAM)&args); | ||
| if (enumArgs.pid != 0) | ||
| EnumWindows(EnumWindowsProcPID, (LPARAM)&enumArgs); | ||
|
|
||
| if (args.hwnd != NULL) | ||
| if (enumArgs.hwnd != NULL) | ||
| { | ||
| RECT rect; | ||
| GetWindowRect(args.hwnd, &rect); | ||
| GetWindowRect(enumArgs.hwnd, &rect); | ||
|
|
||
| if (application.borderless) | ||
| { | ||
| const long long borderlessStyle = GetWindowLongPtr(args.hwnd, GWL_STYLE); | ||
| const long long borderlessStyleEx = GetWindowLongPtr(args.hwnd, GWL_EXSTYLE); | ||
| const long long borderlessStyle = GetWindowLongPtr(enumArgs.hwnd, GWL_STYLE); | ||
| const long long borderlessStyleEx = GetWindowLongPtr(enumArgs.hwnd, GWL_EXSTYLE); | ||
|
|
||
| const long long mask = WS_OVERLAPPED | WS_THICKFRAME | WS_SYSMENU | WS_CAPTION; | ||
| const long long exMask = WS_EX_WINDOWEDGE; | ||
|
|
||
| if ((borderlessStyle & mask) != 0) | ||
| SetWindowLongPtr(args.hwnd, GWL_STYLE, borderlessStyle & ~mask); | ||
| SetWindowLongPtr(enumArgs.hwnd, GWL_STYLE, borderlessStyle & ~mask); | ||
|
|
||
| if ((borderlessStyleEx & exMask) != 0) | ||
| SetWindowLongPtr(args.hwnd, GWL_EXSTYLE, borderlessStyleEx & ~exMask); | ||
| SetWindowLongPtr(enumArgs.hwnd, GWL_EXSTYLE, borderlessStyleEx & ~exMask); | ||
| } | ||
| else if (application.fullscreen) | ||
| { | ||
| RECT rect = {0}; | ||
| GetClientRect(args.hwnd, &rect); | ||
| ClientToScreen(args.hwnd, (LPPOINT)&rect.left); | ||
| ClientToScreen(args.hwnd, (LPPOINT)&rect.right); | ||
| RECT fsRect = {0}; | ||
| GetClientRect(enumArgs.hwnd, &fsRect); | ||
| ClientToScreen(enumArgs.hwnd, (LPPOINT)&fsRect.left); | ||
| ClientToScreen(enumArgs.hwnd, (LPPOINT)&fsRect.right); | ||
|
|
||
| if (rect.left != 0 || rect.top != 0 || rect.right != GetSystemMetrics(SM_CXSCREEN) || rect.bottom != GetSystemMetrics(SM_CYSCREEN)) | ||
| SetWindowPos(args.hwnd, NULL, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), 0); | ||
| if (fsRect.left != 0 || fsRect.top != 0 || fsRect.right != GetSystemMetrics(SM_CXSCREEN) || fsRect.bottom != GetSystemMetrics(SM_CYSCREEN)) | ||
| SetWindowPos(enumArgs.hwnd, NULL, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), 0); | ||
| } | ||
|
|
||
| // TODO: lock cursor | ||
| HWND active = GetForegroundWindow(); | ||
|
|
||
| if (args.hwnd == active) | ||
| if (enumArgs.hwnd == active) | ||
| { | ||
| GetCursorPos(&cursorPosition); | ||
| RECT windowRect = {0}; | ||
| GetClientRect(args.hwnd, &windowRect); | ||
| ClientToScreen(args.hwnd, (LPPOINT)&windowRect.left); | ||
| ClientToScreen(args.hwnd, (LPPOINT)&windowRect.right); | ||
| GetClientRect(enumArgs.hwnd, &windowRect); | ||
| ClientToScreen(enumArgs.hwnd, (LPPOINT)&windowRect.left); | ||
| ClientToScreen(enumArgs.hwnd, (LPPOINT)&windowRect.right); | ||
|
|
||
| if ((cursorPosition.y <= windowRect.bottom && cursorPosition.y >= windowRect.top) && (cursorPosition.x >= windowRect.left && cursorPosition.x <= windowRect.right)) | ||
| ClipCursor(&windowRect); | ||
|
|
@@ -315,11 +348,10 @@ int CALLBACK cursorLockApplications(void *parameters) | |
| } | ||
| } | ||
| } | ||
| Sleep(1); | ||
| } | ||
|
|
||
| ReleaseMutex(mutex); | ||
| CloseHandle(mutex); | ||
| ReleaseMutex(args->mutex); | ||
| Sleep(1); | ||
| } | ||
|
|
||
| ClipCursor(NULL); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constant MAX_CLASS_NAME is defined but never used in the codebase. Consider removing it to keep the code clean, or document where it's intended to be used if it's for future functionality.