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