1 /* 2 * COPYRIGHT: See COPYING in the top level directory 3 * PROJECT: ReactOS Console Server DLL 4 * FILE: win32ss/user/winsrv/consrv/frontends/gui/text.c 5 * PURPOSE: GUI Terminal Front-End - Support for text-mode screen-buffers 6 * PROGRAMMERS: G� van Geldorp 7 * Johannes Anderwald 8 * Jeffrey Morlan 9 * Hermes Belusca-Maito (hermes.belusca@sfr.fr) 10 */ 11 12 /* INCLUDES *******************************************************************/ 13 14 #include <consrv.h> 15 16 #define NDEBUG 17 #include <debug.h> 18 19 #include "guiterm.h" 20 21 /* GLOBALS ********************************************************************/ 22 23 #define IS_WHITESPACE(c) ((c) == L'\0' || (c) == L' ' || (c) == L'\t') 24 25 /* FUNCTIONS ******************************************************************/ 26 27 static COLORREF 28 PaletteRGBFromAttrib(PCONSRV_CONSOLE Console, WORD Attribute) 29 { 30 HPALETTE hPalette = Console->ActiveBuffer->PaletteHandle; 31 PALETTEENTRY pe; 32 33 if (hPalette == NULL) return RGBFromAttrib(Console, Attribute); 34 35 GetPaletteEntries(hPalette, Attribute, 1, &pe); 36 return PALETTERGB(pe.peRed, pe.peGreen, pe.peBlue); 37 } 38 39 static VOID 40 CopyBlock(PTEXTMODE_SCREEN_BUFFER Buffer, 41 PSMALL_RECT Selection) 42 { 43 /* 44 * Pressing the Shift key while copying text, allows us to copy 45 * text without newline characters (inline-text copy mode). 46 */ 47 BOOL InlineCopyMode = !!(GetKeyState(VK_SHIFT) & KEY_PRESSED); 48 49 HANDLE hData; 50 PCHAR_INFO ptr; 51 LPWSTR data, dstPos; 52 ULONG selWidth, selHeight; 53 ULONG xPos, yPos; 54 ULONG size; 55 56 DPRINT("CopyBlock(%u, %u, %u, %u)\n", 57 Selection->Left, Selection->Top, Selection->Right, Selection->Bottom); 58 59 /* Prevent against empty blocks */ 60 if (Selection == NULL) return; 61 if (Selection->Left > Selection->Right || Selection->Top > Selection->Bottom) 62 return; 63 64 selWidth = Selection->Right - Selection->Left + 1; 65 selHeight = Selection->Bottom - Selection->Top + 1; 66 67 /* Basic size for one line... */ 68 size = selWidth; 69 /* ... and for the other lines, add newline characters if needed. */ 70 if (selHeight > 0) 71 { 72 /* 73 * If we are not in inline-text copy mode, each selected line must 74 * finish with \r\n . Otherwise, the lines will be just concatenated. 75 */ 76 size += (selWidth + (!InlineCopyMode ? 2 : 0)) * (selHeight - 1); 77 } 78 else 79 { 80 DPRINT1("This case must never happen, because selHeight is at least == 1\n"); 81 } 82 83 size++; /* Null-termination */ 84 size *= sizeof(WCHAR); 85 86 /* Allocate some memory area to be given to the clipboard, so it will not be freed here */ 87 hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size); 88 if (hData == NULL) return; 89 90 data = GlobalLock(hData); 91 if (data == NULL) 92 { 93 GlobalFree(hData); 94 return; 95 } 96 97 DPRINT("Copying %dx%d selection\n", selWidth, selHeight); 98 dstPos = data; 99 100 for (yPos = 0; yPos < selHeight; yPos++) 101 { 102 ULONG length = selWidth; 103 104 ptr = ConioCoordToPointer(Buffer, 105 Selection->Left, 106 Selection->Top + yPos); 107 108 /* Trim whitespace from the right */ 109 while (length > 0) 110 { 111 if (IS_WHITESPACE(ptr[length-1].Char.UnicodeChar)) 112 --length; 113 else 114 break; 115 } 116 117 /* Copy only the characters, leave attributes alone */ 118 for (xPos = 0; xPos < length; xPos++) 119 { 120 /* 121 * Sometimes, applications can put NULL chars into the screen-buffer 122 * (this behaviour is allowed). Detect this and replace by a space. 123 */ 124 *dstPos++ = (ptr[xPos].Char.UnicodeChar ? ptr[xPos].Char.UnicodeChar : L' '); 125 } 126 127 /* Add newline characters if we are not in inline-text copy mode */ 128 if (!InlineCopyMode) 129 { 130 if (yPos != (selHeight - 1)) 131 { 132 wcscat(dstPos, L"\r\n"); 133 dstPos += 2; 134 } 135 } 136 } 137 138 DPRINT("Setting data <%S> to clipboard\n", data); 139 GlobalUnlock(hData); 140 141 EmptyClipboard(); 142 SetClipboardData(CF_UNICODETEXT, hData); 143 } 144 145 static VOID 146 CopyLines(PTEXTMODE_SCREEN_BUFFER Buffer, 147 PCOORD Begin, 148 PCOORD End) 149 { 150 HANDLE hData; 151 PCHAR_INFO ptr; 152 LPWSTR data, dstPos; 153 ULONG NumChars, size; 154 ULONG xPos, yPos, xBeg, xEnd; 155 156 DPRINT("CopyLines((%u, %u) ; (%u, %u))\n", 157 Begin->X, Begin->Y, End->X, End->Y); 158 159 /* Prevent against empty blocks... */ 160 if (Begin == NULL || End == NULL) return; 161 /* ... or malformed blocks */ 162 if (Begin->Y > End->Y || (Begin->Y == End->Y && Begin->X > End->X)) return; 163 164 /* Compute the number of characters to copy */ 165 if (End->Y == Begin->Y) // top == bottom 166 { 167 NumChars = End->X - Begin->X + 1; 168 } 169 else // if (End->Y > Begin->Y) 170 { 171 NumChars = Buffer->ScreenBufferSize.X - Begin->X; 172 173 if (End->Y >= Begin->Y + 2) 174 { 175 NumChars += (End->Y - Begin->Y - 1) * Buffer->ScreenBufferSize.X; 176 } 177 178 NumChars += End->X + 1; 179 } 180 181 size = (NumChars + 1) * sizeof(WCHAR); /* Null-terminated */ 182 183 /* Allocate some memory area to be given to the clipboard, so it will not be freed here */ 184 hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size); 185 if (hData == NULL) return; 186 187 data = GlobalLock(hData); 188 if (data == NULL) 189 { 190 GlobalFree(hData); 191 return; 192 } 193 194 DPRINT("Copying %d characters\n", NumChars); 195 dstPos = data; 196 197 /* 198 * We need to walk per-lines, and not just looping in the big screen-buffer 199 * array, because of the way things are stored inside it. The downside is 200 * that it makes the code more complicated. 201 */ 202 for (yPos = Begin->Y; (yPos <= (ULONG)End->Y) && (NumChars > 0); yPos++) 203 { 204 xBeg = (yPos == Begin->Y ? Begin->X : 0); 205 xEnd = (yPos == End->Y ? End->X : Buffer->ScreenBufferSize.X - 1); 206 207 ptr = ConioCoordToPointer(Buffer, 0, yPos); 208 209 /* Copy only the characters, leave attributes alone */ 210 for (xPos = xBeg; (xPos <= xEnd) && (NumChars-- > 0); xPos++) 211 { 212 /* 213 * Sometimes, applications can put NULL chars into the screen-buffer 214 * (this behaviour is allowed). Detect this and replace by a space. 215 */ 216 *dstPos++ = (ptr[xPos].Char.UnicodeChar ? ptr[xPos].Char.UnicodeChar : L' '); 217 } 218 } 219 220 DPRINT("Setting data <%S> to clipboard\n", data); 221 GlobalUnlock(hData); 222 223 EmptyClipboard(); 224 SetClipboardData(CF_UNICODETEXT, hData); 225 } 226 227 228 VOID 229 PasteText( 230 IN PCONSRV_CONSOLE Console, 231 IN PWCHAR Buffer, 232 IN SIZE_T cchSize) 233 { 234 USHORT VkKey; // MAKEWORD(low = vkey_code, high = shift_state); 235 INPUT_RECORD er; 236 WCHAR CurChar = 0; 237 238 /* Do nothing if we have nothing to paste */ 239 if (!Buffer || (cchSize <= 0)) 240 return; 241 242 er.EventType = KEY_EVENT; 243 er.Event.KeyEvent.wRepeatCount = 1; 244 while (cchSize--) 245 { 246 /* \r or \n characters. Go to the line only if we get "\r\n" sequence. */ 247 if (CurChar == L'\r' && *Buffer == L'\n') 248 { 249 ++Buffer; 250 continue; 251 } 252 CurChar = *Buffer++; 253 254 /* Get the key code (+ shift state) corresponding to the character */ 255 VkKey = VkKeyScanW(CurChar); 256 if (VkKey == 0xFFFF) 257 { 258 DPRINT1("FIXME: TODO: VkKeyScanW failed - Should simulate the key!\n"); 259 /* 260 * We don't really need the scan/key code because we actually only 261 * use the UnicodeChar for output purposes. It may pose few problems 262 * later on but it's not of big importance. One trick would be to 263 * convert the character to OEM / multibyte and use MapVirtualKey() 264 * on each byte (simulating an Alt-0xxx OEM keyboard press). 265 */ 266 } 267 268 /* Pressing some control keys */ 269 270 /* Pressing the character key, with the control keys maintained pressed */ 271 er.Event.KeyEvent.bKeyDown = TRUE; 272 er.Event.KeyEvent.wVirtualKeyCode = LOBYTE(VkKey); 273 er.Event.KeyEvent.wVirtualScanCode = MapVirtualKeyW(LOBYTE(VkKey), MAPVK_VK_TO_VSC); 274 er.Event.KeyEvent.uChar.UnicodeChar = CurChar; 275 er.Event.KeyEvent.dwControlKeyState = 0; 276 if (HIBYTE(VkKey) & 1) 277 er.Event.KeyEvent.dwControlKeyState |= SHIFT_PRESSED; 278 if (HIBYTE(VkKey) & 2) 279 er.Event.KeyEvent.dwControlKeyState |= LEFT_CTRL_PRESSED; // RIGHT_CTRL_PRESSED; 280 if (HIBYTE(VkKey) & 4) 281 er.Event.KeyEvent.dwControlKeyState |= LEFT_ALT_PRESSED; // RIGHT_ALT_PRESSED; 282 283 ConioProcessInputEvent(Console, &er); 284 285 /* Up all the character and control keys */ 286 er.Event.KeyEvent.bKeyDown = FALSE; 287 ConioProcessInputEvent(Console, &er); 288 } 289 } 290 291 VOID 292 GetSelectionBeginEnd(PCOORD Begin, PCOORD End, 293 PCOORD SelectionAnchor, 294 PSMALL_RECT SmallRect); 295 296 VOID 297 GuiCopyFromTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer, 298 PGUI_CONSOLE_DATA GuiData) 299 { 300 /* 301 * This function supposes that the system clipboard was opened. 302 */ 303 304 BOOL LineSelection = GuiData->LineSelection; 305 306 DPRINT("Selection is (%d|%d) to (%d|%d) in %s mode\n", 307 GuiData->Selection.srSelection.Left, 308 GuiData->Selection.srSelection.Top, 309 GuiData->Selection.srSelection.Right, 310 GuiData->Selection.srSelection.Bottom, 311 (LineSelection ? "line" : "block")); 312 313 if (!LineSelection) 314 { 315 CopyBlock(Buffer, &GuiData->Selection.srSelection); 316 } 317 else 318 { 319 COORD Begin, End; 320 321 GetSelectionBeginEnd(&Begin, &End, 322 &GuiData->Selection.dwSelectionAnchor, 323 &GuiData->Selection.srSelection); 324 325 CopyLines(Buffer, &Begin, &End); 326 } 327 } 328 329 VOID 330 GuiPasteToTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer, 331 PGUI_CONSOLE_DATA GuiData) 332 { 333 /* 334 * This function supposes that the system clipboard was opened. 335 */ 336 337 PCONSRV_CONSOLE Console = Buffer->Header.Console; 338 339 HANDLE hData; 340 LPWSTR pszText; 341 342 hData = GetClipboardData(CF_UNICODETEXT); 343 if (hData == NULL) return; 344 345 pszText = GlobalLock(hData); 346 if (pszText == NULL) return; 347 348 DPRINT("Got data <%S> from clipboard\n", pszText); 349 PasteText(Console, pszText, wcslen(pszText)); 350 351 GlobalUnlock(hData); 352 } 353 354 VOID 355 GuiPaintTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer, 356 PGUI_CONSOLE_DATA GuiData, 357 PRECT rcView, 358 PRECT rcFramebuffer) 359 { 360 PCONSRV_CONSOLE Console = Buffer->Header.Console; 361 // ASSERT(Console == GuiData->Console); 362 363 ULONG TopLine, BottomLine, LeftChar, RightChar; 364 ULONG Line, Char, Start; 365 PCHAR_INFO From; 366 PWCHAR To; 367 WORD LastAttribute, Attribute; 368 ULONG CursorX, CursorY, CursorHeight; 369 HBRUSH CursorBrush, OldBrush; 370 HFONT OldFont, NewFont; 371 BOOLEAN IsUnderline; 372 373 SetRectEmpty(rcFramebuffer); 374 375 if (Buffer->Buffer == NULL) return; 376 377 if (!ConDrvValidateConsoleUnsafe((PCONSOLE)Console, CONSOLE_RUNNING, TRUE)) return; 378 379 rcFramebuffer->left = Buffer->ViewOrigin.X * GuiData->CharWidth + rcView->left; 380 rcFramebuffer->top = Buffer->ViewOrigin.Y * GuiData->CharHeight + rcView->top; 381 rcFramebuffer->right = Buffer->ViewOrigin.X * GuiData->CharWidth + rcView->right; 382 rcFramebuffer->bottom = Buffer->ViewOrigin.Y * GuiData->CharHeight + rcView->bottom; 383 384 LeftChar = rcFramebuffer->left / GuiData->CharWidth; 385 TopLine = rcFramebuffer->top / GuiData->CharHeight; 386 RightChar = rcFramebuffer->right / GuiData->CharWidth; 387 BottomLine = rcFramebuffer->bottom / GuiData->CharHeight; 388 389 if (RightChar >= (ULONG)Buffer->ScreenBufferSize.X) RightChar = Buffer->ScreenBufferSize.X - 1; 390 if (BottomLine >= (ULONG)Buffer->ScreenBufferSize.Y) BottomLine = Buffer->ScreenBufferSize.Y - 1; 391 392 LastAttribute = ConioCoordToPointer(Buffer, LeftChar, TopLine)->Attributes; 393 394 SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute))); 395 SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute))); 396 397 /* We use the underscore flag as a underline flag */ 398 IsUnderline = !!(LastAttribute & COMMON_LVB_UNDERSCORE); 399 /* Select the new font */ 400 NewFont = GuiData->Font[IsUnderline ? FONT_BOLD : FONT_NORMAL]; 401 OldFont = SelectObject(GuiData->hMemDC, NewFont); 402 403 for (Line = TopLine; Line <= BottomLine; Line++) 404 { 405 WCHAR LineBuffer[80]; // Buffer containing a part or all the line to be displayed 406 From = ConioCoordToPointer(Buffer, LeftChar, Line); // Get the first code of the line 407 Start = LeftChar; 408 To = LineBuffer; 409 410 for (Char = LeftChar; Char <= RightChar; Char++) 411 { 412 /* 413 * We flush the buffer if the new attribute is different 414 * from the current one, or if the buffer is full. 415 */ 416 if (From->Attributes != LastAttribute || (Char - Start == sizeof(LineBuffer) / sizeof(WCHAR))) 417 { 418 TextOutW(GuiData->hMemDC, 419 Start * GuiData->CharWidth, 420 Line * GuiData->CharHeight, 421 LineBuffer, 422 Char - Start); 423 Start = Char; 424 To = LineBuffer; 425 Attribute = From->Attributes; 426 if (Attribute != LastAttribute) 427 { 428 LastAttribute = Attribute; 429 SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute))); 430 SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute))); 431 432 /* Change underline state if needed */ 433 if (!!(LastAttribute & COMMON_LVB_UNDERSCORE) != IsUnderline) 434 { 435 IsUnderline = !!(LastAttribute & COMMON_LVB_UNDERSCORE); 436 /* Select the new font */ 437 NewFont = GuiData->Font[IsUnderline ? FONT_BOLD : FONT_NORMAL]; 438 /* OldFont = */ SelectObject(GuiData->hMemDC, NewFont); 439 } 440 } 441 } 442 443 *(To++) = (From++)->Char.UnicodeChar; 444 } 445 446 TextOutW(GuiData->hMemDC, 447 Start * GuiData->CharWidth, 448 Line * GuiData->CharHeight, 449 LineBuffer, 450 RightChar - Start + 1); 451 } 452 453 /* Restore the old font */ 454 SelectObject(GuiData->hMemDC, OldFont); 455 456 /* 457 * Draw the caret 458 */ 459 if (Buffer->CursorInfo.bVisible && 460 Buffer->CursorBlinkOn && 461 !Buffer->ForceCursorOff) 462 { 463 CursorX = Buffer->CursorPosition.X; 464 CursorY = Buffer->CursorPosition.Y; 465 if (LeftChar <= CursorX && CursorX <= RightChar && 466 TopLine <= CursorY && CursorY <= BottomLine) 467 { 468 CursorHeight = ConioEffectiveCursorSize(Console, GuiData->CharHeight); 469 470 Attribute = ConioCoordToPointer(Buffer, Buffer->CursorPosition.X, Buffer->CursorPosition.Y)->Attributes; 471 if (Attribute == DEFAULT_SCREEN_ATTRIB) Attribute = Buffer->ScreenDefaultAttrib; 472 473 CursorBrush = CreateSolidBrush(PaletteRGBFromAttrib(Console, TextAttribFromAttrib(Attribute))); 474 OldBrush = SelectObject(GuiData->hMemDC, CursorBrush); 475 476 PatBlt(GuiData->hMemDC, 477 CursorX * GuiData->CharWidth, 478 CursorY * GuiData->CharHeight + (GuiData->CharHeight - CursorHeight), 479 GuiData->CharWidth, 480 CursorHeight, 481 PATCOPY); 482 483 SelectObject(GuiData->hMemDC, OldBrush); 484 DeleteObject(CursorBrush); 485 } 486 } 487 488 LeaveCriticalSection(&Console->Lock); 489 } 490 491 /* EOF */ 492