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 ULONG TopLine, BottomLine, LeftColumn, RightColumn; 362 ULONG Line, Char, Start; 363 PCHAR_INFO From; 364 PWCHAR To; 365 WORD LastAttribute, Attribute; 366 ULONG CursorX, CursorY, CursorHeight; 367 HBRUSH CursorBrush, OldBrush; 368 HFONT OldFont, NewFont; 369 BOOLEAN IsUnderline; 370 371 // ASSERT(Console == GuiData->Console); 372 373 ConioInitLongRect(rcFramebuffer, 0, 0, 0, 0); 374 375 if (Buffer->Buffer == NULL) 376 return; 377 378 if (!ConDrvValidateConsoleUnsafe((PCONSOLE)Console, CONSOLE_RUNNING, TRUE)) 379 return; 380 381 ConioInitLongRect(rcFramebuffer, 382 Buffer->ViewOrigin.Y * GuiData->CharHeight + rcView->top, 383 Buffer->ViewOrigin.X * GuiData->CharWidth + rcView->left, 384 Buffer->ViewOrigin.Y * GuiData->CharHeight + rcView->bottom, 385 Buffer->ViewOrigin.X * GuiData->CharWidth + rcView->right); 386 387 LeftColumn = rcFramebuffer->left / GuiData->CharWidth; 388 RightColumn = rcFramebuffer->right / GuiData->CharWidth; 389 if (RightColumn >= (ULONG)Buffer->ScreenBufferSize.X) 390 RightColumn = Buffer->ScreenBufferSize.X - 1; 391 392 TopLine = rcFramebuffer->top / GuiData->CharHeight; 393 BottomLine = rcFramebuffer->bottom / GuiData->CharHeight; 394 if (BottomLine >= (ULONG)Buffer->ScreenBufferSize.Y) 395 BottomLine = Buffer->ScreenBufferSize.Y - 1; 396 397 LastAttribute = ConioCoordToPointer(Buffer, LeftColumn, TopLine)->Attributes; 398 399 SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute))); 400 SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute))); 401 402 /* We use the underscore flag as a underline flag */ 403 IsUnderline = !!(LastAttribute & COMMON_LVB_UNDERSCORE); 404 /* Select the new font */ 405 NewFont = GuiData->Font[IsUnderline ? FONT_BOLD : FONT_NORMAL]; 406 OldFont = SelectObject(GuiData->hMemDC, NewFont); 407 408 for (Line = TopLine; Line <= BottomLine; Line++) 409 { 410 WCHAR LineBuffer[80]; // Buffer containing a part or all the line to be displayed 411 From = ConioCoordToPointer(Buffer, LeftColumn, Line); // Get the first code of the line 412 Start = LeftColumn; 413 To = LineBuffer; 414 415 for (Char = LeftColumn; Char <= RightColumn; Char++) 416 { 417 /* 418 * We flush the buffer if the new attribute is different 419 * from the current one, or if the buffer is full. 420 */ 421 if (From->Attributes != LastAttribute || (Char - Start == sizeof(LineBuffer) / sizeof(WCHAR))) 422 { 423 TextOutW(GuiData->hMemDC, 424 Start * GuiData->CharWidth, 425 Line * GuiData->CharHeight, 426 LineBuffer, 427 Char - Start); 428 Start = Char; 429 To = LineBuffer; 430 Attribute = From->Attributes; 431 if (Attribute != LastAttribute) 432 { 433 LastAttribute = Attribute; 434 SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute))); 435 SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute))); 436 437 /* Change underline state if needed */ 438 if (!!(LastAttribute & COMMON_LVB_UNDERSCORE) != IsUnderline) 439 { 440 IsUnderline = !!(LastAttribute & COMMON_LVB_UNDERSCORE); 441 /* Select the new font */ 442 NewFont = GuiData->Font[IsUnderline ? FONT_BOLD : FONT_NORMAL]; 443 /* OldFont = */ SelectObject(GuiData->hMemDC, NewFont); 444 } 445 } 446 } 447 448 *(To++) = (From++)->Char.UnicodeChar; 449 } 450 451 TextOutW(GuiData->hMemDC, 452 Start * GuiData->CharWidth, 453 Line * GuiData->CharHeight, 454 LineBuffer, 455 RightColumn - Start + 1); 456 } 457 458 /* Restore the old font */ 459 SelectObject(GuiData->hMemDC, OldFont); 460 461 /* 462 * Draw the caret 463 */ 464 if (Buffer->CursorInfo.bVisible && 465 Buffer->CursorBlinkOn && 466 !Buffer->ForceCursorOff) 467 { 468 CursorX = Buffer->CursorPosition.X; 469 CursorY = Buffer->CursorPosition.Y; 470 if (LeftColumn <= CursorX && CursorX <= RightColumn && 471 TopLine <= CursorY && CursorY <= BottomLine) 472 { 473 CursorHeight = ConioEffectiveCursorSize(Console, GuiData->CharHeight); 474 475 Attribute = ConioCoordToPointer(Buffer, Buffer->CursorPosition.X, Buffer->CursorPosition.Y)->Attributes; 476 if (Attribute == DEFAULT_SCREEN_ATTRIB) 477 Attribute = Buffer->ScreenDefaultAttrib; 478 479 CursorBrush = CreateSolidBrush(PaletteRGBFromAttrib(Console, TextAttribFromAttrib(Attribute))); 480 OldBrush = SelectObject(GuiData->hMemDC, CursorBrush); 481 482 PatBlt(GuiData->hMemDC, 483 CursorX * GuiData->CharWidth, 484 CursorY * GuiData->CharHeight + (GuiData->CharHeight - CursorHeight), 485 GuiData->CharWidth, 486 CursorHeight, 487 PATCOPY); 488 489 SelectObject(GuiData->hMemDC, OldBrush); 490 DeleteObject(CursorBrush); 491 } 492 } 493 494 LeaveCriticalSection(&Console->Lock); 495 } 496 497 /* EOF */ 498