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) || ConioIsRectEmpty(Selection)) 61 return; 62 63 selWidth = ConioRectWidth(Selection); 64 selHeight = ConioRectHeight(Selection); 65 66 /* Basic size for one line... */ 67 size = selWidth; 68 /* ... and for the other lines, add newline characters if needed. */ 69 if (selHeight > 0) 70 { 71 /* 72 * If we are not in inline-text copy mode, each selected line must 73 * finish with \r\n . Otherwise, the lines will be just concatenated. 74 */ 75 size += (selWidth + (!InlineCopyMode ? 2 : 0)) * (selHeight - 1); 76 } 77 else 78 { 79 DPRINT1("This case must never happen, because selHeight is at least == 1\n"); 80 } 81 82 size++; /* Null-termination */ 83 size *= sizeof(WCHAR); 84 85 /* Allocate some memory area to be given to the clipboard, so it will not be freed here */ 86 hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size); 87 if (hData == NULL) return; 88 89 data = GlobalLock(hData); 90 if (data == NULL) 91 { 92 GlobalFree(hData); 93 return; 94 } 95 96 DPRINT("Copying %dx%d selection\n", selWidth, selHeight); 97 dstPos = data; 98 99 for (yPos = 0; yPos < selHeight; yPos++) 100 { 101 ULONG length = selWidth; 102 103 ptr = ConioCoordToPointer(Buffer, 104 Selection->Left, 105 Selection->Top + yPos); 106 107 /* Trim whitespace from the right */ 108 while (length > 0) 109 { 110 if (IS_WHITESPACE(ptr[length-1].Char.UnicodeChar)) 111 --length; 112 else 113 break; 114 } 115 116 /* Copy only the characters, leave attributes alone */ 117 for (xPos = 0; xPos < length; xPos++) 118 { 119 /* 120 * Sometimes, applications can put NULL chars into the screen-buffer 121 * (this behaviour is allowed). Detect this and replace by a space. 122 * For full-width characters: copy only the character specified 123 * in the leading-byte cell, skipping the trailing-byte cell. 124 */ 125 if (!(ptr[xPos].Attributes & COMMON_LVB_TRAILING_BYTE)) 126 { 127 *dstPos++ = (ptr[xPos].Char.UnicodeChar ? ptr[xPos].Char.UnicodeChar : L' '); 128 } 129 } 130 131 /* Add newline characters if we are not in inline-text copy mode */ 132 if (!InlineCopyMode) 133 { 134 if (yPos != (selHeight - 1)) 135 { 136 wcscat(dstPos, L"\r\n"); 137 dstPos += 2; 138 } 139 } 140 } 141 142 DPRINT("Setting data <%S> to clipboard\n", data); 143 GlobalUnlock(hData); 144 145 EmptyClipboard(); 146 SetClipboardData(CF_UNICODETEXT, hData); 147 } 148 149 static VOID 150 CopyLines(PTEXTMODE_SCREEN_BUFFER Buffer, 151 PCOORD Begin, 152 PCOORD End) 153 { 154 HANDLE hData; 155 PCHAR_INFO ptr; 156 LPWSTR data, dstPos; 157 ULONG NumChars, size; 158 ULONG xPos, yPos, xBeg, xEnd; 159 160 DPRINT("CopyLines((%u, %u) ; (%u, %u))\n", 161 Begin->X, Begin->Y, End->X, End->Y); 162 163 /* Prevent against empty blocks... */ 164 if (Begin == NULL || End == NULL) return; 165 /* ... or malformed blocks */ 166 if (Begin->Y > End->Y || (Begin->Y == End->Y && Begin->X > End->X)) return; 167 168 /* Compute the number of characters to copy */ 169 if (End->Y == Begin->Y) // top == bottom 170 { 171 NumChars = End->X - Begin->X + 1; 172 } 173 else // if (End->Y > Begin->Y) 174 { 175 NumChars = Buffer->ScreenBufferSize.X - Begin->X; 176 177 if (End->Y >= Begin->Y + 2) 178 { 179 NumChars += (End->Y - Begin->Y - 1) * Buffer->ScreenBufferSize.X; 180 } 181 182 NumChars += End->X + 1; 183 } 184 185 size = (NumChars + 1) * sizeof(WCHAR); /* Null-terminated */ 186 187 /* Allocate some memory area to be given to the clipboard, so it will not be freed here */ 188 hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size); 189 if (hData == NULL) return; 190 191 data = GlobalLock(hData); 192 if (data == NULL) 193 { 194 GlobalFree(hData); 195 return; 196 } 197 198 DPRINT("Copying %d characters\n", NumChars); 199 dstPos = data; 200 201 /* 202 * We need to walk per-lines, and not just looping in the big screen-buffer 203 * array, because of the way things are stored inside it. The downside is 204 * that it makes the code more complicated. 205 */ 206 for (yPos = Begin->Y; (yPos <= (ULONG)End->Y) && (NumChars > 0); yPos++) 207 { 208 xBeg = (yPos == Begin->Y ? Begin->X : 0); 209 xEnd = (yPos == End->Y ? End->X : Buffer->ScreenBufferSize.X - 1); 210 211 ptr = ConioCoordToPointer(Buffer, 0, yPos); 212 213 /* Copy only the characters, leave attributes alone */ 214 for (xPos = xBeg; (xPos <= xEnd) && (NumChars-- > 0); xPos++) 215 { 216 /* 217 * Sometimes, applications can put NULL chars into the screen-buffer 218 * (this behaviour is allowed). Detect this and replace by a space. 219 * For full-width characters: copy only the character specified 220 * in the leading-byte cell, skipping the trailing-byte cell. 221 */ 222 if (!(ptr[xPos].Attributes & COMMON_LVB_TRAILING_BYTE)) 223 { 224 *dstPos++ = (ptr[xPos].Char.UnicodeChar ? ptr[xPos].Char.UnicodeChar : L' '); 225 } 226 } 227 } 228 229 DPRINT("Setting data <%S> to clipboard\n", data); 230 GlobalUnlock(hData); 231 232 EmptyClipboard(); 233 SetClipboardData(CF_UNICODETEXT, hData); 234 } 235 236 237 VOID 238 PasteText( 239 IN PCONSRV_CONSOLE Console, 240 IN PWCHAR Buffer, 241 IN SIZE_T cchSize) 242 { 243 USHORT VkKey; // MAKEWORD(low = vkey_code, high = shift_state); 244 INPUT_RECORD er; 245 WCHAR CurChar = 0; 246 247 /* Do nothing if we have nothing to paste */ 248 if (!Buffer || (cchSize <= 0)) 249 return; 250 251 er.EventType = KEY_EVENT; 252 er.Event.KeyEvent.wRepeatCount = 1; 253 while (cchSize--) 254 { 255 /* \r or \n characters. Go to the line only if we get "\r\n" sequence. */ 256 if (CurChar == L'\r' && *Buffer == L'\n') 257 { 258 ++Buffer; 259 continue; 260 } 261 CurChar = *Buffer++; 262 263 /* Get the key code (+ shift state) corresponding to the character */ 264 VkKey = VkKeyScanW(CurChar); 265 if (VkKey == 0xFFFF) 266 { 267 DPRINT1("FIXME: TODO: VkKeyScanW failed - Should simulate the key!\n"); 268 /* 269 * We don't really need the scan/key code because we actually only 270 * use the UnicodeChar for output purposes. It may pose few problems 271 * later on but it's not of big importance. One trick would be to 272 * convert the character to OEM / multibyte and use MapVirtualKey() 273 * on each byte (simulating an Alt-0xxx OEM keyboard press). 274 */ 275 } 276 277 /* Pressing some control keys */ 278 279 /* Pressing the character key, with the control keys maintained pressed */ 280 er.Event.KeyEvent.bKeyDown = TRUE; 281 er.Event.KeyEvent.wVirtualKeyCode = LOBYTE(VkKey); 282 er.Event.KeyEvent.wVirtualScanCode = MapVirtualKeyW(LOBYTE(VkKey), MAPVK_VK_TO_VSC); 283 er.Event.KeyEvent.uChar.UnicodeChar = CurChar; 284 er.Event.KeyEvent.dwControlKeyState = 0; 285 if (HIBYTE(VkKey) & 1) 286 er.Event.KeyEvent.dwControlKeyState |= SHIFT_PRESSED; 287 if (HIBYTE(VkKey) & 2) 288 er.Event.KeyEvent.dwControlKeyState |= LEFT_CTRL_PRESSED; // RIGHT_CTRL_PRESSED; 289 if (HIBYTE(VkKey) & 4) 290 er.Event.KeyEvent.dwControlKeyState |= LEFT_ALT_PRESSED; // RIGHT_ALT_PRESSED; 291 292 ConioProcessInputEvent(Console, &er); 293 294 /* Up all the character and control keys */ 295 er.Event.KeyEvent.bKeyDown = FALSE; 296 ConioProcessInputEvent(Console, &er); 297 } 298 } 299 300 VOID 301 GetSelectionBeginEnd(PCOORD Begin, PCOORD End, 302 PCOORD SelectionAnchor, 303 PSMALL_RECT SmallRect); 304 305 VOID 306 GuiCopyFromTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer, 307 PGUI_CONSOLE_DATA GuiData) 308 { 309 /* 310 * This function supposes that the system clipboard was opened. 311 */ 312 313 BOOL LineSelection = GuiData->LineSelection; 314 315 DPRINT("Selection is (%d|%d) to (%d|%d) in %s mode\n", 316 GuiData->Selection.srSelection.Left, 317 GuiData->Selection.srSelection.Top, 318 GuiData->Selection.srSelection.Right, 319 GuiData->Selection.srSelection.Bottom, 320 (LineSelection ? "line" : "block")); 321 322 if (!LineSelection) 323 { 324 CopyBlock(Buffer, &GuiData->Selection.srSelection); 325 } 326 else 327 { 328 COORD Begin, End; 329 330 GetSelectionBeginEnd(&Begin, &End, 331 &GuiData->Selection.dwSelectionAnchor, 332 &GuiData->Selection.srSelection); 333 334 CopyLines(Buffer, &Begin, &End); 335 } 336 } 337 338 VOID 339 GuiPasteToTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer, 340 PGUI_CONSOLE_DATA GuiData) 341 { 342 /* 343 * This function supposes that the system clipboard was opened. 344 */ 345 346 PCONSRV_CONSOLE Console = (PCONSRV_CONSOLE)Buffer->Header.Console; 347 348 HANDLE hData; 349 LPWSTR pszText; 350 351 hData = GetClipboardData(CF_UNICODETEXT); 352 if (hData == NULL) return; 353 354 pszText = GlobalLock(hData); 355 if (pszText == NULL) return; 356 357 DPRINT("Got data <%S> from clipboard\n", pszText); 358 PasteText(Console, pszText, wcslen(pszText)); 359 360 GlobalUnlock(hData); 361 } 362 363 static VOID 364 GuiPaintCaret( 365 PTEXTMODE_SCREEN_BUFFER Buffer, 366 PGUI_CONSOLE_DATA GuiData, 367 ULONG TopLine, 368 ULONG BottomLine, 369 ULONG LeftColumn, 370 ULONG RightColumn) 371 { 372 PCONSRV_CONSOLE Console = (PCONSRV_CONSOLE)Buffer->Header.Console; 373 374 ULONG CursorX, CursorY, CursorHeight; 375 HBRUSH CursorBrush, OldBrush; 376 WORD Attribute; 377 378 if (Buffer->CursorInfo.bVisible && 379 Buffer->CursorBlinkOn && 380 !Buffer->ForceCursorOff) 381 { 382 CursorX = Buffer->CursorPosition.X; 383 CursorY = Buffer->CursorPosition.Y; 384 if (LeftColumn <= CursorX && CursorX <= RightColumn && 385 TopLine <= CursorY && CursorY <= BottomLine) 386 { 387 CursorHeight = ConioEffectiveCursorSize(Console, GuiData->CharHeight); 388 389 Attribute = ConioCoordToPointer(Buffer, Buffer->CursorPosition.X, Buffer->CursorPosition.Y)->Attributes; 390 if (Attribute == DEFAULT_SCREEN_ATTRIB) 391 Attribute = Buffer->ScreenDefaultAttrib; 392 393 CursorBrush = CreateSolidBrush(PaletteRGBFromAttrib(Console, TextAttribFromAttrib(Attribute))); 394 OldBrush = SelectObject(GuiData->hMemDC, CursorBrush); 395 396 if (Attribute & COMMON_LVB_LEADING_BYTE) 397 { 398 /* The caret is on the leading byte */ 399 PatBlt(GuiData->hMemDC, 400 CursorX * GuiData->CharWidth, 401 CursorY * GuiData->CharHeight + (GuiData->CharHeight - CursorHeight), 402 GuiData->CharWidth * 2, 403 CursorHeight, 404 PATCOPY); 405 } 406 else if (Attribute & COMMON_LVB_TRAILING_BYTE) 407 { 408 /* The caret is on the trailing byte */ 409 PatBlt(GuiData->hMemDC, 410 (CursorX - 1) * GuiData->CharWidth, 411 CursorY * GuiData->CharHeight + (GuiData->CharHeight - CursorHeight), 412 GuiData->CharWidth * 2, 413 CursorHeight, 414 PATCOPY); 415 } 416 else 417 { 418 PatBlt(GuiData->hMemDC, 419 CursorX * GuiData->CharWidth, 420 CursorY * GuiData->CharHeight + (GuiData->CharHeight - CursorHeight), 421 GuiData->CharWidth, 422 CursorHeight, 423 PATCOPY); 424 } 425 426 SelectObject(GuiData->hMemDC, OldBrush); 427 DeleteObject(CursorBrush); 428 } 429 } 430 } 431 432 VOID 433 GuiPaintTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer, 434 PGUI_CONSOLE_DATA GuiData, 435 PRECT rcView, 436 PRECT rcFramebuffer) 437 { 438 PCONSRV_CONSOLE Console = (PCONSRV_CONSOLE)Buffer->Header.Console; 439 ULONG TopLine, BottomLine, LeftColumn, RightColumn; 440 ULONG Line, Char, Start; 441 PCHAR_INFO From; 442 PWCHAR To; 443 WORD LastAttribute, Attribute; 444 HFONT OldFont, NewFont; 445 BOOLEAN IsUnderline; 446 447 // ASSERT(Console == GuiData->Console); 448 449 ConioInitLongRect(rcFramebuffer, 0, 0, 0, 0); 450 451 if (Buffer->Buffer == NULL) 452 return; 453 454 if (!ConDrvValidateConsoleUnsafe((PCONSOLE)Console, CONSOLE_RUNNING, TRUE)) 455 return; 456 457 ConioInitLongRect(rcFramebuffer, 458 Buffer->ViewOrigin.Y * GuiData->CharHeight + rcView->top, 459 Buffer->ViewOrigin.X * GuiData->CharWidth + rcView->left, 460 Buffer->ViewOrigin.Y * GuiData->CharHeight + rcView->bottom, 461 Buffer->ViewOrigin.X * GuiData->CharWidth + rcView->right); 462 463 LeftColumn = rcFramebuffer->left / GuiData->CharWidth; 464 RightColumn = rcFramebuffer->right / GuiData->CharWidth; 465 if (RightColumn >= (ULONG)Buffer->ScreenBufferSize.X) 466 RightColumn = Buffer->ScreenBufferSize.X - 1; 467 468 TopLine = rcFramebuffer->top / GuiData->CharHeight; 469 BottomLine = rcFramebuffer->bottom / GuiData->CharHeight; 470 if (BottomLine >= (ULONG)Buffer->ScreenBufferSize.Y) 471 BottomLine = Buffer->ScreenBufferSize.Y - 1; 472 473 LastAttribute = ConioCoordToPointer(Buffer, LeftColumn, TopLine)->Attributes; 474 475 SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute))); 476 SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute))); 477 478 /* We use the underscore flag as a underline flag */ 479 IsUnderline = !!(LastAttribute & COMMON_LVB_UNDERSCORE); 480 /* Select the new font */ 481 NewFont = GuiData->Font[IsUnderline ? FONT_BOLD : FONT_NORMAL]; 482 OldFont = SelectObject(GuiData->hMemDC, NewFont); 483 484 if (Console->IsCJK) 485 { 486 for (Line = TopLine; Line <= BottomLine; Line++) 487 { 488 for (Char = LeftColumn; Char <= RightColumn; Char++) 489 { 490 From = ConioCoordToPointer(Buffer, Char, Line); 491 Attribute = From->Attributes; 492 SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(Attribute))); 493 SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(Attribute))); 494 495 /* Change underline state if needed */ 496 if (!!(Attribute & COMMON_LVB_UNDERSCORE) != IsUnderline) 497 { 498 IsUnderline = !!(Attribute & COMMON_LVB_UNDERSCORE); 499 500 /* Select the new font */ 501 NewFont = GuiData->Font[IsUnderline ? FONT_BOLD : FONT_NORMAL]; 502 SelectObject(GuiData->hMemDC, NewFont); 503 } 504 505 if (Attribute & COMMON_LVB_TRAILING_BYTE) 506 continue; 507 508 TextOutW(GuiData->hMemDC, 509 Char * GuiData->CharWidth, 510 Line * GuiData->CharHeight, 511 &From->Char.UnicodeChar, 1); 512 } 513 } 514 } 515 else 516 { 517 for (Line = TopLine; Line <= BottomLine; Line++) 518 { 519 WCHAR LineBuffer[80]; // Buffer containing a part or all the line to be displayed 520 From = ConioCoordToPointer(Buffer, LeftColumn, Line); // Get the first code of the line 521 Start = LeftColumn; 522 To = LineBuffer; 523 524 for (Char = LeftColumn; Char <= RightColumn; Char++) 525 { 526 /* 527 * We flush the buffer if the new attribute is different 528 * from the current one, or if the buffer is full. 529 */ 530 if (From->Attributes != LastAttribute || (Char - Start == sizeof(LineBuffer) / sizeof(WCHAR))) 531 { 532 TextOutW(GuiData->hMemDC, 533 Start * GuiData->CharWidth, 534 Line * GuiData->CharHeight, 535 LineBuffer, 536 Char - Start); 537 Start = Char; 538 To = LineBuffer; 539 Attribute = From->Attributes; 540 if (Attribute != LastAttribute) 541 { 542 LastAttribute = Attribute; 543 SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute))); 544 SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute))); 545 546 /* Change underline state if needed */ 547 if (!!(LastAttribute & COMMON_LVB_UNDERSCORE) != IsUnderline) 548 { 549 IsUnderline = !!(LastAttribute & COMMON_LVB_UNDERSCORE); 550 /* Select the new font */ 551 NewFont = GuiData->Font[IsUnderline ? FONT_BOLD : FONT_NORMAL]; 552 SelectObject(GuiData->hMemDC, NewFont); 553 } 554 } 555 } 556 557 *(To++) = (From++)->Char.UnicodeChar; 558 } 559 560 TextOutW(GuiData->hMemDC, 561 Start * GuiData->CharWidth, 562 Line * GuiData->CharHeight, 563 LineBuffer, 564 RightColumn - Start + 1); 565 } 566 } 567 568 /* Restore the old font */ 569 SelectObject(GuiData->hMemDC, OldFont); 570 571 /* Draw the caret */ 572 GuiPaintCaret(Buffer, GuiData, TopLine, BottomLine, LeftColumn, RightColumn); 573 574 LeaveCriticalSection(&Console->Lock); 575 } 576 577 /* EOF */ 578