Windows Console Resizing ignored - Screen buffer messed up - c++

I'm going to ask the same question as countless people before me. I've tried about every solution out there but I seam to get the same output every time. I'm creating a console game engine and want to improve fps by using the windows API. However, Now that I have rotating polygons and such implemented, I'm realizing that the console resizes itself when the requested size extends about 100 x 100 characters. When this happens, the console gets resized to about 50 x 25 and the screen buffer shows absolute gibberish when I scroll to the right.
Here are some pictures:
I will supply just the relevant code for now but if anyone needs anymore I'd be glad to add it.
m_width = width;
m_height = height;
m_hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
m_hConsoleIn = GetStdHandle(STD_INPUT_HANDLE);
if (m_hConsole == INVALID_HANDLE_VALUE)
log += "Invalid std output console handle\n";
COORD bottemRight = { (signed)width, (signed)height };
if (!SetConsoleScreenBufferSize(m_hConsole, bottemRight))
log += "Error setting screen size\n";
m_screenBound = { 0, 0, (signed)width - 1, (signed)height - 1 };
if (!SetConsoleWindowInfo(m_hConsole, TRUE, &m_screenBound))
log += "Error initializing window info\n";
if (!SetConsoleActiveScreenBuffer(m_hConsole))
log += "Error setting active screen buffer\n";
I've also tried the HWND solution:
HWND hwnd = GetConsoleWindow();
RECT rect = { 0, 0, width, height };
MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
I tried switching the window info setter and the screen buffer size setter and changed the window info rect to {0, 0, 1, 1} to no prevail. The output is pretty much exactly what you see in the pictures above.
Thanks in advance!

SOLVED
After noodling around with my code a bit, it turns out (based on the Microsoft documentation) that I need to something like this:
SMALL_RECT screen = { 0, 0, 1, 1 };
SetConsoleWindowInfo(m_handle, true, &screen);
COORD buffer_size = { width, height };
SetConsoleScreenBufferSize(m_handle, &buffer_size);
screen = { 0, 0, width - 1, height - 1 };
SetConsoleWindowInfo(m_handle, &screen);
This works for me now but thanks for your suggestions!

Related

StretchBlt doesn't draw a background window title bar

Here's my objective: a) capture the device context for the entire screen; b) capture a background window device context (including the title bar); c) draw the desktop content on a buffer device context; d) draw the captured window content on the same buffer device context; e) display the drawn device context on a top window, simulating that the desired window is on top.
The issue is that the title bar isn't drawn on the new window, even though the GetWindowRect function seems to be working fine. That's because some other stuff is drawn instead of the title bar (usually the content of windows on the background when the target window is created).
This is my code right now:
case __SourceDC:
{
HDC desktop_dc = GetDC(NULL);
HWND target_hwnd = FindWindowA(NULL, "Everything");
HDC target_dc = GetWindowDC(target_hwnd);
RECT rc{};
GetWindowRect(target_hwnd, &rc);
RECT clrc{};
GetClientRect(target_hwnd, &clrc);
int width = rc.right - rc.left;
int height = rc.bottom - rc.top;
int x_dst = ((double)rc.left / SCREEN_WIDTH) * m_area.width;
int y_dst = ((double)rc.top / SCREEN_HEIGHT) * m_area.height;
int w_dst = ((double)width / SCREEN_WIDTH) * m_area.width;
int h_dst = ((double)height / SCREEN_HEIGHT) * m_area.height;
SetStretchBltMode(hdc, HALFTONE);
StretchBlt(hdc, 0, 0, m_area.width, m_area.height, desktop_dc, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SRCCOPY);
StretchBlt(hdc, x_dst, y_dst, w_dst, h_dst, target_dc, 0, 0, width, height, SRCCOPY);
ReleaseDC(target_hwnd, target_dc);
ReleaseDC(NULL, desktop_dc);
}
break;
All the desktop content and the window content is correctly drawn, except for the title bar (and apparently the borders as well).
As you can see from the image, the myWnd window shows the desktop content, but the title bar is not displayed for the target window. Also, nothing changes when the target window is the foreground window.
What am I missing here? Is this behavior expected? Is StretchBlt working as intended by not capturing the content of the title bar?
Thanks in advance.

Declaring SMALL_RECT = {}, does not resize the console window

I am just playing around with simple console applications in C++.
I am trying to resize the console window, and it works fine. First I resize the actual window, then the buffer and all is fine, but once I tried to compress the code, the window no longer resizes. It is really not that important. I am just generally wondering as most suggestions that I looked at declare the SMALL_RECT that way.
P.S I am using TDM-GCC 64 and Eclipse with std=c++17
This works and the window is resized properly
void SetCMDSizeAndTitle(short width, short height, string title) {
COORD coord = { width, height };
SMALL_RECT rect;
rect.Top = 0;
rect.Left = 0;
rect.Bottom = height - 1;
rect.Right = width - 1;
SetConsoleTitle(TEXT(title.c_str()));
SetConsoleWindowInfo(GetStdHandle(STD_OUTPUT_HANDLE), TRUE, &rect);
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
However this does not work, the window remains the default size
void SetCMDSizeAndTitle(short width, short height, string title) {
COORD coord = { width, height };
SMALL_RECT rect = { 0, 0, height - 1, width -1 };
SetConsoleTitle(TEXT(title.c_str()));
SetConsoleWindowInfo(GetStdHandle(STD_OUTPUT_HANDLE), TRUE, &rect);
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
This is not really that important. It is just nicer when there is less lines of code, and I am just generally curious why it doesn't work.
The reason it is not working is because according to the MSDN the variables are not in the order you assume. The MSDN defined the struct as
typedef struct _SMALL_RECT {
SHORT Left;
SHORT Top;
SHORT Right;
SHORT Bottom;
}
Which means you need to follow that order in the initialization list. That means you should have
SMALL_RECT rect = {0, 0, width -1, height - 1};
// L T R B

Can't center my console window by using the following code

void Initialize_Window(void)
{
RECT rConsole;
GetWindowRect(GetConsoleWindow(), &rConsole);
SetWindowPos(GetConsoleWindow(), NULL, 0, 0, 800, 700, 0);
SetWindowLong(GetConsoleWindow(), GWL_STYLE, GetWindowLong(GetConsoleWindow(), GWL_STYLE) & ~(WS_SIZEBOX | WS_MAXIMIZEBOX));
SetWindowPos(GetConsoleWindow(), NULL, (GetSystemMetrics(SM_CXSCREEN) - rConsole.right - rConsole.left) / 2, (GetSystemMetrics(SM_CYSCREEN) - rConsole.bottom - rConsole.top) / 2, 0, 0, SWP_NOSIZE);
}
I'm trying to center my console window by using the code above, but seems like the window just moved to a random position on my screen every time I execute the program, any idea how to fix it?
You need (GetSystemMetrics(SM_CXSCREEN) - (rConsole.right - rConsole.left))/2 to get center.
Side note: you can use one SetWindowPos instead of two (and do not need to get window Rect)
const int width = 800;
const int height = 700;
//SetWindowLong()...
SetWindowPos(GetConsoleWindow(), NULL,
GetSystemMetrics(SM_CXSCREEN)/2 - width/2,
GetSystemMetrics(SM_CYSCREEN)/2 - height/2,
width, height, SWP_SHOWWINDOW);
Don't use GetSystemMetrics() for this because it only returns the metrics of the primary monitor. Multi monitor setups are quite common these days so users will be rightly upset if you ignore that.
In addition, a window normally should not be aligned to the physical monitor surface, but to the work area which excludes the taskbar(s). Yes, there can be multiple taskbars (called "appbars" in Windows slang) on either side of the screen. An exception where you would actually use the full physical surface are full screen windows.
To cover both aspects we can use MonitorFromWindow() and GetMonitorInfo().
First we get the "nearest" monitor from the window handle. This is the monitor that either shows the window completely or that has the biggest area of the window on it:
HWND hConsoleWnd = ::GetConsoleWindow();
HMONITOR hMonitor = ::MonitorFromWindow( hConsoleWnd, MONITOR_DEFAULTTONEAREST );
Then we get the work area rectangle of that monitor and center the window relative to that:
if( hMonitor )
{
MONITORINFO info{ sizeof(info) }; // set cbSize member and fill the rest with zero
if( ::GetMonitorInfo( hMonitor, &info ) )
{
int width = 800;
int height = 700;
int x = ( info.rcWork.left + info.rcWork.right ) / 2 - width / 2;
int y = ( info.rcWork.top + info.rcWork.bottom ) / 2 - height / 2;
::SetWindowPos( hConsoleWnd, nullptr, x, y, width, height,
SWP_NOZORDER | SWP_NOOWNERZORDER );
}
}
That's it. In a real-world application you should of course not hardcode the window size because it is a user preference. For first launch a default size can be reasonable but even that should not be hardcoded but scaled according to the Windows DPI settings.

Win32 Screensaver multiple monitors with main display not leftmost

I've made a screensaver that simply scrolls user-defined text from right to left, automatically jumping back to the right if it exceeds the left boundary.
It works with multiple monitors flawlessly, barring one exception: if the 'Main Display' is on the right (i.e. Monitor #2 is primary), then I do not get the scrolling text, however the monitor IS blacked out by the code. If the main display is #1, there's no problem.
I've been poring over the code for hours and cannot identify at what stage the issue arises; I can confirm the text is in the right position (I inserted logging code that verifies its current position), but it's as if one of the API calls simply erases it. I've read the documentation for them and all looks ok.
I create a custom DC in WM_CREATE via:
if (( hDC = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL)) == NULL )
To prevent flicker, I create compatible objects to update:
void
TickerScreensaver::Paint_Prep(HDC hDC)
{
_devcon_mem = CreateCompatibleDC(hDC);
_devcon_orig = hDC;
_bmp_mem = CreateCompatibleBitmap(hDC, _width, _height);
}
and when painting in WM_PAINT (after BeginPaint, etc.), do a bit-block transfer to the actual device context:
void
TickerScreensaver::Paint(HDC hDC, RECT rect)
{
_bmp_orig = (HBITMAP)SelectObject(_devcon_mem, _bmp_mem);
FillRect(_devcon_mem, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH));
if ( _gdiplus_token != NULL )
{
Graphics graphics(_devcon_mem);
SolidBrush brush(cfg.display.font_colour);
FontFamily font_family(cfg.display.font_family.c_str());
Font font(&font_family, cfg.display.font_size, FontStyleRegular, UnitPixel);
PointF point_f((f32)cfg.display.text_pos.x, (f32)cfg.display.text_pos.y);
RectF layout_rect(0, 0, 0, 0);
RectF bound_rect;
graphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
graphics.MeasureString(cfg.display.text.c_str(), cfg.display.text.length(), &font, layout_rect, &bound_rect);
cfg.display.offset.x = (DWORD)(0 - bound_rect.Width);
cfg.display.offset.y = (DWORD)(bound_rect.Height / 2);
graphics.DrawString(cfg.display.text.c_str(), cfg.display.text.length(), &font, point_f, &brush);
}
BitBlt(hDC, 0, 0, _width, _height, _devcon_mem, 0, 0, SRCCOPY);
SelectObject(_devcon_mem, _bmp_orig);
}
I calculate the dimensions like so:
void
TickerScreensaver::GetFullscreenRect(HDC hDC, RECT *rect)
{
RECT s = { 0, 0, 0, 0 };
if ( EnumDisplayMonitors(hDC, NULL, EnumMonitorCallback, (LPARAM)&s) )
{
CopyRect(rect, &s);
s.left < 0 ?
_width = s.right + (0 + -s.left) :
_width = s.right;
s.top < 0 ?
_height = s.bottom + (0 + -s.top) :
_height = s.bottom;
}
}
Please note that the calculated width, height, etc., are all 100% accurate; it is purely the drawing code that doesn't appear to be working on the main display, only when it is on the right (which sets the origin to {0,0}, monitor #1 then being negative values). It is also reproduceable on a tri-display, with the main being in the center.
Well, turns out it is nice and simple - in Paint(), we should use a rect using the real width and height, not the one retrieved containing the negative values (the one actually retrieved from the API functions):
RECT r = { 0, 0, _width, _height };
_bmp_orig = (HBITMAP)SelectObject(_devcon_mem, _bmp_mem);
FillRect(_devcon_mem, &r, (HBRUSH)GetStockObject(BLACK_BRUSH));

BitBlt ignores CAPTUREBLT and seems to always capture a cached copy of the target

I am trying to capture screenshots using the BitBlt function. However, every single time I capture a screenshot, the non-client area NEVER changes no matter what I do. It's as if it's getting some cached copy of it. The client area is captured correctly.
If I close and then re-open the window, and take a screenshot, the non-client area will be captured as it is. Any subsequent captures after moving/resizing the window have no effect on the captured screenshot. Again, the client area will be correct.
Furthermore, the CAPTUREBLT flag seems to do absolutely nothing at all. I notice no change with or without it. Here is my capture code:
QPixmap WindowManagerUtils::grabWindow(WId windowId, GrabWindowFlags flags, int x, int y, int w, int h)
{
RECT r;
switch (flags)
{
case WindowManagerUtils::GrabWindowRect:
GetWindowRect(windowId, &r);
break;
case WindowManagerUtils::GrabClientRect:
GetClientRect(windowId, &r);
break;
case WindowManagerUtils::GrabScreenWindow:
GetWindowRect(windowId, &r);
return QPixmap::grabWindow(QApplication::desktop()->winId(), r.left, r.top, r.right - r.left, r.bottom - r.top);
case WindowManagerUtils::GrabScreenClient:
GetClientRect(windowId, &r);
return QPixmap::grabWindow(QApplication::desktop()->winId(), r.left, r.top, r.right - r.left, r.bottom - r.top);
default:
return QPixmap();
}
if (w < 0)
{
w = r.right - r.left;
}
if (h < 0)
{
h = r.bottom - r.top;
}
#ifdef Q_WS_WINCE_WM
if (qt_wince_is_pocket_pc())
{
QWidget *widget = QWidget::find(winId);
if (qobject_cast<QDesktopWidget*>(widget))
{
RECT rect = {0,0,0,0};
AdjustWindowRectEx(&rect, WS_BORDER | WS_CAPTION, FALSE, 0);
int magicNumber = qt_wince_is_high_dpi() ? 4 : 2;
y += rect.top - magicNumber;
}
}
#endif
// Before we start creating objects, let's make CERTAIN of the following so we don't have a mess
Q_ASSERT(flags == WindowManagerUtils::GrabWindowRect || flags == WindowManagerUtils::GrabClientRect);
// Create and setup bitmap
HDC display_dc = NULL;
if (flags == WindowManagerUtils::GrabWindowRect)
{
display_dc = GetWindowDC(NULL);
}
else if (flags == WindowManagerUtils::GrabClientRect)
{
display_dc = GetDC(NULL);
}
HDC bitmap_dc = CreateCompatibleDC(display_dc);
HBITMAP bitmap = CreateCompatibleBitmap(display_dc, w, h);
HGDIOBJ null_bitmap = SelectObject(bitmap_dc, bitmap);
// copy data
HDC window_dc = NULL;
if (flags == WindowManagerUtils::GrabWindowRect)
{
window_dc = GetWindowDC(windowId);
}
else if (flags == WindowManagerUtils::GrabClientRect)
{
window_dc = GetDC(windowId);
}
DWORD ropFlags = SRCCOPY;
#ifndef Q_WS_WINCE
ropFlags = ropFlags | CAPTUREBLT;
#endif
BitBlt(bitmap_dc, 0, 0, w, h, window_dc, x, y, ropFlags);
// clean up all but bitmap
ReleaseDC(windowId, window_dc);
SelectObject(bitmap_dc, null_bitmap);
DeleteDC(bitmap_dc);
QPixmap pixmap = QPixmap::fromWinHBITMAP(bitmap);
DeleteObject(bitmap);
ReleaseDC(NULL, display_dc);
return pixmap;
}
Most of this code comes from Qt's QWidget::grabWindow function, as I wanted to make some changes so it'd be more flexible. Qt's documentation states that:
The grabWindow() function grabs pixels
from the screen, not from the window,
i.e. if there is another window
partially or entirely over the one you
grab, you get pixels from the
overlying window, too.
However, I experience the exact opposite... regardless of the CAPTUREBLT flag. I've tried everything I can think of... nothing works. Any ideas?
Your confusion about BitBlt with CAPTUREBLT behaviour comes from the fact that official BitBlt documentation is unclear and misleading.
It states that
"CAPTUREBLT -- Includes any windows that are layered on top of your window in the resulting image. By default, the image only contains your window."
What actually means (at least for any windows OS without Aero enabled)
"CAPTUREBLT -- Includes any layered(!) windows (see WS_EX_LAYERED extended window style) that overlap your window. Non-layered windows that overlap your window is never included."
Windows without WS_EX_LAYERED extended window style that overlap your window is not included with or without CAPTUREBLT flag (at least for any windows OS without Aero enabled).
QT developers also misunderstood BitBlt/CAPTUREBLT documentation so QT documentation is actually wrong about QPixmap::grabWindow behaviour on WIN32 platform without Aero enabled.
ADD:
If you want to capture your window as it is on the screen you have to capture the entire desktop with CAPTUREBLT flag and then extract the rectangle with your window. (QT developers should do the same thing). It will work correctly in both cases: with and without Aero enabled/available.
I capture all screen and obtains the same results... :(
const uint SRCCOPY = 0x00CC0020; //SRCCOPY
const uint CAPTUREBLT = 0x00CC0020 | 0x40000000; //CAPTUREBLT
bool dv = BitBlt(hCaptureDC, 0, 0, Bounds.Width, Bounds.Height,
hDesktopDC, Bounds.Left, Bounds.Top, _with_tooltips ? CAPTUREBLT : SRCCOPY);