diff --git a/agent/Agent.cc b/agent/Agent.cc index ef7e138..29e4ae4 100644 --- a/agent/Agent.cc +++ b/agent/Agent.cc @@ -38,7 +38,6 @@ const int SC_CONSOLE_MARK = 0xFFF2; const int SC_CONSOLE_SELECT_ALL = 0xFFF5; -const int SYNC_MARKER_LEN = 16; static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType) { @@ -74,7 +73,9 @@ Agent::Agent(LPCWSTR controlPipeName, m_terminal(NULL), m_childProcess(NULL), m_childExitCode(-1), - m_syncCounter(0) + m_syncCounter(0), + m_directMode(false), + m_ptySize(initialCols, initialRows) { trace("Agent starting..."); @@ -82,16 +83,16 @@ Agent::Agent(LPCWSTR controlPipeName, m_console = new Win32Console; m_console->setSmallFont(); - m_console->reposition( - Coord(initialCols, BUFFER_LINE_COUNT), - SmallRect(0, 0, initialCols, initialRows)); + m_console->moveWindow(SmallRect(0, 0, 1, 1)); + m_console->resizeBuffer(Coord(initialCols, BUFFER_LINE_COUNT)); + m_console->moveWindow(SmallRect(0, 0, initialCols, initialRows)); m_console->setCursorPosition(Coord(0, 0)); m_console->setTitle(m_currentTitle); m_controlSocket = makeSocket(controlPipeName); m_dataSocket = makeSocket(dataPipeName); m_terminal = new Terminal(m_dataSocket); - m_consoleInput = new ConsoleInput(m_console, this); + m_consoleInput = new ConsoleInput(this); resetConsoleTracking(false); @@ -196,6 +197,11 @@ void Agent::handlePacket(ReadBuffer &packet) result = handleStartProcessPacket(packet); break; case AgentMsg::SetSize: + // TODO: I think it might make sense to collapse consecutive SetSize + // messages. i.e. The terminal process can probably generate SetSize + // messages faster than they can be processed, and some GUIs might + // generate a flood of them, so if we can read multiple SetSize packets + // at once, we can ignore the early ones. result = handleSetSizePacket(packet); break; case AgentMsg::GetExitCode: @@ -314,8 +320,9 @@ void Agent::onPollTimeout() // Scrape for output *after* the above exit-check to ensure that we collect // the child process's final output. - if (!m_dataSocket->isClosed()) - scrapeOutput(); + if (!m_dataSocket->isClosed()) { + syncConsoleContentAndSize(false); + } if (m_closingDataSocket && !m_dataSocket->isClosed() && @@ -328,9 +335,8 @@ void Agent::onPollTimeout() // Detect window movement. If the window moves down (presumably as a // result of scrolling), then assume that all screen buffer lines down to // the bottom of the window are dirty. -void Agent::markEntireWindowDirty() +void Agent::markEntireWindowDirty(const SmallRect &windowRect) { - SmallRect windowRect = m_console->windowRect(); m_dirtyLineCount = std::max(m_dirtyLineCount, windowRect.top() + windowRect.height()); } @@ -366,40 +372,182 @@ void Agent::scanForDirtyLines() } } -void Agent::resizeWindow(int cols, int rows) +// Clear lines in the line buffer. The `firstRow` parameter is in +// screen-buffer coordinates. +void Agent::clearBufferLines( + const int firstRow, + const int count, + const WORD attributes) { - freezeConsole(); + ASSERT(!m_directMode); + CHAR_INFO blankLine[MAX_CONSOLE_WIDTH]; + memset(&blankLine, 0, sizeof(blankLine)); + for (int col = 0; col < MAX_CONSOLE_WIDTH; ++col) { + blankLine[col].Char.UnicodeChar = L' '; + blankLine[col].Attributes = attributes; + } + for (int row = firstRow; row < firstRow + count; ++row) { + int bufRow = (row + m_scrolledCount) % BUFFER_LINE_COUNT; + memcpy(&m_bufferData[bufRow], &blankLine, sizeof(blankLine)); + } +} - Coord bufferSize = m_console->bufferSize(); - SmallRect windowRect = m_console->windowRect(); - Coord newBufferSize(cols, bufferSize.Y); - SmallRect newWindowRect; +// This function is called with the console frozen, and the console is still +// frozen when it returns. +void Agent::resizeImpl(const ConsoleScreenBufferInfo &origInfo) +{ + const int cols = m_ptySize.X; + const int rows = m_ptySize.Y; - // This resize behavior appears to match what happens when I resize the - // console window by hand. - if (windowRect.top() + windowRect.height() == bufferSize.Y || - windowRect.top() + rows >= bufferSize.Y) { - // Lock the bottom of the new window to the bottom of the buffer if either - // - the window was already at the bottom of the buffer, OR - // - there isn't enough room. - newWindowRect = SmallRect(0, newBufferSize.Y - rows, cols, rows); - } else { - // Keep the top of the window where it is. - newWindowRect = SmallRect(0, windowRect.top(), cols, rows); + { + // + // To accommodate Windows 10, erase all lines up to the top of the + // visible window. It's hard to tell whether this is strictly + // necessary. It ensures that the sync marker won't move downward, + // and it ensures that we won't repeat lines that have already scrolled + // up into the scrollback. + // + // It *is* possible for these blank lines to reappear in the visible + // window (e.g. if the window is made taller), but because we blanked + // the lines in the line buffer, we still don't output them again. + // + const Coord origBufferSize = origInfo.bufferSize(); + const SmallRect origWindowRect = origInfo.windowRect(); + + if (!m_directMode) { + m_console->clearLines(0, origWindowRect.Top, origInfo); + clearBufferLines(0, origWindowRect.Top, origInfo.wAttributes); + if (m_syncRow != -1) { + createSyncMarker(m_syncRow); + } + } + + const Coord finalBufferSize( + cols, + // If there was previously no scrollback (e.g. a full-screen app + // in direct mode) and we're reducing the window height, then + // reduce the console buffer's height too. + (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); + SmallRect tmpWindowRect( + 0, + std::min(origBufferSize.Y - tmpWindowHeight, + origWindowRect.Top), + tmpWindowWidth, + tmpWindowHeight); + if (cursorWasInWindow) { + tmpWindowRect = tmpWindowRect.ensureLineIncluded( + origInfo.cursorPosition().Y); + } + m_console->moveWindow(tmpWindowRect); + + // Step 2: resize the buffer. + unfreezeConsole(); + m_console->resizeBuffer(finalBufferSize); } - if (m_dirtyWindowTop != -1 && m_dirtyWindowTop < windowRect.top()) - markEntireWindowDirty(); - m_dirtyWindowTop = newWindowRect.top(); + // Step 3: expand the window to its full size. + { + freezeConsole(); + const ConsoleScreenBufferInfo info = m_console->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, + info.windowRect().Top), + cols, + rows); + + // + // Once a line in the screen buffer is "dirty", it should stay visible + // in the console window, so that we continue to update its content in + // the terminal. This code is particularly (only?) necessary on + // Windows 10, where making the buffer wider can rewrap lines and move + // the console window upward. + // + if (!m_directMode && m_dirtyLineCount > finalWindowRect.Bottom + 1) { + // In theory, we avoid ensureLineIncluded, because, a massive + // amount of output could have occurred while the console was + // unfrozen, so that the *top* of the window is now below the + // dirtiest tracked line. + finalWindowRect = SmallRect( + 0, m_dirtyLineCount - rows, + cols, rows); + } + + // Highest priority constraint: ensure that the cursor remains visible. + if (cursorWasInWindow) { + finalWindowRect = finalWindowRect.ensureLineIncluded( + info.cursorPosition().Y); + } + + m_console->moveWindow(finalWindowRect); + m_dirtyWindowTop = finalWindowRect.Top; + } +} + +void Agent::resizeWindow(const int cols, const int rows) +{ + if (cols < 1 || + cols > MAX_CONSOLE_WIDTH || + rows < 1 || + rows > BUFFER_LINE_COUNT - 1) { + trace("resizeWindow: invalid size: cols=%d,rows=%d", cols, rows); + return; + } + m_ptySize = Coord(cols, rows); + syncConsoleContentAndSize(true); +} + +void Agent::syncConsoleContentAndSize(bool forceResize) +{ + reopenConsole(); + freezeConsole(); + syncConsoleTitle(); + + const ConsoleScreenBufferInfo info = m_console->bufferInfo(); + + // If an app resizes the buffer height, then we enter "direct mode", where + // we stop trying to track incremental console changes. + const bool newDirectMode = (info.bufferSize().Y != BUFFER_LINE_COUNT); + if (newDirectMode != m_directMode) { + trace("Entering %s mode", newDirectMode ? "direct" : "scrolling"); + resetConsoleTracking(); + m_directMode = newDirectMode; + + // When we switch from direct->scrolling mode, make sure the console is + // the right size. + if (!m_directMode) { + forceResize = true; + } + } + + if (m_directMode) { + directScrapeOutput(info); + } else { + scrollingScrapeOutput(info); + } + + if (forceResize) { + resizeImpl(info); + } - m_console->reposition(newBufferSize, newWindowRect); unfreezeConsole(); } -void Agent::scrapeOutput() +void Agent::syncConsoleTitle() { - freezeConsole(); - std::wstring newTitle = m_console->title(); if (newTitle != m_currentTitle) { std::string command = std::string("\x1b]0;") + @@ -407,9 +555,47 @@ void Agent::scrapeOutput() m_dataSocket->write(command.c_str()); m_currentTitle = newTitle; } +} - const Coord cursor = m_console->cursorPosition(); +void Agent::directScrapeOutput(const ConsoleScreenBufferInfo &info) +{ + const Coord cursor = info.cursorPosition(); const SmallRect windowRect = m_console->windowRect(); + const int stopLine = std::min(windowRect.height(), m_ptySize.Y); + bool sawModifiedLine = false; + + for (int line = 0; line < stopLine; ++line) { + CHAR_INFO curLine[MAX_CONSOLE_WIDTH]; // TODO: bufoverflow + const int w = std::min(windowRect.width(), m_ptySize.X); + m_console->read(SmallRect(0, windowRect.top() + line, w, 1), curLine); + + // TODO: The memcpy can overflow the m_bufferData buffer. + CHAR_INFO (&bufLine)[MAX_CONSOLE_WIDTH] = + m_bufferData[line % BUFFER_LINE_COUNT]; + if (sawModifiedLine || + line > m_maxBufferedLine || + memcmp(curLine, bufLine, sizeof(CHAR_INFO) * w) != 0) { + //trace("sent line %d", line); + m_terminal->sendLine(line, curLine, windowRect.width()); + memset(bufLine, 0, sizeof(bufLine)); + memcpy(bufLine, curLine, sizeof(CHAR_INFO) * w); + for (int col = w; col < MAX_CONSOLE_WIDTH; ++col) { + bufLine[col].Attributes = curLine[w - 1].Attributes; + bufLine[col].Char.UnicodeChar = ' '; + } + m_maxBufferedLine = std::max(m_maxBufferedLine, line); + sawModifiedLine = true; + } + } + + m_terminal->finishOutput(std::pair(cursor.X, + cursor.Y - windowRect.top())); +} + +void Agent::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info) +{ + const Coord cursor = info.cursorPosition(); + const SmallRect windowRect = info.windowRect(); if (m_syncRow != -1) { // If a synchronizing marker was placed into the history, look for it @@ -426,7 +612,7 @@ void Agent::scrapeOutput() m_scrolledCount += (m_syncRow - markerRow); m_syncRow = markerRow; // If the buffer has scrolled, then the entire window is dirty. - markEntireWindowDirty(); + markEntireWindowDirty(windowRect); } } @@ -438,7 +624,7 @@ void Agent::scrapeOutput() if (m_dirtyWindowTop != -1) { if (windowRect.top() > m_dirtyWindowTop) { // The window has moved down, presumably as a result of scrolling. - markEntireWindowDirty(); + markEntireWindowDirty(windowRect); } else if (windowRect.top() < m_dirtyWindowTop) { // The window has moved upward. This is generally not expected to // happen, but the CMD/PowerShell CLS command will move the window @@ -457,10 +643,10 @@ void Agent::scrapeOutput() // Note that it's possible for all the lines on the current window to // be non-dirty. - int firstLine = std::min(m_scrapedLineCount, - windowRect.top() + m_scrolledCount); - int stopLine = std::min(m_dirtyLineCount, - windowRect.top() + windowRect.height()) + + const int firstLine = std::min(m_scrapedLineCount, + windowRect.top() + m_scrolledCount); + const int stopLine = std::min(m_dirtyLineCount, + windowRect.top() + windowRect.height()) + m_scrolledCount; bool sawModifiedLine = false; @@ -468,22 +654,26 @@ void Agent::scrapeOutput() for (int line = firstLine; line < stopLine; ++line) { CHAR_INFO curLine[MAX_CONSOLE_WIDTH]; // TODO: bufoverflow const int w = windowRect.width(); + ASSERT(w >= 1 && w <= MAX_CONSOLE_WIDTH); m_console->read(SmallRect(0, line - m_scrolledCount, w, 1), curLine); + CHAR_INFO trailing; + memset(&trailing, 0, sizeof(trailing)); + trailing.Attributes = curLine[w - 1].Attributes; + trailing.Char.UnicodeChar = L' '; + for (int col = w; col < MAX_CONSOLE_WIDTH; ++col) { + curLine[col] = trailing; + } + // TODO: The memcpy can overflow the m_bufferData buffer. CHAR_INFO (&bufLine)[MAX_CONSOLE_WIDTH] = m_bufferData[line % BUFFER_LINE_COUNT]; if (sawModifiedLine || line > m_maxBufferedLine || - memcmp(curLine, bufLine, sizeof(CHAR_INFO) * w) != 0) { + memcmp(curLine, bufLine, sizeof(curLine)) != 0) { //trace("sent line %d", line); m_terminal->sendLine(line, curLine, windowRect.width()); - memset(bufLine, 0, sizeof(bufLine)); - memcpy(bufLine, curLine, sizeof(CHAR_INFO) * w); - for (int col = w; col < MAX_CONSOLE_WIDTH; ++col) { - bufLine[col].Attributes = curLine[w - 1].Attributes; - bufLine[col].Char.UnicodeChar = L' '; - } + memcpy(bufLine, curLine, sizeof(curLine)); m_maxBufferedLine = std::max(m_maxBufferedLine, line); sawModifiedLine = true; } @@ -501,8 +691,14 @@ void Agent::scrapeOutput() m_terminal->finishOutput(std::pair(cursor.X, cursor.Y + m_scrolledCount)); +} - unfreezeConsole(); +void Agent::reopenConsole() +{ + // Reopen CONOUT. The application may have changed the active screen + // buffer. (See https://github.com/rprichard/winpty/issues/34) + delete m_console; + m_console = new Win32Console(); } void Agent::freezeConsole() @@ -553,7 +749,8 @@ void Agent::createSyncMarker(int row) // Clear the lines around the marker to ensure that Windows 10's rewrapping // does not affect the marker. - m_console->clearLines(row - 1, SYNC_MARKER_LEN + 1); + m_console->clearLines(row - 1, SYNC_MARKER_LEN + 1, + m_console->bufferInfo()); // Write a new marker. m_syncCounter++; diff --git a/agent/Agent.h b/agent/Agent.h index 2540167..eddcd4b 100644 --- a/agent/Agent.h +++ b/agent/Agent.h @@ -25,15 +25,19 @@ #include #include "EventLoop.h" #include "DsrSender.h" +#include "Coord.h" +#include "SmallRect.h" class Win32Console; class ConsoleInput; class Terminal; class ReadBuffer; class NamedPipe; +struct ConsoleScreenBufferInfo; const int BUFFER_LINE_COUNT = 3000; // TODO: Use something like 9000. const int MAX_CONSOLE_WIDTH = 500; +const int SYNC_MARKER_LEN = 16; class Agent : public EventLoop, public DsrSender { @@ -61,10 +65,16 @@ protected: virtual void onPipeIo(NamedPipe *namedPipe); private: - void markEntireWindowDirty(); + void markEntireWindowDirty(const SmallRect &windowRect); void scanForDirtyLines(); + void clearBufferLines(int firstRow, int count, WORD attributes); + void resizeImpl(const ConsoleScreenBufferInfo &origInfo); void resizeWindow(int cols, int rows); - void scrapeOutput(); + void syncConsoleContentAndSize(bool forceResize); + void syncConsoleTitle(); + void directScrapeOutput(const ConsoleScreenBufferInfo &info); + void scrollingScrapeOutput(const ConsoleScreenBufferInfo &info); + void reopenConsole(); void freezeConsole(); void unfreezeConsole(); void syncMarkerText(CHAR_INFO *output); @@ -84,6 +94,8 @@ private: int m_syncRow; int m_syncCounter; + bool m_directMode; + Coord m_ptySize; int m_scrapedLineCount; int m_scrolledCount; int m_maxBufferedLine; diff --git a/agent/ConsoleInput.cc b/agent/ConsoleInput.cc index 7673249..cb4e861 100644 --- a/agent/ConsoleInput.cc +++ b/agent/ConsoleInput.cc @@ -61,8 +61,8 @@ ConsoleInput::KeyDescriptor ConsoleInput::keyDescriptorTable[] = { { ESC"OF", VK_END, 0, 0, }, // gnome-terminal }; -ConsoleInput::ConsoleInput(Win32Console *console, DsrSender *dsrSender) : - m_console(console), +ConsoleInput::ConsoleInput(DsrSender *dsrSender) : + m_console(new Win32Console), m_dsrSender(dsrSender), m_dsrSent(false), lastWriteTick(0) @@ -166,6 +166,11 @@ ConsoleInput::ConsoleInput(Win32Console *console, DsrSender *dsrSender) : } } +ConsoleInput::~ConsoleInput() +{ + delete m_console; +} + void ConsoleInput::writeInput(const std::string &input) { trace("writeInput: %d bytes", input.size()); diff --git a/agent/ConsoleInput.h b/agent/ConsoleInput.h index 4c3e845..00f0abf 100644 --- a/agent/ConsoleInput.h +++ b/agent/ConsoleInput.h @@ -31,7 +31,8 @@ class DsrSender; class ConsoleInput { public: - ConsoleInput(Win32Console *console, DsrSender *dsrSender); + ConsoleInput(DsrSender *dsrSender); + ~ConsoleInput(); void writeInput(const std::string &input); void flushIncompleteEscapeCode(); diff --git a/agent/SmallRect.h b/agent/SmallRect.h index c33f536..e641f3e 100644 --- a/agent/SmallRect.h +++ b/agent/SmallRect.h @@ -85,6 +85,18 @@ struct SmallRect : SMALL_RECT std::max(0, y2 - y1 + 1)); } + SmallRect ensureLineIncluded(SHORT line) const + { + const SHORT h = height(); + if (line < Top) { + return SmallRect(Left, line, width(), h); + } else if (line > Bottom) { + return SmallRect(Left, line - h + 1, width(), h); + } else { + return *this; + } + } + SHORT top() const { return Top; } SHORT left() const { return Left; } SHORT width() const { return Right - Left + 1; } diff --git a/agent/Terminal.cc b/agent/Terminal.cc index 1b04060..d1cdfd4 100644 --- a/agent/Terminal.cc +++ b/agent/Terminal.cc @@ -65,8 +65,11 @@ void Terminal::setConsoleMode(int mode) void Terminal::reset(bool sendClearFirst, int newLine) { - if (sendClearFirst && !m_consoleMode) + if (sendClearFirst && !m_consoleMode) { + // 1;1H ==> move cursor to top-left position + // 2J ==> clear the entire screen m_output->write(CSI"1;1H"CSI"2J"); + } m_remoteLine = newLine; m_cursorHidden = false; m_cursorPos = std::pair(0, newLine); diff --git a/agent/Win32Console.cc b/agent/Win32Console.cc index 53cce32..5c008a9 100644 --- a/agent/Win32Console.cc +++ b/agent/Win32Console.cc @@ -55,12 +55,15 @@ namespace { Win32Console::Win32Console() : m_titleWorkBuf(16) { m_conin = GetStdHandle(STD_INPUT_HANDLE); - m_conout = GetStdHandle(STD_OUTPUT_HANDLE); + m_conout = CreateFileW(L"CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + ASSERT(m_conout != INVALID_HANDLE_VALUE); } Win32Console::~Win32Console() { - CloseHandle(m_conin); CloseHandle(m_conout); } @@ -314,14 +317,12 @@ void Win32Console::dumpConsoleFont(const char *prefix) } } -void Win32Console::clearLines(int row, int count) +void Win32Console::clearLines( + int row, + int count, + const ConsoleScreenBufferInfo &info) { // TODO: error handling - CONSOLE_SCREEN_BUFFER_INFO info; - memset(&info, 0, sizeof(info)); - if (!GetConsoleScreenBufferInfo(m_conout, &info)) { - trace("GetConsoleScreenBufferInfo failed"); - } const int width = SmallRect(info.srWindow).width(); DWORD actual = 0; if (!FillConsoleOutputCharacterW( @@ -372,42 +373,6 @@ void Win32Console::moveWindow(const SmallRect &rect) } } -void Win32Console::reposition(const Coord &newBufferSize, - const SmallRect &newWindowRect) -{ - // Windows has one API for resizing the screen buffer and a different one - // for resizing the window. It seems that either API can fail if the - // window does not fit on the screen buffer. - - const SmallRect origWindowRect(windowRect()); - const SmallRect origBufferRect(Coord(), bufferSize()); - - ASSERT(!newBufferSize.isEmpty()); - SmallRect bufferRect(Coord(), newBufferSize); - ASSERT(bufferRect.contains(newWindowRect)); - - SmallRect tempWindowRect = origWindowRect.intersected(bufferRect); - if (tempWindowRect.width() <= 0) { - tempWindowRect.setLeft(newBufferSize.X - 1); - tempWindowRect.setWidth(1); - } - if (tempWindowRect.height() <= 0) { - tempWindowRect.setTop(newBufferSize.Y - 1); - tempWindowRect.setHeight(1); - } - - // Alternatively, if we can immediately use the new window size, - // do that instead. - if (origBufferRect.contains(newWindowRect)) - tempWindowRect = newWindowRect; - - if (tempWindowRect != origWindowRect) - moveWindow(tempWindowRect); - resizeBuffer(newBufferSize); - if (newWindowRect != tempWindowRect) - moveWindow(newWindowRect); -} - Coord Win32Console::cursorPosition() { return bufferInfo().dwCursorPosition; diff --git a/agent/Win32Console.h b/agent/Win32Console.h index c7830e3..e250bd8 100644 --- a/agent/Win32Console.h +++ b/agent/Win32Console.h @@ -35,15 +35,9 @@ struct ConsoleScreenBufferInfo : CONSOLE_SCREEN_BUFFER_INFO memset(this, 0, sizeof(*this)); } - Coord bufferSize() const - { - return dwSize; - } - - SmallRect windowRect() const - { - return srWindow; - } + Coord bufferSize() const { return dwSize; } + SmallRect windowRect() const { return srWindow; } + Coord cursorPosition() const { return dwCursorPosition; } }; class Win32Console @@ -63,7 +57,7 @@ private: bool setSmallConsoleFontVista(); void dumpConsoleFont(const char *prefix); public: - void clearLines(int row, int count); + void clearLines(int row, int count, const ConsoleScreenBufferInfo &info); // Buffer and window sizes. ConsoleScreenBufferInfo bufferInfo(); @@ -71,7 +65,6 @@ public: SmallRect windowRect(); void resizeBuffer(const Coord &size); void moveWindow(const SmallRect &rect); - void reposition(const Coord &bufferSize, const SmallRect &windowRect); // Cursor. Coord cursorPosition(); diff --git a/misc/BufferResizeTests.cc b/misc/BufferResizeTests.cc new file mode 100755 index 0000000..c2ded1a --- /dev/null +++ b/misc/BufferResizeTests.cc @@ -0,0 +1,91 @@ +#include +#include + +#include "../shared/DebugClient.cc" +#include "TestUtil.cc" + +void dumpInfoToTrace() { + CONSOLE_SCREEN_BUFFER_INFO info; + assert(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)); + trace("win=(%d,%d,%d,%d)", + (int)info.srWindow.Left, + (int)info.srWindow.Top, + (int)info.srWindow.Right, + (int)info.srWindow.Bottom); + trace("buf=(%d,%d)", + (int)info.dwSize.X, + (int)info.dwSize.Y); + trace("cur=(%d,%d)", + (int)info.dwCursorPosition.X, + (int)info.dwCursorPosition.Y); +} + +int main(int argc, char *argv[]) { + if (argc == 1) { + startChildProcess(L"CHILD"); + return 0; + } + + setWindowPos(0, 0, 1, 1); + + if (false) { + // Reducing the buffer height can move the window up. + setBufferSize(80, 25); + setWindowPos(0, 20, 80, 5); + Sleep(2000); + setBufferSize(80, 10); + } + + if (false) { + // Reducing the buffer height moves the window up and the buffer + // contents up too. + setBufferSize(80, 25); + setWindowPos(0, 20, 80, 5); + setCursorPos(0, 20); + printf("TEST1\nTEST2\nTEST3\nTEST4\n"); + fflush(stdout); + Sleep(2000); + setBufferSize(80, 10); + } + + if (false) { + // Reducing the buffer width can move the window left. + setBufferSize(80, 25); + setWindowPos(40, 0, 40, 25); + Sleep(2000); + setBufferSize(60, 25); + } + + if (false) { + // Sometimes the buffer contents are shifted up; sometimes they're + // shifted down. It seems to depend on the cursor position? + + // setBufferSize(80, 25); + // setWindowPos(0, 20, 80, 5); + // setCursorPos(0, 20); + // printf("TESTa\nTESTb\nTESTc\nTESTd\nTESTe"); + // fflush(stdout); + // setCursorPos(0, 0); + // printf("TEST1\nTEST2\nTEST3\nTEST4\nTEST5"); + // fflush(stdout); + // setCursorPos(0, 24); + // Sleep(5000); + // setBufferSize(80, 24); + + setBufferSize(80, 20); + setWindowPos(0, 10, 80, 10); + setCursorPos(0, 18); + + printf("TEST1\nTEST2"); + fflush(stdout); + setCursorPos(0, 18); + + Sleep(2000); + setBufferSize(80, 18); + } + + dumpInfoToTrace(); + Sleep(30000); + + return 0; +} diff --git a/misc/ChangeScreenBuffer.cc b/misc/ChangeScreenBuffer.cc new file mode 100755 index 0000000..7308e7a --- /dev/null +++ b/misc/ChangeScreenBuffer.cc @@ -0,0 +1,54 @@ +// A test program for CreateConsoleScreenBuffer / SetConsoleActiveScreenBuffer +// + +#include +#include +#include +#include +#include + +#include "../shared/DebugClient.cc" +#include "TestUtil.cc" + +int main() +{ + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE childBuffer = CreateConsoleScreenBuffer( + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, CONSOLE_TEXTMODE_BUFFER, NULL); + + SetConsoleActiveScreenBuffer(childBuffer); + + while (true) { + char buf[1024]; + CONSOLE_SCREEN_BUFFER_INFO info; + + assert(GetConsoleScreenBufferInfo(origBuffer, &info)); + trace("child.size=(%d,%d)", (int)info.dwSize.X, (int)info.dwSize.Y); + trace("child.cursor=(%d,%d)", (int)info.dwCursorPosition.X, (int)info.dwCursorPosition.Y); + trace("child.window=(%d,%d,%d,%d)", + (int)info.srWindow.Left, (int)info.srWindow.Top, + (int)info.srWindow.Right, (int)info.srWindow.Bottom); + trace("child.maxSize=(%d,%d)", (int)info.dwMaximumWindowSize.X, (int)info.dwMaximumWindowSize.Y); + + int ch = getch(); + sprintf(buf, "%02x\n", ch); + DWORD actual = 0; + WriteFile(childBuffer, buf, strlen(buf), &actual, NULL); + if (ch == 0x1b/*ESC*/ || ch == 0x03/*CTRL-C*/) + break; + + if (ch == 'b') { + setBufferSize(origBuffer, 40, 25); + } else if (ch == 'w') { + setWindowPos(origBuffer, 1, 1, 38, 23); + } else if (ch == 'c') { + setCursorPos(origBuffer, 10, 10); + } + } + + SetConsoleActiveScreenBuffer(origBuffer); + + return 0; +} diff --git a/misc/DumpLines.py b/misc/DumpLines.py new file mode 100755 index 0000000..4004996 --- /dev/null +++ b/misc/DumpLines.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +import sys + +for i in range(1, int(sys.argv[1]) + 1): + print i, "X" * 78 diff --git a/misc/SetCursorPos.cc b/misc/SetCursorPos.cc new file mode 100755 index 0000000..56ed649 --- /dev/null +++ b/misc/SetCursorPos.cc @@ -0,0 +1,11 @@ +#include + +#include "TestUtil.cc" +#include "../shared/DebugClient.cc" + +int main(int argc, char *argv[]) { + int col = atoi(argv[1]); + int row = atoi(argv[2]); + setCursorPos(col, row); + return 0; +} diff --git a/misc/TestUtil.cc b/misc/TestUtil.cc index d33bac8..c8ff0ed 100755 --- a/misc/TestUtil.cc +++ b/misc/TestUtil.cc @@ -29,26 +29,35 @@ static void startChildProcess(const wchar_t *args) { &sui, &pi); } -static void setBufferSize(int x, int y) { +static void setBufferSize(HANDLE conout, int x, int y) { COORD size = { x, y }; - HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); BOOL success = SetConsoleScreenBufferSize(conout, size); trace("setBufferSize: (%d,%d), result=%d", x, y, success); } -static void setWindowPos(int x, int y, int w, int h) { +static void setWindowPos(HANDLE conout, int x, int y, int w, int h) { SMALL_RECT r = { x, y, x + w - 1, y + h - 1 }; - HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); BOOL success = SetConsoleWindowInfo(conout, /*bAbsolute=*/TRUE, &r); trace("setWindowPos: (%d,%d,%d,%d), result=%d", x, y, w, h, success); } -static void setCursorPos(int x, int y) { +static void setCursorPos(HANDLE conout, int x, int y) { COORD coord = { x, y }; - HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleCursorPosition(conout, coord); } +static void setBufferSize(int x, int y) { + setBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), x, y); +} + +static void setWindowPos(int x, int y, int w, int h) { + setWindowPos(GetStdHandle(STD_OUTPUT_HANDLE), x, y, w, h); +} + +static void setCursorPos(int x, int y) { + setCursorPos(GetStdHandle(STD_OUTPUT_HANDLE), x, y); +} + static void countDown(int sec) { for (int i = sec; i > 0; --i) { printf("%d.. ", i); @@ -58,7 +67,7 @@ static void countDown(int sec) { printf("\n"); } -static void fillBox(int x, int y, int w, int h, char ch, int attributes=7) { +static void writeBox(int x, int y, int w, int h, char ch, int attributes=7) { CHAR_INFO info = { 0 }; info.Char.AsciiChar = ch; info.Attributes = attributes; @@ -71,7 +80,15 @@ static void fillBox(int x, int y, int w, int h, char ch, int attributes=7) { } static void setChar(int x, int y, char ch, int attributes=7) { - fillBox(x, y, 1, 1, ch, attributes); + writeBox(x, y, 1, 1, ch, attributes); +} + +static void fillChar(int x, int y, int repeat, char ch) { + COORD coord = { x, y }; + DWORD actual = 0; + FillConsoleOutputCharacterA( + GetStdHandle(STD_OUTPUT_HANDLE), + ch, repeat, coord, &actual); } static void repeatChar(int count, char ch) { diff --git a/misc/Win10WrapTest2.cc b/misc/Win10WrapTest2.cc index ce5ea4b..7fd2b33 100755 --- a/misc/Win10WrapTest2.cc +++ b/misc/Win10WrapTest2.cc @@ -25,7 +25,7 @@ int main(int argc, char *argv[]) { repeatChar(WIDTH * 5, '.'); repeatChar(10, '\n'); setWindowPos(0, 20, WIDTH, 20); - fillBox(0, 5, 1, 10, '|'); + writeBox(0, 5, 1, 10, '|'); Sleep(120000); }