[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 KeepEvents,
380 IN BOOLEAN WaitForMoreEvents,
381 IN BOOLEAN Unicode,
382 OUT PINPUT_RECORD InputRecord,
383 IN ULONG NumEventsToRead,
384 OUT PULONG NumEventsRead OPTIONAL)
385 {
386 PLIST_ENTRY CurrentInput;
387 ConsoleInput* Input;
388 ULONG i = 0;
389
390 if (Console == NULL || InputBuffer == NULL /* || InputRecord == NULL */)
391 return STATUS_INVALID_PARAMETER;
392
393 /* Validity checks */
394 ASSERT(Console == InputBuffer->Header.Console);
395 ASSERT( (InputRecord != NULL && NumEventsToRead >= 0) ||
396 (InputRecord == NULL && NumEventsToRead == 0) );
397
398 // Do NOT do that !! Use the existing number of events already read, if any...
399 // if (NumEventsRead) *NumEventsRead = 0;
400
401 if (IsListEmpty(&InputBuffer->InputEvents))
402 {
403 /*
404 * No input is available. Wait for more input if requested,
405 * otherwise, we don't wait, so we return success.
406 */
407 return (WaitForMoreEvents ? STATUS_PENDING : STATUS_SUCCESS);
408 }
409
410 /* Only get input if there is any */
411 CurrentInput = InputBuffer->InputEvents.Flink;
412 if (NumEventsRead) i = *NumEventsRead; // We will read the remaining events...
413
414 while ((CurrentInput != &InputBuffer->InputEvents) && (i < NumEventsToRead))
415 {
416 Input = CONTAINING_RECORD(CurrentInput, ConsoleInput, ListEntry);
417
418 *InputRecord = Input->InputEvent;
419
420 if (!Unicode)
421 {
422 ConioInputEventToAnsi(InputBuffer->Header.Console, InputRecord);
423 }
424
425 ++InputRecord;
426 ++i;
427 CurrentInput = CurrentInput->Flink;
428
429 /* Remove the events from the queue if needed */
430 if (!KeepEvents)
431 {
432 RemoveEntryList(&Input->ListEntry);
433 ConsoleFreeHeap(Input);
434 }
435 }
436
437 if (NumEventsRead) *NumEventsRead = i;
438
439 if (IsListEmpty(&InputBuffer->InputEvents))
440 {
441 ResetEvent(InputBuffer->ActiveEvent);
442 }
443
444 /* We read all the inputs available, we return success */
445 return STATUS_SUCCESS;
446 }
447
448 NTSTATUS NTAPI
449 ConDrvWriteConsoleInput(IN PCONSOLE Console,
450 IN PCONSOLE_INPUT_BUFFER InputBuffer,
451 IN BOOLEAN Unicode,
452 IN BOOLEAN AppendToEnd,
453 IN PINPUT_RECORD InputRecord,
454 IN ULONG NumEventsToWrite,
455 OUT PULONG NumEventsWritten OPTIONAL)
456 {
457 NTSTATUS Status = STATUS_SUCCESS;
458 ULONG i;
459
460 if (Console == NULL || InputBuffer == NULL /* || InputRecord == NULL */)
461 return STATUS_INVALID_PARAMETER;
462
463 /* Validity checks */
464 ASSERT(Console == InputBuffer->Header.Console);
465 ASSERT( (InputRecord != NULL && NumEventsToWrite >= 0) ||
466 (InputRecord == NULL && NumEventsToWrite == 0) );
467
468 // Do NOT do that !! Use the existing number of events already written, if any...
469 // if (NumEventsWritten) *NumEventsWritten = 0;
470
471 for (i = (NumEventsWritten ? *NumEventsWritten : 0); i < NumEventsToWrite && NT_SUCCESS(Status); ++i)
472 {
473 if (InputRecord->EventType == KEY_EVENT && !Unicode)
474 {
475 CHAR AsciiChar = InputRecord->Event.KeyEvent.uChar.AsciiChar;
476 ConsoleInputAnsiCharToUnicodeChar(Console,
477 &InputRecord->Event.KeyEvent.uChar.UnicodeChar,
478 &AsciiChar);
479 }
480
481 Status = ConioAddInputEvent(Console, InputRecord++, AppendToEnd);
482 }
483
484 if (NumEventsWritten) *NumEventsWritten = i;
485
486 return Status;
487 }
488
489 NTSTATUS NTAPI
490 ConDrvFlushConsoleInputBuffer(IN PCONSOLE Console,
491 IN PCONSOLE_INPUT_BUFFER InputBuffer)
492 {
493 PLIST_ENTRY CurrentEntry;
494 ConsoleInput* Event;
495
496 if (Console == NULL || InputBuffer == NULL)
497 return STATUS_INVALID_PARAMETER;
498
499 /* Validity check */
500 ASSERT(Console == InputBuffer->Header.Console);
501
502 /* Discard all entries in the input event queue */
503 while (!IsListEmpty(&InputBuffer->InputEvents))
504 {
505 CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
506 Event = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
507 ConsoleFreeHeap(Event);
508 }
509 ResetEvent(InputBuffer->ActiveEvent);
510
511 return STATUS_SUCCESS;
512 }
513
514 NTSTATUS NTAPI
515 ConDrvGetConsoleNumberOfInputEvents(IN PCONSOLE Console,
516 IN PCONSOLE_INPUT_BUFFER InputBuffer,
517 OUT PULONG NumEvents)
518 {
519 PLIST_ENTRY CurrentInput;
520
521 if (Console == NULL || InputBuffer == NULL || NumEvents == NULL)
522 return STATUS_INVALID_PARAMETER;
523
524 /* Validity check */
525 ASSERT(Console == InputBuffer->Header.Console);
526
527 *NumEvents = 0;
528
529 /* If there are any events ... */
530 CurrentInput = InputBuffer->InputEvents.Flink;
531 while (CurrentInput != &InputBuffer->InputEvents)
532 {
533 CurrentInput = CurrentInput->Flink;
534 (*NumEvents)++;
535 }
536
537 return STATUS_SUCCESS;
538 }
539
540 /* EOF */