[KERNEL32][CONSRV]
[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 /*
70 * This pre-processing code MUST be IN consrv ONLY
71 */
72 static ULONG
73 PreprocessInput(PCONSOLE Console,
74 PINPUT_RECORD InputEvent,
75 ULONG NumEventsToWrite)
76 {
77 ULONG NumEvents;
78
79 /*
80 * Loop each event, and for each, check for pause or unpause
81 * and perform adequate behaviour.
82 */
83 for (NumEvents = NumEventsToWrite; NumEvents > 0; --NumEvents)
84 {
85 /* Check for pause or unpause */
86 if (InputEvent->EventType == KEY_EVENT && InputEvent->Event.KeyEvent.bKeyDown)
87 {
88 WORD vk = InputEvent->Event.KeyEvent.wVirtualKeyCode;
89 if (!(Console->PauseFlags & PAUSED_FROM_KEYBOARD))
90 {
91 DWORD cks = InputEvent->Event.KeyEvent.dwControlKeyState;
92 if (Console->InputBuffer.Mode & ENABLE_LINE_INPUT &&
93 (vk == VK_PAUSE ||
94 (vk == 'S' && (cks & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) &&
95 !(cks & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)))))
96 {
97 ConioPause(Console, PAUSED_FROM_KEYBOARD);
98
99 /* Skip the event */
100 RtlMoveMemory(InputEvent,
101 InputEvent + 1,
102 (NumEvents - 1) * sizeof(INPUT_RECORD));
103 --NumEventsToWrite;
104 continue;
105 }
106 }
107 else
108 {
109 if ((vk < VK_SHIFT || vk > VK_CAPITAL) && vk != VK_LWIN &&
110 vk != VK_RWIN && vk != VK_NUMLOCK && vk != VK_SCROLL)
111 {
112 ConioUnpause(Console, PAUSED_FROM_KEYBOARD);
113
114 /* Skip the event */
115 RtlMoveMemory(InputEvent,
116 InputEvent + 1,
117 (NumEvents - 1) * sizeof(INPUT_RECORD));
118 --NumEventsToWrite;
119 continue;
120 }
121 }
122 }
123
124 /* Go to the next event */
125 ++InputEvent;
126 }
127
128 return NumEventsToWrite;
129 }
130
131 /*
132 * This post-processing code MUST be IN consrv ONLY
133 */
134 static VOID
135 PostprocessInput(PCONSOLE Console)
136 {
137 CsrNotifyWait(&Console->ReadWaitQueue,
138 FALSE,
139 NULL,
140 NULL);
141 if (!IsListEmpty(&Console->ReadWaitQueue))
142 {
143 CsrDereferenceWait(&Console->ReadWaitQueue);
144 }
145 }
146
147 NTSTATUS
148 ConioAddInputEvents(PCONSOLE Console,
149 PINPUT_RECORD InputRecords, // InputEvent
150 ULONG NumEventsToWrite,
151 PULONG NumEventsWritten,
152 BOOLEAN AppendToEnd)
153 {
154 NTSTATUS Status = STATUS_SUCCESS;
155 ULONG i = 0;
156 BOOLEAN SetWaitEvent = FALSE;
157
158 if (NumEventsWritten) *NumEventsWritten = 0;
159
160 /*
161 * This pre-processing code MUST be IN consrv ONLY!!
162 */
163 NumEventsToWrite = PreprocessInput(Console, InputRecords, NumEventsToWrite);
164 if (NumEventsToWrite == 0) return STATUS_SUCCESS;
165
166
167 /*
168 * When adding many single events, in the case of repeated mouse move or
169 * key down events, we try to coalesce them so that we do not saturate
170 * too quickly the input buffer.
171 */
172 if (NumEventsToWrite == 1 && !IsListEmpty(&Console->InputBuffer.InputEvents))
173 {
174 PINPUT_RECORD InputRecord = InputRecords; // Only one element
175 PINPUT_RECORD LastInputRecord;
176 ConsoleInput* ConInRec; // Input
177
178 /* Get the "next" event of the input buffer */
179 if (AppendToEnd)
180 {
181 /* Get the tail element */
182 ConInRec = CONTAINING_RECORD(Console->InputBuffer.InputEvents.Blink,
183 ConsoleInput, ListEntry);
184 }
185 else
186 {
187 /* Get the head element */
188 ConInRec = CONTAINING_RECORD(Console->InputBuffer.InputEvents.Flink,
189 ConsoleInput, ListEntry);
190 }
191 LastInputRecord = &ConInRec->InputEvent;
192
193 if (InputRecord->EventType == MOUSE_EVENT &&
194 InputRecord->Event.MouseEvent.dwEventFlags == MOUSE_MOVED)
195 {
196 if (LastInputRecord->EventType == MOUSE_EVENT &&
197 LastInputRecord->Event.MouseEvent.dwEventFlags == MOUSE_MOVED)
198 {
199 /* Update the mouse position */
200 LastInputRecord->Event.MouseEvent.dwMousePosition.X =
201 InputRecord->Event.MouseEvent.dwMousePosition.X;
202 LastInputRecord->Event.MouseEvent.dwMousePosition.Y =
203 InputRecord->Event.MouseEvent.dwMousePosition.Y;
204
205 i = 1;
206 // return STATUS_SUCCESS;
207 Status = STATUS_SUCCESS;
208 }
209 }
210 else if (InputRecord->EventType == KEY_EVENT &&
211 InputRecord->Event.KeyEvent.bKeyDown)
212 {
213 if (LastInputRecord->EventType == KEY_EVENT &&
214 LastInputRecord->Event.KeyEvent.bKeyDown &&
215 (LastInputRecord->Event.KeyEvent.wVirtualScanCode == // Same scancode
216 InputRecord->Event.KeyEvent.wVirtualScanCode) &&
217 (LastInputRecord->Event.KeyEvent.uChar.UnicodeChar == // Same character
218 InputRecord->Event.KeyEvent.uChar.UnicodeChar) &&
219 (LastInputRecord->Event.KeyEvent.dwControlKeyState == // Same Ctrl/Alt/Shift state
220 InputRecord->Event.KeyEvent.dwControlKeyState) )
221 {
222 /* Update the repeat count */
223 LastInputRecord->Event.KeyEvent.wRepeatCount +=
224 InputRecord->Event.KeyEvent.wRepeatCount;
225
226 i = 1;
227 // return STATUS_SUCCESS;
228 Status = STATUS_SUCCESS;
229 }
230 }
231 }
232
233 /* If we coalesced the only one element, we can quit */
234 if (i == 1 && Status == STATUS_SUCCESS /* && NumEventsToWrite == 1 */)
235 goto Done;
236
237 /*
238 * No event coalesced, add them in the usual way.
239 */
240
241 if (AppendToEnd)
242 {
243 /* Go to the beginning of the list */
244 // InputRecords = InputRecords;
245 }
246 else
247 {
248 /* Go to the end of the list */
249 InputRecords = &InputRecords[NumEventsToWrite - 1];
250 }
251
252 /* Set the event if the list is going to be non-empty */
253 if (IsListEmpty(&Console->InputBuffer.InputEvents))
254 SetWaitEvent = TRUE;
255
256 for (i = 0; i < NumEventsToWrite && NT_SUCCESS(Status); ++i)
257 {
258 PINPUT_RECORD InputRecord;
259 ConsoleInput* ConInRec;
260
261 if (AppendToEnd)
262 {
263 /* Select the event and go to the next one */
264 InputRecord = InputRecords++;
265 }
266 else
267 {
268 /* Select the event and go to the previous one */
269 InputRecord = InputRecords--;
270 }
271
272 /* Add event to the queue */
273 ConInRec = ConsoleAllocHeap(0, sizeof(ConsoleInput));
274 if (ConInRec == NULL)
275 {
276 // return STATUS_INSUFFICIENT_RESOURCES;
277 Status = STATUS_INSUFFICIENT_RESOURCES;
278 continue;
279 }
280
281 ConInRec->InputEvent = *InputRecord;
282
283 if (AppendToEnd)
284 {
285 /* Append the event to the end of the queue */
286 InsertTailList(&Console->InputBuffer.InputEvents, &ConInRec->ListEntry);
287 }
288 else
289 {
290 /* Append the event to the beginning of the queue */
291 InsertHeadList(&Console->InputBuffer.InputEvents, &ConInRec->ListEntry);
292 }
293
294 // return STATUS_SUCCESS;
295 Status = STATUS_SUCCESS;
296 }
297
298 if (SetWaitEvent) SetEvent(Console->InputBuffer.ActiveEvent);
299
300 Done:
301 if (NumEventsWritten) *NumEventsWritten = i;
302
303
304 /*
305 * This post-processing code MUST be IN consrv ONLY!!
306 */
307 // if (NT_SUCCESS(Status))
308 if (Status == STATUS_SUCCESS) PostprocessInput(Console);
309
310 return STATUS_SUCCESS;
311 }
312
313 NTSTATUS
314 ConioProcessInputEvent(PCONSOLE Console,
315 PINPUT_RECORD InputEvent)
316 {
317 ULONG NumEventsWritten;
318 return ConioAddInputEvents(Console,
319 InputEvent,
320 1,
321 &NumEventsWritten,
322 TRUE);
323 }
324
325
326 VOID
327 PurgeInputBuffer(PCONSOLE Console)
328 {
329 PLIST_ENTRY CurrentEntry;
330 ConsoleInput* Event;
331
332 while (!IsListEmpty(&Console->InputBuffer.InputEvents))
333 {
334 CurrentEntry = RemoveHeadList(&Console->InputBuffer.InputEvents);
335 Event = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
336 ConsoleFreeHeap(Event);
337 }
338
339 CloseHandle(Console->InputBuffer.ActiveEvent);
340 }
341
342
343 /* PUBLIC DRIVER APIS *********************************************************/
344
345 NTSTATUS NTAPI
346 ConDrvReadConsole(IN PCONSOLE Console,
347 IN PCONSOLE_INPUT_BUFFER InputBuffer,
348 /**/IN PUNICODE_STRING ExeName /**/OPTIONAL/**/,/**/
349 IN BOOLEAN Unicode,
350 OUT PVOID Buffer,
351 IN OUT PCONSOLE_READCONSOLE_CONTROL ReadControl,
352 IN ULONG NumCharsToRead,
353 OUT PULONG NumCharsRead OPTIONAL)
354 {
355 // STATUS_PENDING : Wait if more to read ; STATUS_SUCCESS : Don't wait.
356 NTSTATUS Status = STATUS_PENDING;
357 PLIST_ENTRY CurrentEntry;
358 ConsoleInput *Input;
359 ULONG i;
360
361 if (Console == NULL || InputBuffer == NULL || /* Buffer == NULL || */
362 ReadControl == NULL || ReadControl->nLength != sizeof(CONSOLE_READCONSOLE_CONTROL))
363 {
364 return STATUS_INVALID_PARAMETER;
365 }
366
367 /* Validity checks */
368 ASSERT(Console == InputBuffer->Header.Console);
369 ASSERT((Buffer != NULL) || (Buffer == NULL && NumCharsToRead == 0));
370
371 /* We haven't read anything (yet) */
372
373 i = ReadControl->nInitialChars;
374
375 if (InputBuffer->Mode & ENABLE_LINE_INPUT)
376 {
377 if (Console->LineBuffer == NULL)
378 {
379 /* Starting a new line */
380 Console->LineMaxSize = (WORD)max(256, NumCharsToRead);
381
382 Console->LineBuffer = ConsoleAllocHeap(0, Console->LineMaxSize * sizeof(WCHAR));
383 if (Console->LineBuffer == NULL) return STATUS_NO_MEMORY;
384
385 Console->LineComplete = FALSE;
386 Console->LineUpPressed = FALSE;
387 Console->LineInsertToggle = Console->InsertMode;
388 Console->LineWakeupMask = ReadControl->dwCtrlWakeupMask;
389 Console->LineSize = ReadControl->nInitialChars;
390 Console->LinePos = Console->LineSize;
391
392 /*
393 * Pre-filling the buffer is only allowed in the Unicode API,
394 * so we don't need to worry about ANSI <-> Unicode conversion.
395 */
396 memcpy(Console->LineBuffer, Buffer, Console->LineSize * sizeof(WCHAR));
397 if (Console->LineSize == Console->LineMaxSize)
398 {
399 Console->LineComplete = TRUE;
400 Console->LinePos = 0;
401 }
402 }
403
404 /* If we don't have a complete line yet, process the pending input */
405 while (!Console->LineComplete && !IsListEmpty(&InputBuffer->InputEvents))
406 {
407 /* Remove input event from queue */
408 CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
409 if (IsListEmpty(&InputBuffer->InputEvents))
410 {
411 ResetEvent(InputBuffer->ActiveEvent);
412 }
413 Input = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
414
415 /* Only pay attention to key down */
416 if (Input->InputEvent.EventType == KEY_EVENT &&
417 Input->InputEvent.Event.KeyEvent.bKeyDown)
418 {
419 LineInputKeyDown(Console, ExeName,
420 &Input->InputEvent.Event.KeyEvent);
421 ReadControl->dwControlKeyState = Input->InputEvent.Event.KeyEvent.dwControlKeyState;
422 }
423 ConsoleFreeHeap(Input);
424 }
425
426 /* Check if we have a complete line to read from */
427 if (Console->LineComplete)
428 {
429 while (i < NumCharsToRead && Console->LinePos != Console->LineSize)
430 {
431 WCHAR Char = Console->LineBuffer[Console->LinePos++];
432
433 if (Unicode)
434 {
435 ((PWCHAR)Buffer)[i] = Char;
436 }
437 else
438 {
439 ConsoleInputUnicodeCharToAnsiChar(Console, &((PCHAR)Buffer)[i], &Char);
440 }
441 ++i;
442 }
443
444 if (Console->LinePos == Console->LineSize)
445 {
446 /* Entire line has been read */
447 ConsoleFreeHeap(Console->LineBuffer);
448 Console->LineBuffer = NULL;
449 }
450
451 Status = STATUS_SUCCESS;
452 }
453 }
454 else
455 {
456 /* Character input */
457 while (i < NumCharsToRead && !IsListEmpty(&InputBuffer->InputEvents))
458 {
459 /* Remove input event from queue */
460 CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
461 if (IsListEmpty(&InputBuffer->InputEvents))
462 {
463 ResetEvent(InputBuffer->ActiveEvent);
464 }
465 Input = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
466
467 /* Only pay attention to valid ASCII chars, on key down */
468 if (Input->InputEvent.EventType == KEY_EVENT &&
469 Input->InputEvent.Event.KeyEvent.bKeyDown &&
470 Input->InputEvent.Event.KeyEvent.uChar.UnicodeChar != L'\0')
471 {
472 WCHAR Char = Input->InputEvent.Event.KeyEvent.uChar.UnicodeChar;
473
474 if (Unicode)
475 {
476 ((PWCHAR)Buffer)[i] = Char;
477 }
478 else
479 {
480 ConsoleInputUnicodeCharToAnsiChar(Console, &((PCHAR)Buffer)[i], &Char);
481 }
482 ++i;
483
484 /* Did read something */
485 Status = STATUS_SUCCESS;
486 }
487 ConsoleFreeHeap(Input);
488 }
489 }
490
491 // FIXME: Only set if Status == STATUS_SUCCESS ???
492 if (NumCharsRead) *NumCharsRead = i;
493
494 return Status;
495 }
496
497 NTSTATUS NTAPI
498 ConDrvGetConsoleInput(IN PCONSOLE Console,
499 IN PCONSOLE_INPUT_BUFFER InputBuffer,
500 IN BOOLEAN KeepEvents,
501 IN BOOLEAN WaitForMoreEvents,
502 IN BOOLEAN Unicode,
503 OUT PINPUT_RECORD InputRecord,
504 IN ULONG NumEventsToRead,
505 OUT PULONG NumEventsRead OPTIONAL)
506 {
507 PLIST_ENTRY CurrentInput;
508 ConsoleInput* Input;
509 ULONG i = 0;
510
511 if (Console == NULL || InputBuffer == NULL /* || InputRecord == NULL */)
512 return STATUS_INVALID_PARAMETER;
513
514 /* Validity checks */
515 ASSERT(Console == InputBuffer->Header.Console);
516 ASSERT((InputRecord != NULL) || (InputRecord == NULL && NumEventsToRead == 0));
517
518 if (NumEventsRead) *NumEventsRead = 0;
519
520 if (IsListEmpty(&InputBuffer->InputEvents))
521 {
522 /*
523 * No input is available. Wait for more input if requested,
524 * otherwise, we don't wait, so we return success.
525 */
526 return (WaitForMoreEvents ? STATUS_PENDING : STATUS_SUCCESS);
527 }
528
529 /* Only get input if there is any */
530 CurrentInput = InputBuffer->InputEvents.Flink;
531 i = 0;
532 while ((CurrentInput != &InputBuffer->InputEvents) && (i < NumEventsToRead))
533 {
534 Input = CONTAINING_RECORD(CurrentInput, ConsoleInput, ListEntry);
535
536 *InputRecord = Input->InputEvent;
537
538 ++InputRecord;
539 ++i;
540 CurrentInput = CurrentInput->Flink;
541
542 /* Remove the events from the queue if needed */
543 if (!KeepEvents)
544 {
545 RemoveEntryList(&Input->ListEntry);
546 ConsoleFreeHeap(Input);
547 }
548 }
549
550 if (NumEventsRead) *NumEventsRead = i;
551
552 /* Now translate everything to ANSI */
553 if (!Unicode)
554 {
555 for (; i > 0; --i)
556 {
557 ConioInputEventToAnsi(InputBuffer->Header.Console, --InputRecord);
558 }
559 }
560
561 if (IsListEmpty(&InputBuffer->InputEvents))
562 {
563 ResetEvent(InputBuffer->ActiveEvent);
564 }
565
566 /* We read all the inputs available, we return success */
567 return STATUS_SUCCESS;
568 }
569
570 NTSTATUS NTAPI
571 ConDrvWriteConsoleInput(IN PCONSOLE Console,
572 IN PCONSOLE_INPUT_BUFFER InputBuffer,
573 IN BOOLEAN Unicode,
574 IN BOOLEAN AppendToEnd,
575 IN PINPUT_RECORD InputRecord,
576 IN ULONG NumEventsToWrite,
577 OUT PULONG NumEventsWritten OPTIONAL)
578 {
579 NTSTATUS Status = STATUS_SUCCESS;
580 ULONG i;
581
582 if (Console == NULL || InputBuffer == NULL /* || InputRecord == NULL */)
583 return STATUS_INVALID_PARAMETER;
584
585 /* Validity checks */
586 ASSERT(Console == InputBuffer->Header.Console);
587 ASSERT((InputRecord != NULL) || (InputRecord == NULL && NumEventsToWrite == 0));
588
589 /* First translate everything to UNICODE */
590 if (!Unicode)
591 {
592 for (i = 0; i < NumEventsToWrite; ++i)
593 {
594 ConioInputEventToUnicode(Console, &InputRecord[i]);
595 }
596 }
597
598 /* Now, add the events */
599 // if (NumEventsWritten) *NumEventsWritten = 0;
600 Status = ConioAddInputEvents(Console,
601 InputRecord,
602 NumEventsToWrite,
603 NumEventsWritten,
604 AppendToEnd);
605 // if (NumEventsWritten) *NumEventsWritten = i;
606
607 return Status;
608 }
609
610 NTSTATUS NTAPI
611 ConDrvFlushConsoleInputBuffer(IN PCONSOLE Console,
612 IN PCONSOLE_INPUT_BUFFER InputBuffer)
613 {
614 PLIST_ENTRY CurrentEntry;
615 ConsoleInput* Event;
616
617 if (Console == NULL || InputBuffer == NULL)
618 return STATUS_INVALID_PARAMETER;
619
620 /* Validity check */
621 ASSERT(Console == InputBuffer->Header.Console);
622
623 /* Discard all entries in the input event queue */
624 while (!IsListEmpty(&InputBuffer->InputEvents))
625 {
626 CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
627 Event = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
628 ConsoleFreeHeap(Event);
629 }
630 ResetEvent(InputBuffer->ActiveEvent);
631
632 return STATUS_SUCCESS;
633 }
634
635 NTSTATUS NTAPI
636 ConDrvGetConsoleNumberOfInputEvents(IN PCONSOLE Console,
637 IN PCONSOLE_INPUT_BUFFER InputBuffer,
638 OUT PULONG NumberOfEvents)
639 {
640 PLIST_ENTRY CurrentInput;
641
642 if (Console == NULL || InputBuffer == NULL || NumberOfEvents == NULL)
643 return STATUS_INVALID_PARAMETER;
644
645 /* Validity check */
646 ASSERT(Console == InputBuffer->Header.Console);
647
648 *NumberOfEvents = 0;
649
650 /* If there are any events ... */
651 CurrentInput = InputBuffer->InputEvents.Flink;
652 while (CurrentInput != &InputBuffer->InputEvents)
653 {
654 CurrentInput = CurrentInput->Flink;
655 (*NumberOfEvents)++;
656 }
657
658 return STATUS_SUCCESS;
659 }
660
661 /* EOF */