I have this message loop in my program:
while (true) {
if (PeekMessage(&msg, window, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
You're calling PeekMessage(&msg, window, ...). If window isn't NULL, you'll never get WM_QUIT, because WM_QUIT is not associated with a window.
Instead, just call PeekMessage/GetMessage with a NULL HWND. DispatchMessage will send it to the right WndProc as necessary. (In general, making GetMessage/PeekMessage filter by HWND is a bad idea.)