Skip to content

Commit 314aa84

Browse files
authored
Merge pull request #129 from Stateford/bug-fixes
Refactor thread handling and improve memory management; add configura…
2 parents faaa31f + dc06e6a commit 314aa84

File tree

13 files changed

+229
-106
lines changed

13 files changed

+229
-106
lines changed

.claude/settings.local.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(wc:*)",
5+
"Bash(cmake:*)",
6+
"Bash(ctest:*)",
7+
"Bash(\"build/tests/Release/display-lock-unittests.exe\")",
8+
"Bash(\"display-lock-unittests.exe\")",
9+
"Bash(\"./display-lock-unittests.exe\")",
10+
"Bash(xxd:*)"
11+
]
12+
}
13+
}

CLAUDE.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
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.
8+
9+
## Build Commands
10+
11+
```bash
12+
# Configure and build (Release)
13+
cmake -B build -DCMAKE_BUILD_TYPE=Release
14+
cmake --build build --config Release
15+
16+
# Configure and build (Debug)
17+
cmake -B build -DCMAKE_BUILD_TYPE=Debug
18+
cmake --build build --config Debug
19+
20+
# Run tests (requires Conan for GTest dependency)
21+
pip install conan==1.59.0
22+
ctest --test-dir build -C Release
23+
```
24+
25+
The build requires Visual Studio 2022 (MSVC) on Windows. Conan package manager is used to fetch Google Test for unit testing.
26+
27+
## Architecture
28+
29+
**Component Library (`src/components/`)**: Core functionality as a static library (`display_lock_components`)
30+
- `win.c` - Cursor locking logic and window manipulation
31+
- `settings.c` - Binary config file persistence (`.DLOCK` format in `%APPDATA%/DisplayLock/`)
32+
- `applications.c` - Application whitelist management and monitoring thread
33+
- `notify.c` - System tray icon and notifications
34+
- `update.c` - Version checking via HTTP
35+
- `menu.c` - Menu UI handling
36+
37+
**Main Application (`src/`)**: Win32 dialog-based UI
38+
- `main.c` - Entry point, window class registration, message loop
39+
- `ui.c` - Tab view rendering (Window Select, Settings, Applications)
40+
- `procedures/` - Dialog procedure handlers
41+
42+
**Key Data Structures (`include/common.h`)**:
43+
- `SETTINGS` - User preferences (minimize on start, fullscreen, borderless, etc.)
44+
- `WINDOWLIST` - Array of up to 35 windows for selection
45+
- `APPLICATION_LIST` - Whitelisted applications for automatic cursor locking
46+
47+
**Threading Model**: Cursor locking and application monitoring run in dedicated threads, synchronized via mutex (`DLockApplicationMutex`).
48+
49+
## Testing
50+
51+
Tests use Google Test (fetched via Conan). Test files are in `tests/` with test resources copied to the build directory.
52+
53+
```bash
54+
# Run a single test
55+
ctest --test-dir build -C Release -R SettingsTest
56+
```
57+
58+
## Contributing Guidelines
59+
60+
- Work on `develop` branch for features, `master` for hotfixes
61+
- Avoid external dependencies (keep it lightweight)
62+
- Do not manually change version numbers or build scripts
63+
- Update CHANGELOG.md with changes
64+
- All builds and tests must pass before merge

include/applications.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ BOOL createApplicationDirectory(wchar_t *outPath);
2020
BOOL createApplicationSettings(const wchar_t *appPath, APPLICATION_SETTINGS *application);
2121

2222
BOOL startApplicationThread(HANDLE *thread, int (*callback)(void *parameters), void *args);
23-
void closeApplicationThread(HANDLE thread, BOOL *status);
23+
void closeApplicationThread(HANDLE *thread, volatile BOOL *status);
2424
DWORD getPidFromName(const wchar_t *name);
2525

2626
BOOL CALLBACK EnumWindowsProcPID(HWND hwnd, LPARAM lParam);

include/common.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
#define APPLICATION_VIEW 2
2626

2727
#define APPLICATION_MUTEX_NAME L"DLockApplicationMutex"
28+
29+
// Magic number constants
30+
#define MAX_WINDOW_COUNT 35
31+
#define MAX_TITLE_LENGTH 500
32+
#define MAX_CLASS_NAME 500
33+
2834
// custom messages
2935

3036
#define NOTIFY_MSG (WM_USER + 0x1)
@@ -86,7 +92,7 @@ struct MAIN_WINDOW_CONTROLS
8692

8793
struct WINDOW
8894
{
89-
char title[500];
95+
char title[MAX_TITLE_LENGTH];
9096
int x;
9197
int y;
9298
RECT size;
@@ -97,7 +103,7 @@ struct WINDOW
97103
struct WINDOWLIST
98104
{
99105
int count;
100-
WINDOW windows[35];
106+
WINDOW windows[MAX_WINDOW_COUNT];
101107
};
102108

103109
struct WINDOW_VIEW_CONTROLS
@@ -125,7 +131,7 @@ struct SETTINGS_VIEW_CONTROLS
125131

126132
struct MENU
127133
{
128-
void (*closeThread)(HANDLE thread, BOOL *status);
134+
void (*closeThread)(HANDLE *thread, volatile BOOL *status);
129135
void (*updateComboBox)(HWND control, WINDOWLIST *windows, void (*callback)(WINDOWLIST *));
130136
BOOL (*startThread)
131137
(HANDLE *thread, int (*callback)(void *parameters), void *args);
@@ -145,7 +151,7 @@ struct SETTINGS
145151
struct ARGS
146152
{
147153
SETTINGS *settings;
148-
BOOL *clipRunning;
154+
volatile BOOL *clipRunning;
149155
WINDOW selectedWindow;
150156
HWND hWnd;
151157
MAIN_WINDOW_CONTROLS controls;
@@ -154,7 +160,8 @@ struct ARGS
154160
struct APPLICATION_ARGS
155161
{
156162
APPLICATION_LIST *applicationList;
157-
BOOL *clipRunning;
163+
volatile BOOL *clipRunning;
164+
HANDLE mutex;
158165
};
159166

160167
union VERSION

include/menu.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@
2323
void initMenuObj(MENU *menu);
2424
void updateComboBox(HWND control, WINDOWLIST *windows, void (*callback)(WINDOWLIST *));
2525
BOOL startThread(HANDLE *thread, int (*callback)(void *parameters), void *args);
26-
void closeThread(HANDLE thread, BOOL *status);
26+
void closeThread(HANDLE *thread, volatile BOOL *status);

src/components/applications.c

Lines changed: 75 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,42 @@ BOOL readApplicationList(APPLICATION_LIST *applicationList, const wchar_t *path)
3030
return FALSE;
3131
}
3232

33-
fread(&applicationList->count, sizeof(int), 1, file);
34-
applicationList->applications = (APPLICATION_SETTINGS *)malloc(sizeof(APPLICATION_SETTINGS) * applicationList->count);
35-
for (int i = 0; i < applicationList->count; i++)
33+
if (fread(&applicationList->count, sizeof(int), 1, file) != 1)
3634
{
37-
fread(&applicationList->applications[i], sizeof(APPLICATION_SETTINGS), 1, file);
35+
applicationList->count = 0;
36+
applicationList->applications = NULL;
37+
fclose(file);
38+
return FALSE;
39+
}
40+
41+
if (applicationList->count > 0)
42+
{
43+
applicationList->applications = (APPLICATION_SETTINGS *)malloc(sizeof(APPLICATION_SETTINGS) * applicationList->count);
44+
if (applicationList->applications == NULL)
45+
{
46+
applicationList->count = 0;
47+
fclose(file);
48+
return FALSE;
49+
}
50+
51+
for (int i = 0; i < applicationList->count; i++)
52+
{
53+
if (fread(&applicationList->applications[i], sizeof(APPLICATION_SETTINGS), 1, file) != 1)
54+
{
55+
free(applicationList->applications);
56+
applicationList->applications = NULL;
57+
applicationList->count = 0;
58+
fclose(file);
59+
return FALSE;
60+
}
61+
}
62+
}
63+
else
64+
{
65+
applicationList->applications = NULL;
3866
}
3967

4068
fclose(file);
41-
_fcloseall();
4269

4370
return TRUE;
4471
}
@@ -64,7 +91,6 @@ BOOL writeApplicationList(APPLICATION_LIST *applicationList, const wchar_t *path
6491
}
6592

6693
fclose(file);
67-
_fcloseall();
6894

6995
return TRUE;
7096
}
@@ -76,6 +102,8 @@ BOOL addApplication(APPLICATION_LIST *applicationList, APPLICATION_SETTINGS appl
76102
{
77103
applicationList->count = 1;
78104
applicationList->applications = (APPLICATION_SETTINGS *)malloc(sizeof(APPLICATION_SETTINGS));
105+
if (applicationList->applications == NULL)
106+
return FALSE;
79107
applicationList->applications[0] = application;
80108
}
81109
else
@@ -87,8 +115,11 @@ BOOL addApplication(APPLICATION_LIST *applicationList, APPLICATION_SETTINGS appl
87115
return FALSE;
88116
}
89117

118+
APPLICATION_SETTINGS *temp = (APPLICATION_SETTINGS *)realloc(applicationList->applications, sizeof(APPLICATION_SETTINGS) * (applicationList->count + 1));
119+
if (temp == NULL)
120+
return FALSE;
121+
applicationList->applications = temp;
90122
applicationList->count++;
91-
applicationList->applications = (APPLICATION_SETTINGS *)realloc(applicationList->applications, sizeof(APPLICATION_SETTINGS) * applicationList->count);
92123
applicationList->applications[applicationList->count - 1] = application;
93124
}
94125

@@ -119,7 +150,10 @@ BOOL removeApplication(APPLICATION_LIST *applicationList, int index)
119150
}
120151

121152
applicationList->count--;
122-
applicationList->applications = (APPLICATION_SETTINGS *)realloc(applicationList->applications, sizeof(APPLICATION_SETTINGS) * applicationList->count);
153+
APPLICATION_SETTINGS *temp = (APPLICATION_SETTINGS *)realloc(applicationList->applications, sizeof(APPLICATION_SETTINGS) * applicationList->count);
154+
if (temp != NULL)
155+
applicationList->applications = temp;
156+
// If realloc fails, keep the old pointer (memory is still valid, just larger than needed)
123157
}
124158

125159
return TRUE;
@@ -153,7 +187,7 @@ BOOL createApplicationDirectory(wchar_t *outPath)
153187
if (!SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_RoamingAppData, 0, NULL, &path)))
154188
return FALSE;
155189

156-
wcscpy(outPath, path);
190+
wcscpy_s(outPath, MAX_PATH, path);
157191

158192
// create directory
159193
PathAppend(outPath, TEXT("DisplayLock"));
@@ -173,8 +207,8 @@ BOOL createApplicationSettings(const wchar_t *appPath, APPLICATION_SETTINGS *app
173207
if (basename == appPath)
174208
return FALSE;
175209

176-
wcscpy(application->application_path, appPath);
177-
wcscpy(application->application_name, basename);
210+
wcscpy_s(application->application_path, MAX_PATH, appPath);
211+
wcscpy_s(application->application_name, MAX_PATH, basename);
178212
application->borderless = FALSE;
179213
application->fullscreen = FALSE;
180214
application->enabled = TRUE;
@@ -185,7 +219,7 @@ BOOL createApplicationSettings(const wchar_t *appPath, APPLICATION_SETTINGS *app
185219
BOOL startApplicationThread(HANDLE *thread, int (*callback)(void *parameters), void *args)
186220
{
187221
// TODO: check better error checking
188-
*thread = (HANDLE)_beginthreadex(NULL, 0, callback, args, 0, NULL);
222+
*thread = (HANDLE)(uintptr_t)_beginthreadex(NULL, 0, callback, args, 0, NULL);
189223

190224
if (*thread == NULL)
191225
return FALSE;
@@ -194,15 +228,15 @@ BOOL startApplicationThread(HANDLE *thread, int (*callback)(void *parameters), v
194228
}
195229

196230
// safely closes the thread
197-
void closeApplicationThread(HANDLE thread, BOOL *status)
231+
void closeApplicationThread(HANDLE *thread, volatile BOOL *status)
198232
{
199233
// check to see if thread is running
200-
if (thread != NULL)
234+
if (*thread != NULL)
201235
{
202236
*status = FALSE;
203-
WaitForSingleObject(thread, INFINITE);
204-
CloseHandle(thread);
205-
thread = NULL;
237+
WaitForSingleObject(*thread, INFINITE);
238+
CloseHandle(*thread);
239+
*thread = NULL;
206240
}
207241
}
208242

@@ -251,62 +285,61 @@ int CALLBACK cursorLockApplications(void *parameters)
251285

252286
while (*(args->clipRunning))
253287
{
254-
HANDLE mutex = CreateMutex(NULL, FALSE, APPLICATION_MUTEX_NAME);
255-
WaitForSingleObject(mutex, INFINITE);
288+
WaitForSingleObject(args->mutex, INFINITE);
256289

257290
for (int i = 0; i < args->applicationList->count; i++)
258291
{
259292
APPLICATION_SETTINGS application = args->applicationList->applications[i];
260293

261294
if (application.enabled)
262295
{
263-
EnumWindowsProcPIDArgs args;
264-
args.pid = getPidFromName(application.application_name);
265-
args.hwnd = NULL;
296+
EnumWindowsProcPIDArgs enumArgs;
297+
enumArgs.pid = getPidFromName(application.application_name);
298+
enumArgs.hwnd = NULL;
266299

267-
if (args.pid != 0)
268-
EnumWindows(EnumWindowsProcPID, (LPARAM)&args);
300+
if (enumArgs.pid != 0)
301+
EnumWindows(EnumWindowsProcPID, (LPARAM)&enumArgs);
269302

270-
if (args.hwnd != NULL)
303+
if (enumArgs.hwnd != NULL)
271304
{
272305
RECT rect;
273-
GetWindowRect(args.hwnd, &rect);
306+
GetWindowRect(enumArgs.hwnd, &rect);
274307

275308
if (application.borderless)
276309
{
277-
const long long borderlessStyle = GetWindowLongPtr(args.hwnd, GWL_STYLE);
278-
const long long borderlessStyleEx = GetWindowLongPtr(args.hwnd, GWL_EXSTYLE);
310+
const long long borderlessStyle = GetWindowLongPtr(enumArgs.hwnd, GWL_STYLE);
311+
const long long borderlessStyleEx = GetWindowLongPtr(enumArgs.hwnd, GWL_EXSTYLE);
279312

280313
const long long mask = WS_OVERLAPPED | WS_THICKFRAME | WS_SYSMENU | WS_CAPTION;
281314
const long long exMask = WS_EX_WINDOWEDGE;
282315

283316
if ((borderlessStyle & mask) != 0)
284-
SetWindowLongPtr(args.hwnd, GWL_STYLE, borderlessStyle & ~mask);
317+
SetWindowLongPtr(enumArgs.hwnd, GWL_STYLE, borderlessStyle & ~mask);
285318

286319
if ((borderlessStyleEx & exMask) != 0)
287-
SetWindowLongPtr(args.hwnd, GWL_EXSTYLE, borderlessStyleEx & ~exMask);
320+
SetWindowLongPtr(enumArgs.hwnd, GWL_EXSTYLE, borderlessStyleEx & ~exMask);
288321
}
289322
else if (application.fullscreen)
290323
{
291-
RECT rect = {0};
292-
GetClientRect(args.hwnd, &rect);
293-
ClientToScreen(args.hwnd, (LPPOINT)&rect.left);
294-
ClientToScreen(args.hwnd, (LPPOINT)&rect.right);
324+
RECT fsRect = {0};
325+
GetClientRect(enumArgs.hwnd, &fsRect);
326+
ClientToScreen(enumArgs.hwnd, (LPPOINT)&fsRect.left);
327+
ClientToScreen(enumArgs.hwnd, (LPPOINT)&fsRect.right);
295328

296-
if (rect.left != 0 || rect.top != 0 || rect.right != GetSystemMetrics(SM_CXSCREEN) || rect.bottom != GetSystemMetrics(SM_CYSCREEN))
297-
SetWindowPos(args.hwnd, NULL, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), 0);
329+
if (fsRect.left != 0 || fsRect.top != 0 || fsRect.right != GetSystemMetrics(SM_CXSCREEN) || fsRect.bottom != GetSystemMetrics(SM_CYSCREEN))
330+
SetWindowPos(enumArgs.hwnd, NULL, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), 0);
298331
}
299332

300333
// TODO: lock cursor
301334
HWND active = GetForegroundWindow();
302335

303-
if (args.hwnd == active)
336+
if (enumArgs.hwnd == active)
304337
{
305338
GetCursorPos(&cursorPosition);
306339
RECT windowRect = {0};
307-
GetClientRect(args.hwnd, &windowRect);
308-
ClientToScreen(args.hwnd, (LPPOINT)&windowRect.left);
309-
ClientToScreen(args.hwnd, (LPPOINT)&windowRect.right);
340+
GetClientRect(enumArgs.hwnd, &windowRect);
341+
ClientToScreen(enumArgs.hwnd, (LPPOINT)&windowRect.left);
342+
ClientToScreen(enumArgs.hwnd, (LPPOINT)&windowRect.right);
310343

311344
if ((cursorPosition.y <= windowRect.bottom && cursorPosition.y >= windowRect.top) && (cursorPosition.x >= windowRect.left && cursorPosition.x <= windowRect.right))
312345
ClipCursor(&windowRect);
@@ -315,11 +348,10 @@ int CALLBACK cursorLockApplications(void *parameters)
315348
}
316349
}
317350
}
318-
Sleep(1);
319351
}
320352

321-
ReleaseMutex(mutex);
322-
CloseHandle(mutex);
353+
ReleaseMutex(args->mutex);
354+
Sleep(1);
323355
}
324356

325357
ClipCursor(NULL);

0 commit comments

Comments
 (0)