Sync to trunk revision 63857.
[reactos.git] / win32ss / user / winsrv / consrv / condrv / coninput.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS Console Driver DLL
4 * FILE: win32ss/user/winsrv/consrv/condrv/coninput.c
5 * PURPOSE: Console Input functions
6 * PROGRAMMERS: Jeffrey Morlan
7 * Hermes Belusca-Maito (hermes.belusca@sfr.fr)
8 */
9
10 /* INCLUDES *******************************************************************/
11
12 #include <consrv.h>
13
14 #define NDEBUG
15 #include <debug.h>
16
17 /* GLOBALS ********************************************************************/
18
19 /*
20 * From MSDN:
21 * "The lpMultiByteStr and lpWideCharStr pointers must not be the same.
22 * If they are the same, the function fails, and GetLastError returns
23 * ERROR_INVALID_PARAMETER."
24 */
25 #define ConsoleInputUnicodeCharToAnsiChar(Console, dChar, sWChar) \
26 ASSERT((ULONG_PTR)dChar != (ULONG_PTR)sWChar); \
27 WideCharToMultiByte((Console)->InputCodePage, 0, (sWChar), 1, (dChar), 1, NULL, NULL)
28
29 #define ConsoleInputAnsiCharToUnicodeChar(Console, dWChar, sChar) \
30 ASSERT((ULONG_PTR)dWChar != (ULONG_PTR)sChar); \
31 MultiByteToWideChar((Console)->InputCodePage, 0, (sChar), 1, (dWChar), 1)
32
33 typedef struct ConsoleInput_t
34 {
35 LIST_ENTRY ListEntry;
36 INPUT_RECORD InputEvent;
37 } ConsoleInput;
38
39
40 /* PRIVATE FUNCTIONS **********************************************************/
41
42 static VOID
43 ConioInputEventToAnsi(PCONSOLE Console, PINPUT_RECORD InputEvent)
44 {
45 if (InputEvent->EventType == KEY_EVENT)
46 {
47 WCHAR UnicodeChar = InputEvent->Event.KeyEvent.uChar.UnicodeChar;
48 InputEvent->Event.KeyEvent.uChar.UnicodeChar = 0;
49 ConsoleInputUnicodeCharToAnsiChar(Console,
50 &InputEvent->Event.KeyEvent.uChar.AsciiChar,
51 &UnicodeChar);
52 }
53 }
54
55 static VOID
56 ConioInputEventToUnicode(PCONSOLE Console, PINPUT_RECORD InputEvent)
57 {
58 if (InputEvent->EventType == KEY_EVENT)
59 {
60 CHAR AsciiChar = InputEvent->Event.KeyEvent.uChar.AsciiChar;
61 InputEvent->Event.KeyEvent.uChar.AsciiChar = 0;
62 ConsoleInputAnsiCharToUnicodeChar(Console,
63 &InputEvent->Event.KeyEvent.uChar.UnicodeChar,
64 &AsciiChar);
65 }
66 }
67
68
69 NTSTATUS
70 ConDrvAddInputEvents(PCONSOLE Console,
71 PINPUT_RECORD InputRecords, // InputEvent
72 ULONG NumEventsToWrite,
73 PULONG NumEventsWritten,
74 BOOLEAN AppendToEnd)
75 {
76 NTSTATUS Status = STATUS_SUCCESS;
77 ULONG i = 0;
78 BOOLEAN SetWaitEvent = FALSE;
79
80 if (NumEventsWritten) *NumEventsWritten = 0;
81
82 /*
83 * When adding many single events, in the case of repeated mouse move or
84 * key down events, we try to coalesce them so that we do not saturate
85 * too quickly the input buffer.
86 */
87 if (NumEventsToWrite == 1 && !IsListEmpty(&Console->InputBuffer.InputEvents))
88 {
89 PINPUT_RECORD InputRecord = InputRecords; // Only one element
90 PINPUT_RECORD LastInputRecord;
91 ConsoleInput* ConInRec; // Input
92
93 /* Get the "next" event of the input buffer */
94 if (AppendToEnd)
95 {
96 /* Get the tail element */
97 ConInRec = CONTAINING_RECORD(Console->InputBuffer.InputEvents.Blink,
98 ConsoleInput, ListEntry);
99 }
100 else
101 {
102 /* Get the head element */
103 ConInRec = CONTAINING_RECORD(Console->InputBuffer.InputEvents.Flink,
104 ConsoleInput, ListEntry);
105 }
106 LastInputRecord = &ConInRec->InputEvent;
107
108 if (InputRecord->EventType == MOUSE_EVENT &&
109 InputRecord->Event.MouseEvent.dwEventFlags == MOUSE_MOVED)
110 {
111 if (LastInputRecord->EventType == MOUSE_EVENT &&
112 LastInputRecord->Event.MouseEvent.dwEventFlags == MOUSE_MOVED)
113 {
114 /* Update the mouse position */
115 LastInputRecord->Event.MouseEvent.dwMousePosition.X =
116 InputRecord->Event.MouseEvent.dwMousePosition.X;
117 LastInputRecord->Event.MouseEvent.dwMousePosition.Y =
118 InputRecord->Event.MouseEvent.dwMousePosition.Y;
119
120 i = 1;
121 // return STATUS_SUCCESS;
122 Status = STATUS_SUCCESS;
123 }
124 }
125 else if (InputRecord->EventType == KEY_EVENT &&
126 InputRecord->Event.KeyEvent.bKeyDown)
127 {
128 if (LastInputRecord->EventType == KEY_EVENT &&
129 LastInputRecord->Event.KeyEvent.bKeyDown &&
130 (LastInputRecord->Event.KeyEvent.wVirtualScanCode == // Same scancode
131 InputRecord->Event.KeyEvent.wVirtualScanCode) &&
132 (LastInputRecord->Event.KeyEvent.uChar.UnicodeChar == // Same character
133 InputRecord->Event.KeyEvent.uChar.UnicodeChar) &&
134 (LastInputRecord->Event.KeyEvent.dwControlKeyState == // Same Ctrl/Alt/Shift state
135 InputRecord->Event.KeyEvent.dwControlKeyState) )
136 {
137 /* Update the repeat count */
138 LastInputRecord->Event.KeyEvent.wRepeatCount +=
139 InputRecord->Event.KeyEvent.wRepeatCount;
140
141 i = 1;
142 // return STATUS_SUCCESS;
143 Status = STATUS_SUCCESS;
144 }
145 }
146 }
147
148 /* If we coalesced the only one element, we can quit */
149 if (i == 1 && Status == STATUS_SUCCESS /* && NumEventsToWrite == 1 */)
150 goto Done;
151
152 /*
153 * No event coalesced, add them in the usual way.
154 */
155
156 if (AppendToEnd)
157 {
158 /* Go to the beginning of the list */
159 // InputRecords = InputRecords;
160 }
161 else
162 {
163 /* Go to the end of the list */
164 InputRecords = &InputRecords[NumEventsToWrite - 1];
165 }
166
167 /* Set the event if the list is going to be non-empty */
168 if (IsListEmpty(&Console->InputBuffer.InputEvents))
169 SetWaitEvent = TRUE;
170
171 for (i = 0; i < NumEventsToWrite && NT_SUCCESS(Status); ++i)
172 {
173 PINPUT_RECORD InputRecord;
174 ConsoleInput* ConInRec;
175
176 if (AppendToEnd)
177 {
178 /* Select the event and go to the next one */
179 InputRecord = InputRecords++;
180 }
181 else
182 {
183 /* Select the event and go to the previous one */
184 InputRecord = InputRecords--;
185 }
186
187 /* Add event to the queue */
188 ConInRec = ConsoleAllocHeap(0, sizeof(ConsoleInput));
189 if (ConInRec == NULL)
190 {
191 // return STATUS_INSUFFICIENT_RESOURCES;
192 Status = STATUS_INSUFFICIENT_RESOURCES;
193 continue;
194 }
195
196 ConInRec->InputEvent = *InputRecord;
197
198 if (AppendToEnd)
199 {
200 /* Append the event to the end of the queue */
201 InsertTailList(&Console->InputBuffer.InputEvents, &ConInRec->ListEntry);
202 }
203 else
204 {
205 /* Append the event to the beginning of the queue */
206 InsertHeadList(&Console->InputBuffer.InputEvents, &ConInRec->ListEntry);
207 }
208
209 // return STATUS_SUCCESS;
210 Status = STATUS_SUCCESS;
211 }
212
213 if (SetWaitEvent) SetEvent(Console->InputBuffer.ActiveEvent);
214
215 Done:
216 if (NumEventsWritten) *NumEventsWritten = i;
217
218 return Status;
219 }
220
221
222 ULONG
223 PreprocessInput(PCONSOLE Console,
224 PINPUT_RECORD InputEvent,
225 ULONG NumEventsToWrite);
226 VOID
227 PostprocessInput(PCONSOLE Console);
228
229 NTSTATUS
230 ConioAddInputEvents(PCONSOLE Console,
231 PINPUT_RECORD InputRecords, // InputEvent
232 ULONG NumEventsToWrite,
233 PULONG NumEventsWritten,
234 BOOLEAN AppendToEnd)
235 {
236 NTSTATUS Status = STATUS_SUCCESS;
237
238 if (NumEventsWritten) *NumEventsWritten = 0;
239
240 /*
241 * This pre-processing code MUST be IN consrv ONLY!!
242 */
243 NumEventsToWrite = PreprocessInput(Console, InputRecords, NumEventsToWrite);
244 if (NumEventsToWrite == 0) return STATUS_SUCCESS;
245
246 Status = ConDrvAddInputEvents(Console,
247 InputRecords,
248 NumEventsToWrite,
249 NumEventsWritten,
250 AppendToEnd);
251
252 /*
253 * This post-processing code MUST be IN consrv ONLY!!
254 */
255 // if (NT_SUCCESS(Status))
256 if (Status == STATUS_SUCCESS) PostprocessInput(Console);
257
258 return Status;
259 }
260
261 /* Move elsewhere...*/
262 NTSTATUS
263 ConioProcessInputEvent(PCONSOLE Console,
264 PINPUT_RECORD InputEvent)
265 {
266 ULONG NumEventsWritten;
267 return ConioAddInputEvents(Console,
268 InputEvent,
269 1,
270 &NumEventsWritten,
271 TRUE);
272 }
273
274
275 VOID
276 PurgeInputBuffer(PCONSOLE Console)
277 {
278 PLIST_ENTRY CurrentEntry;
279 ConsoleInput* Event;
280
281 while (!IsListEmpty(&Console->InputBuffer.InputEvents))
282 {
283 CurrentEntry = RemoveHeadList(&Console->InputBuffer.InputEvents);
284 Event = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
285 ConsoleFreeHeap(Event);
286 }
287
288 CloseHandle(Console->InputBuffer.ActiveEvent);
289 }
290
291
292 /* PUBLIC DRIVER APIS *********************************************************/
293
294 NTSTATUS NTAPI
295 ConDrvReadConsole(IN PCONSOLE Console,
296 IN PCONSOLE_INPUT_BUFFER InputBuffer,
297 /**/IN PUNICODE_STRING ExeName /**/OPTIONAL/**/,/**/
298 IN BOOLEAN Unicode,
299 OUT PVOID Buffer,
300 IN OUT PCONSOLE_READCONSOLE_CONTROL ReadControl,
301 IN ULONG NumCharsToRead,
302 OUT PULONG NumCharsRead OPTIONAL)
303 {
304 // STATUS_PENDING : Wait if more to read ; STATUS_SUCCESS : Don't wait.
305 NTSTATUS Status = STATUS_PENDING;
306 PLIST_ENTRY CurrentEntry;
307 ConsoleInput *Input;
308 ULONG i;
309
310 if (Console == NULL || InputBuffer == NULL || /* Buffer == NULL || */
311 ReadControl == NULL || ReadControl->nLength != sizeof(CONSOLE_READCONSOLE_CONTROL))
312 {
313 return STATUS_INVALID_PARAMETER;
314 }
315
316 /* Validity checks */
317 ASSERT(Console == InputBuffer->Header.Console);
318 ASSERT((Buffer != NULL) || (Buffer == NULL && NumCharsToRead == 0));
319
320 /* We haven't read anything (yet) */
321
322 i = ReadControl->nInitialChars;
323
324 if (InputBuffer->Mode & ENABLE_LINE_INPUT)
325 {
326 if (Console->LineBuffer == NULL)
327 {
328 /* Starting a new line */
329 Console->LineMaxSize = max(256, NumCharsToRead);
330
331 Console->LineBuffer = ConsoleAllocHeap(0, Console->LineMaxSize * sizeof(WCHAR));
332 if (Console->LineBuffer == NULL) return STATUS_NO_MEMORY;
333
334 Console->LinePos = Console->LineSize = ReadControl->nInitialChars;
335 Console->LineComplete = Console->LineUpPressed = FALSE;
336 Console->LineInsertToggle = Console->InsertMode;
337 Console->LineWakeupMask = ReadControl->dwCtrlWakeupMask;
338
339 /*
340 * Pre-filling the buffer is only allowed in the Unicode API,
341 * so we don't need to worry about ANSI <-> Unicode conversion.
342 */
343 memcpy(Console->LineBuffer, Buffer, Console->LineSize * sizeof(WCHAR));
344 if (Console->LineSize == Console->LineMaxSize)
345 {
346 Console->LineComplete = TRUE;
347 Console->LinePos = 0;
348 }
349 }
350
351 /* If we don't have a complete line yet, process the pending input */
352 while (!Console->LineComplete && !IsListEmpty(&InputBuffer->InputEvents))
353 {
354 /* Remove input event from queue */
355 CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
356 if (IsListEmpty(&InputBuffer->InputEvents))
357 {
358 ResetEvent(InputBuffer->ActiveEvent);
359 }
360 Input = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
361
362 /* Only pay attention to key down */
363 if (Input->InputEvent.EventType == KEY_EVENT &&
364 Input->InputEvent.Event.KeyEvent.bKeyDown)
365 {
366 LineInputKeyDown(Console, ExeName,
367 &Input->InputEvent.Event.KeyEvent);
368 ReadControl->dwControlKeyState = Input->InputEvent.Event.KeyEvent.dwControlKeyState;
369 }
370 ConsoleFreeHeap(Input);
371 }
372
373 /* Check if we have a complete line to read from */
374 if (Console->LineComplete)
375 {
376 while (i < NumCharsToRead && Console->LinePos != Console->LineSize)
377 {
378 WCHAR Char = Console->LineBuffer[Console->LinePos++];
379
380 if (Unicode)
381 {
382 ((PWCHAR)Buffer)[i] = Char;
383 }
384 else
385 {
386 ConsoleInputUnicodeCharToAnsiChar(Console, &((PCHAR)Buffer)[i], &Char);
387 }
388 ++i;
389 }
390
391 if (Console->LinePos == Console->LineSize)
392 {
393 /* Entire line has been read */
394 ConsoleFreeHeap(Console->LineBuffer);
395 Console->LineBuffer = NULL;
396 }
397
398 Status = STATUS_SUCCESS;
399 }
400 }
401 else
402 {
403 /* Character input */
404 while (i < NumCharsToRead && !IsListEmpty(&InputBuffer->InputEvents))
405 {
406 /* Remove input event from queue */
407 CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
408 if (IsListEmpty(&InputBuffer->InputEvents))
409 {
410 ResetEvent(InputBuffer->ActiveEvent);
411 }
412 Input = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
413
414 /* Only pay attention to valid ASCII chars, on key down */
415 if (Input->InputEvent.EventType == KEY_EVENT &&
416 Input->InputEvent.Event.KeyEvent.bKeyDown &&
417 Input->InputEvent.Event.KeyEvent.uChar.UnicodeChar != L'\0')
418 {
419 WCHAR Char = Input->InputEvent.Event.KeyEvent.uChar.UnicodeChar;
420
421 if (Unicode)
422 {
423 ((PWCHAR)Buffer)[i] = Char;
424 }
425 else
426 {
427 ConsoleInputUnicodeCharToAnsiChar(Console, &((PCHAR)Buffer)[i], &Char);
428 }
429 ++i;
430
431 /* Did read something */
432 Status = STATUS_SUCCESS;
433 }
434 ConsoleFreeHeap(Input);
435 }
436 }
437
438 // FIXME: Only set if Status == STATUS_SUCCESS ???
439 if (NumCharsRead) *NumCharsRead = i;
440
441 return Status;
442 }
443
444 NTSTATUS NTAPI
445 ConDrvGetConsoleInput(IN PCONSOLE Console,
446 IN PCONSOLE_INPUT_BUFFER InputBuffer,
447 IN BOOLEAN KeepEvents,
448 IN BOOLEAN WaitForMoreEvents,
449 IN BOOLEAN Unicode,
450 OUT PINPUT_RECORD InputRecord,
451 IN ULONG NumEventsToRead,
452 OUT PULONG NumEventsRead OPTIONAL)
453 {
454 PLIST_ENTRY CurrentInput;
455 ConsoleInput* Input;
456 ULONG i = 0;
457
458 if (Console == NULL || InputBuffer == NULL /* || InputRecord == NULL */)
459 return STATUS_INVALID_PARAMETER;
460
461 /* Validity checks */
462 ASSERT(Console == InputBuffer->Header.Console);
463 ASSERT((InputRecord != NULL) || (InputRecord == NULL && NumEventsToRead == 0));
464
465 if (NumEventsRead) *NumEventsRead = 0;
466
467 if (IsListEmpty(&InputBuffer->InputEvents))
468 {
469 /*
470 * No input is available. Wait for more input if requested,
471 * otherwise, we don't wait, so we return success.
472 */
473 return (WaitForMoreEvents ? STATUS_PENDING : STATUS_SUCCESS);
474 }
475
476 /* Only get input if there is any */
477 CurrentInput = InputBuffer->InputEvents.Flink;
478 i = 0;
479 while ((CurrentInput != &InputBuffer->InputEvents) && (i < NumEventsToRead))
480 {
481 Input = CONTAINING_RECORD(CurrentInput, ConsoleInput, ListEntry);
482
483 *InputRecord = Input->InputEvent;
484
485 ++InputRecord;
486 ++i;
487 CurrentInput = CurrentInput->Flink;
488
489 /* Remove the events from the queue if needed */
490 if (!KeepEvents)
491 {
492 RemoveEntryList(&Input->ListEntry);
493 ConsoleFreeHeap(Input);
494 }
495 }
496
497 if (NumEventsRead) *NumEventsRead = i;
498
499 /* Now translate everything to ANSI */
500 if (!Unicode)
501 {
502 for (; i > 0; --i)
503 {
504 ConioInputEventToAnsi(InputBuffer->Header.Console, --InputRecord);
505 }
506 }
507
508 if (IsListEmpty(&InputBuffer->InputEvents))
509 {
510 ResetEvent(InputBuffer->ActiveEvent);
511 }
512
513 /* We read all the inputs available, we return success */
514 return STATUS_SUCCESS;
515 }
516
517 NTSTATUS NTAPI
518 ConDrvWriteConsoleInput(IN PCONSOLE Console,
519 IN PCONSOLE_INPUT_BUFFER InputBuffer,
520 IN BOOLEAN Unicode,
521 IN BOOLEAN AppendToEnd,
522 IN PINPUT_RECORD InputRecord,
523 IN ULONG NumEventsToWrite,
524 OUT PULONG NumEventsWritten OPTIONAL)
525 {
526 NTSTATUS Status = STATUS_SUCCESS;
527 ULONG i;
528
529 if (Console == NULL || InputBuffer == NULL /* || InputRecord == NULL */)
530 return STATUS_INVALID_PARAMETER;
531
532 /* Validity checks */
533 ASSERT(Console == InputBuffer->Header.Console);
534 ASSERT((InputRecord != NULL) || (InputRecord == NULL && NumEventsToWrite == 0));
535
536 /* First translate everything to UNICODE */
537 if (!Unicode)
538 {
539 for (i = 0; i < NumEventsToWrite; ++i)
540 {
541 ConioInputEventToUnicode(Console, &InputRecord[i]);
542 }
543 }
544
545 /* Now, add the events */
546 // if (NumEventsWritten) *NumEventsWritten = 0;
547 // ConDrvAddInputEvents
548 Status = ConioAddInputEvents(Console,
549 InputRecord,
550 NumEventsToWrite,
551 NumEventsWritten,
552 AppendToEnd);
553 // if (NumEventsWritten) *NumEventsWritten = i;
554
555 return Status;
556 }
557
558 NTSTATUS NTAPI
559 ConDrvFlushConsoleInputBuffer(IN PCONSOLE Console,
560 IN PCONSOLE_INPUT_BUFFER InputBuffer)
561 {
562 PLIST_ENTRY CurrentEntry;
563 ConsoleInput* Event;
564
565 if (Console == NULL || InputBuffer == NULL)
566 return STATUS_INVALID_PARAMETER;
567
568 /* Validity check */
569 ASSERT(Console == InputBuffer->Header.Console);
570
571 /* Discard all entries in the input event queue */
572 while (!IsListEmpty(&InputBuffer->InputEvents))
573 {
574 CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
575 Event = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
576 ConsoleFreeHeap(Event);
577 }
578 ResetEvent(InputBuffer->ActiveEvent);
579
580 return STATUS_SUCCESS;
581 }
582
583 NTSTATUS NTAPI
584 ConDrvGetConsoleNumberOfInputEvents(IN PCONSOLE Console,
585 IN PCONSOLE_INPUT_BUFFER InputBuffer,
586 OUT PULONG NumberOfEvents)
587 {
588 PLIST_ENTRY CurrentInput;
589
590 if (Console == NULL || InputBuffer == NULL || NumberOfEvents == NULL)
591 return STATUS_INVALID_PARAMETER;
592
593 /* Validity check */
594 ASSERT(Console == InputBuffer->Header.Console);
595
596 *NumberOfEvents = 0;
597
598 /* If there are any events ... */
599 CurrentInput = InputBuffer->InputEvents.Flink;
600 while (CurrentInput != &InputBuffer->InputEvents)
601 {
602 CurrentInput = CurrentInput->Flink;
603 (*NumberOfEvents)++;
604 }
605
606 return STATUS_SUCCESS;
607 }
608
609 /* EOF */