Sync with trunk r63174.
[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 #define ConSrvGetInputBuffer(ProcessData, Handle, Ptr, Access, LockConsole) \
20 ConSrvGetObject((ProcessData), (Handle), (PCONSOLE_IO_OBJECT*)(Ptr), NULL, \
21 (Access), (LockConsole), INPUT_BUFFER)
22 #define ConSrvGetInputBufferAndHandleEntry(ProcessData, Handle, Ptr, Entry, Access, LockConsole) \
23 ConSrvGetObject((ProcessData), (Handle), (PCONSOLE_IO_OBJECT*)(Ptr), (Entry), \
24 (Access), (LockConsole), INPUT_BUFFER)
25 #define ConSrvReleaseInputBuffer(Buff, IsConsoleLocked) \
26 ConSrvReleaseObject(&(Buff)->Header, (IsConsoleLocked))
27
28
29 #define ConsoleInputUnicodeCharToAnsiChar(Console, dChar, sWChar) \
30 WideCharToMultiByte((Console)->CodePage, 0, (sWChar), 1, (dChar), 1, NULL, NULL)
31
32 #define ConsoleInputAnsiCharToUnicodeChar(Console, dWChar, sChar) \
33 MultiByteToWideChar((Console)->CodePage, 0, (sChar), 1, (dWChar), 1)
34
35 typedef struct ConsoleInput_t
36 {
37 LIST_ENTRY ListEntry;
38 INPUT_RECORD InputEvent;
39 } ConsoleInput;
40
41
42 /* PRIVATE FUNCTIONS **********************************************************/
43
44 static VOID FASTCALL
45 ConioInputEventToAnsi(PCONSOLE Console, PINPUT_RECORD InputEvent)
46 {
47 if (InputEvent->EventType == KEY_EVENT)
48 {
49 WCHAR UnicodeChar = InputEvent->Event.KeyEvent.uChar.UnicodeChar;
50 InputEvent->Event.KeyEvent.uChar.UnicodeChar = 0;
51 ConsoleInputUnicodeCharToAnsiChar(Console,
52 &InputEvent->Event.KeyEvent.uChar.AsciiChar,
53 &UnicodeChar);
54 }
55 }
56
57 static VOID FASTCALL
58 ConioInputEventToUnicode(PCONSOLE Console, PINPUT_RECORD InputEvent)
59 {
60 if (InputEvent->EventType == KEY_EVENT)
61 {
62 CHAR AsciiChar = InputEvent->Event.KeyEvent.uChar.AsciiChar;
63 InputEvent->Event.KeyEvent.uChar.AsciiChar = 0;
64 ConsoleInputAnsiCharToUnicodeChar(Console,
65 &InputEvent->Event.KeyEvent.uChar.UnicodeChar,
66 &AsciiChar);
67 }
68 }
69
70 NTSTATUS FASTCALL
71 ConioAddInputEvent(PCONSOLE Console,
72 PINPUT_RECORD InputEvent,
73 BOOLEAN AppendToEnd)
74 {
75 ConsoleInput *ConInRec;
76
77 /* Check for pause or unpause */
78 if (InputEvent->EventType == KEY_EVENT && InputEvent->Event.KeyEvent.bKeyDown)
79 {
80 WORD vk = InputEvent->Event.KeyEvent.wVirtualKeyCode;
81 if (!(Console->PauseFlags & PAUSED_FROM_KEYBOARD))
82 {
83 DWORD cks = InputEvent->Event.KeyEvent.dwControlKeyState;
84 if (Console->InputBuffer.Mode & ENABLE_LINE_INPUT &&
85 (vk == VK_PAUSE || (vk == 'S' &&
86 (cks & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) &&
87 !(cks & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)))))
88 {
89 ConioPause(Console, PAUSED_FROM_KEYBOARD);
90 return STATUS_SUCCESS;
91 }
92 }
93 else
94 {
95 if ((vk < VK_SHIFT || vk > VK_CAPITAL) && vk != VK_LWIN &&
96 vk != VK_RWIN && vk != VK_NUMLOCK && vk != VK_SCROLL)
97 {
98 ConioUnpause(Console, PAUSED_FROM_KEYBOARD);
99 return STATUS_SUCCESS;
100 }
101 }
102 }
103
104 /* Add event to the queue */
105 ConInRec = ConsoleAllocHeap(0, sizeof(ConsoleInput));
106 if (ConInRec == NULL) return STATUS_INSUFFICIENT_RESOURCES;
107
108 ConInRec->InputEvent = *InputEvent;
109
110 if (AppendToEnd)
111 {
112 /* Append the event to the end of the queue */
113 InsertTailList(&Console->InputBuffer.InputEvents, &ConInRec->ListEntry);
114 }
115 else
116 {
117 /* Append the event to the beginning of the queue */
118 InsertHeadList(&Console->InputBuffer.InputEvents, &ConInRec->ListEntry);
119 }
120
121 SetEvent(Console->InputBuffer.ActiveEvent);
122 CsrNotifyWait(&Console->InputBuffer.ReadWaitQueue,
123 FALSE,
124 NULL,
125 NULL);
126 if (!IsListEmpty(&Console->InputBuffer.ReadWaitQueue))
127 {
128 CsrDereferenceWait(&Console->InputBuffer.ReadWaitQueue);
129 }
130
131 return STATUS_SUCCESS;
132 }
133
134 NTSTATUS FASTCALL
135 ConioProcessInputEvent(PCONSOLE Console,
136 PINPUT_RECORD InputEvent)
137 {
138 return ConioAddInputEvent(Console, InputEvent, TRUE);
139 }
140
141 VOID FASTCALL
142 PurgeInputBuffer(PCONSOLE Console)
143 {
144 PLIST_ENTRY CurrentEntry;
145 ConsoleInput* Event;
146
147 while (!IsListEmpty(&Console->InputBuffer.InputEvents))
148 {
149 CurrentEntry = RemoveHeadList(&Console->InputBuffer.InputEvents);
150 Event = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
151 ConsoleFreeHeap(Event);
152 }
153
154 CloseHandle(Console->InputBuffer.ActiveEvent);
155 }
156
157 /*
158 * This function explicitely references Console->ActiveBuffer
159 * (and also makes use of keyboard functions...).
160 * It is possible that it will move into frontends...
161 */
162 VOID NTAPI
163 ConDrvProcessKey(IN PCONSOLE Console,
164 IN BOOLEAN Down,
165 IN UINT VirtualKeyCode,
166 IN UINT VirtualScanCode,
167 IN WCHAR UnicodeChar,
168 IN ULONG ShiftState,
169 IN BYTE KeyStateCtrl)
170 {
171 INPUT_RECORD er;
172
173 /* process Ctrl-C and Ctrl-Break */
174 if ( Console->InputBuffer.Mode & ENABLE_PROCESSED_INPUT &&
175 Down && (VirtualKeyCode == VK_PAUSE || VirtualKeyCode == 'C') &&
176 (ShiftState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) || KeyStateCtrl & 0x80) )
177 {
178 DPRINT1("Console_Api Ctrl-C\n");
179 ConDrvConsoleProcessCtrlEvent(Console, 0, CTRL_C_EVENT);
180
181 if (Console->LineBuffer && !Console->LineComplete)
182 {
183 /* Line input is in progress; end it */
184 Console->LinePos = Console->LineSize = 0;
185 Console->LineComplete = TRUE;
186 }
187 return;
188 }
189
190 if ( (ShiftState & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED)) != 0 &&
191 (VK_UP == VirtualKeyCode || VK_DOWN == VirtualKeyCode) )
192 {
193 if (!Down) return;
194
195 /* scroll up or down */
196 if (VK_UP == VirtualKeyCode)
197 {
198 /* only scroll up if there is room to scroll up into */
199 if (Console->ActiveBuffer->CursorPosition.Y != Console->ActiveBuffer->ScreenBufferSize.Y - 1)
200 {
201 Console->ActiveBuffer->VirtualY = (Console->ActiveBuffer->VirtualY +
202 Console->ActiveBuffer->ScreenBufferSize.Y - 1) %
203 Console->ActiveBuffer->ScreenBufferSize.Y;
204 Console->ActiveBuffer->CursorPosition.Y++;
205 }
206 }
207 else
208 {
209 /* only scroll down if there is room to scroll down into */
210 if (Console->ActiveBuffer->CursorPosition.Y != 0)
211 {
212 Console->ActiveBuffer->VirtualY = (Console->ActiveBuffer->VirtualY + 1) %
213 Console->ActiveBuffer->ScreenBufferSize.Y;
214 Console->ActiveBuffer->CursorPosition.Y--;
215 }
216 }
217
218 ConioDrawConsole(Console);
219 return;
220 }
221
222 er.EventType = KEY_EVENT;
223 er.Event.KeyEvent.bKeyDown = Down;
224 er.Event.KeyEvent.wRepeatCount = 1;
225 er.Event.KeyEvent.wVirtualKeyCode = VirtualKeyCode;
226 er.Event.KeyEvent.wVirtualScanCode = VirtualScanCode;
227 er.Event.KeyEvent.uChar.UnicodeChar = UnicodeChar;
228 er.Event.KeyEvent.dwControlKeyState = ShiftState;
229
230 ConioProcessInputEvent(Console, &er);
231 }
232
233
234 /* PUBLIC DRIVER APIS *********************************************************/
235
236 NTSTATUS NTAPI
237 ConDrvReadConsole(IN PCONSOLE Console,
238 IN PCONSOLE_INPUT_BUFFER InputBuffer,
239 IN BOOLEAN Unicode,
240 OUT PVOID Buffer,
241 IN OUT PCONSOLE_READCONSOLE_CONTROL ReadControl,
242 IN ULONG NumCharsToRead,
243 OUT PULONG NumCharsRead OPTIONAL)
244 {
245 // STATUS_PENDING : Wait if more to read ; STATUS_SUCCESS : Don't wait.
246 NTSTATUS Status = STATUS_PENDING;
247 PLIST_ENTRY CurrentEntry;
248 ConsoleInput *Input;
249 ULONG i;
250
251 if (Console == NULL || InputBuffer == NULL || /* Buffer == NULL || */
252 ReadControl == NULL || ReadControl->nLength != sizeof(CONSOLE_READCONSOLE_CONTROL))
253 {
254 return STATUS_INVALID_PARAMETER;
255 }
256
257 /* Validity checks */
258 ASSERT(Console == InputBuffer->Header.Console);
259 ASSERT( (Buffer != NULL && NumCharsToRead >= 0) ||
260 (Buffer == NULL && NumCharsToRead == 0) );
261
262 /* We haven't read anything (yet) */
263
264 i = ReadControl->nInitialChars;
265
266 if (InputBuffer->Mode & ENABLE_LINE_INPUT)
267 {
268 if (Console->LineBuffer == NULL)
269 {
270 /* Starting a new line */
271 Console->LineMaxSize = (WORD)max(256, NumCharsToRead);
272
273 Console->LineBuffer = ConsoleAllocHeap(0, Console->LineMaxSize * sizeof(WCHAR));
274 if (Console->LineBuffer == NULL) return STATUS_NO_MEMORY;
275
276 Console->LineComplete = FALSE;
277 Console->LineUpPressed = FALSE;
278 Console->LineInsertToggle = Console->InsertMode;
279 Console->LineWakeupMask = ReadControl->dwCtrlWakeupMask;
280 Console->LineSize = ReadControl->nInitialChars;
281 Console->LinePos = Console->LineSize;
282
283 /*
284 * Pre-filling the buffer is only allowed in the Unicode API,
285 * so we don't need to worry about ANSI <-> Unicode conversion.
286 */
287 memcpy(Console->LineBuffer, Buffer, Console->LineSize * sizeof(WCHAR));
288 if (Console->LineSize == Console->LineMaxSize)
289 {
290 Console->LineComplete = TRUE;
291 Console->LinePos = 0;
292 }
293 }
294
295 /* If we don't have a complete line yet, process the pending input */
296 while (!Console->LineComplete && !IsListEmpty(&InputBuffer->InputEvents))
297 {
298 /* Remove input event from queue */
299 CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
300 if (IsListEmpty(&InputBuffer->InputEvents))
301 {
302 ResetEvent(InputBuffer->ActiveEvent);
303 }
304 Input = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
305
306 /* Only pay attention to key down */
307 if (Input->InputEvent.EventType == KEY_EVENT &&
308 Input->InputEvent.Event.KeyEvent.bKeyDown)
309 {
310 LineInputKeyDown(Console, &Input->InputEvent.Event.KeyEvent);
311 ReadControl->dwControlKeyState = Input->InputEvent.Event.KeyEvent.dwControlKeyState;
312 }
313 ConsoleFreeHeap(Input);
314 }
315
316 /* Check if we have a complete line to read from */
317 if (Console->LineComplete)
318 {
319 while (i < NumCharsToRead && Console->LinePos != Console->LineSize)
320 {
321 WCHAR Char = Console->LineBuffer[Console->LinePos++];
322
323 if (Unicode)
324 {
325 ((PWCHAR)Buffer)[i] = Char;
326 }
327 else
328 {
329 ConsoleInputUnicodeCharToAnsiChar(Console, &((PCHAR)Buffer)[i], &Char);
330 }
331 ++i;
332 }
333
334 if (Console->LinePos == Console->LineSize)
335 {
336 /* Entire line has been read */
337 ConsoleFreeHeap(Console->LineBuffer);
338 Console->LineBuffer = NULL;
339 }
340
341 Status = STATUS_SUCCESS;
342 }
343 }
344 else
345 {
346 /* Character input */
347 while (i < NumCharsToRead && !IsListEmpty(&InputBuffer->InputEvents))
348 {
349 /* Remove input event from queue */
350 CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
351 if (IsListEmpty(&InputBuffer->InputEvents))
352 {
353 ResetEvent(InputBuffer->ActiveEvent);
354 }
355 Input = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
356
357 /* Only pay attention to valid ASCII chars, on key down */
358 if (Input->InputEvent.EventType == KEY_EVENT &&
359 Input->InputEvent.Event.KeyEvent.bKeyDown &&
360 Input->InputEvent.Event.KeyEvent.uChar.UnicodeChar != L'\0')
361 {
362 WCHAR Char = Input->InputEvent.Event.KeyEvent.uChar.UnicodeChar;
363
364 if (Unicode)
365 {
366 ((PWCHAR)Buffer)[i] = Char;
367 }
368 else
369 {
370 ConsoleInputUnicodeCharToAnsiChar(Console, &((PCHAR)Buffer)[i], &Char);
371 }
372 ++i;
373
374 /* Did read something */
375 Status = STATUS_SUCCESS;
376 }
377 ConsoleFreeHeap(Input);
378 }
379 }
380
381 if (NumCharsRead) *NumCharsRead = i;
382
383 return Status;
384 }
385
386 NTSTATUS NTAPI
387 ConDrvGetConsoleInput(IN PCONSOLE Console,
388 IN PCONSOLE_INPUT_BUFFER InputBuffer,
389 IN BOOLEAN KeepEvents,
390 IN BOOLEAN WaitForMoreEvents,
391 IN BOOLEAN Unicode,
392 OUT PINPUT_RECORD InputRecord,
393 IN ULONG NumEventsToRead,
394 OUT PULONG NumEventsRead OPTIONAL)
395 {
396 PLIST_ENTRY CurrentInput;
397 ConsoleInput* Input;
398 ULONG i = 0;
399
400 if (Console == NULL || InputBuffer == NULL /* || InputRecord == NULL */)
401 return STATUS_INVALID_PARAMETER;
402
403 /* Validity checks */
404 ASSERT(Console == InputBuffer->Header.Console);
405 ASSERT( (InputRecord != NULL && NumEventsToRead >= 0) ||
406 (InputRecord == NULL && NumEventsToRead == 0) );
407
408 // Do NOT do that !! Use the existing number of events already read, if any...
409 // if (NumEventsRead) *NumEventsRead = 0;
410
411 if (IsListEmpty(&InputBuffer->InputEvents))
412 {
413 /*
414 * No input is available. Wait for more input if requested,
415 * otherwise, we don't wait, so we return success.
416 */
417 return (WaitForMoreEvents ? STATUS_PENDING : STATUS_SUCCESS);
418 }
419
420 /* Only get input if there is any */
421 CurrentInput = InputBuffer->InputEvents.Flink;
422 if (NumEventsRead) i = *NumEventsRead; // We will read the remaining events...
423
424 while ((CurrentInput != &InputBuffer->InputEvents) && (i < NumEventsToRead))
425 {
426 Input = CONTAINING_RECORD(CurrentInput, ConsoleInput, ListEntry);
427
428 *InputRecord = Input->InputEvent;
429
430 if (!Unicode)
431 {
432 ConioInputEventToAnsi(InputBuffer->Header.Console, InputRecord);
433 }
434
435 ++InputRecord;
436 ++i;
437 CurrentInput = CurrentInput->Flink;
438
439 /* Remove the events from the queue if needed */
440 if (!KeepEvents)
441 {
442 RemoveEntryList(&Input->ListEntry);
443 ConsoleFreeHeap(Input);
444 }
445 }
446
447 if (NumEventsRead) *NumEventsRead = i;
448
449 if (IsListEmpty(&InputBuffer->InputEvents))
450 {
451 ResetEvent(InputBuffer->ActiveEvent);
452 }
453
454 /* We read all the inputs available, we return success */
455 return STATUS_SUCCESS;
456 }
457
458 NTSTATUS NTAPI
459 ConDrvWriteConsoleInput(IN PCONSOLE Console,
460 IN PCONSOLE_INPUT_BUFFER InputBuffer,
461 IN BOOLEAN Unicode,
462 IN BOOLEAN AppendToEnd,
463 IN PINPUT_RECORD InputRecord,
464 IN ULONG NumEventsToWrite,
465 OUT PULONG NumEventsWritten OPTIONAL)
466 {
467 NTSTATUS Status = STATUS_SUCCESS;
468 ULONG i;
469
470 if (Console == NULL || InputBuffer == NULL /* || InputRecord == NULL */)
471 return STATUS_INVALID_PARAMETER;
472
473 /* Validity checks */
474 ASSERT(Console == InputBuffer->Header.Console);
475 ASSERT( (InputRecord != NULL && NumEventsToWrite >= 0) ||
476 (InputRecord == NULL && NumEventsToWrite == 0) );
477
478 // Do NOT do that !! Use the existing number of events already written, if any...
479 // if (NumEventsWritten) *NumEventsWritten = 0;
480
481 for (i = (NumEventsWritten ? *NumEventsWritten : 0); i < NumEventsToWrite && NT_SUCCESS(Status); ++i)
482 {
483 if (!Unicode)
484 {
485 ConioInputEventToUnicode(Console, InputRecord);
486 }
487
488 Status = ConioAddInputEvent(Console, InputRecord++, AppendToEnd);
489 }
490
491 if (NumEventsWritten) *NumEventsWritten = i;
492
493 return Status;
494 }
495
496 NTSTATUS NTAPI
497 ConDrvFlushConsoleInputBuffer(IN PCONSOLE Console,
498 IN PCONSOLE_INPUT_BUFFER InputBuffer)
499 {
500 PLIST_ENTRY CurrentEntry;
501 ConsoleInput* Event;
502
503 if (Console == NULL || InputBuffer == NULL)
504 return STATUS_INVALID_PARAMETER;
505
506 /* Validity check */
507 ASSERT(Console == InputBuffer->Header.Console);
508
509 /* Discard all entries in the input event queue */
510 while (!IsListEmpty(&InputBuffer->InputEvents))
511 {
512 CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
513 Event = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
514 ConsoleFreeHeap(Event);
515 }
516 ResetEvent(InputBuffer->ActiveEvent);
517
518 return STATUS_SUCCESS;
519 }
520
521 NTSTATUS NTAPI
522 ConDrvGetConsoleNumberOfInputEvents(IN PCONSOLE Console,
523 IN PCONSOLE_INPUT_BUFFER InputBuffer,
524 OUT PULONG NumberOfEvents)
525 {
526 PLIST_ENTRY CurrentInput;
527
528 if (Console == NULL || InputBuffer == NULL || NumberOfEvents == NULL)
529 return STATUS_INVALID_PARAMETER;
530
531 /* Validity check */
532 ASSERT(Console == InputBuffer->Header.Console);
533
534 *NumberOfEvents = 0;
535
536 /* If there are any events ... */
537 CurrentInput = InputBuffer->InputEvents.Flink;
538 while (CurrentInput != &InputBuffer->InputEvents)
539 {
540 CurrentInput = CurrentInput->Flink;
541 (*NumberOfEvents)++;
542 }
543
544 return STATUS_SUCCESS;
545 }
546
547 /* EOF */