static int GetWordFromConsolePack(TConsoleParams *params) { int WordLen=0; HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); if (hStdOut != INVALID_HANDLE_VALUE) { CONSOLE_SCREEN_BUFFER_INFO csbi; if (GetConsoleScreenBufferInfo(hStdOut, &csbi)) { COORD CurPos; CurPos.X = csbi.srWindow.Left + (SHORT)(params->Pt.x * (csbi.srWindow.Right - csbi.srWindow.Left + 1) / params->ClientRect.right); CurPos.Y = csbi.srWindow.Top + (SHORT)(params->Pt.y * (csbi.srWindow.Bottom - csbi.srWindow.Top + 1) / params->ClientRect.bottom); if ((CurPos.X >= 0) && (CurPos.X <= csbi.dwSize.X - 1) && (CurPos.Y >= 0) && (CurPos.Y <= csbi.dwSize.Y - 1)) { int BegPos; WCHAR *Buf; params->BeginPos = CurPos.X; CurPos.X = 0; Buf = GlobalAlloc(GMEM_FIXED, (csbi.dwSize.X + 1)*sizeof(WCHAR)); if (Buf) { DWORD ActualRead; if ((ReadConsoleOutputCharacterW(hStdOut, Buf, csbi.dwSize.X, CurPos, &ActualRead)) && (ActualRead == (DWORD)csbi.dwSize.X)) { BegPos=0; WordLen=ActualRead; if(WordLen>85) { while(params->BeginPos-BegPos>43 && WordLen>85) { BegPos++; WordLen--; } if(WordLen>85) WordLen=85; params->BeginPos -= BegPos; } if(WordLen) { memset(params->Buffer, 0, sizeof(params->Buffer)); lstrcpynW(params->Buffer, Buf + BegPos, WordLen+1); } } GlobalFree(Buf); } } } } return WordLen; }
// 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; }
BOOL OnPromptBsDeleteWord(bool bForce, bool bBashMargin) { HANDLE hConIn = NULL; if (!IsPromptActionAllowed(bForce, bBashMargin, &hConIn)) { BsDelWordMsg(L"Skipped due to !IsPromptActionAllowed!"); return FALSE; } int iBSCount = 0; BOOL lbWrite = FALSE; DWORD dwLastError = 0; HANDLE hConOut = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO csbi = {}; if (GetConsoleScreenBufferInfo(hConOut, &csbi) && csbi.dwSize.X && csbi.dwSize.Y) { if (csbi.dwCursorPosition.X == 0) { iBSCount = 1; } else { bool bDBCS = false; DWORD nRead = 0; BOOL bReadOk = 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; } } #ifdef _DEBUG wchar_t szDbg[120]; _wsprintf(szDbg, SKIPCOUNT(szDbg) L"CP=%u bDBCS=%u IsDbcs=%u X=%i", nCP, bDBCS, IsDbcs(), csbi.dwCursorPosition.X); BsDelWordMsg(szDbg); #endif int xPos = csbi.dwCursorPosition.X; COORD cr = {0, csbi.dwCursorPosition.Y}; if ((xPos == 0) && (csbi.dwCursorPosition.Y > 0)) { cr.Y--; xPos = csbi.dwSize.X; } COORD cursorFix = {xPos, cr.Y}; wchar_t* pwszLine = (wchar_t*)malloc((csbi.dwSize.X+1)*sizeof(*pwszLine)); if (pwszLine) { pwszLine[csbi.dwSize.X] = 0; // Считать строку if (bDBCS) { CHAR_INFO *pData = (CHAR_INFO*)calloc(csbi.dwSize.X, sizeof(CHAR_INFO)); COORD bufSize = {csbi.dwSize.X, 1}; SMALL_RECT rgn = {0, cr.Y, csbi.dwSize.X-1, cr.Y}; bReadOk = ReadConsoleOutputEx(hConOut, pData, bufSize, rgn, &cursorFix); dwLastError = GetLastError(); _ASSERTE(bReadOk); if (bReadOk) { for (int i = 0; i < csbi.dwSize.X; i++) pwszLine[i] = pData[i].Char.UnicodeChar; nRead = csbi.dwSize.X; xPos = cursorFix.X; } SafeFree(pData); } else { bReadOk = ReadConsoleOutputCharacterW(hConOut, pwszLine, csbi.dwSize.X, cr, &nRead); if (bReadOk && !nRead) bReadOk = FALSE; } if (bReadOk) { // Count chars { if ((int)nRead >= xPos) { int i = xPos - 1; _ASSERTEX(i >= 0); iBSCount = 0; // Only RIGHT brackets here to be sure that `(x86)` will be deleted including left bracket wchar_t cBreaks[] = L"\x20\xA0>])}$.,/\\\""; // Delete all `spaces` first while ((i >= 0) && ((pwszLine[i] == ucSpace) || (pwszLine[i] == ucNoBreakSpace))) iBSCount++, i--; _ASSERTE(cBreaks[0]==ucSpace && cBreaks[1]==ucNoBreakSpace); // delimiters while ((i >= 0) && wcschr(cBreaks+2, pwszLine[i])) iBSCount++, i--; // and all `NON-spaces` while ((i >= 0) && !wcschr(cBreaks, pwszLine[i])) iBSCount++, i--; } } } } // Done, string was processed SafeFree(pwszLine); } } else { BsDelWordMsg(L"GetConsoleScreenBufferInfo failed"); } if (iBSCount > 0) { INPUT_RECORD* pr = (INPUT_RECORD*)calloc((size_t)iBSCount,sizeof(*pr)); if (pr != NULL) { WORD vk = VK_BACK; 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 = 0x0E; } for (int i = 0; i < iBSCount; 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.uChar.UnicodeChar = vk; // BS pr[i].Event.KeyEvent.dwControlKeyState = 0; } DWORD nWritten = 0; while (iBSCount > 0) { lbWrite = WriteConsoleInputW(hConIn, pr, min(iBSCount,256), &nWritten); if (!lbWrite || !nWritten) break; iBSCount -= nWritten; } free(pr); } } else { BsDelWordMsg(L"Nothing to delete"); } UNREFERENCED_PARAMETER(dwLastError); return FALSE; }
// Консоль любит глючить, при попытках запроса более определенного количества ячеек. // 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; }