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
PaletteRGBFromAttrib(PCONSRV_CONSOLE Console,WORD Attribute)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
CopyBlock(PTEXTMODE_SCREEN_BUFFER Buffer,PSMALL_RECT Selection)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
CopyLines(PTEXTMODE_SCREEN_BUFFER Buffer,PCOORD Begin,PCOORD End)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
PasteText(IN PCONSRV_CONSOLE Console,IN PWCHAR Buffer,IN SIZE_T cchSize)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
GuiCopyFromTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,PGUI_CONSOLE_DATA GuiData)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
GuiPasteToTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,PGUI_CONSOLE_DATA GuiData)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
GuiPaintCaret(PTEXTMODE_SCREEN_BUFFER Buffer,PGUI_CONSOLE_DATA GuiData,ULONG TopLine,ULONG BottomLine,ULONG LeftColumn,ULONG RightColumn)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
GuiPaintTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,PGUI_CONSOLE_DATA GuiData,PRECT rcView,PRECT rcFramebuffer)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