xref: /reactos/win32ss/user/winsrv/consrv/lineinput.c (revision 3435c3b5)
1 /*
2  * LICENSE:         GPL - See COPYING in the top level directory
3  * PROJECT:         ReactOS Console Server DLL
4  * FILE:            win32ss/user/winsrv/consrv/lineinput.c
5  * PURPOSE:         Console line input functions
6  * PROGRAMMERS:     Jeffrey Morlan
7  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include "consrv.h"
12 #include "popup.h"
13 
14 #define NDEBUG
15 #include <debug.h>
16 
17 
18 BOOLEAN
19 ConvertInputAnsiToUnicode(PCONSRV_CONSOLE Console,
20                           PVOID    Source,
21                           USHORT   SourceLength,
22                           // BOOLEAN  IsUnicode,
23                           PWCHAR*  Target,
24                           PUSHORT  TargetLength);
25 BOOLEAN
26 ConvertInputUnicodeToAnsi(PCONSRV_CONSOLE Console,
27                           PVOID    Source,
28                           USHORT   SourceLength,
29                           // BOOLEAN  IsAnsi,
30                           PCHAR/* * */   Target,
31                           /*P*/USHORT  TargetLength);
32 
33 
34 VOID
35 HistoryAddEntry(PCONSRV_CONSOLE Console,
36                 PUNICODE_STRING ExeName,
37                 PUNICODE_STRING Entry);
38 BOOL
39 HistoryRecallHistory(PCONSRV_CONSOLE Console,
40                      PUNICODE_STRING ExeName,
41                      INT Offset,
42                      PUNICODE_STRING Entry);
43 VOID
44 HistoryGetCurrentEntry(PCONSRV_CONSOLE Console,
45                        PUNICODE_STRING ExeName,
46                        PUNICODE_STRING Entry);
47 VOID
48 HistoryDeleteCurrentBuffer(PCONSRV_CONSOLE Console,
49                            PUNICODE_STRING ExeName);
50 BOOL
51 HistoryFindEntryByPrefix(PCONSRV_CONSOLE Console,
52                          PUNICODE_STRING ExeName,
53                          PUNICODE_STRING Prefix,
54                          PUNICODE_STRING Entry);
55 
56 
57 /* PRIVATE FUNCTIONS **********************************************************/
58 
59 static VOID
60 LineInputSetPos(PCONSRV_CONSOLE Console,
61                 UINT Pos)
62 {
63     if (Pos != Console->LinePos && (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT))
64     {
65         PCONSOLE_SCREEN_BUFFER Buffer = Console->ActiveBuffer;
66         SHORT OldCursorX = Buffer->CursorPosition.X;
67         SHORT OldCursorY = Buffer->CursorPosition.Y;
68         INT XY = OldCursorY * Buffer->ScreenBufferSize.X + OldCursorX;
69 
70         XY += (Pos - Console->LinePos);
71         if (XY < 0)
72             XY = 0;
73         else if (XY >= Buffer->ScreenBufferSize.Y * Buffer->ScreenBufferSize.X)
74             XY = Buffer->ScreenBufferSize.Y * Buffer->ScreenBufferSize.X - 1;
75 
76         Buffer->CursorPosition.X = XY % Buffer->ScreenBufferSize.X;
77         Buffer->CursorPosition.Y = XY / Buffer->ScreenBufferSize.X;
78         TermSetScreenInfo(Console, Buffer, OldCursorX, OldCursorY);
79     }
80 
81     Console->LinePos = Pos;
82 }
83 
84 static VOID
85 LineInputEdit(PCONSRV_CONSOLE Console,
86               UINT NumToDelete,
87               UINT NumToInsert,
88               PWCHAR Insertion)
89 {
90     PTEXTMODE_SCREEN_BUFFER ActiveBuffer;
91     UINT Pos = Console->LinePos;
92     UINT NewSize = Console->LineSize - NumToDelete + NumToInsert;
93     UINT i;
94 
95     if (GetType(Console->ActiveBuffer) != TEXTMODE_BUFFER) return;
96     ActiveBuffer = (PTEXTMODE_SCREEN_BUFFER)Console->ActiveBuffer;
97 
98     /* Make sure there is always enough room for ending \r\n */
99     if (NewSize + 2 > Console->LineMaxSize)
100         return;
101 
102     memmove(&Console->LineBuffer[Pos + NumToInsert],
103             &Console->LineBuffer[Pos + NumToDelete],
104             (Console->LineSize - (Pos + NumToDelete)) * sizeof(WCHAR));
105     memcpy(&Console->LineBuffer[Pos], Insertion, NumToInsert * sizeof(WCHAR));
106 
107     if (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT)
108     {
109         if (Pos < NewSize)
110         {
111             TermWriteStream(Console, ActiveBuffer,
112                             &Console->LineBuffer[Pos],
113                             NewSize - Pos,
114                             TRUE);
115         }
116         for (i = NewSize; i < Console->LineSize; ++i)
117         {
118             TermWriteStream(Console, ActiveBuffer, L" ", 1, TRUE);
119         }
120         Console->LinePos = i;
121     }
122 
123     Console->LineSize = NewSize;
124     LineInputSetPos(Console, Pos + NumToInsert);
125 }
126 
127 static VOID
128 LineInputRecallHistory(PCONSRV_CONSOLE Console,
129                        PUNICODE_STRING ExeName,
130                        INT Offset)
131 {
132 #if 0
133     PHISTORY_BUFFER Hist = HistoryCurrentBuffer(Console, ExeName);
134     UINT Position = 0;
135 
136     if (!Hist || Hist->NumEntries == 0) return;
137 
138     Position = Hist->Position + Offset;
139     Position = min(max(Position, 0), Hist->NumEntries - 1);
140     Hist->Position = Position;
141 
142     LineInputSetPos(Console, 0);
143     LineInputEdit(Console, Console->LineSize,
144                   Hist->Entries[Hist->Position].Length / sizeof(WCHAR),
145                   Hist->Entries[Hist->Position].Buffer);
146 
147 #else
148 
149     UNICODE_STRING Entry;
150 
151     if (!HistoryRecallHistory(Console, ExeName, Offset, &Entry)) return;
152 
153     LineInputSetPos(Console, 0);
154     LineInputEdit(Console, Console->LineSize,
155                   Entry.Length / sizeof(WCHAR),
156                   Entry.Buffer);
157 #endif
158 }
159 
160 
161 // TESTS!!
162 PPOPUP_WINDOW Popup = NULL;
163 
164 PPOPUP_WINDOW
165 HistoryDisplayCurrentHistory(PCONSRV_CONSOLE Console,
166                              PUNICODE_STRING ExeName);
167 
168 VOID
169 LineInputKeyDown(PCONSRV_CONSOLE Console,
170                  PUNICODE_STRING ExeName,
171                  KEY_EVENT_RECORD *KeyEvent)
172 {
173     UINT Pos = Console->LinePos;
174     UNICODE_STRING Entry;
175 
176     /*
177      * First, deal with control keys...
178      */
179 
180     switch (KeyEvent->wVirtualKeyCode)
181     {
182         case VK_ESCAPE:
183         {
184             /* Clear the entire line */
185             LineInputSetPos(Console, 0);
186             LineInputEdit(Console, Console->LineSize, 0, NULL);
187 
188             // TESTS!!
189             if (Popup)
190             {
191                 DestroyPopupWindow(Popup);
192                 Popup = NULL;
193             }
194             return;
195         }
196 
197         case VK_HOME:
198         {
199             /* Move to start of line. With CTRL, erase everything left of cursor. */
200             LineInputSetPos(Console, 0);
201             if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
202                 LineInputEdit(Console, Pos, 0, NULL);
203             return;
204         }
205 
206         case VK_END:
207         {
208             /* Move to end of line. With CTRL, erase everything right of cursor. */
209             if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
210                 LineInputEdit(Console, Console->LineSize - Pos, 0, NULL);
211             else
212                 LineInputSetPos(Console, Console->LineSize);
213             return;
214         }
215 
216         case VK_LEFT:
217         {
218             /* Move to the left. With CTRL, move to beginning of previous word. */
219             if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
220             {
221                 while (Pos > 0 && Console->LineBuffer[Pos - 1] == L' ') Pos--;
222                 while (Pos > 0 && Console->LineBuffer[Pos - 1] != L' ') Pos--;
223             }
224             else
225             {
226                 Pos -= (Pos > 0);
227             }
228             LineInputSetPos(Console, Pos);
229             return;
230         }
231 
232         case VK_RIGHT:
233         case VK_F1:
234         {
235             /* Move to the right. With CTRL, move to beginning of next word. */
236             if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
237             {
238                 while (Pos < Console->LineSize && Console->LineBuffer[Pos] != L' ') Pos++;
239                 while (Pos < Console->LineSize && Console->LineBuffer[Pos] == L' ') Pos++;
240                 LineInputSetPos(Console, Pos);
241             }
242             else
243             {
244                 /* Recall one character (but don't overwrite current line) */
245                 HistoryGetCurrentEntry(Console, ExeName, &Entry);
246                 if (Pos < Console->LineSize)
247                     LineInputSetPos(Console, Pos + 1);
248                 else if (Pos * sizeof(WCHAR) < Entry.Length)
249                     LineInputEdit(Console, 0, 1, &Entry.Buffer[Pos]);
250             }
251             return;
252         }
253 
254         case VK_INSERT:
255         {
256             /* Toggle between insert and overstrike */
257             Console->LineInsertToggle = !Console->LineInsertToggle;
258             TermSetCursorInfo(Console, Console->ActiveBuffer);
259             return;
260         }
261 
262         case VK_DELETE:
263         {
264             /* Remove one character to right of cursor */
265             if (Pos != Console->LineSize)
266                 LineInputEdit(Console, 1, 0, NULL);
267             return;
268         }
269 
270         case VK_PRIOR:
271         {
272             /* Recall the first history entry */
273             LineInputRecallHistory(Console, ExeName, -((WORD)-1));
274             return;
275         }
276 
277         case VK_NEXT:
278         {
279             /* Recall the last history entry */
280             LineInputRecallHistory(Console, ExeName, +((WORD)-1));
281             return;
282         }
283 
284         case VK_UP:
285         case VK_F5:
286         {
287             /*
288              * Recall the previous history entry. On first time, actually recall
289              * the current (usually last) entry; on subsequent times go back.
290              */
291             LineInputRecallHistory(Console, ExeName, Console->LineUpPressed ? -1 : 0);
292             Console->LineUpPressed = TRUE;
293             return;
294         }
295 
296         case VK_DOWN:
297         {
298             /* Recall the next history entry */
299             LineInputRecallHistory(Console, ExeName, +1);
300             return;
301         }
302 
303         case VK_F3:
304         {
305             /* Recall the remainder of the current history entry */
306             HistoryGetCurrentEntry(Console, ExeName, &Entry);
307             if (Pos * sizeof(WCHAR) < Entry.Length)
308             {
309                 UINT InsertSize = (Entry.Length / sizeof(WCHAR) - Pos);
310                 UINT DeleteSize = min(Console->LineSize - Pos, InsertSize);
311                 LineInputEdit(Console, DeleteSize, InsertSize, &Entry.Buffer[Pos]);
312             }
313             return;
314         }
315 
316         case VK_F6:
317         {
318             /* Insert a ^Z character */
319             KeyEvent->uChar.UnicodeChar = 26;
320             break;
321         }
322 
323         case VK_F7:
324         {
325             if (KeyEvent->dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))
326                 HistoryDeleteCurrentBuffer(Console, ExeName);
327             else
328             {
329                 if (Popup) DestroyPopupWindow(Popup);
330                 Popup = HistoryDisplayCurrentHistory(Console, ExeName);
331             }
332             return;
333         }
334 
335         case VK_F8:
336         {
337             UNICODE_STRING EntryFound;
338 
339             Entry.Length = Console->LinePos * sizeof(WCHAR); // == Pos * sizeof(WCHAR)
340             Entry.Buffer = Console->LineBuffer;
341 
342             if (HistoryFindEntryByPrefix(Console, ExeName, &Entry, &EntryFound))
343             {
344                 LineInputEdit(Console, Console->LineSize - Pos,
345                               EntryFound.Length / sizeof(WCHAR) - Pos,
346                               &EntryFound.Buffer[Pos]);
347                 /* Cursor stays where it was */
348                 LineInputSetPos(Console, Pos);
349             }
350 
351             return;
352         }
353 #if 0
354         {
355             PHISTORY_BUFFER Hist;
356             INT HistPos;
357 
358             /* Search for history entries starting with input */
359             Hist = HistoryCurrentBuffer(Console, ExeName);
360             if (!Hist || Hist->NumEntries == 0) return;
361 
362             /*
363              * Like Up/F5, on first time start from current (usually last) entry,
364              * but on subsequent times start at previous entry.
365              */
366             if (Console->LineUpPressed)
367                 Hist->Position = (Hist->Position ? Hist->Position : Hist->NumEntries) - 1;
368             Console->LineUpPressed = TRUE;
369 
370             Entry.Length = Console->LinePos * sizeof(WCHAR); // == Pos * sizeof(WCHAR)
371             Entry.Buffer = Console->LineBuffer;
372 
373             /*
374              * Keep going backwards, even wrapping around to the end,
375              * until we get back to starting point.
376              */
377             HistPos = Hist->Position;
378             do
379             {
380                 if (RtlPrefixUnicodeString(&Entry, &Hist->Entries[HistPos], FALSE))
381                 {
382                     Hist->Position = HistPos;
383                     LineInputEdit(Console, Console->LineSize - Pos,
384                                   Hist->Entries[HistPos].Length / sizeof(WCHAR) - Pos,
385                                   &Hist->Entries[HistPos].Buffer[Pos]);
386                     /* Cursor stays where it was */
387                     LineInputSetPos(Console, Pos);
388                     return;
389                 }
390                 if (--HistPos < 0) HistPos += Hist->NumEntries;
391             } while (HistPos != Hist->Position);
392 
393             return;
394         }
395 #endif
396 
397         return;
398     }
399 
400 
401     /*
402      * OK, we deal with normal keys, we can continue...
403      */
404 
405     if (KeyEvent->uChar.UnicodeChar == L'\b' && (GetConsoleInputBufferMode(Console) & ENABLE_PROCESSED_INPUT))
406     {
407         /*
408          * Backspace handling - if processed input enabled then we handle it
409          * here, otherwise we treat it like a normal character.
410          */
411         if (Pos > 0)
412         {
413             LineInputSetPos(Console, Pos - 1);
414             LineInputEdit(Console, 1, 0, NULL);
415         }
416     }
417     else if (KeyEvent->uChar.UnicodeChar == L'\r')
418     {
419         /*
420          * Only add a history entry if console echo is enabled. This ensures
421          * that anything sent to the console when echo is disabled (e.g.
422          * binary data, or secrets like passwords...) does not remain stored
423          * in memory.
424          */
425         if (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT)
426         {
427             Entry.Length = Entry.MaximumLength = Console->LineSize * sizeof(WCHAR);
428             Entry.Buffer = Console->LineBuffer;
429             HistoryAddEntry(Console, ExeName, &Entry);
430         }
431 
432         /* TODO: Expand aliases */
433         DPRINT1("TODO: Expand aliases\n");
434 
435         LineInputSetPos(Console, Console->LineSize);
436         Console->LineBuffer[Console->LineSize++] = L'\r';
437         if ((GetType(Console->ActiveBuffer) == TEXTMODE_BUFFER) &&
438             (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT))
439         {
440             TermWriteStream(Console, (PTEXTMODE_SCREEN_BUFFER)(Console->ActiveBuffer), L"\r", 1, TRUE);
441         }
442 
443         /*
444          * Add \n if processed input. There should usually be room for it,
445          * but an exception to the rule exists: the buffer could have been
446          * pre-filled with LineMaxSize - 1 characters.
447          */
448         if ((GetConsoleInputBufferMode(Console) & ENABLE_PROCESSED_INPUT) &&
449             Console->LineSize < Console->LineMaxSize)
450         {
451             Console->LineBuffer[Console->LineSize++] = L'\n';
452             if ((GetType(Console->ActiveBuffer) == TEXTMODE_BUFFER) &&
453                 (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT))
454             {
455                 TermWriteStream(Console, (PTEXTMODE_SCREEN_BUFFER)(Console->ActiveBuffer), L"\n", 1, TRUE);
456             }
457         }
458         Console->LineComplete = TRUE;
459         Console->LinePos = 0;
460     }
461     else if (KeyEvent->uChar.UnicodeChar != L'\0')
462     {
463         if (KeyEvent->uChar.UnicodeChar < 0x20 &&
464             Console->LineWakeupMask & (1 << KeyEvent->uChar.UnicodeChar))
465         {
466             /* Control key client wants to handle itself (e.g. for tab completion) */
467             Console->LineBuffer[Console->LineSize++] = L' ';
468             Console->LineBuffer[Console->LinePos] = KeyEvent->uChar.UnicodeChar;
469             Console->LineComplete = TRUE;
470             Console->LinePos = 0;
471         }
472         else
473         {
474             /* Normal character */
475             BOOL Overstrike = !Console->LineInsertToggle && (Console->LinePos != Console->LineSize);
476             DPRINT("Overstrike = %s\n", Overstrike ? "true" : "false");
477             LineInputEdit(Console, (Overstrike ? 1 : 0), 1, &KeyEvent->uChar.UnicodeChar);
478         }
479     }
480 }
481 
482 
483 /* PUBLIC SERVER APIS *********************************************************/
484 
485 /* EOF */
486