void ReadBackHelper(HANDLE hOut, COORD coordReadBackPos, DWORD dwReadBackLength, wistd::unique_ptr<char[]>& pszReadBack) { // Add one so it can be zero terminated. DWORD cbBuffer = dwReadBackLength + 1; wistd::unique_ptr<char[]> pszRead = wil::make_unique_failfast<char[]>(cbBuffer); ZeroMemory(pszRead.get(), cbBuffer * sizeof(char)); DWORD dwRead = 0; VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputCharacterA(hOut, pszRead.get(), dwReadBackLength, coordReadBackPos, &dwRead), L"Read back data in the buffer."); VERIFY_ARE_EQUAL(dwReadBackLength, dwRead, L"Verify API reports we read back the number of characters we asked for."); pszReadBack.swap(pszRead); }
void FileTests::TestWriteFileRaw() { // \x7 is bell // \x8 is backspace // \x9 is tab // \xa is linefeed // \xd is carriage return // All should be ignored/printed in raw mode. PCSTR strTest = "z\x7y\x8z\x9y\xaz\xdy"; DWORD const cchTest = (DWORD)strlen(strTest); String strReadBackExpected(strTest); HANDLE const hOut = GetStdOutputHandle(); VERIFY_IS_NOT_NULL(hOut, L"Verify we have the standard output handle."); CONSOLE_SCREEN_BUFFER_INFOEX csbiexBefore = { 0 }; csbiexBefore.cbSize = sizeof(csbiexBefore); VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(hOut, &csbiexBefore), L"Retrieve screen buffer properties before writing."); VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleMode(hOut, 0), L"Set raw write mode."); COORD const coordZero = { 0 }; VERIFY_ARE_EQUAL(coordZero, csbiexBefore.dwCursorPosition, L"Cursor should be at 0,0 in fresh buffer."); DWORD dwWritten = 0; VERIFY_WIN32_BOOL_SUCCEEDED(WriteFile(hOut, strTest, cchTest, &dwWritten, nullptr), L"Write text into buffer using WriteFile"); VERIFY_ARE_EQUAL(cchTest, dwWritten); CONSOLE_SCREEN_BUFFER_INFOEX csbiexAfter = { 0 }; csbiexAfter.cbSize = sizeof(csbiexAfter); VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleScreenBufferInfoEx(hOut, &csbiexAfter), L"Retrieve screen buffer properties after writing."); csbiexBefore.dwCursorPosition.X += (SHORT)cchTest; VERIFY_ARE_EQUAL(csbiexBefore.dwCursorPosition, csbiexAfter.dwCursorPosition, L"Verify cursor moved expected number of squares for the write length."); DWORD const cbReadBackBuffer = cchTest + 2; // +1 so we can read back a "space" that should be after what we wrote. +1 more so this can be null terminated for String class comparison. wistd::unique_ptr<char[]> strReadBack = wil::make_unique_failfast<char[]>(cbReadBackBuffer); ZeroMemory(strReadBack.get(), cbReadBackBuffer * sizeof(char)); DWORD dwRead = 0; VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleOutputCharacterA(hOut, strReadBack.get(), cchTest + 1, coordZero, &dwRead), L"Read back the data in the buffer."); // +1 to read back the space that should be after the text we wrote strReadBackExpected += " "; // add in the space that should appear after the written text (buffer should be space filled when empty) VERIFY_ARE_EQUAL(strReadBackExpected, String(strReadBack.get()), L"Ensure that the buffer contents match what we expected based on what we wrote."); }
// bBashMargin - sh.exe has pad in one space cell on right edge of window BOOL OnReadConsoleClick(SHORT xPos, SHORT yPos, bool bForce, bool bBashMargin) { TODO("Тут бы нужно еще учитывать, что консоль могла прокрутиться вверх на несколько строк, если был ENABLE_WRAP_AT_EOL_OUTPUT"); TODO("Еще интересно, что будет, если координата начала вдруг окажется за пределами буфера (типа сузили окно, и курсор уехал)"); HANDLE hConIn = NULL; if (!IsPromptActionAllowed(bForce, bBashMargin, &hConIn)) return FALSE; BOOL lbRc = FALSE, lbWrite = FALSE; int nChars = 0; DWORD nWritten = 0, dwLastError = 0; HANDLE hConOut = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO csbi = {}; if (GetConsoleScreenBufferInfo(hConOut, &csbi) && csbi.dwSize.X && csbi.dwSize.Y) { bool bHomeEnd = false; lbRc = TRUE; // When we are outside of standard ReadConsole[A|W] // it's almost impossible to detect where readline was started. // So, it's more safe do not try to go upper lines at all. if (!gReadConsoleInfo.InReadConsoleTID && (yPos < csbi.dwCursorPosition.Y)) { yPos = csbi.dwCursorPosition.Y; } nChars = (csbi.dwSize.X * (yPos - csbi.dwCursorPosition.Y)) + (xPos - csbi.dwCursorPosition.X); if (nChars != 0) { char* pszLine = (char*)malloc(csbi.dwSize.X+1); wchar_t* pwszLine = (wchar_t*)malloc((csbi.dwSize.X+1)*sizeof(*pwszLine)); if (pszLine && pwszLine) { int nChecked = 0; int iCount; DWORD nRead; COORD cr; SHORT nPrevSpaces = 0, nPrevChars = 0; SHORT nWhole = 0, nPrint = 0; bool bDBCS = false; // Если в консоли выбрана DBCS кодировка - там все не просто DWORD nCP = GetConsoleOutputCP(); if (nCP && nCP != CP_UTF7 && nCP != CP_UTF8 && nCP != 1200 && nCP != 1201) { CPINFO cp = {}; if (GetCPInfo(nCP, &cp) && (cp.MaxCharSize > 1)) { bDBCS = true; } } TODO("DBCS!!! Must to convert cursor pos ('DBCS') to char pos!"); // Ok, теперь нужно проверить, не был ли клик сделан "за пределами строки ввода" SHORT y = csbi.dwCursorPosition.Y; while (true) { cr.Y = y; if (nChars > 0) { cr.X = (y == csbi.dwCursorPosition.Y) ? csbi.dwCursorPosition.X : 0; iCount = (y == yPos) ? (xPos - cr.X) : (csbi.dwSize.X - cr.X); if (iCount < 0) break; } else { cr.X = 0; iCount = ((y == csbi.dwCursorPosition.Y) ? csbi.dwCursorPosition.X : csbi.dwSize.X) - ((y == yPos) ? xPos : 0); if (iCount < 0) break; } // Считать строку if (bDBCS) { // На DBCS кодировках "ReadConsoleOutputCharacterW" фигню возвращает BOOL bReadOk = ReadConsoleOutputCharacterA(hConOut, pszLine, iCount, cr, &nRead); dwLastError = GetLastError(); if (!bReadOk || !nRead) { // Однако и ReadConsoleOutputCharacterA может глючить, пробуем "W" bReadOk = ReadConsoleOutputCharacterW(hConOut, pwszLine, iCount, cr, &nRead); dwLastError = GetLastError(); if (!bReadOk || !nRead) break; bDBCS = false; // Thread string as simple Unicode. } else { nRead = MultiByteToWideChar(nCP, 0, pszLine, nRead, pwszLine, csbi.dwSize.X); } // Check chars count if (((int)nRead) <= 0) break; } else { if (!ReadConsoleOutputCharacterW(hConOut, pwszLine, iCount, cr, &nRead) || !nRead) break; } if (nRead > (DWORD)csbi.dwSize.X) { _ASSERTEX(nRead <= (DWORD)csbi.dwSize.X); break; } pwszLine[nRead] = 0; nWhole = nPrint = (SHORT)nRead; // Сначала посмотреть сколько в конце строки пробелов while ((nPrint > 0) && (pwszLine[nPrint-1] == L' ')) { nPrint--; } // В каком направлении идем if (nChars > 0) // Вниз { // Если знаков (не пробелов) больше 0 - учитываем и концевые пробелы предыдущей строки if (nPrint > 0) { nChecked += nPrevSpaces + nPrint; } else { // Если на предыдущей строке значащих символов не было - завершаем if (nPrevChars <= 0) break; } } else // Вверх { if (nPrint <= 0) break; // На первой же пустой строке мы останавливаемся nChecked += nWhole; } nPrevChars = nPrint; nPrevSpaces = nWhole - nPrint; _ASSERTEX(nPrevSpaces>=0); // Цикл + условие if (nChars > 0) { if ((++y) > yPos) break; } else { if ((--y) < yPos) break; } } SafeFree(pszLine); SafeFree(pwszLine); // Changed? nChars = (nChars > 0) ? nChecked : -nChecked; //nChars = (csbi.dwSize.X * (yPos - csbi.dwCursorPosition.Y)) // + (xPos - csbi.dwCursorPosition.X); } } if (nChars != 0) { int nCount = bHomeEnd ? 1 : (nChars < 0) ? (-nChars) : nChars; if (!bHomeEnd && (nCount > (csbi.dwSize.X * (csbi.srWindow.Bottom - csbi.srWindow.Top)))) { bHomeEnd = true; nCount = 1; } INPUT_RECORD* pr = (INPUT_RECORD*)calloc((size_t)nCount,sizeof(*pr)); if (pr != NULL) { WORD vk = bHomeEnd ? ((nChars < 0) ? VK_HOME : VK_END) : ((nChars < 0) ? VK_LEFT : VK_RIGHT); HKL hkl = GetKeyboardLayout(gReadConsoleInfo.InReadConsoleTID ? gReadConsoleInfo.InReadConsoleTID : gReadConsoleInfo.LastReadConsoleInputTID); WORD sc = MapVirtualKeyEx(vk, 0/*MAPVK_VK_TO_VSC*/, hkl); if (!sc) { _ASSERTEX(sc!=NULL && "Can't determine SC?"); sc = (vk == VK_LEFT) ? 0x4B : (vk == VK_RIGHT) ? 0x4D : (vk == VK_HOME) ? 0x47 : (vk == VK_RIGHT) ? 0x4F : 0; } for (int i = 0; i < nCount; i++) { pr[i].EventType = KEY_EVENT; pr[i].Event.KeyEvent.bKeyDown = TRUE; pr[i].Event.KeyEvent.wRepeatCount = 1; pr[i].Event.KeyEvent.wVirtualKeyCode = vk; pr[i].Event.KeyEvent.wVirtualScanCode = sc; pr[i].Event.KeyEvent.dwControlKeyState = ENHANCED_KEY; } while (nCount > 0) { lbWrite = WriteConsoleInputW(hConIn, pr, min(nCount,256), &nWritten); if (!lbWrite || !nWritten) break; nCount -= nWritten; } free(pr); } } } UNREFERENCED_PARAMETER(dwLastError); return lbRc; }
// Консоль любит глючить, при попытках запроса более определенного количества ячеек. // MAX_CONREAD_SIZE подобрано экспериментально BOOL ReadConsoleOutputEx(HANDLE hOut, CHAR_INFO *pData, COORD bufSize, SMALL_RECT rgn) { BOOL lbRc = FALSE; DWORD nTick1 = GetTickCount(), nTick2 = 0, nTick3 = 0, nTick4 = 0, nTick5 = 0; static bool bDBCS = false, bDBCS_Checked = false; if (!bDBCS_Checked) { bDBCS = (GetSystemMetrics(SM_DBCSENABLED) != 0); bDBCS_Checked = true; } bool bDBCS_CP = bDBCS; LPCSTR szLeads = NULL; UINT MaxCharSize = 0; DWORD nCP, nCP1, nMode; if (bDBCS) { nCP = GetConsoleOutputCP(); nCP1 = GetConsoleCP(); GetConsoleMode(hOut, &nMode); szLeads = GetCpInfoLeads(nCP, &MaxCharSize); if (!szLeads || !*szLeads || MaxCharSize < 2) { bDBCS_CP = false; } } size_t nBufWidth = bufSize.X; int nWidth = (rgn.Right - rgn.Left + 1); int nHeight = (rgn.Bottom - rgn.Top + 1); int nCurSize = nWidth * nHeight; _ASSERTE(bufSize.X >= nWidth); _ASSERTE(bufSize.Y >= nHeight); _ASSERTE(rgn.Top>=0 && rgn.Bottom>=rgn.Top); _ASSERTE(rgn.Left>=0 && rgn.Right>=rgn.Left); //MSectionLock RCS; //if (gpSrv->pReqSizeSection && !RCS.Lock(gpSrv->pReqSizeSection, TRUE, 30000)) //{ // _ASSERTE(FALSE); // SetLastError(ERROR_INVALID_PARAMETER); // return FALSE; //} COORD bufCoord = {0,0}; DWORD dwErrCode = 0; nTick2 = GetTickCount(); if (!bDBCS_CP && (nCurSize <= MAX_CONREAD_SIZE)) { if (ReadConsoleOutputW(hOut, pData, bufSize, bufCoord, &rgn)) lbRc = TRUE; nTick3 = GetTickCount(); } if (!lbRc) { // Придется читать построчно // Теоретически - можно и блоками, но оверхед очень маленький, а велик // шанс обломаться, если консоль "глючит". Поэтому построчно... //bufSize.X = TextWidth; bufSize.Y = 1; bufCoord.X = 0; bufCoord.Y = 0; //rgn = gpSrv->sbi.srWindow; int Y1 = rgn.Top; int Y2 = rgn.Bottom; CHAR_INFO* pLine = pData; if (!bDBCS_CP) { for (int y = Y1; y <= Y2; y++, rgn.Top++, pLine+=nBufWidth) { nTick3 = GetTickCount(); rgn.Bottom = rgn.Top; lbRc = ReadConsoleOutputW(hOut, pLine, bufSize, bufCoord, &rgn); if (!lbRc) { dwErrCode = GetLastError(); _ASSERTE(FALSE && "ReadConsoleOutputW failed in MyReadConsoleOutput"); break; } nTick4 = GetTickCount(); } } else { DWORD nAttrsMax = bufSize.X; DWORD nCharsMax = nAttrsMax/* *4 */; // -- максимум там вроде на некоторых CP - 4 байта wchar_t* pszChars = (wchar_t*)malloc(nCharsMax*sizeof(*pszChars)); char* pszCharsA = (char*)malloc(nCharsMax*sizeof(*pszCharsA)); WORD* pnAttrs = (WORD*)malloc(bufSize.X*sizeof(*pnAttrs)); if (pszChars && pszCharsA && pnAttrs) { COORD crRead = {rgn.Left,Y1}; DWORD nChars, nAttrs, nCharsA; CHAR_INFO* pLine = pData; for (; crRead.Y <= Y2; crRead.Y++, pLine+=nBufWidth) { nTick3 = GetTickCount(); rgn.Bottom = rgn.Top; nChars = nCharsA = nAttrs = 0; BOOL lbRcTxt = ReadConsoleOutputCharacterA(hOut, pszCharsA, nCharsMax, crRead, &nCharsA); dwErrCode = GetLastError(); if (!lbRcTxt || !nCharsA) { nCharsA = 0; lbRcTxt = ReadConsoleOutputCharacterW(hOut, pszChars, nCharsMax, crRead, &nChars); dwErrCode = GetLastError(); } BOOL lbRcAtr = ReadConsoleOutputAttribute(hOut, pnAttrs, bufSize.X, crRead, &nAttrs); dwErrCode = GetLastError(); lbRc = lbRcTxt && lbRcAtr; if (!lbRc) { dwErrCode = GetLastError(); _ASSERTE(FALSE && "ReadConsoleOutputAttribute failed in MyReadConsoleOutput"); CHAR_INFO* p = pLine; for (size_t i = 0; i < nAttrsMax; ++i, ++p) { p->Attributes = 4; // red on black p->Char.UnicodeChar = 0xFFFE; // not a character } break; } else { if (nCharsA) { nChars = MultiByteToWideChar(nCP, 0, pszCharsA, nCharsA, pszChars, nCharsMax); } CHAR_INFO* p = pLine; wchar_t* psz = pszChars; WORD* pn = pnAttrs; //int i = nAttrsMax; //while ((i--) > 0) //{ // p->Attributes = *(pn++); // p->Char.UnicodeChar = *(psz++); // p++; //} size_t x1 = min(nChars,nAttrsMax); size_t x2 = nAttrsMax; for (size_t i = 0; i < x1; ++i, ++p) { p->Attributes = *pn; p->Char.UnicodeChar = *psz; WARNING("Некорректно! pn может указывать на начало блока DBCS/QBCS"); pn++; // += MaxCharSize; psz++; } WORD nLastAttr = pnAttrs[max(0,(int)nAttrs-1)]; for (size_t i = x1; i < x2; ++i, ++p) { p->Attributes = nLastAttr; p->Char.UnicodeChar = L' '; } } nTick4 = GetTickCount(); } } SafeFree(pszChars); SafeFree(pszCharsA); SafeFree(pnAttrs); } nTick5 = GetTickCount(); } UNREFERENCED_PARAMETER(nTick1); UNREFERENCED_PARAMETER(nTick2); UNREFERENCED_PARAMETER(nTick3); UNREFERENCED_PARAMETER(nTick4); UNREFERENCED_PARAMETER(nTick5); return lbRc; }