xref: /reactos/win32ss/user/winsrv/consrv/lineinput.c (revision ac43fd2b)
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         for (i = Pos; i < NewSize; i++)
110         {
111             TermWriteStream(Console, ActiveBuffer, &Console->LineBuffer[i], 1, TRUE);
112         }
113         for (; i < Console->LineSize; i++)
114         {
115             TermWriteStream(Console, ActiveBuffer, L" ", 1, TRUE);
116         }
117         Console->LinePos = i;
118     }
119 
120     Console->LineSize = NewSize;
121     LineInputSetPos(Console, Pos + NumToInsert);
122 }
123 
124 static VOID
125 LineInputRecallHistory(PCONSRV_CONSOLE Console,
126                        PUNICODE_STRING ExeName,
127                        INT Offset)
128 {
129 #if 0
130     PHISTORY_BUFFER Hist = HistoryCurrentBuffer(Console, ExeName);
131     UINT Position = 0;
132 
133     if (!Hist || Hist->NumEntries == 0) return;
134 
135     Position = Hist->Position + Offset;
136     Position = min(max(Position, 0), Hist->NumEntries - 1);
137     Hist->Position = Position;
138 
139     LineInputSetPos(Console, 0);
140     LineInputEdit(Console, Console->LineSize,
141                   Hist->Entries[Hist->Position].Length / sizeof(WCHAR),
142                   Hist->Entries[Hist->Position].Buffer);
143 
144 #else
145 
146     UNICODE_STRING Entry;
147 
148     if (!HistoryRecallHistory(Console, ExeName, Offset, &Entry)) return;
149 
150     LineInputSetPos(Console, 0);
151     LineInputEdit(Console, Console->LineSize,
152                   Entry.Length / sizeof(WCHAR),
153                   Entry.Buffer);
154 #endif
155 }
156 
157 
158 // TESTS!!
159 PPOPUP_WINDOW Popup = NULL;
160 
161 PPOPUP_WINDOW
162 HistoryDisplayCurrentHistory(PCONSRV_CONSOLE Console,
163                              PUNICODE_STRING ExeName);
164 
165 VOID
166 LineInputKeyDown(PCONSRV_CONSOLE Console,
167                  PUNICODE_STRING ExeName,
168                  KEY_EVENT_RECORD *KeyEvent)
169 {
170     UINT Pos = Console->LinePos;
171     UNICODE_STRING Entry;
172 
173     /*
174      * First, deal with control keys...
175      */
176 
177     switch (KeyEvent->wVirtualKeyCode)
178     {
179         case VK_ESCAPE:
180         {
181             /* Clear the entire line */
182             LineInputSetPos(Console, 0);
183             LineInputEdit(Console, Console->LineSize, 0, NULL);
184 
185             // TESTS!!
186             if (Popup)
187             {
188                 DestroyPopupWindow(Popup);
189                 Popup = NULL;
190             }
191             return;
192         }
193 
194         case VK_HOME:
195         {
196             /* Move to start of line. With CTRL, erase everything left of cursor. */
197             LineInputSetPos(Console, 0);
198             if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
199                 LineInputEdit(Console, Pos, 0, NULL);
200             return;
201         }
202 
203         case VK_END:
204         {
205             /* Move to end of line. With CTRL, erase everything right of cursor. */
206             if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
207                 LineInputEdit(Console, Console->LineSize - Pos, 0, NULL);
208             else
209                 LineInputSetPos(Console, Console->LineSize);
210             return;
211         }
212 
213         case VK_LEFT:
214         {
215             /* Move to the left. With CTRL, move to beginning of previous word. */
216             if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
217             {
218                 while (Pos > 0 && Console->LineBuffer[Pos - 1] == L' ') Pos--;
219                 while (Pos > 0 && Console->LineBuffer[Pos - 1] != L' ') Pos--;
220             }
221             else
222             {
223                 Pos -= (Pos > 0);
224             }
225             LineInputSetPos(Console, Pos);
226             return;
227         }
228 
229         case VK_RIGHT:
230         case VK_F1:
231         {
232             /* Move to the right. With CTRL, move to beginning of next word. */
233             if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
234             {
235                 while (Pos < Console->LineSize && Console->LineBuffer[Pos] != L' ') Pos++;
236                 while (Pos < Console->LineSize && Console->LineBuffer[Pos] == L' ') Pos++;
237                 LineInputSetPos(Console, Pos);
238             }
239             else
240             {
241                 /* Recall one character (but don't overwrite current line) */
242                 HistoryGetCurrentEntry(Console, ExeName, &Entry);
243                 if (Pos < Console->LineSize)
244                     LineInputSetPos(Console, Pos + 1);
245                 else if (Pos * sizeof(WCHAR) < Entry.Length)
246                     LineInputEdit(Console, 0, 1, &Entry.Buffer[Pos]);
247             }
248             return;
249         }
250 
251         case VK_INSERT:
252         {
253             /* Toggle between insert and overstrike */
254             Console->LineInsertToggle = !Console->LineInsertToggle;
255             TermSetCursorInfo(Console, Console->ActiveBuffer);
256             return;
257         }
258 
259         case VK_DELETE:
260         {
261             /* Remove one character to right of cursor */
262             if (Pos != Console->LineSize)
263                 LineInputEdit(Console, 1, 0, NULL);
264             return;
265         }
266 
267         case VK_PRIOR:
268         {
269             /* Recall the first history entry */
270             LineInputRecallHistory(Console, ExeName, -((WORD)-1));
271             return;
272         }
273 
274         case VK_NEXT:
275         {
276             /* Recall the last history entry */
277             LineInputRecallHistory(Console, ExeName, +((WORD)-1));
278             return;
279         }
280 
281         case VK_UP:
282         case VK_F5:
283         {
284             /*
285              * Recall the previous history entry. On first time, actually recall
286              * the current (usually last) entry; on subsequent times go back.
287              */
288             LineInputRecallHistory(Console, ExeName, Console->LineUpPressed ? -1 : 0);
289             Console->LineUpPressed = TRUE;
290             return;
291         }
292 
293         case VK_DOWN:
294         {
295             /* Recall the next history entry */
296             LineInputRecallHistory(Console, ExeName, +1);
297             return;
298         }
299 
300         case VK_F3:
301         {
302             /* Recall the remainder of the current history entry */
303             HistoryGetCurrentEntry(Console, ExeName, &Entry);
304             if (Pos * sizeof(WCHAR) < Entry.Length)
305             {
306                 UINT InsertSize = (Entry.Length / sizeof(WCHAR) - Pos);
307                 UINT DeleteSize = min(Console->LineSize - Pos, InsertSize);
308                 LineInputEdit(Console, DeleteSize, InsertSize, &Entry.Buffer[Pos]);
309             }
310             return;
311         }
312 
313         case VK_F6:
314         {
315             /* Insert a ^Z character */
316             KeyEvent->uChar.UnicodeChar = 26;
317             break;
318         }
319 
320         case VK_F7:
321         {
322             if (KeyEvent->dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))
323                 HistoryDeleteCurrentBuffer(Console, ExeName);
324             else
325             {
326                 if (Popup) DestroyPopupWindow(Popup);
327                 Popup = HistoryDisplayCurrentHistory(Console, ExeName);
328             }
329             return;
330         }
331 
332         case VK_F8:
333         {
334             UNICODE_STRING EntryFound;
335 
336             Entry.Length = Console->LinePos * sizeof(WCHAR); // == Pos * sizeof(WCHAR)
337             Entry.Buffer = Console->LineBuffer;
338 
339             if (HistoryFindEntryByPrefix(Console, ExeName, &Entry, &EntryFound))
340             {
341                 LineInputEdit(Console, Console->LineSize - Pos,
342                               EntryFound.Length / sizeof(WCHAR) - Pos,
343                               &EntryFound.Buffer[Pos]);
344                 /* Cursor stays where it was */
345                 LineInputSetPos(Console, Pos);
346             }
347 
348             return;
349         }
350 #if 0
351         {
352             PHISTORY_BUFFER Hist;
353             INT HistPos;
354 
355             /* Search for history entries starting with input */
356             Hist = HistoryCurrentBuffer(Console, ExeName);
357             if (!Hist || Hist->NumEntries == 0) return;
358 
359             /*
360              * Like Up/F5, on first time start from current (usually last) entry,
361              * but on subsequent times start at previous entry.
362              */
363             if (Console->LineUpPressed)
364                 Hist->Position = (Hist->Position ? Hist->Position : Hist->NumEntries) - 1;
365             Console->LineUpPressed = TRUE;
366 
367             Entry.Length = Console->LinePos * sizeof(WCHAR); // == Pos * sizeof(WCHAR)
368             Entry.Buffer = Console->LineBuffer;
369 
370             /*
371              * Keep going backwards, even wrapping around to the end,
372              * until we get back to starting point.
373              */
374             HistPos = Hist->Position;
375             do
376             {
377                 if (RtlPrefixUnicodeString(&Entry, &Hist->Entries[HistPos], FALSE))
378                 {
379                     Hist->Position = HistPos;
380                     LineInputEdit(Console, Console->LineSize - Pos,
381                                   Hist->Entries[HistPos].Length / sizeof(WCHAR) - Pos,
382                                   &Hist->Entries[HistPos].Buffer[Pos]);
383                     /* Cursor stays where it was */
384                     LineInputSetPos(Console, Pos);
385                     return;
386                 }
387                 if (--HistPos < 0) HistPos += Hist->NumEntries;
388             } while (HistPos != Hist->Position);
389 
390             return;
391         }
392 #endif
393 
394         return;
395     }
396 
397 
398     /*
399      * OK, we deal with normal keys, we can continue...
400      */
401 
402     if (KeyEvent->uChar.UnicodeChar == L'\b' && (GetConsoleInputBufferMode(Console) & ENABLE_PROCESSED_INPUT))
403     {
404         /*
405          * Backspace handling - if processed input enabled then we handle it
406          * here, otherwise we treat it like a normal character.
407          */
408         if (Pos > 0)
409         {
410             LineInputSetPos(Console, Pos - 1);
411             LineInputEdit(Console, 1, 0, NULL);
412         }
413     }
414     else if (KeyEvent->uChar.UnicodeChar == L'\r')
415     {
416         /*
417          * Only add a history entry if console echo is enabled. This ensures
418          * that anything sent to the console when echo is disabled (e.g.
419          * binary data, or secrets like passwords...) does not remain stored
420          * in memory.
421          */
422         if (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT)
423         {
424             Entry.Length = Entry.MaximumLength = Console->LineSize * sizeof(WCHAR);
425             Entry.Buffer = Console->LineBuffer;
426             HistoryAddEntry(Console, ExeName, &Entry);
427         }
428 
429         /* TODO: Expand aliases */
430         DPRINT1("TODO: Expand aliases\n");
431 
432         LineInputSetPos(Console, Console->LineSize);
433         Console->LineBuffer[Console->LineSize++] = L'\r';
434         if (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT)
435         {
436             if (GetType(Console->ActiveBuffer) == TEXTMODE_BUFFER)
437             {
438                 TermWriteStream(Console, (PTEXTMODE_SCREEN_BUFFER)(Console->ActiveBuffer), L"\r", 1, TRUE);
439             }
440         }
441 
442         /*
443          * Add \n if processed input. There should usually be room for it,
444          * but an exception to the rule exists: the buffer could have been
445          * pre-filled with LineMaxSize - 1 characters.
446          */
447         if ((GetConsoleInputBufferMode(Console) & ENABLE_PROCESSED_INPUT) &&
448             Console->LineSize < Console->LineMaxSize)
449         {
450             Console->LineBuffer[Console->LineSize++] = L'\n';
451             if (GetConsoleInputBufferMode(Console) & ENABLE_ECHO_INPUT)
452             {
453                 if (GetType(Console->ActiveBuffer) == TEXTMODE_BUFFER)
454                 {
455                     TermWriteStream(Console, (PTEXTMODE_SCREEN_BUFFER)(Console->ActiveBuffer), L"\n", 1, TRUE);
456                 }
457             }
458         }
459         Console->LineComplete = TRUE;
460         Console->LinePos = 0;
461     }
462     else if (KeyEvent->uChar.UnicodeChar != L'\0')
463     {
464         if (KeyEvent->uChar.UnicodeChar < 0x20 &&
465             Console->LineWakeupMask & (1 << KeyEvent->uChar.UnicodeChar))
466         {
467             /* Control key client wants to handle itself (e.g. for tab completion) */
468             Console->LineBuffer[Console->LineSize++] = L' ';
469             Console->LineBuffer[Console->LinePos] = KeyEvent->uChar.UnicodeChar;
470             Console->LineComplete = TRUE;
471             Console->LinePos = 0;
472         }
473         else
474         {
475             /* Normal character */
476             BOOL Overstrike = !Console->LineInsertToggle && (Console->LinePos != Console->LineSize);
477             DPRINT("Overstrike = %s\n", Overstrike ? "true" : "false");
478             LineInputEdit(Console, (Overstrike ? 1 : 0), 1, &KeyEvent->uChar.UnicodeChar);
479         }
480     }
481 }
482 
483 
484 /* PUBLIC SERVER APIS *********************************************************/
485 
486 /* EOF */
487