reactos/win32ss/user/winsrv/consrv/frontends/gui/text.c
Hermès Bélusca-Maïto 75d0346c54
[CONSRV] Finally fix CONSOLE vs. CONSRV_CONSOLE/WINSRV_CONSOLE stuff. CORE-9496
What remains to be cleared up are the ugly casts.
2020-04-11 16:27:09 +02:00

578 lines
19 KiB
C

/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS Console Server DLL
* FILE: win32ss/user/winsrv/consrv/frontends/gui/text.c
* PURPOSE: GUI Terminal Front-End - Support for text-mode screen-buffers
* PROGRAMMERS: Gé van Geldorp
* Johannes Anderwald
* Jeffrey Morlan
* Hermes Belusca-Maito (hermes.belusca@sfr.fr)
*/
/* INCLUDES *******************************************************************/
#include <consrv.h>
#define NDEBUG
#include <debug.h>
#include "guiterm.h"
/* GLOBALS ********************************************************************/
#define IS_WHITESPACE(c) ((c) == L'\0' || (c) == L' ' || (c) == L'\t')
/* FUNCTIONS ******************************************************************/
static COLORREF
PaletteRGBFromAttrib(PCONSRV_CONSOLE Console, WORD Attribute)
{
HPALETTE hPalette = Console->ActiveBuffer->PaletteHandle;
PALETTEENTRY pe;
if (hPalette == NULL) return RGBFromAttrib(Console, Attribute);
GetPaletteEntries(hPalette, Attribute, 1, &pe);
return PALETTERGB(pe.peRed, pe.peGreen, pe.peBlue);
}
static VOID
CopyBlock(PTEXTMODE_SCREEN_BUFFER Buffer,
PSMALL_RECT Selection)
{
/*
* Pressing the Shift key while copying text, allows us to copy
* text without newline characters (inline-text copy mode).
*/
BOOL InlineCopyMode = !!(GetKeyState(VK_SHIFT) & KEY_PRESSED);
HANDLE hData;
PCHAR_INFO ptr;
LPWSTR data, dstPos;
ULONG selWidth, selHeight;
ULONG xPos, yPos;
ULONG size;
DPRINT("CopyBlock(%u, %u, %u, %u)\n",
Selection->Left, Selection->Top, Selection->Right, Selection->Bottom);
/* Prevent against empty blocks */
if ((Selection == NULL) || ConioIsRectEmpty(Selection))
return;
selWidth = ConioRectWidth(Selection);
selHeight = ConioRectHeight(Selection);
/* Basic size for one line... */
size = selWidth;
/* ... and for the other lines, add newline characters if needed. */
if (selHeight > 0)
{
/*
* If we are not in inline-text copy mode, each selected line must
* finish with \r\n . Otherwise, the lines will be just concatenated.
*/
size += (selWidth + (!InlineCopyMode ? 2 : 0)) * (selHeight - 1);
}
else
{
DPRINT1("This case must never happen, because selHeight is at least == 1\n");
}
size++; /* Null-termination */
size *= sizeof(WCHAR);
/* Allocate some memory area to be given to the clipboard, so it will not be freed here */
hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
if (hData == NULL) return;
data = GlobalLock(hData);
if (data == NULL)
{
GlobalFree(hData);
return;
}
DPRINT("Copying %dx%d selection\n", selWidth, selHeight);
dstPos = data;
for (yPos = 0; yPos < selHeight; yPos++)
{
ULONG length = selWidth;
ptr = ConioCoordToPointer(Buffer,
Selection->Left,
Selection->Top + yPos);
/* Trim whitespace from the right */
while (length > 0)
{
if (IS_WHITESPACE(ptr[length-1].Char.UnicodeChar))
--length;
else
break;
}
/* Copy only the characters, leave attributes alone */
for (xPos = 0; xPos < length; xPos++)
{
/*
* Sometimes, applications can put NULL chars into the screen-buffer
* (this behaviour is allowed). Detect this and replace by a space.
* For full-width characters: copy only the character specified
* in the leading-byte cell, skipping the trailing-byte cell.
*/
if (!(ptr[xPos].Attributes & COMMON_LVB_TRAILING_BYTE))
{
*dstPos++ = (ptr[xPos].Char.UnicodeChar ? ptr[xPos].Char.UnicodeChar : L' ');
}
}
/* Add newline characters if we are not in inline-text copy mode */
if (!InlineCopyMode)
{
if (yPos != (selHeight - 1))
{
wcscat(dstPos, L"\r\n");
dstPos += 2;
}
}
}
DPRINT("Setting data <%S> to clipboard\n", data);
GlobalUnlock(hData);
EmptyClipboard();
SetClipboardData(CF_UNICODETEXT, hData);
}
static VOID
CopyLines(PTEXTMODE_SCREEN_BUFFER Buffer,
PCOORD Begin,
PCOORD End)
{
HANDLE hData;
PCHAR_INFO ptr;
LPWSTR data, dstPos;
ULONG NumChars, size;
ULONG xPos, yPos, xBeg, xEnd;
DPRINT("CopyLines((%u, %u) ; (%u, %u))\n",
Begin->X, Begin->Y, End->X, End->Y);
/* Prevent against empty blocks... */
if (Begin == NULL || End == NULL) return;
/* ... or malformed blocks */
if (Begin->Y > End->Y || (Begin->Y == End->Y && Begin->X > End->X)) return;
/* Compute the number of characters to copy */
if (End->Y == Begin->Y) // top == bottom
{
NumChars = End->X - Begin->X + 1;
}
else // if (End->Y > Begin->Y)
{
NumChars = Buffer->ScreenBufferSize.X - Begin->X;
if (End->Y >= Begin->Y + 2)
{
NumChars += (End->Y - Begin->Y - 1) * Buffer->ScreenBufferSize.X;
}
NumChars += End->X + 1;
}
size = (NumChars + 1) * sizeof(WCHAR); /* Null-terminated */
/* Allocate some memory area to be given to the clipboard, so it will not be freed here */
hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
if (hData == NULL) return;
data = GlobalLock(hData);
if (data == NULL)
{
GlobalFree(hData);
return;
}
DPRINT("Copying %d characters\n", NumChars);
dstPos = data;
/*
* We need to walk per-lines, and not just looping in the big screen-buffer
* array, because of the way things are stored inside it. The downside is
* that it makes the code more complicated.
*/
for (yPos = Begin->Y; (yPos <= (ULONG)End->Y) && (NumChars > 0); yPos++)
{
xBeg = (yPos == Begin->Y ? Begin->X : 0);
xEnd = (yPos == End->Y ? End->X : Buffer->ScreenBufferSize.X - 1);
ptr = ConioCoordToPointer(Buffer, 0, yPos);
/* Copy only the characters, leave attributes alone */
for (xPos = xBeg; (xPos <= xEnd) && (NumChars-- > 0); xPos++)
{
/*
* Sometimes, applications can put NULL chars into the screen-buffer
* (this behaviour is allowed). Detect this and replace by a space.
* For full-width characters: copy only the character specified
* in the leading-byte cell, skipping the trailing-byte cell.
*/
if (!(ptr[xPos].Attributes & COMMON_LVB_TRAILING_BYTE))
{
*dstPos++ = (ptr[xPos].Char.UnicodeChar ? ptr[xPos].Char.UnicodeChar : L' ');
}
}
}
DPRINT("Setting data <%S> to clipboard\n", data);
GlobalUnlock(hData);
EmptyClipboard();
SetClipboardData(CF_UNICODETEXT, hData);
}
VOID
PasteText(
IN PCONSRV_CONSOLE Console,
IN PWCHAR Buffer,
IN SIZE_T cchSize)
{
USHORT VkKey; // MAKEWORD(low = vkey_code, high = shift_state);
INPUT_RECORD er;
WCHAR CurChar = 0;
/* Do nothing if we have nothing to paste */
if (!Buffer || (cchSize <= 0))
return;
er.EventType = KEY_EVENT;
er.Event.KeyEvent.wRepeatCount = 1;
while (cchSize--)
{
/* \r or \n characters. Go to the line only if we get "\r\n" sequence. */
if (CurChar == L'\r' && *Buffer == L'\n')
{
++Buffer;
continue;
}
CurChar = *Buffer++;
/* Get the key code (+ shift state) corresponding to the character */
VkKey = VkKeyScanW(CurChar);
if (VkKey == 0xFFFF)
{
DPRINT1("FIXME: TODO: VkKeyScanW failed - Should simulate the key!\n");
/*
* We don't really need the scan/key code because we actually only
* use the UnicodeChar for output purposes. It may pose few problems
* later on but it's not of big importance. One trick would be to
* convert the character to OEM / multibyte and use MapVirtualKey()
* on each byte (simulating an Alt-0xxx OEM keyboard press).
*/
}
/* Pressing some control keys */
/* Pressing the character key, with the control keys maintained pressed */
er.Event.KeyEvent.bKeyDown = TRUE;
er.Event.KeyEvent.wVirtualKeyCode = LOBYTE(VkKey);
er.Event.KeyEvent.wVirtualScanCode = MapVirtualKeyW(LOBYTE(VkKey), MAPVK_VK_TO_VSC);
er.Event.KeyEvent.uChar.UnicodeChar = CurChar;
er.Event.KeyEvent.dwControlKeyState = 0;
if (HIBYTE(VkKey) & 1)
er.Event.KeyEvent.dwControlKeyState |= SHIFT_PRESSED;
if (HIBYTE(VkKey) & 2)
er.Event.KeyEvent.dwControlKeyState |= LEFT_CTRL_PRESSED; // RIGHT_CTRL_PRESSED;
if (HIBYTE(VkKey) & 4)
er.Event.KeyEvent.dwControlKeyState |= LEFT_ALT_PRESSED; // RIGHT_ALT_PRESSED;
ConioProcessInputEvent(Console, &er);
/* Up all the character and control keys */
er.Event.KeyEvent.bKeyDown = FALSE;
ConioProcessInputEvent(Console, &er);
}
}
VOID
GetSelectionBeginEnd(PCOORD Begin, PCOORD End,
PCOORD SelectionAnchor,
PSMALL_RECT SmallRect);
VOID
GuiCopyFromTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
PGUI_CONSOLE_DATA GuiData)
{
/*
* This function supposes that the system clipboard was opened.
*/
BOOL LineSelection = GuiData->LineSelection;
DPRINT("Selection is (%d|%d) to (%d|%d) in %s mode\n",
GuiData->Selection.srSelection.Left,
GuiData->Selection.srSelection.Top,
GuiData->Selection.srSelection.Right,
GuiData->Selection.srSelection.Bottom,
(LineSelection ? "line" : "block"));
if (!LineSelection)
{
CopyBlock(Buffer, &GuiData->Selection.srSelection);
}
else
{
COORD Begin, End;
GetSelectionBeginEnd(&Begin, &End,
&GuiData->Selection.dwSelectionAnchor,
&GuiData->Selection.srSelection);
CopyLines(Buffer, &Begin, &End);
}
}
VOID
GuiPasteToTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
PGUI_CONSOLE_DATA GuiData)
{
/*
* This function supposes that the system clipboard was opened.
*/
PCONSRV_CONSOLE Console = (PCONSRV_CONSOLE)Buffer->Header.Console;
HANDLE hData;
LPWSTR pszText;
hData = GetClipboardData(CF_UNICODETEXT);
if (hData == NULL) return;
pszText = GlobalLock(hData);
if (pszText == NULL) return;
DPRINT("Got data <%S> from clipboard\n", pszText);
PasteText(Console, pszText, wcslen(pszText));
GlobalUnlock(hData);
}
static VOID
GuiPaintCaret(
PTEXTMODE_SCREEN_BUFFER Buffer,
PGUI_CONSOLE_DATA GuiData,
ULONG TopLine,
ULONG BottomLine,
ULONG LeftColumn,
ULONG RightColumn)
{
PCONSRV_CONSOLE Console = (PCONSRV_CONSOLE)Buffer->Header.Console;
ULONG CursorX, CursorY, CursorHeight;
HBRUSH CursorBrush, OldBrush;
WORD Attribute;
if (Buffer->CursorInfo.bVisible &&
Buffer->CursorBlinkOn &&
!Buffer->ForceCursorOff)
{
CursorX = Buffer->CursorPosition.X;
CursorY = Buffer->CursorPosition.Y;
if (LeftColumn <= CursorX && CursorX <= RightColumn &&
TopLine <= CursorY && CursorY <= BottomLine)
{
CursorHeight = ConioEffectiveCursorSize(Console, GuiData->CharHeight);
Attribute = ConioCoordToPointer(Buffer, Buffer->CursorPosition.X, Buffer->CursorPosition.Y)->Attributes;
if (Attribute == DEFAULT_SCREEN_ATTRIB)
Attribute = Buffer->ScreenDefaultAttrib;
CursorBrush = CreateSolidBrush(PaletteRGBFromAttrib(Console, TextAttribFromAttrib(Attribute)));
OldBrush = SelectObject(GuiData->hMemDC, CursorBrush);
if (Attribute & COMMON_LVB_LEADING_BYTE)
{
/* The caret is on the leading byte */
PatBlt(GuiData->hMemDC,
CursorX * GuiData->CharWidth,
CursorY * GuiData->CharHeight + (GuiData->CharHeight - CursorHeight),
GuiData->CharWidth * 2,
CursorHeight,
PATCOPY);
}
else if (Attribute & COMMON_LVB_TRAILING_BYTE)
{
/* The caret is on the trailing byte */
PatBlt(GuiData->hMemDC,
(CursorX - 1) * GuiData->CharWidth,
CursorY * GuiData->CharHeight + (GuiData->CharHeight - CursorHeight),
GuiData->CharWidth * 2,
CursorHeight,
PATCOPY);
}
else
{
PatBlt(GuiData->hMemDC,
CursorX * GuiData->CharWidth,
CursorY * GuiData->CharHeight + (GuiData->CharHeight - CursorHeight),
GuiData->CharWidth,
CursorHeight,
PATCOPY);
}
SelectObject(GuiData->hMemDC, OldBrush);
DeleteObject(CursorBrush);
}
}
}
VOID
GuiPaintTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
PGUI_CONSOLE_DATA GuiData,
PRECT rcView,
PRECT rcFramebuffer)
{
PCONSRV_CONSOLE Console = (PCONSRV_CONSOLE)Buffer->Header.Console;
ULONG TopLine, BottomLine, LeftColumn, RightColumn;
ULONG Line, Char, Start;
PCHAR_INFO From;
PWCHAR To;
WORD LastAttribute, Attribute;
HFONT OldFont, NewFont;
BOOLEAN IsUnderline;
// ASSERT(Console == GuiData->Console);
ConioInitLongRect(rcFramebuffer, 0, 0, 0, 0);
if (Buffer->Buffer == NULL)
return;
if (!ConDrvValidateConsoleUnsafe((PCONSOLE)Console, CONSOLE_RUNNING, TRUE))
return;
ConioInitLongRect(rcFramebuffer,
Buffer->ViewOrigin.Y * GuiData->CharHeight + rcView->top,
Buffer->ViewOrigin.X * GuiData->CharWidth + rcView->left,
Buffer->ViewOrigin.Y * GuiData->CharHeight + rcView->bottom,
Buffer->ViewOrigin.X * GuiData->CharWidth + rcView->right);
LeftColumn = rcFramebuffer->left / GuiData->CharWidth;
RightColumn = rcFramebuffer->right / GuiData->CharWidth;
if (RightColumn >= (ULONG)Buffer->ScreenBufferSize.X)
RightColumn = Buffer->ScreenBufferSize.X - 1;
TopLine = rcFramebuffer->top / GuiData->CharHeight;
BottomLine = rcFramebuffer->bottom / GuiData->CharHeight;
if (BottomLine >= (ULONG)Buffer->ScreenBufferSize.Y)
BottomLine = Buffer->ScreenBufferSize.Y - 1;
LastAttribute = ConioCoordToPointer(Buffer, LeftColumn, TopLine)->Attributes;
SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute)));
SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute)));
/* We use the underscore flag as a underline flag */
IsUnderline = !!(LastAttribute & COMMON_LVB_UNDERSCORE);
/* Select the new font */
NewFont = GuiData->Font[IsUnderline ? FONT_BOLD : FONT_NORMAL];
OldFont = SelectObject(GuiData->hMemDC, NewFont);
if (Console->IsCJK)
{
for (Line = TopLine; Line <= BottomLine; Line++)
{
for (Char = LeftColumn; Char <= RightColumn; Char++)
{
From = ConioCoordToPointer(Buffer, Char, Line);
Attribute = From->Attributes;
SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(Attribute)));
SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(Attribute)));
/* Change underline state if needed */
if (!!(Attribute & COMMON_LVB_UNDERSCORE) != IsUnderline)
{
IsUnderline = !!(Attribute & COMMON_LVB_UNDERSCORE);
/* Select the new font */
NewFont = GuiData->Font[IsUnderline ? FONT_BOLD : FONT_NORMAL];
SelectObject(GuiData->hMemDC, NewFont);
}
if (Attribute & COMMON_LVB_TRAILING_BYTE)
continue;
TextOutW(GuiData->hMemDC,
Char * GuiData->CharWidth,
Line * GuiData->CharHeight,
&From->Char.UnicodeChar, 1);
}
}
}
else
{
for (Line = TopLine; Line <= BottomLine; Line++)
{
WCHAR LineBuffer[80]; // Buffer containing a part or all the line to be displayed
From = ConioCoordToPointer(Buffer, LeftColumn, Line); // Get the first code of the line
Start = LeftColumn;
To = LineBuffer;
for (Char = LeftColumn; Char <= RightColumn; Char++)
{
/*
* We flush the buffer if the new attribute is different
* from the current one, or if the buffer is full.
*/
if (From->Attributes != LastAttribute || (Char - Start == sizeof(LineBuffer) / sizeof(WCHAR)))
{
TextOutW(GuiData->hMemDC,
Start * GuiData->CharWidth,
Line * GuiData->CharHeight,
LineBuffer,
Char - Start);
Start = Char;
To = LineBuffer;
Attribute = From->Attributes;
if (Attribute != LastAttribute)
{
LastAttribute = Attribute;
SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute)));
SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute)));
/* Change underline state if needed */
if (!!(LastAttribute & COMMON_LVB_UNDERSCORE) != IsUnderline)
{
IsUnderline = !!(LastAttribute & COMMON_LVB_UNDERSCORE);
/* Select the new font */
NewFont = GuiData->Font[IsUnderline ? FONT_BOLD : FONT_NORMAL];
SelectObject(GuiData->hMemDC, NewFont);
}
}
}
*(To++) = (From++)->Char.UnicodeChar;
}
TextOutW(GuiData->hMemDC,
Start * GuiData->CharWidth,
Line * GuiData->CharHeight,
LineBuffer,
RightColumn - Start + 1);
}
}
/* Restore the old font */
SelectObject(GuiData->hMemDC, OldFont);
/* Draw the caret */
GuiPaintCaret(Buffer, GuiData, TopLine, BottomLine, LeftColumn, RightColumn);
LeaveCriticalSection(&Console->Lock);
}
/* EOF */