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