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