diff --git a/src/agent/Agent.cc b/src/agent/Agent.cc index 74be0b9..ba8fc0c 100644 --- a/src/agent/Agent.cc +++ b/src/agent/Agent.cc @@ -42,6 +42,7 @@ #include "../shared/WindowsVersion.h" #include "../shared/WinptyAssert.h" +#include "ConsoleFont.h" #include "ConsoleInput.h" #include "NamedPipe.h" #include "Scraper.h" @@ -59,39 +60,48 @@ static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType) return FALSE; } -// In versions of the Windows console before Windows 10, the SelectAll and -// Mark commands both run quickly, but Mark changes the cursor position read -// by GetConsoleScreenBufferInfo. Therefore, use SelectAll to be less -// intrusive. +// We can detect the new Windows 10 console by observing the effect of the +// Mark command. In older consoles, Mark temporarily moves the cursor to the +// top-left of the console window. In the new console, the cursor isn't +// initially moved. // -// Starting with the new Windows 10 console, the Mark command no longer moves -// the cursor, and SelectAll uses a lot of CPU time. Therefore, use Mark. -// -// The Windows 10 legacy-mode console behaves the same way as previous console -// versions, so detect which syscommand to use by testing whether Mark changes -// the cursor position. -static void initConsoleFreezeMethod( +// We might like to use Mark to freeze the console, but we can't, because when +// the Mark command ends, the console moves the cursor back to its starting +// point, even if the console application has moved it in the meantime. +static void detectNewWindows10Console( Win32Console &console, Win32ConsoleBuffer &buffer) { - const ConsoleScreenBufferInfo info = buffer.bufferInfo(); + ConsoleScreenBufferInfo info = buffer.bufferInfo(); - // Make sure the buffer and window aren't 1x1. (Is that even possible?) - buffer.resizeBuffer(Coord( - std::max(2, info.dwSize.X), - std::max(2, info.dwSize.Y))); - buffer.moveWindow(SmallRect(0, 0, 2, 2)); - const Coord initialPosition(1, 1); - buffer.setCursorPosition(initialPosition); + // Make sure the window isn't 1x1. + if (info.srWindow.Left == info.srWindow.Right && + info.srWindow.Top == info.srWindow.Bottom) { + // Make sure the buffer isn't 1x1. + if (info.dwSize.X == 1 && info.dwSize.Y == 1) { + setSmallFont(buffer.conout(), 80, false); + buffer.resizeBuffer(Coord(80, 25)); + info = buffer.bufferInfo(); + } + buffer.moveWindow(SmallRect(0, 0, 2, 1)); + info = buffer.bufferInfo(); + ASSERT(info.srWindow.Right > info.srWindow.Left && + "Could not expand console window from 1x1"); + } // Test whether MARK moves the cursor. + const Coord initialPosition(info.srWindow.Right, info.srWindow.Bottom); + buffer.setCursorPosition(initialPosition); ASSERT(!console.frozen()); console.setFreezeUsesMark(true); console.setFrozen(true); - const bool useMark = (buffer.cursorPosition() == initialPosition); + const bool isNewW10 = (buffer.cursorPosition() == initialPosition); console.setFrozen(false); - trace("Using %s syscommand to freeze console", - useMark ? "MARK" : "SELECT_ALL"); - console.setFreezeUsesMark(useMark); + buffer.setCursorPosition(Coord(0, 0)); + + trace("Attempting to detect new Windows 10 console using MARK: %s", + isNewW10 ? "detected" : "not detected"); + console.setFreezeUsesMark(false); + console.setNewW10(isNewW10); } static inline WriteBuffer newPacket() { @@ -141,7 +151,7 @@ Agent::Agent(LPCWSTR controlPipeName, m_errorBuffer = Win32ConsoleBuffer::createErrorBuffer(); } - initConsoleFreezeMethod(m_console, *primaryBuffer); + detectNewWindows10Console(m_console, *primaryBuffer); m_controlPipe = &connectToControlPipe(controlPipeName); m_coninPipe = &createDataServerPipe(false, L"conin"); diff --git a/src/agent/ConsoleFont.cc b/src/agent/ConsoleFont.cc index 808080d..f2941b6 100644 --- a/src/agent/ConsoleFont.cc +++ b/src/agent/ConsoleFont.cc @@ -27,11 +27,14 @@ #include #include +#include +#include #include #include "../shared/DebugClient.h" #include "../shared/OsModule.h" #include "../shared/StringUtil.h" +#include "../shared/WindowsVersion.h" #include "../shared/WinptyAssert.h" #include "../shared/winpty_snprintf.h" @@ -40,121 +43,212 @@ namespace { #define COUNT_OF(x) (sizeof(x) / sizeof((x)[0])) // See https://en.wikipedia.org/wiki/List_of_CJK_fonts -const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // Japanese -const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // Simplified Chinese -const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // Traditional Chinese -const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // Korean +const wchar_t kLucidaConsole[] = L"Lucida Console"; +const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // 932, Japanese +const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // 936, Chinese Simplified +const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // 949, Korean +const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // 950, Chinese Traditional -struct Font { - int codePage; - const wchar_t *faceName; - int pxSize; +struct FontSize { + int size; + int width; }; -const Font kFonts[] = { - // MS Gothic double-width handling seems to be broken with console versions - // prior to Windows 10 (including Windows 10's legacy mode), and it's - // especially broken in Windows 8 and 8.1. AFAICT, MS Gothic at size 9 - // avoids problems in Windows 7 and minimizes problems in 8/8.1. - // - // Test by running: misc/Utf16Echo A2 A3 2014 3044 30FC 4000 - // - // The first three codepoints are always rendered as half-width with the - // Windows Japanese fonts. (Of these, the first two must be half-width, - // but U+2014 could be either.) The last three are rendered as full-width, - // and they are East_Asian_Width=Wide. - // - // Windows 7 fails by modeling all codepoints as full-width with font - // sizes 14 and above. - // - // Windows 8 gets U+00A2, U+00A3, U+2014, U+30FC, and U+4000 wrong, but - // using a point size not listed in the console properties dialog - // (e.g. "9") is less wrong: - // - // | code point | - // font | 00A2 00A3 2014 3044 30FC 4000 | cell size - // ------------+---------------------------------+---------- - // 8 | F F F F H H | 4x8 - // 9 | F F F F F F | 5x9 - // 16 | F F F F H H | 8x16 - // raster 6x13 | H H H F F H(*) | 6x13 - // - // (*) The Raster Font renders U+4000 as a white box (i.e. an unsupported - // character). - // - { 932, kMSGothic, 9 }, +struct Font { + const wchar_t *face; + int size; +}; - // kNSimSun: I verified that `misc/Utf16Echo A2 A3 2014 3044 30FC 4000` - // did something sensible with Windows 8. It *did* with the listed font - // sizes, but not with unlisted sizes. Listed sizes: - // - 6 ==> 3x7px - // - 8 ==> 4x9px - // - 10 ==> 5x11px - // - 12 ==> 6x14px - // - 14 ==> 7x16px - // - 16 ==> 8x18px - // ... - // - 36 ==> 18x41px - // - 72 ==> 36x82px - // U+2014 is modeled and rendered as full-width. - { 936, kNSimSun, 8 }, +// Ideographs in East Asian languages take two columns rather than one. +// In the console screen buffer, a "full-width" character will occupy two +// cells of the buffer, the first with attribute 0x100 and the second with +// attribute 0x200. +// +// Windows does not correctly identify code points as double-width in all +// configurations. It depends heavily on the code page, the font facename, +// and (somehow) even the font size. In the 437 code page (MS-DOS), for +// example, no codepoints are interpreted as double-width. When the console +// is in an East Asian code page (932, 936, 949, or 950), then sometimes +// selecting a "Western" facename like "Lucida Console" or "Consolas" doesn't +// register, or if the font *can* be chosen, then the console doesn't handle +// double-width correctly. I tested the double-width handling by writing +// several code points with WriteConsole and checking whether one or two cells +// were filled. +// +// In the Japanese code page (932), Microsoft's default font is MS Gothic. +// MS Gothic double-width handling seems to be broken with console versions +// prior to Windows 10 (including Windows 10's legacy mode), and it's +// especially broken in Windows 8 and 8.1. +// +// Test by running: misc/Utf16Echo A2 A3 2014 3044 30FC 4000 +// +// The first three codepoints are always rendered as half-width with the +// Windows Japanese fonts. (Of these, the first two must be half-width, +// but U+2014 could be either.) The last three are rendered as full-width, +// and they are East_Asian_Width=Wide. +// +// Windows 7 fails by modeling all codepoints as full-width with font +// sizes 22 and above. +// +// Windows 8 gets U+00A2, U+00A3, U+2014, U+30FC, and U+4000 wrong, but +// using a point size not listed in the console properties dialog +// (e.g. "9") is less wrong: +// +// | code point | +// font | 00A2 00A3 2014 3044 30FC 4000 | cell size +// ------------+---------------------------------+---------- +// 8 | F F F F H H | 4x8 +// 9 | F F F F F F | 5x9 +// 16 | F F F F H H | 8x16 +// raster 6x13 | H H H F F H(*) | 6x13 +// +// (*) The Raster Font renders U+4000 as a white box (i.e. an unsupported +// character). +// - // kMingLight: I verified that `misc/Utf16Echo A2 A3 2014 3044 30FC 4000` - // did something sensible with Windows 8. It *did* with the listed font - // sizes, but not with unlisted sizes. Listed sizes: - // - 6 => 3x7px - // - 8 => 4x10px - // - 10 => 5x12px - // - 12 => 6x14px - // - 14 => 7x17px - // - 16 => 8x19px - // ... - // - 36 => 18x43px - // - 72 => 36x86px - // U+2014 is modeled and rendered as full-width. - { 950, kMingLight, 8 }, +// See: +// - misc/Font-Report-June2016 directory for per-size details +// - misc/font-notes.txt +// - misc/Utf16Echo.cc, misc/FontSurvey.cc, misc/SetFont.cc, misc/GetFont.cc - // kGulimChe: I verified that `misc/Utf16Echo A2 A3 2014 3044 30FC 4000` - // did something sensible with Windows 8. It *did* with the listed font - // sizes, but not with unlisted sizes. Listed sizes: - // - 6 ==> 3x7px - // - 8 ==> 4x9px - // - 10 ==> 5x11px - // - 12 ==> 6x14px - // - 14 ==> 7x16px - // - 16 ==> 8x18px - // ... - // - 36 ==> 18x41px - // - 72 ==> 36x83px - { 949, kGulimChe, 8 }, +const FontSize kLucidaFontSizes[] = { + { 2, 1 }, + { 4, 2 }, + { 5, 3 }, + { 6, 4 }, + { 8, 5 }, + { 10, 6 }, + { 12, 7 }, + { 14, 8 }, + { 16, 10 }, + { 18, 11 }, + { 20, 12 }, + { 36, 22 }, + { 48, 29 }, + { 60, 36 }, + { 72, 43 }, +}; - // Listed sizes: - // - 5 ==> 2x5px - // - 6 ==> 3x6px - // - 7 ==> 3x6px - // - 8 ==> 4x8px - // - 10 ==> 5x10px - // - 12 ==> 6x12px - // - 14 ==> 7x14px - // - 16 ==> 8x16px - // ... - // - 36 ==> 17x36px - // - 72 ==> 34x72px - { 0, L"Consolas", 8 }, +// Japanese. Used on Vista and Windows 7. +const FontSize k932GothicVista[] = { + { 2, 1 }, + { 4, 2 }, + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 13, 7 }, + { 15, 8 }, + { 17, 9 }, + { 19, 10 }, + { 21, 11 }, + // All larger fonts are more broken w.r.t. full-size East Asian characters. +}; - // Listed sizes: - // - 5 ==> 3x5px - // - 6 ==> 4x6px - // - 7 ==> 4x7px - // - 8 ==> 5x8px - // - 10 ==> 6x10px - // - 12 ==> 7x12px - // - 14 ==> 8x14px - // - 16 ==> 10x16px - // ... - // - 36 ==> 22x36px - // - 72 ==> 43x72px - { 0, L"Lucida Console", 6 }, +// Japanese. Used on Windows 8, 8.1, and the legacy 10 console. +const FontSize k932GothicWin8[] = { + // All of these characters are broken w.r.t. full-size East Asian + // characters, but they're equally broken. + { 3, 2 }, + { 5, 3 }, + { 7, 4 }, + { 9, 5 }, + { 11, 6 }, + { 13, 7 }, + { 15, 8 }, + { 17, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Japanese. Used on the new Windows 10 console. +const FontSize k932GothicWin10[] = { + { 2, 1 }, + { 4, 2 }, + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Chinese Simplified. +const FontSize k936SimSun[] = { + { 2, 1 }, + { 4, 2 }, + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Korean. +const FontSize k949GulimChe[] = { + { 2, 1 }, + { 4, 2 }, + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Chinese Traditional. +const FontSize k950MingLight[] = { + { 2, 1 }, + { 4, 2 }, + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, }; // Some of these types and functions are missing from the MinGW headers. @@ -422,15 +516,109 @@ static bool setFontVista( return true; } -static void setSmallFontVista(VistaFontAPI &api, HANDLE conout) { - int codePage = GetConsoleOutputCP(); - for (size_t i = 0; i < COUNT_OF(kFonts); ++i) { - if (kFonts[i].codePage == 0 || kFonts[i].codePage == codePage) { - if (setFontVista(api, conout, - kFonts[i].faceName, kFonts[i].pxSize)) { - trace("setSmallFontVista: success"); - return; +static Font selectSmallFont(int codePage, int columns, bool isNewW10) { + // Iterate over a set of font sizes according to the code page, and select + // one. + + const wchar_t *face = nullptr; + const FontSize *table = nullptr; + size_t tableSize = 0; + + switch (codePage) { + case 932: // Japanese + face = kMSGothic; + if (isNewW10) { + table = k932GothicWin10; + tableSize = COUNT_OF(k932GothicWin10); + } else if (isAtLeastWindows8()) { + table = k932GothicWin8; + tableSize = COUNT_OF(k932GothicWin8); + } else { + table = k932GothicVista; + tableSize = COUNT_OF(k932GothicVista); } + break; + case 936: // Chinese Simplified + face = kNSimSun; + table = k936SimSun; + tableSize = COUNT_OF(k936SimSun); + break; + case 949: // Korean + face = kGulimChe; + table = k949GulimChe; + tableSize = COUNT_OF(k949GulimChe); + break; + case 950: // Chinese Traditional + face = kMingLight; + table = k950MingLight; + tableSize = COUNT_OF(k950MingLight); + break; + default: + face = kLucidaConsole; + table = kLucidaFontSizes; + tableSize = COUNT_OF(kLucidaFontSizes); + break; + } + + size_t bestIndex = static_cast(-1); + std::tuple bestScore = std::make_tuple(-1, -1); + + // We might want to pick the smallest possible font, because we don't know + // how large the monitor is (and the monitor size can change). We might + // want to pick a larger font to accommodate console programs that resize + // the console on their own, like DOS edit.com, which tends to resize the + // console to 80 columns. + + for (size_t i = 0; i < tableSize; ++i) { + const int width = table[i].width * columns; + + // In general, we'd like to pick a font size where cutting the number + // of columns in half doesn't immediately violate the minimum width + // constraint. (e.g. To run DOS edit.com, a user might resize their + // terminal to ~100 columns so it's big enough to show the 80 columns + // post-resize.) To achieve this, give priority to fonts that allow + // this halving. We don't want to encourage *very* large fonts, + // though, so disable the effect as the number of columns scales from + // 80 to 40. + const int halfColumns = std::min(columns, std::max(40, columns / 2)); + const int halfWidth = table[i].width * halfColumns; + + std::tuple thisScore = std::make_tuple(-1, -1); + if (width >= 160 && halfWidth >= 160) { + // Both sizes are good. Prefer the smaller fonts. + thisScore = std::make_tuple(2, -width); + } else if (width >= 160) { + // Prefer the smaller fonts. + thisScore = std::make_tuple(1, -width); + } else { + // Otherwise, prefer the largest font in our table. + thisScore = std::make_tuple(0, width); + } + if (thisScore > bestScore) { + bestIndex = i; + bestScore = thisScore; + } + } + + ASSERT(bestIndex != static_cast(-1)); + return Font { face, table[bestIndex].size }; +} + +static void setSmallFontVista(VistaFontAPI &api, HANDLE conout, + int columns, bool isNewW10) { + int codePage = GetConsoleOutputCP(); + const auto font = selectSmallFont(codePage, columns, isNewW10); + if (setFontVista(api, conout, font.face, font.size)) { + trace("setSmallFontVista: success"); + return; + } + if (codePage == 932 || codePage == 936 || + codePage == 949 || codePage == 950) { + trace("setSmallFontVista: falling back to default codepage font instead"); + const auto fontFB = selectSmallFont(0, columns, isNewW10); + if (setFontVista(api, conout, fontFB.face, fontFB.size)) { + trace("setSmallFontVista: fallback was successful"); + return; } } trace("setSmallFontVista: failure"); @@ -486,15 +674,17 @@ static void setSmallFontXP(UndocumentedXPFontAPI &api, HANDLE conout) { // maximize the possible size of the console in rows*cols, try to configure // the console with a small font. Unfortunately, we cannot make the font *too* // small, because there is also a minimum window size in pixels. -void setSmallFont(HANDLE conout) { - trace("setSmallFont: attempting to set a small font (CP=%u OutputCP=%u)", +void setSmallFont(HANDLE conout, int columns, bool isNewW10) { + trace("setSmallFont: attempting to set a small font for %d columns " + "(CP=%u OutputCP=%u)", + columns, static_cast(GetConsoleCP()), static_cast(GetConsoleOutputCP())); VistaFontAPI vista; if (vista.valid()) { dumpVistaFont(vista, conout, "previous font: "); dumpFontTable(conout, "previous font table: "); - setSmallFontVista(vista, conout); + setSmallFontVista(vista, conout, columns, isNewW10); dumpVistaFont(vista, conout, "new font: "); dumpFontTable(conout, "new font table: "); return; diff --git a/src/agent/ConsoleFont.h b/src/agent/ConsoleFont.h index f8769a5..99cb106 100644 --- a/src/agent/ConsoleFont.h +++ b/src/agent/ConsoleFont.h @@ -23,6 +23,6 @@ #include -void setSmallFont(HANDLE conout); +void setSmallFont(HANDLE conout, int columns, bool isNewW10); #endif // CONSOLEFONT_H diff --git a/src/agent/Scraper.cc b/src/agent/Scraper.cc index 641e9bf..1da4f4e 100755 --- a/src/agent/Scraper.cc +++ b/src/agent/Scraper.cc @@ -59,10 +59,26 @@ Scraper::Scraper( m_bufferData.resize(BUFFER_LINE_COUNT); - setSmallFont(buffer.conout()); + // Setup the initial screen buffer and window size. + // + // Use SetConsoleWindowInfo to shrink the console window as much as + // possible -- to a 1x1 cell at the top-left. This call always succeeds. + // Prior to the new Windows 10 console, it also actually resizes the GUI + // window to 1x1 cell. Nevertheless, even though the GUI window can + // therefore be narrower than its minimum, calling + // SetConsoleScreenBufferSize with a 1x1 size still fails. + // + // While the small font intends to support large buffers, a user could + // still hit a limit imposed by their monitor width, so cap the new window + // size to GetLargestConsoleWindowSize(). + setSmallFont(buffer.conout(), initialSize.X, m_console.isNewW10()); buffer.moveWindow(SmallRect(0, 0, 1, 1)); buffer.resizeBuffer(Coord(initialSize.X, BUFFER_LINE_COUNT)); - buffer.moveWindow(SmallRect(0, 0, initialSize.X, initialSize.Y)); + const auto largest = GetLargestConsoleWindowSize(buffer.conout()); + buffer.moveWindow(SmallRect( + 0, 0, + std::min(initialSize.X, largest.X), + std::min(initialSize.Y, largest.Y))); buffer.setCursorPosition(Coord(0, 0)); // For the sake of the color translation heuristic, set the console color @@ -159,11 +175,18 @@ void Scraper::clearBufferLines( } } +static bool cursorInWindow(const ConsoleScreenBufferInfo &info) +{ + return info.dwCursorPosition.Y >= info.srWindow.Top && + info.dwCursorPosition.Y <= info.srWindow.Bottom; +} + void Scraper::resizeImpl(const ConsoleScreenBufferInfo &origInfo) { ASSERT(m_console.frozen()); const int cols = m_ptySize.X; const int rows = m_ptySize.Y; + Coord finalBufferSize; { // @@ -188,7 +211,7 @@ void Scraper::resizeImpl(const ConsoleScreenBufferInfo &origInfo) } } - const Coord finalBufferSize( + finalBufferSize = Coord( cols, // If there was previously no scrollback (e.g. a full-screen app // in direct mode) and we're reducing the window height, then @@ -196,44 +219,62 @@ void Scraper::resizeImpl(const ConsoleScreenBufferInfo &origInfo) (origWindowRect.height() == origBufferSize.Y) ? rows : std::max(rows, origBufferSize.Y)); - const bool cursorWasInWindow = - origInfo.cursorPosition().Y >= origWindowRect.Top && - origInfo.cursorPosition().Y <= origWindowRect.Bottom; - // Step 1: move the window. - const int tmpWindowWidth = std::min(origBufferSize.X, finalBufferSize.X); - const int tmpWindowHeight = std::min(origBufferSize.Y, rows); + // Reset the console font size. We need to do this before shrinking + // the window, because we might need to make the font bigger to permit + // a smaller window width. Making the font smaller could expand the + // screen buffer, which would hang the conhost process in the + // Windows 10 (10240 build) if the console selection is in progress, so + // unfreeze it first. + m_console.setFrozen(false); + setSmallFont(m_consoleBuffer->conout(), cols, m_console.isNewW10()); + } + + // We try to make the font small enough so that the entire screen buffer + // fits on the monitor, but it can't be guaranteed. + const auto largest = + GetLargestConsoleWindowSize(m_consoleBuffer->conout()); + const short visibleCols = std::min(cols, largest.X); + const short visibleRows = std::min(rows, largest.Y); + + { + // Make the window small enough. We want the console frozen during + // this step so we don't accidentally move the window above the cursor. + m_console.setFrozen(true); + const auto info = m_consoleBuffer->bufferInfo(); + const auto &bufferSize = info.dwSize; + const int tmpWindowWidth = std::min(bufferSize.X, visibleCols); + const int tmpWindowHeight = std::min(bufferSize.Y, visibleRows); SmallRect tmpWindowRect( 0, - std::min(origBufferSize.Y - tmpWindowHeight, - origWindowRect.Top), + std::min(bufferSize.Y - tmpWindowHeight, + info.windowRect().Top), tmpWindowWidth, tmpWindowHeight); - if (cursorWasInWindow) { + if (cursorInWindow(info)) { tmpWindowRect = tmpWindowRect.ensureLineIncluded( - origInfo.cursorPosition().Y); + info.cursorPosition().Y); } m_consoleBuffer->moveWindow(tmpWindowRect); + } - // Step 2: resize the buffer. + { + // Resize the buffer to the final desired size. m_console.setFrozen(false); m_consoleBuffer->resizeBuffer(finalBufferSize); } - // Step 3: expand the window to its full size. { + // Expand the window to its full size. m_console.setFrozen(true); const ConsoleScreenBufferInfo info = m_consoleBuffer->bufferInfo(); - const bool cursorWasInWindow = - info.cursorPosition().Y >= info.windowRect().Top && - info.cursorPosition().Y <= info.windowRect().Bottom; SmallRect finalWindowRect( 0, - std::min(info.bufferSize().Y - rows, + std::min(info.bufferSize().Y - visibleRows, info.windowRect().Top), - cols, - rows); + visibleCols, + visibleRows); // // Once a line in the screen buffer is "dirty", it should stay visible @@ -248,12 +289,12 @@ void Scraper::resizeImpl(const ConsoleScreenBufferInfo &origInfo) // unfrozen, so that the *top* of the window is now below the // dirtiest tracked line. finalWindowRect = SmallRect( - 0, m_dirtyLineCount - rows, - cols, rows); + 0, m_dirtyLineCount - visibleRows, + visibleCols, visibleRows); } // Highest priority constraint: ensure that the cursor remains visible. - if (cursorWasInWindow) { + if (cursorInWindow(info)) { finalWindowRect = finalWindowRect.ensureLineIncluded( info.cursorPosition().Y); } diff --git a/src/agent/Win32Console.h b/src/agent/Win32Console.h index 774931d..ed83877 100644 --- a/src/agent/Win32Console.h +++ b/src/agent/Win32Console.h @@ -51,6 +51,8 @@ public: std::wstring title(); void setTitle(const std::wstring &title); void setFreezeUsesMark(bool useMark) { m_freezeUsesMark = useMark; } + void setNewW10(bool isNewW10) { m_isNewW10 = isNewW10; } + bool isNewW10() { return m_isNewW10; } void setFrozen(bool frozen=true); bool frozen() { return m_frozen; } @@ -58,6 +60,7 @@ private: HWND m_hwnd = nullptr; bool m_frozen = false; bool m_freezeUsesMark = false; + bool m_isNewW10 = false; std::vector m_titleWorkBuf; };