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 
266     /* Initialize the console pointer for our frontend */
267     FrontEnd->Console = Console;
268 
269     /** HACK HACK!! Copy FrontEnd into the console!! **/
270     DPRINT("Using FrontEndIFace HACK(1), should be removed after proper implementation!\n");
271     Console->FrontEndIFace = *FrontEnd;
272 
273     Status = FrontEnd->Vtbl->InitFrontEnd(FrontEnd, FrontEnd->Console);
274     if (!NT_SUCCESS(Status))
275         DPRINT1("InitFrontEnd failed, Status = 0x%08lx\n", Status);
276 
277     /** HACK HACK!! Be sure FrontEndIFace is correctly updated in the console!! **/
278     DPRINT("Using FrontEndIFace HACK(2), should be removed after proper implementation!\n");
279     Console->FrontEndIFace = *FrontEnd;
280 
281     return Status;
282 }
283 
284 static VOID NTAPI
285 ConSrvTermDeinitTerminal(IN OUT PTERMINAL This)
286 {
287     PFRONTEND FrontEnd = This->Context;
288     FrontEnd->Vtbl->DeinitFrontEnd(FrontEnd);
289 }
290 
291 
292 
293 /************ Line discipline ***************/
294 
295 static NTSTATUS NTAPI
296 ConSrvTermReadStream(IN OUT PTERMINAL This,
297                      IN BOOLEAN Unicode,
298                      /**PWCHAR Buffer,**/
299                      OUT PVOID Buffer,
300                      IN OUT PCONSOLE_READCONSOLE_CONTROL ReadControl,
301                      IN PVOID Parameter OPTIONAL,
302                      IN ULONG NumCharsToRead,
303                      OUT PULONG NumCharsRead OPTIONAL)
304 {
305     PFRONTEND FrontEnd = This->Context;
306     PCONSRV_CONSOLE Console = FrontEnd->Console;
307     PCONSOLE_INPUT_BUFFER InputBuffer = &Console->InputBuffer;
308     PUNICODE_STRING ExeName = Parameter;
309 
310     // STATUS_PENDING : Wait if more to read ; STATUS_SUCCESS : Don't wait.
311     NTSTATUS Status = STATUS_PENDING;
312 
313     PLIST_ENTRY CurrentEntry;
314     ConsoleInput *Input;
315     ULONG i = 0;
316 
317     /* Validity checks */
318     // ASSERT(Console == InputBuffer->Header.Console);
319     ASSERT((Buffer != NULL) || (Buffer == NULL && NumCharsToRead == 0));
320 
321     /* We haven't read anything (yet) */
322 
323     if (InputBuffer->Mode & ENABLE_LINE_INPUT)
324     {
325         /* COOKED mode, call the line discipline */
326 
327         if (Console->LineBuffer == NULL)
328         {
329             /* Start a new line */
330             Console->LineMaxSize = max(256, NumCharsToRead);
331 
332             /*
333              * Fixup ReadControl->nInitialChars in case the number of initial
334              * characters is bigger than the number of characters to be read.
335              * It will always be, lesser than or equal to Console->LineMaxSize.
336              */
337             ReadControl->nInitialChars = min(ReadControl->nInitialChars, NumCharsToRead);
338 
339             Console->LineBuffer = ConsoleAllocHeap(0, Console->LineMaxSize * sizeof(WCHAR));
340             if (Console->LineBuffer == NULL) return STATUS_NO_MEMORY;
341 
342             Console->LinePos = Console->LineSize = ReadControl->nInitialChars;
343             Console->LineComplete = Console->LineUpPressed = FALSE;
344             Console->LineInsertToggle = Console->InsertMode;
345             Console->LineWakeupMask = ReadControl->dwCtrlWakeupMask;
346 
347             /*
348              * Pre-fill the buffer with the nInitialChars from the user buffer.
349              * Since pre-filling is only allowed in Unicode, we don't need to
350              * worry about ANSI <-> Unicode conversion.
351              */
352             memcpy(Console->LineBuffer, Buffer, Console->LineSize * sizeof(WCHAR));
353             if (Console->LineSize >= Console->LineMaxSize)
354             {
355                 Console->LineComplete = TRUE;
356                 Console->LinePos = 0;
357             }
358         }
359 
360         /* If we don't have a complete line yet, process the pending input */
361         while (!Console->LineComplete && !IsListEmpty(&InputBuffer->InputEvents))
362         {
363             /* Remove an input event from the queue */
364             _InterlockedDecrement((PLONG)&InputBuffer->NumberOfEvents);
365             CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
366             if (IsListEmpty(&InputBuffer->InputEvents))
367             {
368                 NtClearEvent(InputBuffer->ActiveEvent);
369             }
370             Input = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
371 
372             /* Only pay attention to key down */
373             if (Input->InputEvent.EventType == KEY_EVENT &&
374                 Input->InputEvent.Event.KeyEvent.bKeyDown)
375             {
376                 LineInputKeyDown(Console, ExeName,
377                                  &Input->InputEvent.Event.KeyEvent);
378                 ReadControl->dwControlKeyState = Input->InputEvent.Event.KeyEvent.dwControlKeyState;
379             }
380             ConsoleFreeHeap(Input);
381         }
382 
383         /* Check if we have a complete line to read from */
384         if (Console->LineComplete)
385         {
386             /*
387              * Console->LinePos keeps the next position of the character to read
388              * in the line buffer across the different calls of the function,
389              * so that the line buffer can be read by chunks after all the input
390              * has been buffered.
391              */
392 
393             while (i < NumCharsToRead && Console->LinePos < Console->LineSize)
394             {
395                 WCHAR Char = Console->LineBuffer[Console->LinePos++];
396 
397                 if (Unicode)
398                 {
399                     ((PWCHAR)Buffer)[i] = Char;
400                 }
401                 else
402                 {
403                     ConsoleInputUnicodeCharToAnsiChar(Console, &((PCHAR)Buffer)[i], &Char);
404                 }
405                 ++i;
406             }
407 
408             if (Console->LinePos >= Console->LineSize)
409             {
410                 /* The entire line has been read */
411                 ConsoleFreeHeap(Console->LineBuffer);
412                 Console->LineBuffer = NULL;
413                 Console->LinePos = Console->LineMaxSize = Console->LineSize = 0;
414                 // Console->LineComplete = Console->LineUpPressed = FALSE;
415                 Console->LineComplete = FALSE;
416             }
417 
418             Status = STATUS_SUCCESS;
419         }
420     }
421     else
422     {
423         /* RAW mode */
424 
425         /* Character input */
426         while (i < NumCharsToRead && !IsListEmpty(&InputBuffer->InputEvents))
427         {
428             /* Remove an input event from the queue */
429             _InterlockedDecrement((PLONG)&InputBuffer->NumberOfEvents);
430             CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
431             if (IsListEmpty(&InputBuffer->InputEvents))
432             {
433                 NtClearEvent(InputBuffer->ActiveEvent);
434             }
435             Input = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
436 
437             /* Only pay attention to valid characters, on key down */
438             if (Input->InputEvent.EventType == KEY_EVENT  &&
439                 Input->InputEvent.Event.KeyEvent.bKeyDown &&
440                 Input->InputEvent.Event.KeyEvent.uChar.UnicodeChar != L'\0')
441             {
442                 WCHAR Char = Input->InputEvent.Event.KeyEvent.uChar.UnicodeChar;
443 
444                 if (Unicode)
445                 {
446                     ((PWCHAR)Buffer)[i] = Char;
447                 }
448                 else
449                 {
450                     ConsoleInputUnicodeCharToAnsiChar(Console, &((PCHAR)Buffer)[i], &Char);
451                 }
452                 ++i;
453 
454                 /* Did read something */
455                 Status = STATUS_SUCCESS;
456             }
457             ConsoleFreeHeap(Input);
458         }
459     }
460 
461     // FIXME: Only set if Status == STATUS_SUCCESS ???
462     if (NumCharsRead) *NumCharsRead = i;
463 
464     return Status;
465 }
466 
467 
468 
469 
470 /* GLOBALS ********************************************************************/
471 
472 #define TAB_WIDTH   8
473 
474 // See condrv/text.c
475 /*static*/ VOID
476 ClearLineBuffer(PTEXTMODE_SCREEN_BUFFER Buff);
477 
478 static VOID
479 ConioNextLine(PTEXTMODE_SCREEN_BUFFER Buff, PSMALL_RECT UpdateRect, PUINT ScrolledLines)
480 {
481     /* If we hit bottom, slide the viewable screen */
482     if (++Buff->CursorPosition.Y == Buff->ScreenBufferSize.Y)
483     {
484         Buff->CursorPosition.Y--;
485         if (++Buff->VirtualY == Buff->ScreenBufferSize.Y)
486         {
487             Buff->VirtualY = 0;
488         }
489         (*ScrolledLines)++;
490         ClearLineBuffer(Buff);
491         if (UpdateRect->Top != 0)
492         {
493             UpdateRect->Top--;
494         }
495     }
496     UpdateRect->Left = 0;
497     UpdateRect->Right = Buff->ScreenBufferSize.X - 1;
498     UpdateRect->Bottom = Buff->CursorPosition.Y;
499 }
500 
501 static NTSTATUS
502 ConioWriteConsole(PFRONTEND FrontEnd,
503                   PTEXTMODE_SCREEN_BUFFER Buff,
504                   PWCHAR Buffer,
505                   DWORD Length,
506                   BOOL Attrib)
507 {
508     PCONSRV_CONSOLE Console = FrontEnd->Console;
509 
510     UINT i;
511     PCHAR_INFO Ptr;
512     SMALL_RECT UpdateRect;
513     SHORT CursorStartX, CursorStartY;
514     UINT ScrolledLines;
515     BOOLEAN bFullwidth;
516     BOOLEAN bCJK = Console->IsCJK;
517 
518     /* If nothing to write, bail out now */
519     if (Length == 0)
520         return STATUS_SUCCESS;
521 
522     CursorStartX = Buff->CursorPosition.X;
523     CursorStartY = Buff->CursorPosition.Y;
524     UpdateRect.Left = Buff->ScreenBufferSize.X;
525     UpdateRect.Top  = Buff->CursorPosition.Y;
526     UpdateRect.Right  = -1;
527     UpdateRect.Bottom = Buff->CursorPosition.Y;
528     ScrolledLines = 0;
529 
530     for (i = 0; i < Length; i++)
531     {
532         /*
533          * If we are in processed mode, interpret special characters and
534          * display them correctly. Otherwise, just put them into the buffer.
535          */
536         if (Buff->Mode & ENABLE_PROCESSED_OUTPUT)
537         {
538             /* --- CR --- */
539             if (Buffer[i] == L'\r')
540             {
541                 Buff->CursorPosition.X = 0;
542                 CursorStartX = Buff->CursorPosition.X;
543                 UpdateRect.Left  = min(UpdateRect.Left , Buff->CursorPosition.X);
544                 UpdateRect.Right = max(UpdateRect.Right, Buff->CursorPosition.X);
545                 continue;
546             }
547             /* --- LF --- */
548             else if (Buffer[i] == L'\n')
549             {
550                 Buff->CursorPosition.X = 0; // TODO: Make this behaviour optional!
551                 CursorStartX = Buff->CursorPosition.X;
552                 ConioNextLine(Buff, &UpdateRect, &ScrolledLines);
553                 continue;
554             }
555             /* --- BS --- */
556             else if (Buffer[i] == L'\b')
557             {
558                 /* Only handle BS if we are not on the first position of the first line */
559                 if (Buff->CursorPosition.X == 0 && Buff->CursorPosition.Y == 0)
560                     continue;
561 
562                 if (Buff->CursorPosition.X == 0)
563                 {
564                     /* Slide virtual position up */
565                     Buff->CursorPosition.X = Buff->ScreenBufferSize.X - 1;
566                     Buff->CursorPosition.Y--;
567                     // TODO? : Update CursorStartY = Buff->CursorPosition.Y;
568                     UpdateRect.Top = min(UpdateRect.Top, Buff->CursorPosition.Y);
569                 }
570                 else
571                 {
572                     Buff->CursorPosition.X--;
573                 }
574                 Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
575 
576                 if (Ptr->Attributes & COMMON_LVB_LEADING_BYTE)
577                 {
578                     /*
579                      * The cursor just moved on the leading byte of the same
580                      * current character. We should go one position before to
581                      * go to the actual previous character to erase.
582                      */
583 
584                     /* Only handle BS if we are not on the first position of the first line */
585                     if (Buff->CursorPosition.X == 0 && Buff->CursorPosition.Y == 0)
586                         continue;
587 
588                     if (Buff->CursorPosition.X == 0)
589                     {
590                         /* Slide virtual position up */
591                         Buff->CursorPosition.X = Buff->ScreenBufferSize.X - 1;
592                         Buff->CursorPosition.Y--;
593                         // TODO? : Update CursorStartY = Buff->CursorPosition.Y;
594                         UpdateRect.Top = min(UpdateRect.Top, Buff->CursorPosition.Y);
595                     }
596                     else
597                     {
598                         Buff->CursorPosition.X--;
599                     }
600                     Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
601                 }
602 
603                 if (Ptr->Attributes & COMMON_LVB_TRAILING_BYTE)
604                 {
605                     /* The cursor is on the trailing byte of a full-width character */
606 
607                     /* Delete its trailing byte... */
608                     Ptr->Char.UnicodeChar = L' ';
609                     if (Attrib)
610                         Ptr->Attributes = Buff->ScreenDefaultAttrib;
611                     Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
612 
613                     if (Buff->CursorPosition.X > 0)
614                         Buff->CursorPosition.X--;
615                     /* ... and now its leading byte */
616                     Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
617                 }
618 
619                 Ptr->Char.UnicodeChar = L' ';
620                 if (Attrib)
621                     Ptr->Attributes = Buff->ScreenDefaultAttrib;
622                 Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
623 
624                 UpdateRect.Left  = min(UpdateRect.Left , Buff->CursorPosition.X);
625                 UpdateRect.Right = max(UpdateRect.Right, Buff->CursorPosition.X);
626                 continue;
627             }
628             /* --- TAB --- */
629             else if (Buffer[i] == L'\t')
630             {
631                 UINT EndX;
632 
633                 Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
634 
635                 if (Ptr->Attributes & COMMON_LVB_TRAILING_BYTE)
636                 {
637                     /*
638                      * The cursor is on the trailing byte of a full-width character.
639                      * Go back one position to be on its leading byte.
640                      */
641                     if (Buff->CursorPosition.X > 0)
642                         Buff->CursorPosition.X--;
643                     Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
644                 }
645 
646                 UpdateRect.Left = min(UpdateRect.Left, Buff->CursorPosition.X);
647 
648                 EndX = (Buff->CursorPosition.X + TAB_WIDTH) & ~(TAB_WIDTH - 1);
649                 EndX = min(EndX, (UINT)Buff->ScreenBufferSize.X);
650 
651                 while ((UINT)Buff->CursorPosition.X < EndX)
652                 {
653                     Ptr->Char.UnicodeChar = L' ';
654                     if (Attrib)
655                         Ptr->Attributes = Buff->ScreenDefaultAttrib;
656                     Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
657 
658                     ++Ptr;
659                     Buff->CursorPosition.X++;
660                 }
661                 if (Buff->CursorPosition.X < Buff->ScreenBufferSize.X)
662                 {
663                     /* If the following cell is the trailing byte of a full-width character, reset it */
664                     if (Ptr->Attributes & COMMON_LVB_TRAILING_BYTE)
665                     {
666                         Ptr->Char.UnicodeChar = L' ';
667                         if (Attrib)
668                             Ptr->Attributes = Buff->ScreenDefaultAttrib;
669                         Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
670                     }
671                 }
672                 UpdateRect.Right = max(UpdateRect.Right, Buff->CursorPosition.X);
673 
674                 if (Buff->CursorPosition.X >= Buff->ScreenBufferSize.X)
675                 {
676                     if (Buff->Mode & ENABLE_WRAP_AT_EOL_OUTPUT)
677                     {
678                         /* Wrapping mode: Go to next line */
679                         Buff->CursorPosition.X = 0;
680                         CursorStartX = Buff->CursorPosition.X;
681                         ConioNextLine(Buff, &UpdateRect, &ScrolledLines);
682                     }
683                     else
684                     {
685                         /* The cursor wraps back to its starting position on the same line */
686                         Buff->CursorPosition.X = CursorStartX;
687                     }
688                 }
689                 continue;
690             }
691             /* --- BEL ---*/
692             else if (Buffer[i] == L'\a')
693             {
694                 FrontEnd->Vtbl->RingBell(FrontEnd);
695                 continue;
696             }
697         }
698         UpdateRect.Left  = min(UpdateRect.Left , Buff->CursorPosition.X);
699         UpdateRect.Right = max(UpdateRect.Right, Buff->CursorPosition.X);
700 
701         /* For Chinese, Japanese and Korean */
702         bFullwidth = (bCJK && IS_FULL_WIDTH(Buffer[i]));
703 
704         /* Check whether we can insert the full-width character */
705         if (bFullwidth)
706         {
707             /* It spans two cells and should all fit on the current line */
708             if (Buff->CursorPosition.X >= Buff->ScreenBufferSize.X - 1)
709             {
710                 if (Buff->Mode & ENABLE_WRAP_AT_EOL_OUTPUT)
711                 {
712                     /* Wrapping mode: Go to next line */
713                     Buff->CursorPosition.X = 0;
714                     CursorStartX = Buff->CursorPosition.X;
715                     ConioNextLine(Buff, &UpdateRect, &ScrolledLines);
716                 }
717                 else
718                 {
719                     /* The cursor wraps back to its starting position on the same line */
720                     Buff->CursorPosition.X = CursorStartX;
721                 }
722             }
723 
724             /*
725              * Now be sure we can fit the full-width character.
726              * If the screenbuffer is one cell wide we cannot display
727              * the full-width character, so just skip it.
728              */
729             if (Buff->CursorPosition.X >= Buff->ScreenBufferSize.X - 1)
730             {
731                 DPRINT1("Cannot display full-width character! CursorPosition.X = %d, ScreenBufferSize.X = %d\n",
732                         Buff->CursorPosition.X, Buff->ScreenBufferSize.X);
733                 continue;
734             }
735         }
736 
737         Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
738 
739         /*
740          * Check whether we are overwriting part of a full-width character,
741          * in which case we need to invalidate it.
742          */
743         if (Ptr->Attributes & COMMON_LVB_TRAILING_BYTE)
744         {
745             /*
746              * The cursor is on the trailing byte of a full-width character.
747              * Go back one position to kill the previous leading byte.
748              */
749             if (Buff->CursorPosition.X > 0)
750             {
751                 Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X - 1, Buff->CursorPosition.Y);
752                 Ptr->Char.UnicodeChar = L' ';
753                 if (Attrib)
754                     Ptr->Attributes = Buff->ScreenDefaultAttrib;
755                 Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
756             }
757             Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
758         }
759 
760         /* Insert the character */
761         if (bFullwidth)
762         {
763             ASSERT(Buff->CursorPosition.X < Buff->ScreenBufferSize.X - 1);
764 
765             /* Set the leading byte */
766             Ptr->Char.UnicodeChar = Buffer[i];
767             if (Attrib)
768                 Ptr->Attributes = Buff->ScreenDefaultAttrib;
769             Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
770             Ptr->Attributes |= COMMON_LVB_LEADING_BYTE;
771 
772             /* Set the trailing byte */
773             Buff->CursorPosition.X++;
774             Ptr = ConioCoordToPointer(Buff, Buff->CursorPosition.X, Buff->CursorPosition.Y);
775             // Ptr->Char.UnicodeChar = Buffer[i]; // L' ';
776             if (Attrib)
777                 Ptr->Attributes = Buff->ScreenDefaultAttrib;
778             Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
779             Ptr->Attributes |= COMMON_LVB_TRAILING_BYTE;
780         }
781         else
782         {
783             Ptr->Char.UnicodeChar = Buffer[i];
784             if (Attrib)
785                 Ptr->Attributes = Buff->ScreenDefaultAttrib;
786             Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
787         }
788 
789         ++Ptr;
790         Buff->CursorPosition.X++;
791 
792         if (Buff->CursorPosition.X < Buff->ScreenBufferSize.X)
793         {
794             /* If the following cell is the trailing byte of a full-width character, reset it */
795             if (Ptr->Attributes & COMMON_LVB_TRAILING_BYTE)
796             {
797                 Ptr->Char.UnicodeChar = L' ';
798                 if (Attrib)
799                     Ptr->Attributes = Buff->ScreenDefaultAttrib;
800                 Ptr->Attributes &= ~COMMON_LVB_SBCSDBCS;
801             }
802         }
803 
804         if (Buff->CursorPosition.X >= Buff->ScreenBufferSize.X)
805         {
806             if (Buff->Mode & ENABLE_WRAP_AT_EOL_OUTPUT)
807             {
808                 /* Wrapping mode: Go to next line */
809                 Buff->CursorPosition.X = 0;
810                 CursorStartX = Buff->CursorPosition.X;
811                 ConioNextLine(Buff, &UpdateRect, &ScrolledLines);
812             }
813             else
814             {
815                 /* The cursor wraps back to its starting position on the same line */
816                 Buff->CursorPosition.X = CursorStartX;
817             }
818         }
819     }
820 
821     if (!ConioIsRectEmpty(&UpdateRect) && (PCONSOLE_SCREEN_BUFFER)Buff == Console->ActiveBuffer)
822     {
823         // TermWriteStream(Console, &UpdateRect, CursorStartX, CursorStartY,
824                         // ScrolledLines, Buffer, Length);
825         FrontEnd->Vtbl->WriteStream(FrontEnd,
826                                     &UpdateRect,
827                                     CursorStartX,
828                                     CursorStartY,
829                                     ScrolledLines,
830                                     Buffer,
831                                     Length);
832     }
833 
834     return STATUS_SUCCESS;
835 }
836 
837 
838 
839 static NTSTATUS NTAPI
840 ConSrvTermWriteStream(IN OUT PTERMINAL This,
841                       PTEXTMODE_SCREEN_BUFFER Buff,
842                       PWCHAR Buffer,
843                       DWORD Length,
844                       BOOL Attrib)
845 {
846     PFRONTEND FrontEnd = This->Context;
847     return ConioWriteConsole(FrontEnd,
848                              Buff,
849                              Buffer,
850                              Length,
851                              Attrib);
852 }
853 
854 /************ Line discipline ***************/
855 
856 
857 
858 VOID
859 ConioDrawConsole(PCONSRV_CONSOLE Console)
860 {
861     SMALL_RECT Region;
862     PCONSOLE_SCREEN_BUFFER ActiveBuffer = Console->ActiveBuffer;
863 
864     if (!ActiveBuffer) return;
865 
866     ConioInitRect(&Region, 0, 0,
867                   ActiveBuffer->ViewSize.Y - 1,
868                   ActiveBuffer->ViewSize.X - 1);
869     TermDrawRegion(Console, &Region);
870     // Console->FrontEndIFace.Vtbl->DrawRegion(&Console->FrontEndIFace, &Region);
871 }
872 
873 static VOID NTAPI
874 ConSrvTermDrawRegion(IN OUT PTERMINAL This,
875                 SMALL_RECT* Region)
876 {
877     PFRONTEND FrontEnd = This->Context;
878     FrontEnd->Vtbl->DrawRegion(FrontEnd, Region);
879 }
880 
881 static BOOL NTAPI
882 ConSrvTermSetCursorInfo(IN OUT PTERMINAL This,
883                    PCONSOLE_SCREEN_BUFFER ScreenBuffer)
884 {
885     PFRONTEND FrontEnd = This->Context;
886     return FrontEnd->Vtbl->SetCursorInfo(FrontEnd, ScreenBuffer);
887 }
888 
889 static BOOL NTAPI
890 ConSrvTermSetScreenInfo(IN OUT PTERMINAL This,
891                    PCONSOLE_SCREEN_BUFFER ScreenBuffer,
892                    SHORT OldCursorX,
893                    SHORT OldCursorY)
894 {
895     PFRONTEND FrontEnd = This->Context;
896     return FrontEnd->Vtbl->SetScreenInfo(FrontEnd,
897                                          ScreenBuffer,
898                                          OldCursorX,
899                                          OldCursorY);
900 }
901 
902 static VOID NTAPI
903 ConSrvTermResizeTerminal(IN OUT PTERMINAL This)
904 {
905     PFRONTEND FrontEnd = This->Context;
906     FrontEnd->Vtbl->ResizeTerminal(FrontEnd);
907 }
908 
909 static VOID NTAPI
910 ConSrvTermSetActiveScreenBuffer(IN OUT PTERMINAL This)
911 {
912     PFRONTEND FrontEnd = This->Context;
913     FrontEnd->Vtbl->SetActiveScreenBuffer(FrontEnd);
914 }
915 
916 static VOID NTAPI
917 ConSrvTermReleaseScreenBuffer(IN OUT PTERMINAL This,
918                          IN PCONSOLE_SCREEN_BUFFER ScreenBuffer)
919 {
920     PFRONTEND FrontEnd = This->Context;
921     FrontEnd->Vtbl->ReleaseScreenBuffer(FrontEnd, ScreenBuffer);
922 }
923 
924 static VOID NTAPI
925 ConSrvTermGetLargestConsoleWindowSize(IN OUT PTERMINAL This,
926                                  PCOORD pSize)
927 {
928     PFRONTEND FrontEnd = This->Context;
929     FrontEnd->Vtbl->GetLargestConsoleWindowSize(FrontEnd, pSize);
930 }
931 
932 static BOOL NTAPI
933 ConSrvTermSetPalette(IN OUT PTERMINAL This,
934                 HPALETTE PaletteHandle,
935                 UINT PaletteUsage)
936 {
937     PFRONTEND FrontEnd = This->Context;
938     return FrontEnd->Vtbl->SetPalette(FrontEnd, PaletteHandle, PaletteUsage);
939 }
940 
941 static INT NTAPI
942 ConSrvTermShowMouseCursor(IN OUT PTERMINAL This,
943                      BOOL Show)
944 {
945     PFRONTEND FrontEnd = This->Context;
946     return FrontEnd->Vtbl->ShowMouseCursor(FrontEnd, Show);
947 }
948 
949 static TERMINAL_VTBL ConSrvTermVtbl =
950 {
951     ConSrvTermInitTerminal,
952     ConSrvTermDeinitTerminal,
953 
954     ConSrvTermReadStream,
955     ConSrvTermWriteStream,
956 
957     ConSrvTermDrawRegion,
958     ConSrvTermSetCursorInfo,
959     ConSrvTermSetScreenInfo,
960     ConSrvTermResizeTerminal,
961     ConSrvTermSetActiveScreenBuffer,
962     ConSrvTermReleaseScreenBuffer,
963     ConSrvTermGetLargestConsoleWindowSize,
964     ConSrvTermSetPalette,
965     ConSrvTermShowMouseCursor,
966 };
967 
968 #if 0
969 VOID
970 ResetFrontEnd(IN PCONSOLE Console)
971 {
972     if (!Console) return;
973 
974     /* Reinitialize the frontend interface */
975     RtlZeroMemory(&Console->FrontEndIFace, sizeof(Console->FrontEndIFace));
976     Console->FrontEndIFace.Vtbl = &ConSrvTermVtbl;
977 }
978 #endif
979 
980 /* EOF */
981