SetWindowPos() cross-process with multiple monitors and different display scalings - c++

I have already asked a similar question here, but now the problems seems to be a bit different, so I figured I'd create a new question for it.
I am using SetWindowPos() to move/resize windows from another process. This works fine for as long as all screens are using the same display scaling, but under the following scenario it doesn't work as expected:
Primary Screen is at (0,0) with 3440x1440 and 150% scaling.
Secondary Screen is at (3440, 0) with 900x1440 and 100% scaling.
My application is PROCESS_PER_MONITOR_DPI_AWARE_V2 and the target application is PROCESS_DPI_UNAWARE (gets scaled by Windows).
Now if I move a window so that the upper left is on the primary screen and the center is still on the secondary screen, for example to (3400, 0).
SetWindowPos(hwnd, HWND_BOTTOM, 3300, 0, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
Then this is what happens:
The window is scaled according to the second screen's 100% display scaling.
The window is not moved to (3300, 0). Instead, the coordinates it receives in the WM_WINDOWPOSCHANGING message are (2200, 0). The coordinates seem to get scaled down to logical coordinates.
I am therefore unable to move a window to that location. I tried to use PhysicalToLogicalPointForPerMonitorDPI() on the coordinates I pass to SetWindowPos(), but without success (it doesn't even change the coordinates).
Now it seems like I just can't move the window to any position such that the upper left is on the primary screen, but the window's center is still on the secondary screen, because Windows will scale the coordinates down and if I manually scale them up, then I would already position the window on the second screen and Windows does not apply the scaling anymore. Even if I was able to get around that, manually calculating the scaling is possible with a two-screen setup, but quickly becomes too complex with more screens. So, how do I get this to work?
EDIT1: I tried to use SetThreadDpiAwarenessContext() as suggested, but it still doesn't work. Now, when I move the window to (3000,0) it is moved to (4500,0) instead. It seems like I somehow need to scale the coordinates I pass to SetWindowPos(), but I have no idea how.
m_previousContext = SetThreadDpiAwarenessContext(GetWindowDpiAwarenessContext(hwnd));
if(m_previousContext == NULL)
Log::out<Log::Level::Error>("Failed to set thread dpi awareness context.");
SetWindowPos(hwnd, HWND_BOTTOM, 3000, 0, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
Also, isn't this really inefficient if I regularly resize and move windows around?
EDIT2: I've attach a link to a minimal working binary. You can download it from Google Drive here. It requires Windows 10, version 1607 to run. When I run it on the setup mentioned above with SetWindowPos.exe 002108B6 3000 0 then the window is moved to (4500,0) instead.
Below is the code:
int main(int argc, char** argv)
{
if(argc < 4)
{
std::cerr << "Usage: SetWindowPos.exe <HWND> <x> <y>" << std::endl;
return 1;
}
HWND hwnd;
int x, y;
try
{
hwnd = (HWND)hexStrToInt(argv[1]); // I've omitted the implementation of hexStrToInt
x = atoi(argv[2]);
y = atoi(argv[3]);
}
catch(...)
{
std::cerr << "Invalid arguments." << std::endl;
return 1;
}
if(IsWindow(hwnd) == FALSE)
{
std::cerr << "Invalid window handle " << argv[1] << "." << std::endl;
return 1;
}
auto context = SetThreadDpiAwarenessContext(GetWindowDpiAwarenessContext(hwnd));
SetWindowPos(hwnd, HWND_BOTTOM, x, y, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
SetThreadDpiAwarenessContext(context);
return 0;
}

Use SetThreadDpiAwarenessContext to temporarily set your thread awareness mode to the same value as the target application.
SetThreadDpiAwarenessContext
High-DPI Scaling Improvements for Desktop Applications in the Windows 10 Creators Update (1703)
High DPI Scaling Improvements for Desktop Applications and “Mixed Mode” DPI Scaling in the Windows 10 Anniversary Update (1607)

Related

Mouse input not being released from other process's window

I am writing a C++ Windows program that displays game stats/friends info over games using a Win32 window and a DirectX11 renderer. (that renders a UI that is controlled with the mouse and keyboard)
The window is overlaid on top of the game’s window and has the flags WS_EX_TRANSPARENT and WS_POPUP set.
When the window is activated, I set WS_EX_LAYERED to capture inputs.
The created window is positioned on top of the target window if GetWindow(target_, GW_HWNDPREV) is different from the handle of the created window.
It is placed on top of it by calling SetWindowPos with SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_ASYNCWINDOWPOS.
I’ve double-checked that the flags are set correctly and that the functions are being called.
I also tried using ShowWindow with the SW_SHOW flag, but the result remained unchanged.
I’m currently running my tests on Portal 2, but ideally, I would want this to work on the majority of games. (OS used is Windows 11 22H2)
To activate the window and release the mouse capture from the game, I am calling SetForegroundWindow, SetActiveWindow, and SetFocus, all with the HWND of my window.
This approach works correctly when I run the program from Visual Studio, but when I run the compiled executable, the mouse remains locked in the game.
Both builds were tested in debug and release mode, and I really can't figure out why this is happening.
LRESULT Renderer::WndProc(...) {
switch (message) {
case WM_SIZE:
// resize buffers and recreate render target view
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(handle, message, w_param, l_param);
}
bool Window::Create(...) {
// ...
hwnd_ = CreateWindowEx(
wndclass,
class_name_.c_str(),
title_.c_str(),
WS_POPUP,
0, 0, 10, 10,
nullptr,
nullptr,
nullptr,
nullptr
);
SetLayeredWindowAttributes(hwnd_, 0, 255, LWA_ALPHA);
UpdateWindow(hwnd_);
constexpr MARGINS margin = {-1, -1, -1, -1};
const auto result = DwmExtendFrameIntoClientArea(hwnd_, &margin);
// ...
}
void Window::Activate() {
// Remove the WS_EX_LAYERED attribute.
SetClickThrough(false);
SetForegroundWindow(hwnd_);
SetActiveWindow(hwnd_);
SetFocus(hwnd_);
}
// ----------------------
// Sample main routine pseudocode:
// ----------------------
renderer->window.Create(...);
while (renderer->is_running()) {
renderer->BeginFrame();
// Position the window on top of the game found.
renderer->window().FollowTarget();
// Toggle the visibility using the F2 key.
// If transitioning from hidden to visible, call the window
// activation routine.
if (utils::KeyPressed(VK_F2)) {
if (ui->is_visible()) {
// .. window deactivation not included
ui->set_visible(false);
}
else {
renderer->window().Activate();
ui->set_visible(true);
}
}
ui->Draw(renderer);
renderer->Present();
}
I considered using a low-level keyboard/mouse hook to capture inputs, or offscreen rendering and presenting it in the game using a DirectX hook, but I’d rather avoid it as it would require many games to manually whitelist it.
Is there something else I’m missing or a different approach I should be taking?

Positioning Windows under WorkerW

I am trying to achieve something and having an unexpected behaviour which after a day of research, I suspect it's related to the difference between client coordinates and screen coordinates. But I come from a web background and have very limited understanding and experience in C++.
So below is the code I am trying to have to work:
bool wallpaper::attach(Napi::Buffer<void *> handle, int x, int y) {
HWND target = static_cast<HWND>(*reinterpret_cast<void **>(handle.Data()));
HWND progman = FindWindowA("Progman", NULL);
LRESULT result = SendMessageTimeoutA(
progman,
0x052C,
NULL,
NULL,
SMTO_NORMAL,
1000,
NULL);
EnumWindows(&FindWorkerW, reinterpret_cast<LPARAM>(&workerw));
SetWindowPos(
target,
workerw,
x,
y,
NULL,
NULL,
SWP_NOSIZE
);
SetParent(target, workerw);
// ShowWindow(target, SW_SHOWMAXIMIZED);
return true;
}
I have a setup of three monitors each with a resolution of 1980x1080 next to each other horizontally. The setup is in the below order:
Display 3 (left)
Display 1 (middle)
Display 2 ( right)
When I fetch information from the node module system information, I get the coordinates for each display as per below:
Display 1 0, 0
Display 2 1920, 0
Display 3 -1920, 0
The problem I have is that when using these same values through x and y, they do set the window fine on each monitor. But when the code goes through the line SetParent, the coordinate becomes irrelevant and I get the following result:
Window for Display 1 shows on Display 3
Window for Display 2 shows on Display 1
Window for Display 3 doesn't show
If though I hardcode the -1920 value to 3840, it does show the Window for Display 3 on Display 2.
I've read around and saw the following methods, ClientToScreen, ScreenToClient, MapWindowPoints. But I am unable to understand exactly how to adapt these for my use case.
If any anyone would be kind to hint me in the right direction (I read about the difference between client and screen coordinates but I still didn't understand it and values returned by ClientToScreen method and such are in LONG format).
After spending a day reading around and trying to make sense of C++ concepts and the functions available from Windows, the code below worked for me
POINT pt = {};
pt.x = 0;
pt.y = 0;
SetParent(hwnd, workerw);
ScreenToClient(workerw, &pt);
SetWindowPos(hwnd, HWND_TOP, pt.x, pt.y, NULL, NULL, SWP_NOSIZE);
I did hardcode each coordinate which are being translated accordingly and set on the relevant display.

Set the console window size to match screen buffer on Windows

Playing around with the console functions to control the layout of a console program, and I cannot change its size.
Currently, what I can do is disable resizing, remove buttons and change the buffer size, but if I try to resize the window itself, all attempts fail; albeit, some functions do resize the window, but in pixels, so scale is drastically off.
What I've done so far (not a C++ person, just fiddling around, go easy on syntax-isms):
#include <iostream>
#include <Windows.h>
using namespace std;
COORD conBufferSize = { 150, 40 };
SMALL_RECT conScreen = { 0, 0, 150, 40 };
CONSOLE_SCREEN_BUFFER_INFOEX csbie;
int main()
{
// console opened for application
HWND hwConsole = GetConsoleWindow();
// hide it
ShowWindow(hwConsole, SW_HIDE);
// get the style for it
DWORD style = GetWindowLong(hwConsole, GWL_STYLE);
// disable maximizing and minimizing and resizing
style &= ~(WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SIZEBOX);
SetWindowLong(hwConsole, GWL_STYLE, style);
HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
SetConsoleScreenBufferSize(hConsole, conBufferSize);
// this does nothing to the window itself as best I can tell
// if by "window" it means what portion of the display window you view "into"
// commented out here for functionality
// SetConsoleWindowInfo(hConsole, TRUE, &conScreen);
SetConsoleActiveScreenBuffer(hConsole);
// this sequence works, but seems by accident
// surely there is another method?
csbie.cbSize = sizeof(csbie);
GetConsoleScreenBufferInfoEx(hConsole, &csbie);
csbie.srWindow = conScreen;
SetConsoleScreenBufferInfoEx(hConsole, &csbie);
// required to update styles
// using the cx/cy parameters sets size in pixels
// that is much smaller than buffer size which accounts for font size
// therefore this "doesn't" work
SetWindowPos(hwConsole, HWND_TOP, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE| SWP_SHOWWINDOW);
// to look at the console until close
while (1) {
}
return 0;
}
Now from what I can reason, if I have a screen buffer that is 100 columns by 40 rows, that doesn't translate directly to the size of the window housing the buffer. So my next thought is that I need to determine how many pixels the current console font is using, then multiply the buffer dimensions by that to determine the pixel size and use SetWindowPos or the SetConsoleScreenBufferInfoEx methods.
One thing I am unsure about is why the srWindow attribute is able to modify the display window with a similar description to that of SetConsoleWindowInfo and yet that produces no discernable change.
One thing I am unsure about is why the srWindow attribute is able to
modify the display window with a similar description to that of
SetConsoleWindowInfo and yet that produces no discernable change.
According to the doc,
The function fails if the specified window rectangle extends beyond
the boundaries of the console screen buffer. This means that the Top
and Left members of the lpConsoleWindow rectangle (or the calculated
top and left coordinates, if bAbsolute is FALSE) cannot be less than
zero. Similarly, the Bottom and Right members (or the calculated
bottom and right coordinates) cannot be greater than (screen buffer
height – 1) and (screen buffer width – 1), respectively. The function
also fails if the Right member (or calculated right coordinate) is
less than or equal to the Left member (or calculated left coordinate)
or if the Bottom member (or calculated bottom coordinate) is less than
or equal to the Top member (or calculated top coordinate).
Use GetConsoleScreenBufferInfoEx to get the size of the screen buffer (e.p: 120,30). As long as the SMALL_RECT set is less than 120, 30, you will see the screen buffer size change after calling SetConsoleActiveScreenBuffer.
SMALL_RECT conScreen = { 0, 0, 50, 20 };
...
SetConsoleWindowInfo(hConsole, TRUE, &conScreen);
SetConsoleActiveScreenBuffer(hConsole);
Please always check the return value of each api when debugging.

Windows Toolbar - Controlling button size and padding

I'm trying to understand the behaviour of a Windows toolbar - in particular how the following values interact:
the size of the bitmap image used
the effective size of a toolbar button
the padding between the image and the button edge
the height of the toolbar
Text displayed by a button is not relevant in my case.
What I actually want to do is provide an option for the user so he can choose from several toolbar button sizes (that will display bitmaps of say, 16x16, 32x32, or 48x48 pixels) and redisplay the toolbar accordingly after the option value changes. This is implemented by destroying the toolbar's image lists and rebuilding them with the appropriate bitmaps. The problem I currently have is that when switching from size 16 to 48 and back to size 16, the toolbar looks slightly different than before.
This is what the toolbar looks like when the application starts (correct):
Once I switch to size 48 and back again, it looks like this (wrong):
All buttons are higher than before, and each dropdown button has additional space around its bitmap and its dropdown arrow.
(For testing purposes, the toolbar has been made high enough to accomodate all button sizes without requiring an increase in height. This is to rule out the possibility that the change in button size stems from a possible toolbar resize, necessitated by temporarily switching to size 48.)
It looks as if additional padding were being rendered between a button bitmap and the button edge - as if rebuilding the toolbar with larger bitmaps/buttons caused Windows to internally increase the padding (which would make sense), but not decrease it when I subsequently rebuild the toolbar with the smaller bitmaps/buttons. However, sending TB_GETPADDING always returns 0x00060007, which indicates that the standard (correct) padding for 16x16 bitmaps is in place.
In an attempt to solve the problem by setting padding myself, I set the TBSTYLE_AUTOSIZE style on all non-separator buttons (this is required in order to apply padding). With this style, without even calling TB_SETPADDING, after switching to size 48 and back again, the toolbar looks like this:
In this case, the button height is also wrong.
The question is: What is causing the buttons to be displayed differently after rebuilding the image lists?
Some aside notes:
When building the toolbar, I call TB_SETBITMAPSIZE, but neither TB_SETBUTTONSIZE nor TB_SETPADDING, because the bitmap size is all I have, and I assumed the button size would be derived correctly from that.
I'm aware I could simply build the entire toolbar window from scratch (not just the image lists), but would like to avoid that, so I can keep working with the same toolbar window handle.
I'm aware of the CCS_NORESIZE toolbar style (it's currently set) and the TB_AUTOSIZE message, but experiments with them have not led to any insights.
I can't say what is the problem(there is no code in the question) but it is most probable
that the solution of destroying the list of images causes this. You dont need to destroy the lists
but to remove the buttons and then add new ones. The bellow code works fine:
Create ToolBar:
if((toolBarHwnd = CreateWindowEx(
0,
TOOLBARCLASSNAME,,
NULL,
WS_VISIBLE | WS_CHILD | TBSTYLE_WRAPABLE,
0,
0, //820,
0,
0,
winHwnd, //main window
(HMENU)IDC_TOOLBAR,
hThisInstance,
NULL
)) == NULL){/*Error*/}
Create ImageList's for your images:
HIMAGELIST g_hImageListSmall = NULL, g_hImageListMedium = NULL, g_hImageListLarge = NULL;
int numButtons = 3
g_hImageListSmall = ImageList_Create(16, 16, // Dimensions of individual bitmaps.
ILC_COLOR16 | ILC_MASK, // Ensures transparent background.
numButtons, 0);
g_hImageListMedium = ImageList_Create(32, 32,
ILC_COLOR16 | ILC_MASK,
numButtons, 0);
g_hImageListLarge = ImageList_Create(48, 48,
ILC_COLOR16 | ILC_MASK,
numButtons, 0);
Add images to the lists:
HBITMAP hBitmapImageSmall = NULL, hBitmapImageMedium = NULL, hBitmapImageLarge = NULL;
hBitmapImageSmall = LoadImage(NULL, L"....YourBitmap.bmp", IMAGE_BITMAP, 16, 16, 0x10);
ImageList_Add(g_hImageListSmall , hBitmapImageSmall, NULL);
ImageList_Add(g_hImageListSmall , hBitmapImageSmall, NULL);
ImageList_Add(g_hImageListSmall , hBitmapImageSmall, NULL); //I am using the same image
hBitmapImageMedium = LoadImage(NULL, L"....YourBitmap.bmp", IMAGE_BITMAP, 32, 32, 0x10);
ImageList_Add(g_hImageListSmall , hBitmapImageMedium , NULL);
ImageList_Add(g_hImageListSmall , hBitmapImageMedium , NULL);
ImageList_Add(g_hImageListSmall , hBitmapImageMedium , NULL);
The same with the large one(48x48)
Add g_hImageListSmall to the ToolBar for start:
//Set the image list.
SendMessage(toolBarHwnd, TB_SETIMAGELIST, (WPARAM)0, (LPARAM)g_hImageListSmall);
// Initialize button info.
// IDM_NEW, IDM_OPEN, and IDM_SAVE are application-defined command constants.
TBBUTTON tbButtons[numButtons] =
{
{ 0, IDM_NEW, TBSTATE_ENABLED, BTNS_AUTOSIZE, {0}, 0, (INT_PTR)NULL },
{ 1, IDM_OPEN, TBSTATE_ENABLED, BTNS_AUTOSIZE, {0}, 0, (INT_PTR)NULL},
{ 2, IDM_SAVE, TBSTATE_ENABLED, BTNS_AUTOSIZE, {0}, 0, (INT_PTR)NULL}
};
// Add buttons.
SendMessage(toolBarHwnd, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);
SendMessage(toolBarHwnd, TB_ADDBUTTONS, (WPARAM)numButtons, (LPARAM)&tbButtons);
// Resize the toolbar
SendMessage(toolBarHwnd, TB_AUTOSIZE, 0, 0);
That is the first step.
Write two functions:
void RemoveButtons(void){
int nCount, i;
// Remove all of the existing buttons, starting with the last one.
nCount = SendMessage(toolBarHwnd, TB_BUTTONCOUNT, 0, 0);
for(i = nCount - 1; i >= 0; i--){ SendMessage(toolBarHwnd, TB_DELETEBUTTON, i, 0); }
return;
}
enum{SMALL, MEDIUM, LARGE};
void AddButtons(int sizeButtons){
if(sizeButtons == SMALL){
SendMessage(toolBarHwnd, TB_SETIMAGELIST, (WPARAM)0, (LPARAM)g_hImageListSmall);
}
else if(sizeButtons == MEDIUM){
SendMessage(toolBarHwnd, TB_SETIMAGELIST, (WPARAM)0, (LPARAM)g_hImageListMedium);
}
else{
SendMessage(toolBarHwnd, TB_SETIMAGELIST, (WPARAM)0, (LPARAM)g_hImageListLarge);
}
// Add buttons.
SendMessage(toolBarHwnd, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);
SendMessage(toolBarHwnd, TB_ADDBUTTONS, (WPARAM)numButtons, (LPARAM)&tbButtons);
// Resize the toolbar
SendMessage(toolBarHwnd, TB_AUTOSIZE, 0, 0);
return;
}
When ever you want to change the size of the buttons in ToolBar:
RemoveButtons();
AddButtons(LARGE); //or SMALL, MEDIUM
References:
How to Create Toolbars
How to Customize Toolbars
The common controls have been a major bug factory in Windows. Microsoft has had a great deal of trouble keeping them compatible across 6 major Windows releases and 10 versions of comctl32.dll. Particularly the visual style renderers have been a problem spot.
Core issue is that the api for them was set in stone 18 years ago with no reasonable way to make it work differently from the way it worked in their first release. Their code acquired a great many deal of appcompat hacks to achieve this. Such an hack will for example doctor a value that was returned by the previous version so that the client program has no idea, and doesn't need to know, that it is working with a very different version from the one it was tested against.
This has side-effects, the kind you'll discover when you use the controls in an unusual way that's very different from the way they are normally used by meat-and-potatoes Windows programs. Exactly like your scenario. Very high odds that you are battling internal state of the toolbar that you cannot see and doesn't get properly restored when you switch sizes. Quite undebuggable, that internal state isn't visible at all. Other than from the undesirable side-effects.
The solution is the one you already know. Recreate the toolbar from scratch. It can't go wrong that way.

Clearing the screen after switching away from fullscreen mode?

So I've got an OpenGL application running that can toggle between fullscreen mode and windowed mode. It currently does this by resizing the window and changing styles.
However, it seems to not invalidate the screen when switching from fullscreen mode to windowed mode, which leaves things I've drawn lingering onscreen after the switch.
Interestingly, it only exhibits this behavior in single monitor mode. If I'm running with multiple monitors, it invalidates okay, and clears my drawing.
I don't think this is a driver bug, as it's happening on two separate computers using two separate video cards(although admittedly they are both nVidia.), I'm thinking I'm doing something wrong somewhere.
I've tried a bunch of methods for getting Windows to clear the screen of my previously fullscreen drawings, but nothing seems to work. InvalidateRect(), RedrawWindow(), ChangeDisplaySettings()...
Specifically:
InvalidateRect(m_hwnd, &rectx, true); // rect being the dimensions of either the screen or my window.
InvalidateRect(HWND_DESKTOP, NULL, TRUE); // Doesn't seem to do anything.
RedrawWindow(NULL, NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
ChangeDisplaySettings(NULL, 0);
Well, actually, one thing that does seem to work is ShowWindow(hwnd, SW_HIDE) before resizing. However that loses focus for a moment, allowing other applications to grab my application's input, and seems a bad way to go about it.
I should note that I'm not doing any actual display mode changes when I'm seeing this behavior; just staying at the current resolution for fullscreen.
I'm a bit clueless where I'm going wrong. Simplified code:
if(m_isFullscreen)
{
ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);
}
else
{
ChangeDisplaySettings(&m_dmSavedScreenSettings, 0);
}
if(m_isFullscreen)
{
dwExStyle = WS_EX_APPWINDOW;
dwStyle = WS_POPUP;
ShowCursor(false);
}
else
{
dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
dwStyle = WS_OVERLAPPEDWINDOW;
if(m_isRunning) // Because ShowCursor uses a counter to indicate, and windowed mode defaults to cursor on, we don't need to increment the counter and double it being on.
{
ShowCursor(true);
}
}
RECT rect;
rect.left = 0;
rect.top = 0;
if(m_isFullscreen) { rect.right = 1280; } else { rect.right = 640; }
if(m_isFullscreen) { rect.bottom = 1024; } else { rect.bottom = 480; }
AdjustWindowRectEx(&rect, dwStyle, false, dwExStyle);
SetWindowLongPtr(m_hwnd, GWL_STYLE, dwStyle | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
SetWindowLongPtr(m_hwnd, GWL_EXSTYLE, dwExStyle);
if(m_isFullscreen)
{
MoveWindow(m_hwnd, 0, 0, 1280, 1024, true);
}
else
{
MoveWindow(m_hwnd, 0, 0, 640, 480, true); // windowed
}
And that's more or less it. Some other supporting code and error checking, but that's what I'm doing... dmSavedSettings is saved before m_hwnd is assigned from NULL, and not afterwards. My initial window creation works fine, and fullscreen works fine. It's just returning to Windowed after being fullscreen that's the issue.
If you set a null background brush in your window class, windows will not be cleared automatically. You must add a WM_PAINT handler that calls your OpenGL display handler, which in turn clears the viewport (glClearColor) and redraws.
As datenwolf mentions in another answer's comment, you want to use SetWindowPos() instead of MoveWindow() when making use of SetWindowLongPtr().
My dirty background problems were solved by calling ChangeDisplaySettings(NULL, 0) AFTER resizing my window. Doing it before does little, but afterwards appears to work fine.