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