1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS Console Server DLL
4  * FILE:            win32ss/user/winsrv/consrv/frontends/terminal.c
5  * PURPOSE:         ConSrv terminal.
6  * PROGRAMMERS:     Hermes Belusca-Maito (hermes.belusca@sfr.fr)
7  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include <consrv.h>
12 #include "concfg/font.h"
13 
14 // #include "frontends/gui/guiterm.h"
15 #ifdef TUITERM_COMPILE
16 #include "frontends/tui/tuiterm.h"
17 #endif
18 
19 #define NDEBUG
20 #include <debug.h>
21 
22 
23 
24 
25 
26 /********** HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK ************/
27 
28 /* GLOBALS ********************************************************************/
29 
30 /*
31  * From MSDN:
32  * "The lpMultiByteStr and lpWideCharStr pointers must not be the same.
33  *  If they are the same, the function fails, and GetLastError returns
34  *  ERROR_INVALID_PARAMETER."
35  */
36 #define ConsoleInputUnicodeCharToAnsiChar(Console, dChar, sWChar) \
37 do { \
38     ASSERT((ULONG_PTR)(dChar) != (ULONG_PTR)(sWChar)); \
39     WideCharToMultiByte((Console)->InputCodePage, 0, (sWChar), 1, (dChar), 1, NULL, NULL); \
40 } while (0)
41 
42 #define ConsoleInputAnsiCharToUnicodeChar(Console, dWChar, sChar) \
43 do { \
44     ASSERT((ULONG_PTR)(dWChar) != (ULONG_PTR)(sChar)); \
45     MultiByteToWideChar((Console)->InputCodePage, 0, (sChar), 1, (dWChar), 1); \
46 } while (0)
47 
48 /* PRIVATE FUNCTIONS **********************************************************/
49 
50 #if 0
51 
52 static VOID
53 ConioInputEventToAnsi(PCONSOLE Console, PINPUT_RECORD InputEvent)
54 {
55     if (InputEvent->EventType == KEY_EVENT)
56     {
57         WCHAR UnicodeChar = InputEvent->Event.KeyEvent.uChar.UnicodeChar;
58         InputEvent->Event.KeyEvent.uChar.UnicodeChar = 0;
59         ConsoleInputUnicodeCharToAnsiChar(Console,
60                                           &InputEvent->Event.KeyEvent.uChar.AsciiChar,
61                                           &UnicodeChar);
62     }
63 }
64 
65 static VOID
66 ConioInputEventToUnicode(PCONSOLE Console, PINPUT_RECORD InputEvent)
67 {
68     if (InputEvent->EventType == KEY_EVENT)
69     {
70         CHAR AsciiChar = InputEvent->Event.KeyEvent.uChar.AsciiChar;
71         InputEvent->Event.KeyEvent.uChar.AsciiChar = 0;
72         ConsoleInputAnsiCharToUnicodeChar(Console,
73                                           &InputEvent->Event.KeyEvent.uChar.UnicodeChar,
74                                           &AsciiChar);
75     }
76 }
77 
78 #endif
79 
80 /********** HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK ************/
81 
82 
83 
84 
85 
86 
87 
88 
89 /* CONSRV TERMINAL FRONTENDS INTERFACE ****************************************/
90 
91 /***************/
92 #ifdef TUITERM_COMPILE
93 NTSTATUS NTAPI
94 TuiLoadFrontEnd(IN OUT PFRONTEND FrontEnd,
95                 IN OUT PCONSOLE_STATE_INFO ConsoleInfo,
96                 IN OUT PCONSOLE_INIT_INFO ConsoleInitInfo,
97                 IN HANDLE ConsoleLeaderProcessHandle);
98 NTSTATUS NTAPI
99 TuiUnloadFrontEnd(IN OUT PFRONTEND FrontEnd);
100 #endif
101 
102 NTSTATUS NTAPI
103 GuiLoadFrontEnd(IN OUT PFRONTEND FrontEnd,
104                 IN OUT PCONSOLE_STATE_INFO ConsoleInfo,
105                 IN OUT PCONSOLE_INIT_INFO ConsoleInitInfo,
106                 IN HANDLE ConsoleLeaderProcessHandle);
107 NTSTATUS NTAPI
108 GuiUnloadFrontEnd(IN OUT PFRONTEND FrontEnd);
109 /***************/
110 
111 typedef
112 NTSTATUS (NTAPI *FRONTEND_LOAD)(IN OUT PFRONTEND FrontEnd,
113                                 IN OUT PCONSOLE_STATE_INFO ConsoleInfo,
114                                 IN OUT PCONSOLE_INIT_INFO ConsoleInitInfo,
115                                 IN HANDLE ConsoleLeaderProcessHandle);
116 
117 typedef
118 NTSTATUS (NTAPI *FRONTEND_UNLOAD)(IN OUT PFRONTEND FrontEnd);
119 
120 /*
121  * If we are not in GUI-mode, start the text-mode terminal emulator.
122  * If we fail, try to start the GUI-mode terminal emulator.
123  *
124  * Try to open the GUI-mode terminal emulator. Two cases are possible:
125  * - We are in GUI-mode, therefore GuiMode == TRUE, the previous test-case
126  *   failed and we start GUI-mode terminal emulator.
127  * - We are in text-mode, therefore GuiMode == FALSE, the previous test-case
128  *   succeeded BUT we failed at starting text-mode terminal emulator.
129  *   Then GuiMode was switched to TRUE in order to try to open the GUI-mode
130  *   terminal emulator (Win32k will automatically switch to graphical mode,
131  *   therefore no additional code is needed).
132  */
133 
134 /*
135  * NOTE: Each entry of the table should be retrieved when loading a front-end
136  *       (examples of the CSR servers which register some data for CSRSS).
137  */
138 static struct
139 {
140     CHAR            FrontEndName[80];
141     FRONTEND_LOAD   FrontEndLoad;
142     FRONTEND_UNLOAD FrontEndUnload;
143 } FrontEndLoadingMethods[] =
144 {
145 #ifdef TUITERM_COMPILE
146     {"TUI", TuiLoadFrontEnd,    TuiUnloadFrontEnd},
147 #endif
148     {"GUI", GuiLoadFrontEnd,    GuiUnloadFrontEnd},
149 
150 //  {"Not found", 0, NULL}
151 };
152 
153 static NTSTATUS
154 ConSrvLoadFrontEnd(IN OUT PFRONTEND FrontEnd,
155                    IN OUT PCONSOLE_STATE_INFO ConsoleInfo,
156                    IN OUT PCONSOLE_INIT_INFO ConsoleInitInfo,
157                    IN HANDLE ConsoleLeaderProcessHandle)
158 {
159     NTSTATUS Status = STATUS_SUCCESS;
160     ULONG i;
161 
162     /*
163      * Choose an adequate terminal front-end to load, and load it
164      */
165     for (i = 0; i < ARRAYSIZE(FrontEndLoadingMethods); ++i)
166     {
167         DPRINT("CONSRV: Trying to load %s frontend...\n",
168                FrontEndLoadingMethods[i].FrontEndName);
169         Status = FrontEndLoadingMethods[i].FrontEndLoad(FrontEnd,
170                                                         ConsoleInfo,
171                                                         ConsoleInitInfo,
172                                                         ConsoleLeaderProcessHandle);
173         if (NT_SUCCESS(Status))
174         {
175             /* Save the unload callback */
176             FrontEnd->UnloadFrontEnd = FrontEndLoadingMethods[i].FrontEndUnload;
177 
178             DPRINT("CONSRV: %s frontend loaded successfully\n",
179                    FrontEndLoadingMethods[i].FrontEndName);
180             break;
181         }
182         else
183         {
184             DPRINT1("CONSRV: Loading %s frontend failed, Status = 0x%08lx , continuing...\n",
185                     FrontEndLoadingMethods[i].FrontEndName, Status);
186         }
187     }
188 
189     return Status;
190 }
191 
192 static NTSTATUS
193 ConSrvUnloadFrontEnd(IN PFRONTEND FrontEnd)
194 {
195     if (FrontEnd == NULL) return STATUS_INVALID_PARAMETER;
196     // return FrontEnd->Vtbl->UnloadFrontEnd(FrontEnd);
197     return FrontEnd->UnloadFrontEnd(FrontEnd);
198 }
199 
200 // See after...
201 static TERMINAL_VTBL ConSrvTermVtbl;
202 
203 NTSTATUS NTAPI
204 ConSrvInitTerminal(IN OUT PTERMINAL Terminal,
205                    IN OUT PCONSOLE_STATE_INFO ConsoleInfo,
206                    IN OUT PCONSOLE_INIT_INFO ConsoleInitInfo,
207                    IN HANDLE ConsoleLeaderProcessHandle)
208 {
209     NTSTATUS Status;
210     PFRONTEND FrontEnd;
211 
212     /* Load a suitable frontend for the ConSrv terminal */
213     FrontEnd = ConsoleAllocHeap(HEAP_ZERO_MEMORY, sizeof(*FrontEnd));
214     if (!FrontEnd) return STATUS_NO_MEMORY;
215 
216     Status = ConSrvLoadFrontEnd(FrontEnd,
217                                 ConsoleInfo,
218                                 ConsoleInitInfo,
219                                 ConsoleLeaderProcessHandle);
220     if (!NT_SUCCESS(Status))
221     {
222         DPRINT1("CONSRV: Failed to initialize a frontend, Status = 0x%08lx\n", Status);
223         ConsoleFreeHeap(FrontEnd);
224         return Status;
225     }
226     DPRINT("CONSRV: Frontend initialized\n");
227 
228     /* Initialize the ConSrv terminal */
229     Terminal->Vtbl = &ConSrvTermVtbl;
230     // Terminal->Console will be initialized by ConDrvAttachTerminal
231     Terminal->Context = FrontEnd; /* We store the frontend pointer in the terminal private context */
232 
233     return STATUS_SUCCESS;
234 }
235 
236 NTSTATUS NTAPI
237 ConSrvDeinitTerminal(IN OUT PTERMINAL Terminal)
238 {
239     NTSTATUS Status = STATUS_SUCCESS;
240     PFRONTEND FrontEnd = Terminal->Context;
241 
242     /* Reset the ConSrv terminal */
243     Terminal->Context = NULL;
244     Terminal->Vtbl = NULL;
245 
246     /* Unload the frontend */
247     if (FrontEnd != NULL)
248     {
249         Status = ConSrvUnloadFrontEnd(FrontEnd);
250         ConsoleFreeHeap(FrontEnd);
251     }
252 
253     return Status;
254 }
255 
256 
257 /* CONSRV TERMINAL INTERFACE **************************************************/
258 
259 static NTSTATUS NTAPI
260 ConSrvTermInitTerminal(IN OUT PTERMINAL This,
261                        IN PCONSOLE Console)
262 {
263     NTSTATUS Status;
264     PFRONTEND FrontEnd = This->Context;
265     PCONSRV_CONSOLE ConSrvConsole = (PCONSRV_CONSOLE)Console;
266 
267     /* Initialize the console pointer for our frontend */
268     FrontEnd->Console = ConSrvConsole;
269 
270     /** HACK HACK!! Copy FrontEnd into the console!! **/
271     DPRINT("Using FrontEndIFace HACK(1), should be removed after proper implementation!\n");
272     ConSrvConsole->FrontEndIFace = *FrontEnd;
273 
274     Status = FrontEnd->Vtbl->InitFrontEnd(FrontEnd, ConSrvConsole);
275     if (!NT_SUCCESS(Status))
276         DPRINT1("InitFrontEnd failed, Status = 0x%08lx\n", Status);
277 
278     /** HACK HACK!! Be sure FrontEndIFace is correctly updated in the console!! **/
279     DPRINT("Using FrontEndIFace HACK(2), should be removed after proper implementation!\n");
280     ConSrvConsole->FrontEndIFace = *FrontEnd;
281 
282     return Status;
283 }
284 
285 static VOID NTAPI
286 ConSrvTermDeinitTerminal(IN OUT PTERMINAL This)
287 {
288     PFRONTEND FrontEnd = This->Context;
289     FrontEnd->Vtbl->DeinitFrontEnd(FrontEnd);
290 }
291 
292 
293 
294 /************ Line discipline ***************/
295 
296 static NTSTATUS NTAPI
297 ConSrvTermReadStream(IN OUT PTERMINAL This,
298                      IN BOOLEAN Unicode,
299                      /**PWCHAR Buffer,**/
300                      OUT PVOID Buffer,
301                      IN OUT PCONSOLE_READCONSOLE_CONTROL ReadControl,
302                      IN PVOID Parameter OPTIONAL,
303                      IN ULONG NumCharsToRead,
304                      OUT PULONG NumCharsRead OPTIONAL)
305 {
306     PFRONTEND FrontEnd = This->Context;
307     PCONSRV_CONSOLE Console = FrontEnd->Console;
308     PCONSOLE_INPUT_BUFFER InputBuffer = &Console->InputBuffer;
309     PUNICODE_STRING ExeName = Parameter;
310 
311     // STATUS_PENDING : Wait if more to read ; STATUS_SUCCESS : Don't wait.
312     NTSTATUS Status = STATUS_PENDING;
313 
314     PLIST_ENTRY CurrentEntry;
315     ConsoleInput *Input;
316     ULONG i = 0;
317 
318     /* Validity checks */
319     // ASSERT(Console == InputBuffer->Header.Console);
320     ASSERT((Buffer != NULL) || (Buffer == NULL && NumCharsToRead == 0));
321 
322     /* We haven't read anything (yet) */
323 
324     if (InputBuffer->Mode & ENABLE_LINE_INPUT)
325     {
326         /* COOKED mode, call the line discipline */
327 
328         if (Console->LineBuffer == NULL)
329         {
330             /* Start a new line */
331             Console->LineMaxSize = max(256, NumCharsToRead);
332 
333             /*
334              * Fixup ReadControl->nInitialChars in case the number of initial
335              * characters is bigger than the number of characters to be read.
336              * It will always be, lesser than or equal to Console->LineMaxSize.
337              */
338             ReadControl->nInitialChars = min(ReadControl->nInitialChars, NumCharsToRead);
339 
340             Console->LineBuffer = ConsoleAllocHeap(0, Console->LineMaxSize * sizeof(WCHAR));
341             if (Console->LineBuffer == NULL) return STATUS_NO_MEMORY;
342 
343             Console->LinePos = Console->LineSize = ReadControl->nInitialChars;
344             Console->LineComplete = Console->LineUpPressed = FALSE;
345             Console->LineInsertToggle = Console->InsertMode;
346             Console->LineWakeupMask = ReadControl->dwCtrlWakeupMask;
347 
348             /*
349              * Pre-fill the buffer with the nInitialChars from the user buffer.
350              * Since pre-filling is only allowed in Unicode, we don't need to
351              * worry about ANSI <-> Unicode conversion.
352              */
353             memcpy(Console->LineBuffer, Buffer, Console->LineSize * sizeof(WCHAR));
354             if (Console->LineSize >= Console->LineMaxSize)
355             {
356                 Console->LineComplete = TRUE;
357                 Console->LinePos = 0;
358             }
359         }
360 
361         /* If we don't have a complete line yet, process the pending input */
362         while (!Console->LineComplete && !IsListEmpty(&InputBuffer->InputEvents))
363         {
364             /* Remove an input event from the queue */
365             _InterlockedDecrement((PLONG)&InputBuffer->NumberOfEvents);
366             CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
367             if (IsListEmpty(&InputBuffer->InputEvents))
368             {
369                 NtClearEvent(InputBuffer->ActiveEvent);
370             }
371             Input = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
372 
373             /* Only pay attention to key down */
374             if (Input->InputEvent.EventType == KEY_EVENT &&
375                 Input->InputEvent.Event.KeyEvent.bKeyDown)
376             {
377                 LineInputKeyDown(Console, ExeName,
378                                  &Input->InputEvent.Event.KeyEvent);
379                 ReadControl->dwControlKeyState = Input->InputEvent.Event.KeyEvent.dwControlKeyState;
380             }
381             ConsoleFreeHeap(Input);
382         }
383 
384         /* Check if we have a complete line to read from */
385         if (Console->LineComplete)
386         {
387             /*
388              * Console->LinePos keeps the next position of the character to read
389              * in the line buffer across the different calls of the function,
390              * so that the line buffer can be read by chunks after all the input
391              * has been buffered.
392              */
393 
394             while (i < NumCharsToRead && Console->LinePos < Console->LineSize)
395             {
396                 WCHAR Char = Console->LineBuffer[Console->LinePos++];
397 
398                 if (Unicode)
399                 {
400                     ((PWCHAR)Buffer)[i] = Char;
401                 }
402                 else
403                 {
404                     ConsoleInputUnicodeCharToAnsiChar(Console, &((PCHAR)Buffer)[i], &Char);
405                 }
406                 ++i;
407             }
408 
409             if (Console->LinePos >= Console->LineSize)
410             {
411                 /* The entire line has been read */
412                 ConsoleFreeHeap(Console->LineBuffer);
413                 Console->LineBuffer = NULL;
414                 Console->LinePos = Console->LineMaxSize = Console->LineSize = 0;
415                 // Console->LineComplete = Console->LineUpPressed = FALSE;
416                 Console->LineComplete = FALSE;
417             }
418 
419             Status = STATUS_SUCCESS;
420         }
421     }
422     else
423     {
424         /* RAW mode */
425 
426         /* Character input */
427         while (i < NumCharsToRead && !IsListEmpty(&InputBuffer->InputEvents))
428         {
429             /* Remove an input event from the queue */
430             _InterlockedDecrement((PLONG)&InputBuffer->NumberOfEvents);
431             CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
432             if (IsListEmpty(&InputBuffer->InputEvents))
433             {
434                 NtClearEvent(InputBuffer->ActiveEvent);
435             }
436             Input = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
437 
438             /* Only pay attention to valid characters, on key down */
439             if (Input->InputEvent.EventType == KEY_EVENT  &&
440                 Input->InputEvent.Event.KeyEvent.bKeyDown &&
441                 Input->InputEvent.Event.KeyEvent.uChar.UnicodeChar != L'\0')
442             {
443                 WCHAR Char = Input->InputEvent.Event.KeyEvent.uChar.UnicodeChar;
444 
445                 if (Unicode)
446                 {
447                     ((PWCHAR)Buffer)[i] = Char;
448                 }
449                 else
450                 {
451                     ConsoleInputUnicodeCharToAnsiChar(Console, &((PCHAR)Buffer)[i], &Char);
452                 }
453                 ++i;
454 
455                 /* Did read something */
456                 Status = STATUS_SUCCESS;
457             }
458             ConsoleFreeHeap(Input);
459         }
460     }
461 
462     // FIXME: Only set if Status == STATUS_SUCCESS ???
463     if (NumCharsRead) *NumCharsRead = i;
464 
465     return Status;
466 }
467 
468 
469 
470 
471 /* GLOBALS ********************************************************************/
472 
473 #define TAB_WIDTH   8
474 
475 // See condrv/text.c
476 /*static*/ VOID
477 ClearLineBuffer(PTEXTMODE_SCREEN_BUFFER Buff);
478 
479 static VOID
480 ConioNextLine(PTEXTMODE_SCREEN_BUFFER Buff, PSMALL_RECT UpdateRect, PUINT ScrolledLines)
481 {
482     /* If we hit bottom, slide the viewable screen */
483     if (++Buff->CursorPosition.Y == Buff->ScreenBufferSize.Y)
484     {
485         Buff->CursorPosition.Y--;
486         if (++Buff->VirtualY == Buff->ScreenBufferSize.Y)
487         {
488             Buff->VirtualY = 0;
489         }
490         (*ScrolledLines)++;
491         ClearLineBuffer(Buff);
492         if (UpdateRect->Top != 0)
493         {
494             UpdateRect->Top--;
495         }
496     }
497     UpdateRect->Left = 0;
498     UpdateRect->Right = Buff->ScreenBufferSize.X - 1;
499     UpdateRect->Bottom = Buff->CursorPosition.Y;
500 }
501 
502 static NTSTATUS
503 ConioWriteConsole(PFRONTEND FrontEnd,
504                   PTEXTMODE_SCREEN_BUFFER Buff,
505                   PWCHAR Buffer,
506                   DWORD Length,
507                   BOOL Attrib)
508 {
509     PCONSRV_CONSOLE Console = FrontEnd->Console;
510 
511     UINT i;
512     PCHAR_INFO Ptr;
513     SMALL_RECT UpdateRect;
514     SHORT CursorStartX, CursorStartY;
515     UINT ScrolledLines;
516     BOOLEAN bFullwidth;
517     BOOLEAN bCJK = Console->IsCJK;
518 
519     /* If nothing to write, bail out now */
520     if (Length == 0)
521         return STATUS_SUCCESS;
522 
523     CursorStartX = Buff->CursorPosition.X;
524     CursorStartY = Buff->CursorPosition.Y;
525     UpdateRect.Left = Buff->ScreenBufferSize.X;
526     UpdateRect.Top  = Buff->CursorPosition.Y;
527     UpdateRect.Right  = -1;
528     UpdateRect.Bottom = Buff->CursorPosition.Y;
529     ScrolledLines = 0;
530 
531     for (i = 0; i < Length; i++)
532     {
533         /*
534          * If we are in processed mode, interpret special characters and
535          * display them correctly. Otherwise, just put them into the buffer.
536          */
537         if (Buff->Mode & ENABLE_PROCESSED_OUTPUT)
538         {
539             /* --- CR --- */
540             if (Buffer[i] == L'\r')
541             {
542                 Buff->CursorPosition.X = 0;
543                 CursorStartX = Buff->CursorPosition.X;
544                 UpdateRect.Left  = min(UpdateRect.Left , Buff->CursorPosition.X);
545                 UpdateRect.Right = max(UpdateRect.Right, Buff->CursorPosition.X);
546                 continue;
547             }
548             /* --- LF --- */
549             else if (Buffer[i] == L'\n')
550             {
551                 Buff->CursorPosition.X = 0; // TODO: Make this behaviour optional!
552                 CursorStartX = Buff->CursorPosition.X;
553                 ConioNextLine(Buff, &UpdateRect, &ScrolledLines);
554                 continue;
555             }
556             /* --- BS --- */
557             else if (Buffer[i] == L'\b')
558             {
559                 /* Only handle BS if we are not on the first position of the first line */
560                 if (Buff->CursorPosition.X == 0 && Buff->CursorPosition.Y == 0)
561                     continue;
562 
563                 if (Buff->CursorPosition.X == 0)
564                 {
565                     /* Slide virtual position up */
566                     Buff->CursorPosition.X = Buff->ScreenBufferSize.X - 1;
567                     Buff->CursorPosition.Y--;
568                     // TODO? : Update CursorStartY = Buff->CursorPosition.Y;
569                     UpdateRect.Top = min(UpdateRect.Top, Buff->CursorPosition.Y);
570                 }
571                 else
572                 {
573                     Buff->CursorPosition.X--;
574                 }
575                 Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
576 
577                 if (Ptr->Attributes & COMMON_LVB_LEADING_BYTE)
578                 {
579                     /*
580                      * The cursor just moved on the leading byte of the same
581                      * current character. We should go one position before to
582                      * go to the actual previous character to erase.
583                      */
584 
585                     /* Only handle BS if we are not on the first position of the first line */
586                     if (Buff->CursorPosition.X == 0 && Buff->CursorPosition.Y == 0)
587                         continue;
588 
589                     if (Buff->CursorPosition.X == 0)
590                     {
591                         /* Slide virtual position up */
592                         Buff->CursorPosition.X = Buff->ScreenBufferSize.X - 1;
593                         Buff->CursorPosition.Y--;
594                         // TODO? : Update CursorStartY = Buff->CursorPosition.Y;
595                         UpdateRect.Top = min(UpdateRect.Top, Buff->CursorPosition.Y);
596                     }
597                     else
598                     {
599                         Buff->CursorPosition.X--;
600                     }
601                     Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
602                 }
603 
604                 if (Ptr->Attributes & COMMON_LVB_TRAILING_BYTE)
605                 {
606                     /* The cursor is on the trailing byte of a full-width character */
607 
608                     /* Delete its trailing byte... */
609                     Ptr->Char.UnicodeChar = L' ';
610                     if (Attrib)
611                         Ptr->Attributes = Buff->ScreenDefaultAttrib;
612                     Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
613 
614                     if (Buff->CursorPosition.X > 0)
615                         Buff->CursorPosition.X--;
616                     /* ... and now its leading byte */
617                     Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
618                 }
619 
620                 Ptr->Char.UnicodeChar = L' ';
621                 if (Attrib)
622                     Ptr->Attributes = Buff->ScreenDefaultAttrib;
623                 Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
624 
625                 UpdateRect.Left  = min(UpdateRect.Left , Buff->CursorPosition.X);
626                 UpdateRect.Right = max(UpdateRect.Right, Buff->CursorPosition.X);
627                 continue;
628             }
629             /* --- TAB --- */
630             else if (Buffer[i] == L'\t')
631             {
632                 UINT EndX;
633 
634                 Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
635 
636                 if (Ptr->Attributes & COMMON_LVB_TRAILING_BYTE)
637                 {
638                     /*
639                      * The cursor is on the trailing byte of a full-width character.
640                      * Go back one position to be on its leading byte.
641                      */
642                     if (Buff->CursorPosition.X > 0)
643                         Buff->CursorPosition.X--;
644                     Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
645                 }
646 
647                 UpdateRect.Left = min(UpdateRect.Left, Buff->CursorPosition.X);
648 
649                 EndX = (Buff->CursorPosition.X + TAB_WIDTH) & ~(TAB_WIDTH - 1);
650                 EndX = min(EndX, (UINT)Buff->ScreenBufferSize.X);
651 
652                 while ((UINT)Buff->CursorPosition.X < EndX)
653                 {
654                     Ptr->Char.UnicodeChar = L' ';
655                     if (Attrib)
656                         Ptr->Attributes = Buff->ScreenDefaultAttrib;
657                     Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
658 
659                     ++Ptr;
660                     Buff->CursorPosition.X++;
661                 }
662                 if (Buff->CursorPosition.X < Buff->ScreenBufferSize.X)
663                 {
664                     /* If the following cell is the trailing byte of a full-width character, reset it */
665                     if (Ptr->Attributes & COMMON_LVB_TRAILING_BYTE)
666                     {
667                         Ptr->Char.UnicodeChar = L' ';
668                         if (Attrib)
669                             Ptr->Attributes = Buff->ScreenDefaultAttrib;
670                         Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
671                     }
672                 }
673                 UpdateRect.Right = max(UpdateRect.Right, Buff->CursorPosition.X);
674 
675                 if (Buff->CursorPosition.X >= Buff->ScreenBufferSize.X)
676                 {
677                     if (Buff->Mode & ENABLE_WRAP_AT_EOL_OUTPUT)
678                     {
679                         /* Wrapping mode: Go to next line */
680                         Buff->CursorPosition.X = 0;
681                         CursorStartX = Buff->CursorPosition.X;
682                         ConioNextLine(Buff, &UpdateRect, &ScrolledLines);
683                     }
684                     else
685                     {
686                         /* The cursor wraps back to its starting position on the same line */
687                         Buff->CursorPosition.X = CursorStartX;
688                     }
689                 }
690                 continue;
691             }
692             /* --- BEL ---*/
693             else if (Buffer[i] == L'\a')
694             {
695                 FrontEnd->Vtbl->RingBell(FrontEnd);
696                 continue;
697             }
698         }
699         UpdateRect.Left  = min(UpdateRect.Left , Buff->CursorPosition.X);
700         UpdateRect.Right = max(UpdateRect.Right, Buff->CursorPosition.X);
701 
702         /* For Chinese, Japanese and Korean */
703         bFullwidth = (bCJK && IS_FULL_WIDTH(Buffer[i]));
704 
705         /* Check whether we can insert the full-width character */
706         if (bFullwidth)
707         {
708             /* It spans two cells and should all fit on the current line */
709             if (Buff->CursorPosition.X >= Buff->ScreenBufferSize.X - 1)
710             {
711                 if (Buff->Mode & ENABLE_WRAP_AT_EOL_OUTPUT)
712                 {
713                     /* Wrapping mode: Go to next line */
714                     Buff->CursorPosition.X = 0;
715                     CursorStartX = Buff->CursorPosition.X;
716                     ConioNextLine(Buff, &UpdateRect, &ScrolledLines);
717                 }
718                 else
719                 {
720                     /* The cursor wraps back to its starting position on the same line */
721                     Buff->CursorPosition.X = CursorStartX;
722                 }
723             }
724 
725             /*
726              * Now be sure we can fit the full-width character.
727              * If the screenbuffer is one cell wide we cannot display
728              * the full-width character, so just skip it.
729              */
730             if (Buff->CursorPosition.X >= Buff->ScreenBufferSize.X - 1)
731             {
732                 DPRINT1("Cannot display full-width character! CursorPosition.X = %d, ScreenBufferSize.X = %d\n",
733                         Buff->CursorPosition.X, Buff->ScreenBufferSize.X);
734                 continue;
735             }
736         }
737 
738         Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
739 
740         /*
741          * Check whether we are overwriting part of a full-width character,
742          * in which case we need to invalidate it.
743          */
744         if (Ptr->Attributes & COMMON_LVB_TRAILING_BYTE)
745         {
746             /*
747              * The cursor is on the trailing byte of a full-width character.
748              * Go back one position to kill the previous leading byte.
749              */
750             if (Buff->CursorPosition.X > 0)
751             {
752                 Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X - 1, Buff->CursorPosition.Y);
753                 Ptr->Char.UnicodeChar = L' ';
754                 if (Attrib)
755                     Ptr->Attributes = Buff->ScreenDefaultAttrib;
756                 Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
757             }
758             Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
759         }
760 
761         /* Insert the character */
762         if (bFullwidth)
763         {
764             ASSERT(Buff->CursorPosition.X < Buff->ScreenBufferSize.X - 1);
765 
766             /* Set the leading byte */
767             Ptr->Char.UnicodeChar = Buffer[i];
768             if (Attrib)
769                 Ptr->Attributes = Buff->ScreenDefaultAttrib;
770             Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
771             Ptr->Attributes |= COMMON_LVB_LEADING_BYTE;
772 
773             /* Set the trailing byte */
774             Buff->CursorPosition.X++;
775             Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
776             // Ptr->Char.UnicodeChar = Buffer[i]; // L' ';
777             if (Attrib)
778                 Ptr->Attributes = Buff->ScreenDefaultAttrib;
779             Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
780             Ptr->Attributes |= COMMON_LVB_TRAILING_BYTE;
781         }
782         else
783         {
784             Ptr->Char.UnicodeChar = Buffer[i];
785             if (Attrib)
786                 Ptr->Attributes = Buff->ScreenDefaultAttrib;
787             Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
788         }
789 
790         ++Ptr;
791         Buff->CursorPosition.X++;
792 
793         if (Buff->CursorPosition.X < Buff->ScreenBufferSize.X)
794         {
795             /* If the following cell is the trailing byte of a full-width character, reset it */
796             if (Ptr->Attributes & COMMON_LVB_TRAILING_BYTE)
797             {
798                 Ptr->Char.UnicodeChar = L' ';
799                 if (Attrib)
800                     Ptr->Attributes = Buff->ScreenDefaultAttrib;
801                 Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
802             }
803         }
804 
805         if (Buff->CursorPosition.X >= Buff->ScreenBufferSize.X)
806         {
807             if (Buff->Mode & ENABLE_WRAP_AT_EOL_OUTPUT)
808             {
809                 /* Wrapping mode: Go to next line */
810                 Buff->CursorPosition.X = 0;
811                 CursorStartX = Buff->CursorPosition.X;
812                 ConioNextLine(Buff, &UpdateRect, &ScrolledLines);
813             }
814             else
815             {
816                 /* The cursor wraps back to its starting position on the same line */
817                 Buff->CursorPosition.X = CursorStartX;
818             }
819         }
820     }
821 
822     if (!ConioIsRectEmpty(&UpdateRect) && (PCONSOLE_SCREEN_BUFFER)Buff == Console->ActiveBuffer)
823     {
824         // TermWriteStream(Console, &UpdateRect, CursorStartX, CursorStartY,
825                         // ScrolledLines, Buffer, Length);
826         FrontEnd->Vtbl->WriteStream(FrontEnd,
827                                     &UpdateRect,
828                                     CursorStartX,
829                                     CursorStartY,
830                                     ScrolledLines,
831                                     Buffer,
832                                     Length);
833     }
834 
835     return STATUS_SUCCESS;
836 }
837 
838 
839 
840 static NTSTATUS NTAPI
841 ConSrvTermWriteStream(IN OUT PTERMINAL This,
842                       PTEXTMODE_SCREEN_BUFFER Buff,
843                       PWCHAR Buffer,
844                       DWORD Length,
845                       BOOL Attrib)
846 {
847     PFRONTEND FrontEnd = This->Context;
848     return ConioWriteConsole(FrontEnd,
849                              Buff,
850                              Buffer,
851                              Length,
852                              Attrib);
853 }
854 
855 /************ Line discipline ***************/
856 
857 
858 
859 VOID
860 ConioDrawConsole(PCONSRV_CONSOLE Console)
861 {
862     SMALL_RECT Region;
863     PCONSOLE_SCREEN_BUFFER ActiveBuffer = Console->ActiveBuffer;
864 
865     if (!ActiveBuffer) return;
866 
867     ConioInitRect(&Region, 0, 0,
868                   ActiveBuffer->ViewSize.Y - 1,
869                   ActiveBuffer->ViewSize.X - 1);
870     TermDrawRegion(Console, &Region);
871     // Console->FrontEndIFace.Vtbl->DrawRegion(&Console->FrontEndIFace, &Region);
872 }
873 
874 static VOID NTAPI
875 ConSrvTermDrawRegion(IN OUT PTERMINAL This,
876                 SMALL_RECT* Region)
877 {
878     PFRONTEND FrontEnd = This->Context;
879     FrontEnd->Vtbl->DrawRegion(FrontEnd, Region);
880 }
881 
882 static BOOL NTAPI
883 ConSrvTermSetCursorInfo(IN OUT PTERMINAL This,
884                    PCONSOLE_SCREEN_BUFFER ScreenBuffer)
885 {
886     PFRONTEND FrontEnd = This->Context;
887     return FrontEnd->Vtbl->SetCursorInfo(FrontEnd, ScreenBuffer);
888 }
889 
890 static BOOL NTAPI
891 ConSrvTermSetScreenInfo(IN OUT PTERMINAL This,
892                    PCONSOLE_SCREEN_BUFFER ScreenBuffer,
893                    SHORT OldCursorX,
894                    SHORT OldCursorY)
895 {
896     PFRONTEND FrontEnd = This->Context;
897     return FrontEnd->Vtbl->SetScreenInfo(FrontEnd,
898                                          ScreenBuffer,
899                                          OldCursorX,
900                                          OldCursorY);
901 }
902 
903 static VOID NTAPI
904 ConSrvTermResizeTerminal(IN OUT PTERMINAL This)
905 {
906     PFRONTEND FrontEnd = This->Context;
907     FrontEnd->Vtbl->ResizeTerminal(FrontEnd);
908 }
909 
910 static VOID NTAPI
911 ConSrvTermSetActiveScreenBuffer(IN OUT PTERMINAL This)
912 {
913     PFRONTEND FrontEnd = This->Context;
914     FrontEnd->Vtbl->SetActiveScreenBuffer(FrontEnd);
915 }
916 
917 static VOID NTAPI
918 ConSrvTermReleaseScreenBuffer(IN OUT PTERMINAL This,
919                          IN PCONSOLE_SCREEN_BUFFER ScreenBuffer)
920 {
921     PFRONTEND FrontEnd = This->Context;
922     FrontEnd->Vtbl->ReleaseScreenBuffer(FrontEnd, ScreenBuffer);
923 }
924 
925 static VOID NTAPI
926 ConSrvTermGetLargestConsoleWindowSize(IN OUT PTERMINAL This,
927                                  PCOORD pSize)
928 {
929     PFRONTEND FrontEnd = This->Context;
930     FrontEnd->Vtbl->GetLargestConsoleWindowSize(FrontEnd, pSize);
931 }
932 
933 static BOOL NTAPI
934 ConSrvTermSetPalette(IN OUT PTERMINAL This,
935                 HPALETTE PaletteHandle,
936                 UINT PaletteUsage)
937 {
938     PFRONTEND FrontEnd = This->Context;
939     return FrontEnd->Vtbl->SetPalette(FrontEnd, PaletteHandle, PaletteUsage);
940 }
941 
942 static INT NTAPI
943 ConSrvTermShowMouseCursor(IN OUT PTERMINAL This,
944                      BOOL Show)
945 {
946     PFRONTEND FrontEnd = This->Context;
947     return FrontEnd->Vtbl->ShowMouseCursor(FrontEnd, Show);
948 }
949 
950 static TERMINAL_VTBL ConSrvTermVtbl =
951 {
952     ConSrvTermInitTerminal,
953     ConSrvTermDeinitTerminal,
954 
955     ConSrvTermReadStream,
956     ConSrvTermWriteStream,
957 
958     ConSrvTermDrawRegion,
959     ConSrvTermSetCursorInfo,
960     ConSrvTermSetScreenInfo,
961     ConSrvTermResizeTerminal,
962     ConSrvTermSetActiveScreenBuffer,
963     ConSrvTermReleaseScreenBuffer,
964     ConSrvTermGetLargestConsoleWindowSize,
965     ConSrvTermSetPalette,
966     ConSrvTermShowMouseCursor,
967 };
968 
969 #if 0
970 VOID
971 ResetFrontEnd(IN PCONSOLE Console)
972 {
973     PCONSRV_CONSOLE ConSrvConsole = (PCONSRV_CONSOLE)Console;
974     if (!Console) return;
975 
976     /* Reinitialize the frontend interface */
977     RtlZeroMemory(&ConSrvConsole->FrontEndIFace, sizeof(ConSrvConsole->FrontEndIFace));
978     ConSrvConsole->FrontEndIFace.Vtbl = &ConSrvTermVtbl;
979 }
980 #endif
981 
982 /* EOF */
983