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