/* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS Console Driver DLL * FILE: win32ss/user/winsrv/consrv/condrv/text.c * PURPOSE: Console Output Functions for text-mode screen-buffers * PROGRAMMERS: Jeffrey Morlan * Hermes Belusca-Maito (hermes.belusca@sfr.fr) */ /* INCLUDES *******************************************************************/ #include #define NDEBUG #include #define COMMON_LEAD_TRAIL (COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE) /* GLOBALS ********************************************************************/ /* * From MSDN: * "The lpMultiByteStr and lpWideCharStr pointers must not be the same. * If they are the same, the function fails, and GetLastError returns * ERROR_INVALID_PARAMETER." */ #define ConsoleOutputUnicodeToAnsiChar(Console, dChar, sWChar) \ do { \ ASSERT((ULONG_PTR)(dChar) != (ULONG_PTR)(sWChar)); \ WideCharToMultiByte((Console)->OutputCodePage, 0, (sWChar), 1, (dChar), 1, NULL, NULL); \ } while (0) #define ConsoleOutputAnsiToUnicodeChar(Console, dWChar, sChar) \ do { \ ASSERT((ULONG_PTR)(dWChar) != (ULONG_PTR)(sChar)); \ MultiByteToWideChar((Console)->OutputCodePage, 0, (sChar), 1, (dWChar), 1); \ } while (0) /* PRIVATE FUNCTIONS **********************************************************/ CONSOLE_IO_OBJECT_TYPE TEXTMODE_BUFFER_GetType(PCONSOLE_SCREEN_BUFFER This) { // return This->Header.Type; return TEXTMODE_BUFFER; } static CONSOLE_SCREEN_BUFFER_VTBL TextVtbl = { TEXTMODE_BUFFER_GetType, }; /*static*/ VOID ClearLineBuffer(PTEXTMODE_SCREEN_BUFFER Buff); NTSTATUS CONSOLE_SCREEN_BUFFER_Initialize(OUT PCONSOLE_SCREEN_BUFFER* Buffer, IN PCONSOLE Console, IN PCONSOLE_SCREEN_BUFFER_VTBL Vtbl, IN SIZE_T Size); VOID CONSOLE_SCREEN_BUFFER_Destroy(IN OUT PCONSOLE_SCREEN_BUFFER Buffer); NTSTATUS TEXTMODE_BUFFER_Initialize(OUT PCONSOLE_SCREEN_BUFFER* Buffer, IN PCONSOLE Console, IN HANDLE ProcessHandle, IN PTEXTMODE_BUFFER_INFO TextModeInfo) { NTSTATUS Status = STATUS_SUCCESS; PTEXTMODE_SCREEN_BUFFER NewBuffer = NULL; UNREFERENCED_PARAMETER(ProcessHandle); if (Console == NULL || Buffer == NULL || TextModeInfo == NULL) return STATUS_INVALID_PARAMETER; *Buffer = NULL; Status = CONSOLE_SCREEN_BUFFER_Initialize((PCONSOLE_SCREEN_BUFFER*)&NewBuffer, Console, &TextVtbl, sizeof(TEXTMODE_SCREEN_BUFFER)); if (!NT_SUCCESS(Status)) return Status; NewBuffer->Header.Type = TEXTMODE_BUFFER; NewBuffer->Buffer = ConsoleAllocHeap(HEAP_ZERO_MEMORY, TextModeInfo->ScreenBufferSize.X * TextModeInfo->ScreenBufferSize.Y * sizeof(CHAR_INFO)); if (NewBuffer->Buffer == NULL) { CONSOLE_SCREEN_BUFFER_Destroy((PCONSOLE_SCREEN_BUFFER)NewBuffer); return STATUS_INSUFFICIENT_RESOURCES; } NewBuffer->ScreenBufferSize = NewBuffer->OldScreenBufferSize = TextModeInfo->ScreenBufferSize; NewBuffer->ViewSize = NewBuffer->OldViewSize = Console->ConsoleSize; NewBuffer->ViewOrigin.X = NewBuffer->ViewOrigin.Y = 0; NewBuffer->VirtualY = 0; NewBuffer->CursorBlinkOn = NewBuffer->ForceCursorOff = FALSE; NewBuffer->CursorInfo.bVisible = (TextModeInfo->IsCursorVisible && (TextModeInfo->CursorSize != 0)); NewBuffer->CursorInfo.dwSize = min(max(TextModeInfo->CursorSize, 0), 100); NewBuffer->ScreenDefaultAttrib = TextModeInfo->ScreenAttrib; NewBuffer->PopupDefaultAttrib = TextModeInfo->PopupAttrib; /* Initialize buffer to be empty with default attributes */ for (NewBuffer->CursorPosition.Y = 0 ; NewBuffer->CursorPosition.Y < NewBuffer->ScreenBufferSize.Y; NewBuffer->CursorPosition.Y++) { ClearLineBuffer(NewBuffer); } NewBuffer->CursorPosition.X = NewBuffer->CursorPosition.Y = 0; NewBuffer->Mode = ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT; *Buffer = (PCONSOLE_SCREEN_BUFFER)NewBuffer; return STATUS_SUCCESS; } VOID TEXTMODE_BUFFER_Destroy(IN OUT PCONSOLE_SCREEN_BUFFER Buffer) { PTEXTMODE_SCREEN_BUFFER Buff = (PTEXTMODE_SCREEN_BUFFER)Buffer; /* * IMPORTANT !! Reinitialize the type so that we don't enter a recursive * infinite loop when calling CONSOLE_SCREEN_BUFFER_Destroy. */ Buffer->Header.Type = SCREEN_BUFFER; ConsoleFreeHeap(Buff->Buffer); CONSOLE_SCREEN_BUFFER_Destroy(Buffer); } PCHAR_INFO ConioCoordToPointer(PTEXTMODE_SCREEN_BUFFER Buff, ULONG X, ULONG Y) { return &Buff->Buffer[((Y + Buff->VirtualY) % Buff->ScreenBufferSize.Y) * Buff->ScreenBufferSize.X + X]; } /*static*/ VOID ClearLineBuffer(PTEXTMODE_SCREEN_BUFFER Buff) { PCHAR_INFO Ptr = ConioCoordToPointer(Buff, 0, Buff->CursorPosition.Y); SHORT Pos; for (Pos = 0; Pos < Buff->ScreenBufferSize.X; Pos++, Ptr++) { /* Fill the cell */ Ptr->Char.UnicodeChar = L' '; Ptr->Attributes = Buff->ScreenDefaultAttrib; } } static VOID ConioComputeUpdateRect(IN PTEXTMODE_SCREEN_BUFFER Buff, IN OUT PSMALL_RECT UpdateRect, IN PCOORD Start, IN UINT Length) { if ((UINT)Buff->ScreenBufferSize.X <= Start->X + Length) { UpdateRect->Left = 0; UpdateRect->Right = Buff->ScreenBufferSize.X - 1; } else { UpdateRect->Left = Start->X; UpdateRect->Right = Start->X + Length - 1; } UpdateRect->Top = Start->Y; UpdateRect->Bottom = Start->Y + (Start->X + Length - 1) / Buff->ScreenBufferSize.X; if (Buff->ScreenBufferSize.Y <= UpdateRect->Bottom) { UpdateRect->Bottom = Buff->ScreenBufferSize.Y - 1; } } /* * Move from one rectangle to another. We must be careful about the order that * this is done, to avoid overwriting parts of the source before they are moved. */ static VOID ConioMoveRegion(PTEXTMODE_SCREEN_BUFFER ScreenBuffer, PSMALL_RECT SrcRegion, PSMALL_RECT DstRegion, PSMALL_RECT ClipRegion, CHAR_INFO FillChar) { int Width = ConioRectWidth(SrcRegion); int Height = ConioRectHeight(SrcRegion); int SX, SY; int DX, DY; int XDelta, YDelta; int i, j; SY = SrcRegion->Top; DY = DstRegion->Top; YDelta = 1; if (SY < DY) { /* Moving down: work from bottom up */ SY = SrcRegion->Bottom; DY = DstRegion->Bottom; YDelta = -1; } for (i = 0; i < Height; i++) { PCHAR_INFO SRow = ConioCoordToPointer(ScreenBuffer, 0, SY); PCHAR_INFO DRow = ConioCoordToPointer(ScreenBuffer, 0, DY); SX = SrcRegion->Left; DX = DstRegion->Left; XDelta = 1; if (SX < DX) { /* Moving right: work from right to left */ SX = SrcRegion->Right; DX = DstRegion->Right; XDelta = -1; } for (j = 0; j < Width; j++) { CHAR_INFO Cell = SRow[SX]; if (SX >= ClipRegion->Left && SX <= ClipRegion->Right && SY >= ClipRegion->Top && SY <= ClipRegion->Bottom) { SRow[SX] = FillChar; } if (DX >= ClipRegion->Left && DX <= ClipRegion->Right && DY >= ClipRegion->Top && DY <= ClipRegion->Bottom) { DRow[DX] = Cell; } SX += XDelta; DX += XDelta; } SY += YDelta; DY += YDelta; } } // FIXME! NTSTATUS NTAPI ConDrvWriteConsoleInput(IN PCONSOLE Console, IN PCONSOLE_INPUT_BUFFER InputBuffer, IN BOOLEAN AppendToEnd, IN PINPUT_RECORD InputRecord, IN ULONG NumEventsToWrite, OUT PULONG NumEventsWritten OPTIONAL); NTSTATUS ConioResizeBuffer(PCONSOLE Console, PTEXTMODE_SCREEN_BUFFER ScreenBuffer, COORD Size) { PCHAR_INFO Buffer; DWORD Offset = 0; PCHAR_INFO ptr; WORD CurrentAttribute; USHORT CurrentY; PCHAR_INFO OldBuffer; DWORD i; DWORD diff; /* Buffer size is not allowed to be smaller than the view size */ if (Size.X < ScreenBuffer->ViewSize.X || Size.Y < ScreenBuffer->ViewSize.Y) return STATUS_INVALID_PARAMETER; if (Size.X == ScreenBuffer->ScreenBufferSize.X && Size.Y == ScreenBuffer->ScreenBufferSize.Y) { // FIXME: Trigger a buffer resize event ?? return STATUS_SUCCESS; } if (Console->FixedSize) { /* * The console is in fixed-size mode, so we cannot resize anything * at the moment. However, keep those settings somewhere so that * we can try to set them up when we will be allowed to do so. */ ScreenBuffer->OldScreenBufferSize = Size; return STATUS_NOT_SUPPORTED; // STATUS_SUCCESS } Buffer = ConsoleAllocHeap(HEAP_ZERO_MEMORY, Size.X * Size.Y * sizeof(CHAR_INFO)); if (!Buffer) return STATUS_NO_MEMORY; DPRINT("Resizing (%d,%d) to (%d,%d)\n", ScreenBuffer->ScreenBufferSize.X, ScreenBuffer->ScreenBufferSize.Y, Size.X, Size.Y); OldBuffer = ScreenBuffer->Buffer; for (CurrentY = 0; CurrentY < ScreenBuffer->ScreenBufferSize.Y && CurrentY < Size.Y; CurrentY++) { ptr = ConioCoordToPointer(ScreenBuffer, 0, CurrentY); if (Size.X <= ScreenBuffer->ScreenBufferSize.X) { /* Reduce size */ RtlCopyMemory(Buffer + Offset, ptr, Size.X * sizeof(CHAR_INFO)); Offset += Size.X; } else { /* Enlarge size */ RtlCopyMemory(Buffer + Offset, ptr, ScreenBuffer->ScreenBufferSize.X * sizeof(CHAR_INFO)); Offset += ScreenBuffer->ScreenBufferSize.X; /* The attribute to be used is the one of the last cell of the current line */ CurrentAttribute = ConioCoordToPointer(ScreenBuffer, ScreenBuffer->ScreenBufferSize.X - 1, CurrentY)->Attributes; diff = Size.X - ScreenBuffer->ScreenBufferSize.X; /* Zero-out the new part of the buffer */ for (i = 0; i < diff; i++) { ptr = Buffer + Offset; ptr->Char.UnicodeChar = L' '; ptr->Attributes = CurrentAttribute; ++Offset; } } } if (Size.Y > ScreenBuffer->ScreenBufferSize.Y) { diff = Size.X * (Size.Y - ScreenBuffer->ScreenBufferSize.Y); /* Zero-out the new part of the buffer */ for (i = 0; i < diff; i++) { ptr = Buffer + Offset; ptr->Char.UnicodeChar = L' '; ptr->Attributes = ScreenBuffer->ScreenDefaultAttrib; ++Offset; } } (void)InterlockedExchangePointer((PVOID volatile*)&ScreenBuffer->Buffer, Buffer); ConsoleFreeHeap(OldBuffer); ScreenBuffer->ScreenBufferSize = ScreenBuffer->OldScreenBufferSize = Size; ScreenBuffer->VirtualY = 0; /* Ensure cursor and window are within buffer */ if (ScreenBuffer->CursorPosition.X >= Size.X) ScreenBuffer->CursorPosition.X = Size.X - 1; if (ScreenBuffer->CursorPosition.Y >= Size.Y) ScreenBuffer->CursorPosition.Y = Size.Y - 1; if (ScreenBuffer->ViewOrigin.X > Size.X - ScreenBuffer->ViewSize.X) ScreenBuffer->ViewOrigin.X = Size.X - ScreenBuffer->ViewSize.X; if (ScreenBuffer->ViewOrigin.Y > Size.Y - ScreenBuffer->ViewSize.Y) ScreenBuffer->ViewOrigin.Y = Size.Y - ScreenBuffer->ViewSize.Y; /* * Trigger a buffer resize event */ if (Console->InputBuffer.Mode & ENABLE_WINDOW_INPUT) { ULONG NumEventsWritten; INPUT_RECORD er; er.EventType = WINDOW_BUFFER_SIZE_EVENT; er.Event.WindowBufferSizeEvent.dwSize = ScreenBuffer->ScreenBufferSize; // ConioProcessInputEvent(Console, &er); ConDrvWriteConsoleInput(Console, &Console->InputBuffer, TRUE, &er, 1, &NumEventsWritten); } return STATUS_SUCCESS; } NTSTATUS NTAPI ConDrvChangeScreenBufferAttributes(IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, IN USHORT NewScreenAttrib, IN USHORT NewPopupAttrib) { ULONG X, Y, Length; PCHAR_INFO Ptr; COORD TopLeft = {0}; ULONG NumCodesToWrite; USHORT OldScreenAttrib, OldPopupAttrib; if (Console == NULL || Buffer == NULL) { return STATUS_INVALID_PARAMETER; } /* Validity check */ ASSERT(Console == Buffer->Header.Console); NumCodesToWrite = Buffer->ScreenBufferSize.X * Buffer->ScreenBufferSize.Y; OldScreenAttrib = Buffer->ScreenDefaultAttrib; OldPopupAttrib = Buffer->PopupDefaultAttrib; X = TopLeft.X; Y = (TopLeft.Y + Buffer->VirtualY) % Buffer->ScreenBufferSize.Y; Length = NumCodesToWrite; while (Length--) { Ptr = ConioCoordToPointer(Buffer, X, Y); /* * Change the current colors only if they are the old ones. */ /* Foreground color */ if ((Ptr->Attributes & 0x0F) == (OldScreenAttrib & 0x0F)) Ptr->Attributes = (Ptr->Attributes & 0xFFF0) | (NewScreenAttrib & 0x0F); if ((Ptr->Attributes & 0x0F) == (OldPopupAttrib & 0x0F)) Ptr->Attributes = (Ptr->Attributes & 0xFFF0) | (NewPopupAttrib & 0x0F); /* Background color */ if ((Ptr->Attributes & 0xF0) == (OldScreenAttrib & 0xF0)) Ptr->Attributes = (Ptr->Attributes & 0xFF0F) | (NewScreenAttrib & 0xF0); if ((Ptr->Attributes & 0xF0) == (OldPopupAttrib & 0xF0)) Ptr->Attributes = (Ptr->Attributes & 0xFF0F) | (NewPopupAttrib & 0xF0); // ++Ptr; if (++X == Buffer->ScreenBufferSize.X) { X = 0; if (++Y == Buffer->ScreenBufferSize.Y) { Y = 0; } } } /* Save foreground and background colors for both screen and popup */ Buffer->ScreenDefaultAttrib = (NewScreenAttrib & 0x00FF); Buffer->PopupDefaultAttrib = (NewPopupAttrib & 0x00FF); /* Refresh the display if needed */ if ((PCONSOLE_SCREEN_BUFFER)Buffer == Console->ActiveBuffer) { SMALL_RECT UpdateRect; ConioComputeUpdateRect(Buffer, &UpdateRect, &TopLeft, NumCodesToWrite); TermDrawRegion(Console, &UpdateRect); } return STATUS_SUCCESS; } /* PUBLIC DRIVER APIS *********************************************************/ NTSTATUS NTAPI ConDrvReadConsoleOutput(IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, IN BOOLEAN Unicode, OUT PCHAR_INFO CharInfo/*Buffer*/, IN OUT PSMALL_RECT ReadRegion) { SHORT X, Y; SMALL_RECT ScreenBuffer; PCHAR_INFO CurCharInfo; SMALL_RECT CapturedReadRegion; PCHAR_INFO Ptr; if (Console == NULL || Buffer == NULL || CharInfo == NULL || ReadRegion == NULL) { return STATUS_INVALID_PARAMETER; } /* Validity check */ ASSERT(Console == Buffer->Header.Console); CapturedReadRegion = *ReadRegion; /* Make sure ReadRegion is inside the screen buffer */ ConioInitRect(&ScreenBuffer, 0, 0, Buffer->ScreenBufferSize.Y - 1, Buffer->ScreenBufferSize.X - 1); if (!ConioGetIntersection(&CapturedReadRegion, &ScreenBuffer, &CapturedReadRegion)) { /* * It is okay to have a ReadRegion completely outside * the screen buffer. No data is read then. */ return STATUS_SUCCESS; } CurCharInfo = CharInfo; for (Y = CapturedReadRegion.Top; Y <= CapturedReadRegion.Bottom; ++Y) { Ptr = ConioCoordToPointer(Buffer, CapturedReadRegion.Left, Y); for (X = CapturedReadRegion.Left; X <= CapturedReadRegion.Right; ++X) { if (Unicode) { CurCharInfo->Char.UnicodeChar = Ptr->Char.UnicodeChar; } else { // ConsoleOutputUnicodeToAnsiChar(Console, &CurCharInfo->Char.AsciiChar, &Ptr->Char.UnicodeChar); WideCharToMultiByte(Console->OutputCodePage, 0, &Ptr->Char.UnicodeChar, 1, &CurCharInfo->Char.AsciiChar, 1, NULL, NULL); } CurCharInfo->Attributes = (Ptr->Attributes & ~COMMON_LEAD_TRAIL); ++Ptr; ++CurCharInfo; } } *ReadRegion = CapturedReadRegion; return STATUS_SUCCESS; } NTSTATUS NTAPI ConDrvWriteConsoleOutput(IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, IN BOOLEAN Unicode, IN PCHAR_INFO CharInfo/*Buffer*/, IN OUT PSMALL_RECT WriteRegion) { SHORT X, Y; SMALL_RECT ScreenBuffer; PCHAR_INFO CurCharInfo; SMALL_RECT CapturedWriteRegion; PCHAR_INFO Ptr; if (Console == NULL || Buffer == NULL || CharInfo == NULL || WriteRegion == NULL) { return STATUS_INVALID_PARAMETER; } /* Validity check */ ASSERT(Console == Buffer->Header.Console); CapturedWriteRegion = *WriteRegion; /* Make sure WriteRegion is inside the screen buffer */ ConioInitRect(&ScreenBuffer, 0, 0, Buffer->ScreenBufferSize.Y - 1, Buffer->ScreenBufferSize.X - 1); if (!ConioGetIntersection(&CapturedWriteRegion, &ScreenBuffer, &CapturedWriteRegion)) { /* * It is okay to have a WriteRegion completely outside * the screen buffer. No data is written then. */ return STATUS_SUCCESS; } CurCharInfo = CharInfo; for (Y = CapturedWriteRegion.Top; Y <= CapturedWriteRegion.Bottom; ++Y) { Ptr = ConioCoordToPointer(Buffer, CapturedWriteRegion.Left, Y); for (X = CapturedWriteRegion.Left; X <= CapturedWriteRegion.Right; ++X) { if (Unicode) { Ptr->Char.UnicodeChar = CurCharInfo->Char.UnicodeChar; } else { ConsoleOutputAnsiToUnicodeChar(Console, &Ptr->Char.UnicodeChar, &CurCharInfo->Char.AsciiChar); } Ptr->Attributes = CurCharInfo->Attributes; ++Ptr; ++CurCharInfo; } } TermDrawRegion(Console, &CapturedWriteRegion); *WriteRegion = CapturedWriteRegion; return STATUS_SUCCESS; } /* * NOTE: This function is strongly inspired by ConDrvWriteConsoleOutput... * FIXME: This function MUST be moved into consrv/conoutput.c because only * consrv knows how to manipulate VDM screenbuffers. */ NTSTATUS NTAPI ConDrvWriteConsoleOutputVDM(IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, IN PCHAR_CELL CharInfo/*Buffer*/, IN COORD CharInfoSize, IN PSMALL_RECT WriteRegion) { SHORT X, Y; SMALL_RECT ScreenBuffer; PCHAR_CELL CurCharInfo; SMALL_RECT CapturedWriteRegion; PCHAR_INFO Ptr; if (Console == NULL || Buffer == NULL || CharInfo == NULL || WriteRegion == NULL) { return STATUS_INVALID_PARAMETER; } /* Validity check */ ASSERT(Console == Buffer->Header.Console); CapturedWriteRegion = *WriteRegion; /* Make sure WriteRegion is inside the screen buffer */ ConioInitRect(&ScreenBuffer, 0, 0, Buffer->ScreenBufferSize.Y - 1, Buffer->ScreenBufferSize.X - 1); if (!ConioGetIntersection(&CapturedWriteRegion, &ScreenBuffer, &CapturedWriteRegion)) { /* * It is okay to have a WriteRegion completely outside * the screen buffer. No data is written then. */ return STATUS_SUCCESS; } // CurCharInfo = CharInfo; for (Y = CapturedWriteRegion.Top; Y <= CapturedWriteRegion.Bottom; ++Y) { /**/CurCharInfo = CharInfo + Y * CharInfoSize.X + CapturedWriteRegion.Left;/**/ Ptr = ConioCoordToPointer(Buffer, CapturedWriteRegion.Left, Y); for (X = CapturedWriteRegion.Left; X <= CapturedWriteRegion.Right; ++X) { ConsoleOutputAnsiToUnicodeChar(Console, &Ptr->Char.UnicodeChar, &CurCharInfo->Char); Ptr->Attributes = CurCharInfo->Attributes; ++Ptr; ++CurCharInfo; } } return STATUS_SUCCESS; } NTSTATUS NTAPI ConDrvWriteConsole(IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER ScreenBuffer, IN BOOLEAN Unicode, IN PVOID StringBuffer, IN ULONG NumCharsToWrite, OUT PULONG NumCharsWritten OPTIONAL) { NTSTATUS Status = STATUS_SUCCESS; PWCHAR Buffer = NULL; ULONG Written = 0; ULONG Length; if (Console == NULL || ScreenBuffer == NULL /* || StringBuffer == NULL */) return STATUS_INVALID_PARAMETER; /* Validity checks */ ASSERT(Console == ScreenBuffer->Header.Console); ASSERT((StringBuffer != NULL) || (StringBuffer == NULL && NumCharsToWrite == 0)); /* Stop here if the console is paused */ if (Console->UnpauseEvent != NULL) return STATUS_PENDING; /* Convert the string to UNICODE */ if (Unicode) { Buffer = StringBuffer; } else { Length = MultiByteToWideChar(Console->OutputCodePage, 0, (PCHAR)StringBuffer, NumCharsToWrite, NULL, 0); Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, Length * sizeof(WCHAR)); if (Buffer) { MultiByteToWideChar(Console->OutputCodePage, 0, (PCHAR)StringBuffer, NumCharsToWrite, (PWCHAR)Buffer, Length); } else { Status = STATUS_NO_MEMORY; } } /* Send it */ if (Buffer) { if (NT_SUCCESS(Status)) { Status = TermWriteStream(Console, ScreenBuffer, Buffer, NumCharsToWrite, TRUE); if (NT_SUCCESS(Status)) { Written = NumCharsToWrite; } } if (!Unicode) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer); } if (NumCharsWritten) *NumCharsWritten = Written; return Status; } NTSTATUS FASTCALL IntReadConsoleOutputStringAscii(IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, OUT PVOID StringBuffer, IN ULONG NumCodesToRead, IN PCOORD ReadCoord, OUT PULONG NumCodesRead OPTIONAL) { ULONG CodeSize = RTL_FIELD_SIZE(CODE_ELEMENT, AsciiChar); LPBYTE ReadBuffer = StringBuffer; SHORT Xpos = ReadCoord->X; SHORT Ypos = (ReadCoord->Y + Buffer->VirtualY) % Buffer->ScreenBufferSize.Y; ULONG i; PCHAR_INFO Ptr; BOOLEAN bCJK = Console->IsCJK; for (i = 0; i < NumCodesToRead; ++i) { Ptr = ConioCoordToPointer(Buffer, Xpos, Ypos); ConsoleOutputUnicodeToAnsiChar(Console, (PCHAR)ReadBuffer, &Ptr->Char.UnicodeChar); ReadBuffer += CodeSize; Xpos++; if (Xpos == Buffer->ScreenBufferSize.X) { Xpos = 0; Ypos++; if (Ypos == Buffer->ScreenBufferSize.Y) { Ypos = 0; } } /* For Chinese, Japanese and Korean */ if (bCJK && (Ptr->Attributes & COMMON_LVB_LEADING_BYTE)) { Xpos++; if (Xpos == Buffer->ScreenBufferSize.X) { Xpos = 0; Ypos++; if (Ypos == Buffer->ScreenBufferSize.Y) { Ypos = 0; } } ++i; } } if (NumCodesRead) *NumCodesRead = i; return STATUS_SUCCESS; } NTSTATUS FASTCALL IntReadConsoleOutputStringUnicode(IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, OUT PVOID StringBuffer, IN ULONG NumCodesToRead, IN PCOORD ReadCoord, OUT PULONG NumCodesRead OPTIONAL) { ULONG CodeSize = RTL_FIELD_SIZE(CODE_ELEMENT, UnicodeChar); LPBYTE ReadBuffer = StringBuffer; SHORT Xpos = ReadCoord->X; SHORT Ypos = (ReadCoord->Y + Buffer->VirtualY) % Buffer->ScreenBufferSize.Y; ULONG i, nNumChars = 0; PCHAR_INFO Ptr; BOOLEAN bCJK = Console->IsCJK; for (i = 0; i < NumCodesToRead; ++i, ++nNumChars) { Ptr = ConioCoordToPointer(Buffer, Xpos, Ypos); *(PWCHAR)ReadBuffer = Ptr->Char.UnicodeChar; ReadBuffer += CodeSize; Xpos++; if (Xpos == Buffer->ScreenBufferSize.X) { Xpos = 0; Ypos++; if (Ypos == Buffer->ScreenBufferSize.Y) { Ypos = 0; } } /* For Chinese, Japanese and Korean */ if (bCJK && (Ptr->Attributes & COMMON_LVB_LEADING_BYTE)) { Xpos++; if (Xpos == Buffer->ScreenBufferSize.X) { Xpos = 0; Ypos++; if (Ypos == Buffer->ScreenBufferSize.Y) { Ypos = 0; } } ++i; } } if (NumCodesRead) *NumCodesRead = nNumChars; return STATUS_SUCCESS; } NTSTATUS FASTCALL IntReadConsoleOutputStringAttributes(IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, OUT PVOID StringBuffer, IN ULONG NumCodesToRead, IN PCOORD ReadCoord, OUT PULONG NumCodesRead OPTIONAL) { ULONG CodeSize = RTL_FIELD_SIZE(CODE_ELEMENT, Attribute); LPBYTE ReadBuffer = StringBuffer; SHORT Xpos = ReadCoord->X; SHORT Ypos = (ReadCoord->Y + Buffer->VirtualY) % Buffer->ScreenBufferSize.Y; ULONG i; PCHAR_INFO Ptr; for (i = 0; i < NumCodesToRead; ++i) { Ptr = ConioCoordToPointer(Buffer, Xpos, Ypos); *(PWORD)ReadBuffer = Ptr->Attributes; ReadBuffer += CodeSize; Xpos++; if (Xpos == Buffer->ScreenBufferSize.X) { Xpos = 0; Ypos++; if (Ypos == Buffer->ScreenBufferSize.Y) { Ypos = 0; } } } if (Xpos > 0 && Console->IsCJK) { ReadBuffer -= CodeSize; *(PWORD)ReadBuffer &= ~COMMON_LVB_LEADING_BYTE; } if (NumCodesRead) *NumCodesRead = NumCodesToRead; return STATUS_SUCCESS; } NTSTATUS NTAPI ConDrvReadConsoleOutputString(IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, IN CODE_TYPE CodeType, OUT PVOID StringBuffer, IN ULONG NumCodesToRead, IN PCOORD ReadCoord, OUT PULONG NumCodesRead OPTIONAL) { if (Console == NULL || Buffer == NULL || ReadCoord == NULL /* || EndCoord == NULL */) { return STATUS_INVALID_PARAMETER; } /* Validity checks */ ASSERT(Console == Buffer->Header.Console); ASSERT((StringBuffer != NULL) || (StringBuffer == NULL && NumCodesToRead == 0)); if (NumCodesRead) *NumCodesRead = 0; switch (CodeType) { case CODE_ASCII: return IntReadConsoleOutputStringAscii(Console, Buffer, StringBuffer, NumCodesToRead, ReadCoord, NumCodesRead); case CODE_UNICODE: return IntReadConsoleOutputStringUnicode(Console, Buffer, StringBuffer, NumCodesToRead, ReadCoord, NumCodesRead); case CODE_ATTRIBUTE: return IntReadConsoleOutputStringAttributes(Console, Buffer, StringBuffer, NumCodesToRead, ReadCoord, NumCodesRead); default: return STATUS_INVALID_PARAMETER; } } static NTSTATUS IntWriteConsoleOutputStringUnicode( IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, IN PVOID StringBuffer, IN ULONG NumCodesToWrite, IN PCOORD WriteCoord, OUT PULONG NumCodesWritten OPTIONAL) { NTSTATUS Status = STATUS_SUCCESS; PWCHAR WriteBuffer = StringBuffer; ULONG i, X, Y, Length; PCHAR_INFO Ptr; BOOLEAN bCJK = Console->IsCJK; if (!StringBuffer) goto Cleanup; X = WriteCoord->X; Y = (WriteCoord->Y + Buffer->VirtualY) % Buffer->ScreenBufferSize.Y; Length = NumCodesToWrite; for (i = 0; i < Length; ++i) { Ptr = ConioCoordToPointer(Buffer, X, Y); Ptr->Char.UnicodeChar = *WriteBuffer; ++WriteBuffer; ++X; if (X == Buffer->ScreenBufferSize.X) { X = 0; ++Y; if (Y == Buffer->ScreenBufferSize.Y) { Y = 0; } } /* For Chinese, Japanese and Korean */ if (bCJK && Ptr->Char.UnicodeChar >= 0x80 && mk_wcwidth_cjk(Ptr->Char.UnicodeChar) == 2) { /* A full-width character cannot cross a line boundary */ if (X == Buffer->ScreenBufferSize.X - 1) { /* go to next line */ X = 0; ++Y; if (Y == Buffer->ScreenBufferSize.Y) { Y = 0; } Ptr = ConioCoordToPointer(Buffer, X, Y); } /* the leading byte */ Ptr->Attributes = Buffer->ScreenDefaultAttrib; Ptr->Attributes |= COMMON_LVB_LEADING_BYTE; ++i; /* the trailing byte */ Ptr = ConioCoordToPointer(Buffer, X, Y); Ptr->Attributes = Buffer->ScreenDefaultAttrib; Ptr->Attributes |= COMMON_LVB_TRAILING_BYTE; ++X; if (X == Buffer->ScreenBufferSize.X) { X = 0; ++Y; if (Y == Buffer->ScreenBufferSize.Y) { Y = 0; } } } } Cleanup: if (NumCodesWritten) *NumCodesWritten = NumCodesToWrite; return Status; } static NTSTATUS IntWriteConsoleOutputStringAscii( IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, IN PVOID StringBuffer, IN ULONG NumCodesToWrite, IN PCOORD WriteCoord, OUT PULONG NumCodesWritten OPTIONAL) { NTSTATUS Status; PWCHAR tmpString; ULONG Length; if (!StringBuffer) { if (NumCodesWritten) *NumCodesWritten = NumCodesToWrite; return STATUS_SUCCESS; } /* Convert the ASCII string into Unicode before writing it to the console */ Length = MultiByteToWideChar(Console->OutputCodePage, 0, StringBuffer, NumCodesToWrite, NULL, 0); tmpString = ConsoleAllocHeap(0, Length * sizeof(WCHAR)); if (!tmpString) return STATUS_NO_MEMORY; MultiByteToWideChar(Console->OutputCodePage, 0, StringBuffer, NumCodesToWrite, tmpString, Length); Status = IntWriteConsoleOutputStringUnicode(Console, Buffer, tmpString, Length, WriteCoord, NumCodesWritten); ConsoleFreeHeap(tmpString); return Status; } static NTSTATUS IntWriteConsoleOutputStringAttribute( IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, IN PVOID StringBuffer, IN ULONG NumCodesToWrite, IN PCOORD WriteCoord, OUT PULONG NumCodesWritten OPTIONAL) { NTSTATUS Status = STATUS_SUCCESS; PWORD WriteBuffer = StringBuffer; ULONG i, X, Y, Length; PCHAR_INFO Ptr; if (!StringBuffer) goto Cleanup; X = WriteCoord->X; Y = (WriteCoord->Y + Buffer->VirtualY) % Buffer->ScreenBufferSize.Y; Length = NumCodesToWrite; for (i = 0; i < Length; ++i) { Ptr = ConioCoordToPointer(Buffer, X, Y); Ptr->Attributes = (*WriteBuffer & ~COMMON_LEAD_TRAIL); ++WriteBuffer; ++X; if (X == Buffer->ScreenBufferSize.X) { X = 0; ++Y; if (Y == Buffer->ScreenBufferSize.Y) { Y = 0; } } } Cleanup: if (NumCodesWritten) *NumCodesWritten = NumCodesToWrite; return Status; } NTSTATUS NTAPI ConDrvWriteConsoleOutputString( IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, IN CODE_TYPE CodeType, IN PVOID StringBuffer, IN ULONG NumCodesToWrite, IN PCOORD WriteCoord, OUT PULONG NumCodesWritten OPTIONAL) { NTSTATUS Status; SMALL_RECT UpdateRect; if (Console == NULL || Buffer == NULL || WriteCoord == NULL /* || EndCoord == NULL */) { return STATUS_INVALID_PARAMETER; } /* Validity checks */ ASSERT(Console == Buffer->Header.Console); ASSERT((StringBuffer != NULL) || (StringBuffer == NULL && NumCodesToWrite == 0)); if (NumCodesWritten) *NumCodesWritten = 0; switch (CodeType) { case CODE_ASCII: Status = IntWriteConsoleOutputStringAscii( Console, Buffer, StringBuffer, NumCodesToWrite, WriteCoord, NumCodesWritten); break; case CODE_UNICODE: Status = IntWriteConsoleOutputStringUnicode( Console, Buffer, StringBuffer, NumCodesToWrite, WriteCoord, NumCodesWritten); break; case CODE_ATTRIBUTE: Status = IntWriteConsoleOutputStringAttribute( Console, Buffer, StringBuffer, NumCodesToWrite, WriteCoord, NumCodesWritten); break; default: Status = STATUS_INVALID_PARAMETER; break; } if ((PCONSOLE_SCREEN_BUFFER)Buffer == Console->ActiveBuffer) { ConioComputeUpdateRect(Buffer, &UpdateRect, WriteCoord, NumCodesToWrite); TermDrawRegion(Console, &UpdateRect); } return Status; } NTSTATUS NTAPI ConDrvFillConsoleOutput(IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, IN CODE_TYPE CodeType, IN CODE_ELEMENT Code, IN ULONG NumCodesToWrite, IN PCOORD WriteCoord, OUT PULONG NumCodesWritten OPTIONAL) { ULONG X, Y, i; PCHAR_INFO Ptr; BOOLEAN bLead, bFullwidth; if (Console == NULL || Buffer == NULL || WriteCoord == NULL) { return STATUS_INVALID_PARAMETER; } /* Validity check */ ASSERT(Console == Buffer->Header.Console); // // FIXME: Make overflow checks on WriteCoord !!!!!! // if (NumCodesWritten) *NumCodesWritten = 0; if (CodeType == CODE_ASCII) { /* Conversion from the ASCII char to the UNICODE char */ CODE_ELEMENT tmp; ConsoleOutputAnsiToUnicodeChar(Console, &tmp.UnicodeChar, &Code.AsciiChar); Code = tmp; } X = WriteCoord->X; Y = (WriteCoord->Y + Buffer->VirtualY) % Buffer->ScreenBufferSize.Y; // Ptr = ConioCoordToPointer(Buffer, X, Y); // Doesn't work // Ptr = &Buffer->Buffer[X + Y * Buffer->ScreenBufferSize.X]; // May work /* For Chinese, Japanese and Korean */ bLead = TRUE; bFullwidth = FALSE; if (Console->IsCJK) { bFullwidth = (mk_wcwidth_cjk(Code.UnicodeChar) == 2); if (X > 0) { Ptr = ConioCoordToPointer(Buffer, X - 1, Y); if (Ptr->Attributes & COMMON_LVB_LEADING_BYTE) { Ptr->Char.UnicodeChar = L' '; Ptr->Attributes &= ~COMMON_LVB_LEADING_BYTE; } } } for (i = 0; i < NumCodesToWrite; ++i) { Ptr = ConioCoordToPointer(Buffer, X, Y); switch (CodeType) { case CODE_ASCII: case CODE_UNICODE: Ptr->Char.UnicodeChar = Code.UnicodeChar; Ptr->Attributes &= ~COMMON_LEAD_TRAIL; if (bFullwidth) { if (bLead) Ptr->Attributes |= COMMON_LVB_LEADING_BYTE; else Ptr->Attributes |= COMMON_LVB_TRAILING_BYTE; } break; case CODE_ATTRIBUTE: Ptr->Attributes &= ~COMMON_LEAD_TRAIL; Ptr->Attributes |= (Code.Attribute & ~COMMON_LEAD_TRAIL); break; } ++X; if (X == Buffer->ScreenBufferSize.X) { X = 0; ++Y; if (Y == Buffer->ScreenBufferSize.Y) { Y = 0; } } bLead = !bLead; } if ((NumCodesToWrite & 1) & bFullwidth) { if (X + Y * Buffer->ScreenBufferSize.X > 0) { Ptr = ConioCoordToPointer(Buffer, X - 1, Y); Ptr->Char.UnicodeChar = L' '; Ptr->Attributes &= ~COMMON_LEAD_TRAIL; } } if ((PCONSOLE_SCREEN_BUFFER)Buffer == Console->ActiveBuffer) { SMALL_RECT UpdateRect; ConioComputeUpdateRect(Buffer, &UpdateRect, WriteCoord, NumCodesToWrite); TermDrawRegion(Console, &UpdateRect); } if (NumCodesWritten) *NumCodesWritten = NumCodesToWrite; // Written; return STATUS_SUCCESS; } NTSTATUS NTAPI ConDrvGetConsoleScreenBufferInfo(IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, OUT PCOORD ScreenBufferSize, OUT PCOORD CursorPosition, OUT PCOORD ViewOrigin, OUT PCOORD ViewSize, OUT PCOORD MaximumViewSize, OUT PWORD Attributes) { COORD LargestWindowSize; if (Console == NULL || Buffer == NULL || ScreenBufferSize == NULL || CursorPosition == NULL || ViewOrigin == NULL || ViewSize == NULL || MaximumViewSize == NULL || Attributes == NULL) { return STATUS_INVALID_PARAMETER; } /* Validity check */ ASSERT(Console == Buffer->Header.Console); *ScreenBufferSize = Buffer->ScreenBufferSize; *CursorPosition = Buffer->CursorPosition; *ViewOrigin = Buffer->ViewOrigin; *ViewSize = Buffer->ViewSize; *Attributes = Buffer->ScreenDefaultAttrib; /* * Retrieve the largest possible console window size, taking * into account the size of the console screen buffer. */ TermGetLargestConsoleWindowSize(Console, &LargestWindowSize); LargestWindowSize.X = min(LargestWindowSize.X, Buffer->ScreenBufferSize.X); LargestWindowSize.Y = min(LargestWindowSize.Y, Buffer->ScreenBufferSize.Y); *MaximumViewSize = LargestWindowSize; return STATUS_SUCCESS; } NTSTATUS NTAPI ConDrvSetConsoleTextAttribute(IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, IN WORD Attributes) { if (Console == NULL || Buffer == NULL) return STATUS_INVALID_PARAMETER; /* Validity check */ ASSERT(Console == Buffer->Header.Console); Buffer->ScreenDefaultAttrib = Attributes; return STATUS_SUCCESS; } NTSTATUS NTAPI ConDrvSetConsoleScreenBufferSize(IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, IN PCOORD Size) { NTSTATUS Status; if (Console == NULL || Buffer == NULL || Size == NULL) return STATUS_INVALID_PARAMETER; /* Validity check */ ASSERT(Console == Buffer->Header.Console); Status = ConioResizeBuffer(Console, Buffer, *Size); if (NT_SUCCESS(Status)) TermResizeTerminal(Console); return Status; } NTSTATUS NTAPI ConDrvScrollConsoleScreenBuffer(IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, IN BOOLEAN Unicode, IN PSMALL_RECT ScrollRectangle, IN BOOLEAN UseClipRectangle, IN PSMALL_RECT ClipRectangle OPTIONAL, IN PCOORD DestinationOrigin, IN CHAR_INFO FillChar) { COORD CapturedDestinationOrigin; SMALL_RECT ScreenBuffer; SMALL_RECT SrcRegion; SMALL_RECT DstRegion; SMALL_RECT UpdateRegion; SMALL_RECT CapturedClipRectangle; if (Console == NULL || Buffer == NULL || ScrollRectangle == NULL || (UseClipRectangle ? ClipRectangle == NULL : FALSE) || DestinationOrigin == NULL) { return STATUS_INVALID_PARAMETER; } /* Validity check */ ASSERT(Console == Buffer->Header.Console); CapturedDestinationOrigin = *DestinationOrigin; /* Make sure the source rectangle is inside the screen buffer */ ConioInitRect(&ScreenBuffer, 0, 0, Buffer->ScreenBufferSize.Y - 1, Buffer->ScreenBufferSize.X - 1); if (!ConioGetIntersection(&SrcRegion, &ScreenBuffer, ScrollRectangle)) { return STATUS_SUCCESS; } /* If the source was clipped on the left or top, adjust the destination accordingly */ if (ScrollRectangle->Left < 0) { CapturedDestinationOrigin.X -= ScrollRectangle->Left; } if (ScrollRectangle->Top < 0) { CapturedDestinationOrigin.Y -= ScrollRectangle->Top; } if (UseClipRectangle) { CapturedClipRectangle = *ClipRectangle; if (!ConioGetIntersection(&CapturedClipRectangle, &CapturedClipRectangle, &ScreenBuffer)) { return STATUS_SUCCESS; } } else { CapturedClipRectangle = ScreenBuffer; } ConioInitRect(&DstRegion, CapturedDestinationOrigin.Y, CapturedDestinationOrigin.X, CapturedDestinationOrigin.Y + ConioRectHeight(&SrcRegion) - 1, CapturedDestinationOrigin.X + ConioRectWidth(&SrcRegion ) - 1); if (!Unicode) { WCHAR tmp; ConsoleOutputAnsiToUnicodeChar(Console, &tmp, &FillChar.Char.AsciiChar); FillChar.Char.UnicodeChar = tmp; } ConioMoveRegion(Buffer, &SrcRegion, &DstRegion, &CapturedClipRectangle, FillChar); if ((PCONSOLE_SCREEN_BUFFER)Buffer == Console->ActiveBuffer) { ConioGetUnion(&UpdateRegion, &SrcRegion, &DstRegion); if (ConioGetIntersection(&UpdateRegion, &UpdateRegion, &CapturedClipRectangle)) { /* Draw update region */ TermDrawRegion(Console, &UpdateRegion); } } return STATUS_SUCCESS; } NTSTATUS NTAPI ConDrvSetConsoleWindowInfo(IN PCONSOLE Console, IN PTEXTMODE_SCREEN_BUFFER Buffer, IN BOOLEAN Absolute, IN PSMALL_RECT WindowRect) { SMALL_RECT CapturedWindowRect; COORD LargestWindowSize; if (Console == NULL || Buffer == NULL || WindowRect == NULL) return STATUS_INVALID_PARAMETER; /* Validity check */ ASSERT(Console == Buffer->Header.Console); CapturedWindowRect = *WindowRect; if (!Absolute) { /* Relative positions are given, transform them to absolute ones */ CapturedWindowRect.Left += Buffer->ViewOrigin.X; CapturedWindowRect.Top += Buffer->ViewOrigin.Y; CapturedWindowRect.Right += Buffer->ViewOrigin.X + Buffer->ViewSize.X - 1; CapturedWindowRect.Bottom += Buffer->ViewOrigin.Y + Buffer->ViewSize.Y - 1; } /* * The MSDN documentation on SetConsoleWindowInfo is partially wrong about * the performed checks this API performs. While it is correct that the * 'Right'/'Bottom' members cannot be strictly smaller than the 'Left'/'Top' * members, they can be equal. * Also, if the 'Left' or 'Top' members are negative, this is automatically * corrected for, and the window rectangle coordinates are shifted accordingly. */ if ((CapturedWindowRect.Right < CapturedWindowRect.Left) || (CapturedWindowRect.Bottom < CapturedWindowRect.Top)) { return STATUS_INVALID_PARAMETER; } /* * Forbid window sizes larger than the largest allowed console window size, * taking into account the size of the console screen buffer. */ TermGetLargestConsoleWindowSize(Console, &LargestWindowSize); LargestWindowSize.X = min(LargestWindowSize.X, Buffer->ScreenBufferSize.X); LargestWindowSize.Y = min(LargestWindowSize.Y, Buffer->ScreenBufferSize.Y); if ((CapturedWindowRect.Right - CapturedWindowRect.Left + 1 > LargestWindowSize.X) || (CapturedWindowRect.Bottom - CapturedWindowRect.Top + 1 > LargestWindowSize.Y)) { return STATUS_INVALID_PARAMETER; } /* Shift the window rectangle coordinates if 'Left' or 'Top' are negative */ if (CapturedWindowRect.Left < 0) { CapturedWindowRect.Right -= CapturedWindowRect.Left; CapturedWindowRect.Left = 0; } if (CapturedWindowRect.Top < 0) { CapturedWindowRect.Bottom -= CapturedWindowRect.Top; CapturedWindowRect.Top = 0; } /* Clip the window rectangle to the screen buffer */ CapturedWindowRect.Right = min(CapturedWindowRect.Right , Buffer->ScreenBufferSize.X); CapturedWindowRect.Bottom = min(CapturedWindowRect.Bottom, Buffer->ScreenBufferSize.Y); Buffer->ViewOrigin.X = CapturedWindowRect.Left; Buffer->ViewOrigin.Y = CapturedWindowRect.Top; Buffer->ViewSize.X = CapturedWindowRect.Right - CapturedWindowRect.Left + 1; Buffer->ViewSize.Y = CapturedWindowRect.Bottom - CapturedWindowRect.Top + 1; TermResizeTerminal(Console); return STATUS_SUCCESS; } /* EOF */