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