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