Positioning Windows under WorkerW - c++

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.

Related

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.

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

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)

Manipulating the positions of desktop icons

I am currently trying to retrieve the list of icons from my desktop to change their locations and / or hide them as well as display others.
I tried to get the FolderView in the code below but it doesn't even show the number of icons I have on the desktop because count return 0.
HWND hDesktop = GetDesktopWindow();
HWND hDefView = FindWindowEx(hDesktop, NULL, L"SHELLDLL_DefView", NULL);
HWND folderView = FindWindowEx(hShellWnd, NULL, L"SysListView32", NULL);
int count = (int) SendMessage(folderView, LVM_GETITEMCOUNT, 0, 0);
cout << count << endl;
I did tests on the variables and is noticed that hDefView is NULL.
Probably the reason why count return 0.
EDIT : After replace GetDesktopWindow by GetShellWindow the result is always the same, 0
The shell window hierarchy is not documented nor stable. "ProgMan" is usually the parent of "SHELLDLL_DefView" but if you change to slideshow wallpaper it can also be "WorkerW".
It is much better to inspect/manipulate the desktop with the documented shell COM interfaces: IShellWindows, IShellBrowser, IFolderView and IShellFolder.

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.

How to draw on the windows desktop using the GDI API? [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Draw on screen with GDI+ (or GDI) similar to Inspect
I'm attempting to write a snake game that has no windows, but freezes the foreground and draws the snake on top of it. When the game ends the foreground should be unfrozen.
I have written some testing code that is supposed to draw a square on the foreground, but all it seems to do is freeze the desktop for a second and freeze the window in the foreground until I minimize, maximize, close it, or bring another window into the foreground, and it doesn't draw anything. In the code, I try to store a bitmap of the desktop so that I can essentially reset it to it's original state and paint the square in a different position. Can anybody spot the problem with my code?
//Handle to the desktop window
HWND hDesktopWindow = GetDesktopWindow();
//Lock the window to prevent other applications drawing on it
if(!LockWindowUpdate(hDesktopWindow)){
return 1;
}
//Calling GetDC with argument NULL retrieves the desktop's DC
HDC hdcDesktop = GetDCEx(hDesktopWindow, NULL, DCX_CACHE | DCX_LOCKWINDOWUPDATE);
//Create a compatible DC to allow us to store a bitmap of the desktop
HDC hdcCompatible;
if((hdcCompatible = CreateCompatibleDC(hdcDesktop)) == NULL){
return 1;
}
//Create a compatible bitmap with the same dimensions as the desktop
HBITMAP hScrBitmap;
int cx = GetSystemMetrics(SM_CXSCREEN);
int cy = GetSystemMetrics(SM_CYSCREEN);
if((hScrBitmap = CreateCompatibleBitmap(hdcDesktop, cx, cy)) == NULL){
return 1;
}
//Select the bitmap into the compatible DC
SelectObject(hdcCompatible, hScrBitmap);
//Copy the Desktop into the bitmap
if(!BitBlt(hdcCompatible, 0, 0, cx, cy, hdcDesktop, 0, 0, SRCCOPY)){
return 1;
}
//Create a DC compatible with the bitmaps DC for drawing the rectangle
HDC hdcRectangle;
if(!CreateCompatibleDC((HDC)hScrBitmap)){
return 1;
}
//Create a compatible bitmap for the rectangle to be drawn in
HBITMAP hRectangleBitmap;
if(!CreateCompatibleBitmap(hdcRectangle, 100, 100)){
return 1;
}
//Fill the rectangle bitmap
if(!FloodFill(hdcRectangle, 0, 0, RGB(255,0,0))){
return 1;
}
//Copy the rectangle onto the desktop bitmap
if(!BitBlt(hdcCompatible, 100, 100, 100, 100, hdcRectangle, 0, 0, SRCCOPY)){
return 1;
}
//Copy the bitmap onto the actual desktop
if(!BitBlt(hdcDesktop, 0, 0, cx, cy, hdcCompatible, 0, 0, SRCCOPY)){
return 1;
}
//Allow time to view the result
Sleep(1000);
//Allow other applications to draw on the desktop again
LockWindowUpdate(NULL);
//Cleanup
ReleaseDC(hDesktopWindow, hdcDesktop);
DeleteDC(hdcCompatible);
DeleteObject(hScrBitmap);
Any help would be greatly appreciated :)
Trying to do this directly on the desktop is going to be problematic. You'd be better off by taking a snapshot of the desktop, then create a window that's the size of the whole desktop, copy the snapshot to the window, and do all your drawing there. (This was a common trick done in old screensavers that did things like "erode" the desktop.)
You don't own the desktop window, so you'll always have problems with invalidation and repaints.
if(!CreateCompatibleDC((HDC)hScrBitmap)){
return 1;
}
When you write C code like this then a single-point-of-return tends to be very important. A call like this is going to return FALSE, can't cast a HBITMAP to HDC, and the show is over badly. No diagnostic and no call to unlock again.
Favor the C++ RAII pattern to ensure that you always unlock:
class DesktopLocker {
public:
DesktopLocker() { LockWindowUpdate(GetDesktopWindow()); }
~DesktopLocker() { LockWindowUpdate(NULL); }
};
void foo() {
DesktopLocker lock;
// etc...
}
There's not much wisdom of painting directly to the desktop window, there's little guarantee that whatever you draw will last. Sleeping and locking updates are just band-aids. Take a look at the source of Rainmeter, it's well done.