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