[KERNEL32-CONSRV]
[reactos.git] / win32ss / user / consrv / lineinput.c
1 /*
2 * LICENSE: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Console Server DLL
4 * FILE: win32ss/user/consrv/tuiconsole.c
5 * PURPOSE: Console line input functions
6 * PROGRAMMERS: Jeffrey Morlan
7 */
8
9 /* INCLUDES ******************************************************************/
10
11 #include "consrv.h"
12 #include "conio.h"
13
14 #define NDEBUG
15 #include <debug.h>
16
17 typedef struct tagHISTORY_BUFFER
18 {
19 LIST_ENTRY ListEntry;
20 UINT Position;
21 UINT MaxEntries;
22 UINT NumEntries;
23 PUNICODE_STRING Entries;
24 UNICODE_STRING ExeName;
25 } HISTORY_BUFFER, *PHISTORY_BUFFER;
26
27 /* FUNCTIONS *****************************************************************/
28
29 static PHISTORY_BUFFER
30 HistoryCurrentBuffer(PCSRSS_CONSOLE Console)
31 {
32 /* TODO: use actual EXE name sent from process that called ReadConsole */
33 UNICODE_STRING ExeName = { 14, 14, L"cmd.exe" };
34 PLIST_ENTRY Entry = Console->HistoryBuffers.Flink;
35 PHISTORY_BUFFER Hist;
36
37 for (; Entry != &Console->HistoryBuffers; Entry = Entry->Flink)
38 {
39 Hist = CONTAINING_RECORD(Entry, HISTORY_BUFFER, ListEntry);
40 if (RtlEqualUnicodeString(&ExeName, &Hist->ExeName, FALSE))
41 return Hist;
42 }
43
44 /* Couldn't find the buffer, create a new one */
45 Hist = HeapAlloc(ConSrvHeap, 0, sizeof(HISTORY_BUFFER) + ExeName.Length);
46 if (!Hist)
47 return NULL;
48 Hist->MaxEntries = Console->HistoryBufferSize;
49 Hist->NumEntries = 0;
50 Hist->Entries = HeapAlloc(ConSrvHeap, 0, Hist->MaxEntries * sizeof(UNICODE_STRING));
51 if (!Hist->Entries)
52 {
53 HeapFree(ConSrvHeap, 0, Hist);
54 return NULL;
55 }
56 Hist->ExeName.Length = Hist->ExeName.MaximumLength = ExeName.Length;
57 Hist->ExeName.Buffer = (PWCHAR)(Hist + 1);
58 memcpy(Hist->ExeName.Buffer, ExeName.Buffer, ExeName.Length);
59 InsertHeadList(&Console->HistoryBuffers, &Hist->ListEntry);
60 return Hist;
61 }
62
63 static VOID
64 HistoryAddEntry(PCSRSS_CONSOLE Console)
65 {
66 UNICODE_STRING NewEntry;
67 PHISTORY_BUFFER Hist;
68 INT i;
69
70 NewEntry.Length = NewEntry.MaximumLength = Console->LineSize * sizeof(WCHAR);
71 NewEntry.Buffer = Console->LineBuffer;
72
73 if (!(Hist = HistoryCurrentBuffer(Console)))
74 return;
75
76 /* Don't add blank or duplicate entries */
77 if (NewEntry.Length == 0 || Hist->MaxEntries == 0 ||
78 (Hist->NumEntries > 0 &&
79 RtlEqualUnicodeString(&Hist->Entries[Hist->NumEntries - 1], &NewEntry, FALSE)))
80 {
81 return;
82 }
83
84 if (Console->HistoryNoDup)
85 {
86 /* Check if this line has been entered before */
87 for (i = Hist->NumEntries - 1; i >= 0; i--)
88 {
89 if (RtlEqualUnicodeString(&Hist->Entries[i], &NewEntry, FALSE))
90 {
91 /* Just rotate the list to bring this entry to the end */
92 NewEntry = Hist->Entries[i];
93 memmove(&Hist->Entries[i], &Hist->Entries[i + 1],
94 (Hist->NumEntries - (i + 1)) * sizeof(UNICODE_STRING));
95 Hist->Entries[Hist->NumEntries - 1] = NewEntry;
96 Hist->Position = Hist->NumEntries - 1;
97 return;
98 }
99 }
100 }
101
102 if (Hist->NumEntries == Hist->MaxEntries)
103 {
104 /* List is full, remove oldest entry */
105 RtlFreeUnicodeString(&Hist->Entries[0]);
106 memmove(&Hist->Entries[0], &Hist->Entries[1],
107 --Hist->NumEntries * sizeof(UNICODE_STRING));
108 }
109
110 if (NT_SUCCESS(RtlDuplicateUnicodeString(0, &NewEntry, &Hist->Entries[Hist->NumEntries])))
111 Hist->NumEntries++;
112 Hist->Position = Hist->NumEntries - 1;
113 }
114
115 static VOID
116 HistoryGetCurrentEntry(PCSRSS_CONSOLE Console, PUNICODE_STRING Entry)
117 {
118 PHISTORY_BUFFER Hist;
119 if (!(Hist = HistoryCurrentBuffer(Console)) || Hist->NumEntries == 0)
120 Entry->Length = 0;
121 else
122 *Entry = Hist->Entries[Hist->Position];
123 }
124
125 static PHISTORY_BUFFER
126 HistoryFindBuffer(PCSRSS_CONSOLE Console, PUNICODE_STRING ExeName)
127 {
128 PLIST_ENTRY Entry = Console->HistoryBuffers.Flink;
129 while (Entry != &Console->HistoryBuffers)
130 {
131 /* For the history APIs, the caller is allowed to give only part of the name */
132 PHISTORY_BUFFER Hist = CONTAINING_RECORD(Entry, HISTORY_BUFFER, ListEntry);
133 if (RtlPrefixUnicodeString(ExeName, &Hist->ExeName, TRUE))
134 return Hist;
135 Entry = Entry->Flink;
136 }
137 return NULL;
138 }
139
140 VOID FASTCALL
141 HistoryDeleteBuffer(PHISTORY_BUFFER Hist)
142 {
143 if (!Hist)
144 return;
145 while (Hist->NumEntries != 0)
146 RtlFreeUnicodeString(&Hist->Entries[--Hist->NumEntries]);
147 HeapFree(ConSrvHeap, 0, Hist->Entries);
148 RemoveEntryList(&Hist->ListEntry);
149 HeapFree(ConSrvHeap, 0, Hist);
150 }
151
152 CSR_API(SrvGetConsoleCommandHistoryLength)
153 {
154 PCONSOLE_GETCOMMANDHISTORYLENGTH GetCommandHistoryLengthRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.GetCommandHistoryLengthRequest;
155 PCONSOLE_PROCESS_DATA ProcessData = ConsoleGetPerProcessData(CsrGetClientThread()->Process);
156 PCSRSS_CONSOLE Console;
157 NTSTATUS Status;
158 PHISTORY_BUFFER Hist;
159 ULONG Length = 0;
160 INT i;
161
162 if (!CsrValidateMessageBuffer(ApiMessage,
163 (PVOID*)&GetCommandHistoryLengthRequest->ExeName.Buffer,
164 GetCommandHistoryLengthRequest->ExeName.Length,
165 sizeof(BYTE)))
166 {
167 return STATUS_INVALID_PARAMETER;
168 }
169
170 Status = ConioConsoleFromProcessData(ProcessData, &Console);
171 if (NT_SUCCESS(Status))
172 {
173 Hist = HistoryFindBuffer(Console, &GetCommandHistoryLengthRequest->ExeName);
174 if (Hist)
175 {
176 for (i = 0; i < Hist->NumEntries; i++)
177 Length += Hist->Entries[i].Length + sizeof(WCHAR);
178 }
179 GetCommandHistoryLengthRequest->Length = Length;
180 ConioUnlockConsole(Console);
181 }
182 return Status;
183 }
184
185 CSR_API(SrvGetConsoleCommandHistory)
186 {
187 PCONSOLE_GETCOMMANDHISTORY GetCommandHistoryRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.GetCommandHistoryRequest;
188 PCONSOLE_PROCESS_DATA ProcessData = ConsoleGetPerProcessData(CsrGetClientThread()->Process);
189 PCSRSS_CONSOLE Console;
190 NTSTATUS Status;
191 PHISTORY_BUFFER Hist;
192 PBYTE Buffer = (PBYTE)GetCommandHistoryRequest->History;
193 ULONG BufferSize = GetCommandHistoryRequest->Length;
194 INT i;
195
196 if ( !CsrValidateMessageBuffer(ApiMessage,
197 (PVOID*)&GetCommandHistoryRequest->History,
198 GetCommandHistoryRequest->Length,
199 sizeof(BYTE)) ||
200 !CsrValidateMessageBuffer(ApiMessage,
201 (PVOID*)&GetCommandHistoryRequest->ExeName.Buffer,
202 GetCommandHistoryRequest->ExeName.Length,
203 sizeof(BYTE)) )
204 {
205 return STATUS_INVALID_PARAMETER;
206 }
207
208 Status = ConioConsoleFromProcessData(ProcessData, &Console);
209 if (NT_SUCCESS(Status))
210 {
211 Hist = HistoryFindBuffer(Console, &GetCommandHistoryRequest->ExeName);
212 if (Hist)
213 {
214 for (i = 0; i < Hist->NumEntries; i++)
215 {
216 if (BufferSize < (Hist->Entries[i].Length + sizeof(WCHAR)))
217 {
218 Status = STATUS_BUFFER_OVERFLOW;
219 break;
220 }
221 memcpy(Buffer, Hist->Entries[i].Buffer, Hist->Entries[i].Length);
222 Buffer += Hist->Entries[i].Length;
223 *(PWCHAR)Buffer = L'\0';
224 Buffer += sizeof(WCHAR);
225 }
226 }
227 GetCommandHistoryRequest->Length = Buffer - (PBYTE)GetCommandHistoryRequest->History;
228 ConioUnlockConsole(Console);
229 }
230 return Status;
231 }
232
233 CSR_API(SrvExpungeConsoleCommandHistory)
234 {
235 PCONSOLE_EXPUNGECOMMANDHISTORY ExpungeCommandHistoryRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.ExpungeCommandHistoryRequest;
236 PCONSOLE_PROCESS_DATA ProcessData = ConsoleGetPerProcessData(CsrGetClientThread()->Process);
237 PCSRSS_CONSOLE Console;
238 PHISTORY_BUFFER Hist;
239 NTSTATUS Status;
240
241 if (!CsrValidateMessageBuffer(ApiMessage,
242 (PVOID*)&ExpungeCommandHistoryRequest->ExeName.Buffer,
243 ExpungeCommandHistoryRequest->ExeName.Length,
244 sizeof(BYTE)))
245 {
246 return STATUS_INVALID_PARAMETER;
247 }
248
249 Status = ConioConsoleFromProcessData(ProcessData, &Console);
250 if (NT_SUCCESS(Status))
251 {
252 Hist = HistoryFindBuffer(Console, &ExpungeCommandHistoryRequest->ExeName);
253 HistoryDeleteBuffer(Hist);
254 ConioUnlockConsole(Console);
255 }
256 return Status;
257 }
258
259 CSR_API(SrvSetConsoleNumberOfCommands)
260 {
261 PCONSOLE_SETHISTORYNUMBERCOMMANDS SetHistoryNumberCommandsRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.SetHistoryNumberCommandsRequest;
262 PCONSOLE_PROCESS_DATA ProcessData = ConsoleGetPerProcessData(CsrGetClientThread()->Process);
263 PCSRSS_CONSOLE Console;
264 PHISTORY_BUFFER Hist;
265 NTSTATUS Status;
266 UINT MaxEntries = SetHistoryNumberCommandsRequest->NumCommands;
267 PUNICODE_STRING OldEntryList, NewEntryList;
268
269 if (!CsrValidateMessageBuffer(ApiMessage,
270 (PVOID*)&SetHistoryNumberCommandsRequest->ExeName.Buffer,
271 SetHistoryNumberCommandsRequest->ExeName.Length,
272 sizeof(BYTE)))
273 {
274 return STATUS_INVALID_PARAMETER;
275 }
276
277 Status = ConioConsoleFromProcessData(ProcessData, &Console);
278 if (NT_SUCCESS(Status))
279 {
280 Hist = HistoryFindBuffer(Console, &SetHistoryNumberCommandsRequest->ExeName);
281 if (Hist)
282 {
283 OldEntryList = Hist->Entries;
284 NewEntryList = HeapAlloc(ConSrvHeap, 0,
285 MaxEntries * sizeof(UNICODE_STRING));
286 if (!NewEntryList)
287 {
288 Status = STATUS_NO_MEMORY;
289 }
290 else
291 {
292 /* If necessary, shrink by removing oldest entries */
293 for (; Hist->NumEntries > MaxEntries; Hist->NumEntries--)
294 {
295 RtlFreeUnicodeString(Hist->Entries++);
296 Hist->Position += (Hist->Position == 0);
297 }
298
299 Hist->MaxEntries = MaxEntries;
300 Hist->Entries = memcpy(NewEntryList, Hist->Entries,
301 Hist->NumEntries * sizeof(UNICODE_STRING));
302 HeapFree(ConSrvHeap, 0, OldEntryList);
303 }
304 }
305 ConioUnlockConsole(Console);
306 }
307 return Status;
308 }
309
310 CSR_API(SrvGetConsoleHistory)
311 {
312 PCONSOLE_GETSETHISTORYINFO HistoryInfoRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.HistoryInfoRequest;
313 PCSRSS_CONSOLE Console;
314 NTSTATUS Status = ConioConsoleFromProcessData(ConsoleGetPerProcessData(CsrGetClientThread()->Process), &Console);
315 if (NT_SUCCESS(Status))
316 {
317 HistoryInfoRequest->HistoryBufferSize = Console->HistoryBufferSize;
318 HistoryInfoRequest->NumberOfHistoryBuffers = Console->NumberOfHistoryBuffers;
319 HistoryInfoRequest->dwFlags = Console->HistoryNoDup;
320 ConioUnlockConsole(Console);
321 }
322 return Status;
323 }
324
325 CSR_API(SrvSetConsoleHistory)
326 {
327 PCONSOLE_GETSETHISTORYINFO HistoryInfoRequest = &((PCONSOLE_API_MESSAGE)ApiMessage)->Data.HistoryInfoRequest;
328 PCSRSS_CONSOLE Console;
329 NTSTATUS Status = ConioConsoleFromProcessData(ConsoleGetPerProcessData(CsrGetClientThread()->Process), &Console);
330 if (NT_SUCCESS(Status))
331 {
332 Console->HistoryBufferSize = HistoryInfoRequest->HistoryBufferSize;
333 Console->NumberOfHistoryBuffers = HistoryInfoRequest->NumberOfHistoryBuffers;
334 Console->HistoryNoDup = HistoryInfoRequest->dwFlags & HISTORY_NO_DUP_FLAG;
335 ConioUnlockConsole(Console);
336 }
337 return Status;
338 }
339
340 static VOID
341 LineInputSetPos(PCSRSS_CONSOLE Console, UINT Pos)
342 {
343 if (Pos != Console->LinePos && Console->Mode & ENABLE_ECHO_INPUT)
344 {
345 PCSRSS_SCREEN_BUFFER Buffer = Console->ActiveBuffer;
346 UINT OldCursorX = Buffer->CurrentX;
347 UINT OldCursorY = Buffer->CurrentY;
348 INT XY = OldCursorY * Buffer->MaxX + OldCursorX;
349
350 XY += (Pos - Console->LinePos);
351 if (XY < 0)
352 XY = 0;
353 else if (XY >= Buffer->MaxY * Buffer->MaxX)
354 XY = Buffer->MaxY * Buffer->MaxX - 1;
355
356 Buffer->CurrentX = XY % Buffer->MaxX;
357 Buffer->CurrentY = XY / Buffer->MaxX;
358 ConioSetScreenInfo(Console, Buffer, OldCursorX, OldCursorY);
359 }
360
361 Console->LinePos = Pos;
362 }
363
364 static VOID
365 LineInputEdit(PCSRSS_CONSOLE Console, UINT NumToDelete, UINT NumToInsert, WCHAR *Insertion)
366 {
367 UINT Pos = Console->LinePos;
368 UINT NewSize = Console->LineSize - NumToDelete + NumToInsert;
369 INT i;
370
371 /* Make sure there's always enough room for ending \r\n */
372 if (NewSize + 2 > Console->LineMaxSize)
373 return;
374
375 memmove(&Console->LineBuffer[Pos + NumToInsert],
376 &Console->LineBuffer[Pos + NumToDelete],
377 (Console->LineSize - (Pos + NumToDelete)) * sizeof(WCHAR));
378 memcpy(&Console->LineBuffer[Pos], Insertion, NumToInsert * sizeof(WCHAR));
379
380 if (Console->Mode & ENABLE_ECHO_INPUT)
381 {
382 for (i = Pos; i < NewSize; i++)
383 {
384 CHAR AsciiChar;
385 WideCharToMultiByte(Console->OutputCodePage, 0,
386 &Console->LineBuffer[i], 1,
387 &AsciiChar, 1, NULL, NULL);
388 ConioWriteConsole(Console, Console->ActiveBuffer, &AsciiChar, 1, TRUE);
389 }
390 for (; i < Console->LineSize; i++)
391 {
392 ConioWriteConsole(Console, Console->ActiveBuffer, " ", 1, TRUE);
393 }
394 Console->LinePos = i;
395 }
396
397 Console->LineSize = NewSize;
398 LineInputSetPos(Console, Pos + NumToInsert);
399 }
400
401 static VOID
402 LineInputRecallHistory(PCSRSS_CONSOLE Console, INT Offset)
403 {
404 PHISTORY_BUFFER Hist;
405
406 if (!(Hist = HistoryCurrentBuffer(Console)) || Hist->NumEntries == 0)
407 return;
408
409 Offset += Hist->Position;
410 Offset = max(Offset, 0);
411 Offset = min(Offset, Hist->NumEntries - 1);
412 Hist->Position = Offset;
413
414 LineInputSetPos(Console, 0);
415 LineInputEdit(Console, Console->LineSize,
416 Hist->Entries[Offset].Length / sizeof(WCHAR),
417 Hist->Entries[Offset].Buffer);
418 }
419
420 VOID FASTCALL
421 LineInputKeyDown(PCSRSS_CONSOLE Console, KEY_EVENT_RECORD *KeyEvent)
422 {
423 UINT Pos = Console->LinePos;
424 PHISTORY_BUFFER Hist;
425 UNICODE_STRING Entry;
426 INT HistPos;
427
428 switch (KeyEvent->wVirtualKeyCode)
429 {
430 case VK_ESCAPE:
431 /* Clear entire line */
432 LineInputSetPos(Console, 0);
433 LineInputEdit(Console, Console->LineSize, 0, NULL);
434 return;
435 case VK_HOME:
436 /* Move to start of line. With ctrl, erase everything left of cursor */
437 LineInputSetPos(Console, 0);
438 if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
439 LineInputEdit(Console, Pos, 0, NULL);
440 return;
441 case VK_END:
442 /* Move to end of line. With ctrl, erase everything right of cursor */
443 if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
444 LineInputEdit(Console, Console->LineSize - Pos, 0, NULL);
445 else
446 LineInputSetPos(Console, Console->LineSize);
447 return;
448 case VK_LEFT:
449 /* Move left. With ctrl, move to beginning of previous word */
450 if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
451 {
452 while (Pos > 0 && Console->LineBuffer[Pos - 1] == L' ') Pos--;
453 while (Pos > 0 && Console->LineBuffer[Pos - 1] != L' ') Pos--;
454 }
455 else
456 {
457 Pos -= (Pos > 0);
458 }
459 LineInputSetPos(Console, Pos);
460 return;
461 case VK_RIGHT:
462 case VK_F1:
463 /* Move right. With ctrl, move to beginning of next word */
464 if (KeyEvent->dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
465 {
466 while (Pos < Console->LineSize && Console->LineBuffer[Pos] != L' ') Pos++;
467 while (Pos < Console->LineSize && Console->LineBuffer[Pos] == L' ') Pos++;
468 LineInputSetPos(Console, Pos);
469 return;
470 }
471 else
472 {
473 /* Recall one character (but don't overwrite current line) */
474 HistoryGetCurrentEntry(Console, &Entry);
475 if (Pos < Console->LineSize)
476 LineInputSetPos(Console, Pos + 1);
477 else if (Pos * sizeof(WCHAR) < Entry.Length)
478 LineInputEdit(Console, 0, 1, &Entry.Buffer[Pos]);
479 }
480 return;
481 case VK_INSERT:
482 /* Toggle between insert and overstrike */
483 Console->LineInsertToggle = !Console->LineInsertToggle;
484 ConioSetCursorInfo(Console, Console->ActiveBuffer);
485 return;
486 case VK_DELETE:
487 /* Remove character to right of cursor */
488 if (Pos != Console->LineSize)
489 LineInputEdit(Console, 1, 0, NULL);
490 return;
491 case VK_PRIOR:
492 /* Recall first history entry */
493 LineInputRecallHistory(Console, -((WORD)-1));
494 return;
495 case VK_NEXT:
496 /* Recall last history entry */
497 LineInputRecallHistory(Console, +((WORD)-1));
498 return;
499 case VK_UP:
500 case VK_F5:
501 /* Recall previous history entry. On first time, actually recall the
502 * current (usually last) entry; on subsequent times go back. */
503 LineInputRecallHistory(Console, Console->LineUpPressed ? -1 : 0);
504 Console->LineUpPressed = TRUE;
505 return;
506 case VK_DOWN:
507 /* Recall next history entry */
508 LineInputRecallHistory(Console, +1);
509 return;
510 case VK_F3:
511 /* Recall remainder of current history entry */
512 HistoryGetCurrentEntry(Console, &Entry);
513 if (Pos * sizeof(WCHAR) < Entry.Length)
514 {
515 UINT InsertSize = (Entry.Length / sizeof(WCHAR) - Pos);
516 UINT DeleteSize = min(Console->LineSize - Pos, InsertSize);
517 LineInputEdit(Console, DeleteSize, InsertSize, &Entry.Buffer[Pos]);
518 }
519 return;
520 case VK_F6:
521 /* Insert a ^Z character */
522 KeyEvent->uChar.UnicodeChar = 26;
523 break;
524 case VK_F7:
525 if (KeyEvent->dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))
526 HistoryDeleteBuffer(HistoryCurrentBuffer(Console));
527 return;
528 case VK_F8:
529 /* Search for history entries starting with input. */
530 if (!(Hist = HistoryCurrentBuffer(Console)) || Hist->NumEntries == 0)
531 return;
532
533 /* Like Up/F5, on first time start from current (usually last) entry,
534 * but on subsequent times start at previous entry. */
535 if (Console->LineUpPressed)
536 Hist->Position = (Hist->Position ? Hist->Position : Hist->NumEntries) - 1;
537 Console->LineUpPressed = TRUE;
538
539 Entry.Length = Console->LinePos * sizeof(WCHAR);
540 Entry.Buffer = Console->LineBuffer;
541
542 /* Keep going backwards, even wrapping around to the end,
543 * until we get back to starting point */
544 HistPos = Hist->Position;
545 do
546 {
547 if (RtlPrefixUnicodeString(&Entry, &Hist->Entries[HistPos], FALSE))
548 {
549 Hist->Position = HistPos;
550 LineInputEdit(Console, Console->LineSize - Pos,
551 Hist->Entries[HistPos].Length / sizeof(WCHAR) - Pos,
552 &Hist->Entries[HistPos].Buffer[Pos]);
553 /* Cursor stays where it was */
554 LineInputSetPos(Console, Pos);
555 return;
556 }
557 if (--HistPos < 0) HistPos += Hist->NumEntries;
558 } while (HistPos != Hist->Position);
559 return;
560 }
561
562 if (KeyEvent->uChar.UnicodeChar == L'\b' && Console->Mode & ENABLE_PROCESSED_INPUT)
563 {
564 /* backspace handling - if processed input enabled then we handle it here
565 * otherwise we treat it like a normal char. */
566 if (Pos > 0)
567 {
568 LineInputSetPos(Console, Pos - 1);
569 LineInputEdit(Console, 1, 0, NULL);
570 }
571 }
572 else if (KeyEvent->uChar.UnicodeChar == L'\r')
573 {
574 HistoryAddEntry(Console);
575
576 /* TODO: Expand aliases */
577
578 LineInputSetPos(Console, Console->LineSize);
579 Console->LineBuffer[Console->LineSize++] = L'\r';
580 if (Console->Mode & ENABLE_ECHO_INPUT)
581 ConioWriteConsole(Console, Console->ActiveBuffer, "\r", 1, TRUE);
582
583 /* Add \n if processed input. There should usually be room for it,
584 * but an exception to the rule exists: the buffer could have been
585 * pre-filled with LineMaxSize - 1 characters. */
586 if (Console->Mode & ENABLE_PROCESSED_INPUT &&
587 Console->LineSize < Console->LineMaxSize)
588 {
589 Console->LineBuffer[Console->LineSize++] = L'\n';
590 if (Console->Mode & ENABLE_ECHO_INPUT)
591 ConioWriteConsole(Console, Console->ActiveBuffer, "\n", 1, TRUE);
592 }
593 Console->LineComplete = TRUE;
594 Console->LinePos = 0;
595 }
596 else if (KeyEvent->uChar.UnicodeChar != L'\0')
597 {
598 if (KeyEvent->uChar.UnicodeChar < 0x20 &&
599 Console->LineWakeupMask & (1 << KeyEvent->uChar.UnicodeChar))
600 {
601 /* Control key client wants to handle itself (e.g. for tab completion) */
602 Console->LineBuffer[Console->LineSize++] = L' ';
603 Console->LineBuffer[Console->LinePos] = KeyEvent->uChar.UnicodeChar;
604 Console->LineComplete = TRUE;
605 Console->LinePos = 0;
606 }
607 else
608 {
609 /* Normal character */
610 BOOL Overstrike = Console->LineInsertToggle && Console->LinePos != Console->LineSize;
611 LineInputEdit(Console, Overstrike, 1, &KeyEvent->uChar.UnicodeChar);
612 }
613 }
614 }
615
616 /* EOF */