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 /* PRIVATE FUNCTIONS **********************************************************/
18 
19 // ConDrvAddInputEvents
20 static NTSTATUS
21 AddInputEvents(PCONSOLE Console,
22                PINPUT_RECORD InputRecords, // InputEvent
23                ULONG NumEventsToWrite,
24                PULONG NumEventsWritten,
25                BOOLEAN AppendToEnd)
26 {
27     NTSTATUS Status = STATUS_SUCCESS;
28     ULONG i = 0;
29     BOOLEAN SetWaitEvent = FALSE;
30 
31     if (NumEventsWritten) *NumEventsWritten = 0;
32 
33     /*
34      * When adding many single events, in the case of repeated mouse move or
35      * key down events, we try to coalesce them so that we do not saturate
36      * too quickly the input buffer.
37      */
38     if (NumEventsToWrite == 1 && !IsListEmpty(&Console->InputBuffer.InputEvents))
39     {
40         PINPUT_RECORD InputRecord = InputRecords; // Only one element
41         PINPUT_RECORD LastInputRecord;
42         ConsoleInput* ConInRec; // Input
43 
44         /* Get the "next" event of the input buffer */
45         if (AppendToEnd)
46         {
47             /* Get the tail element */
48             ConInRec = CONTAINING_RECORD(Console->InputBuffer.InputEvents.Blink,
49                                          ConsoleInput, ListEntry);
50         }
51         else
52         {
53             /* Get the head element */
54             ConInRec = CONTAINING_RECORD(Console->InputBuffer.InputEvents.Flink,
55                                          ConsoleInput, ListEntry);
56         }
57         LastInputRecord = &ConInRec->InputEvent;
58 
59         if (InputRecord->EventType == MOUSE_EVENT &&
60             InputRecord->Event.MouseEvent.dwEventFlags == MOUSE_MOVED)
61         {
62             if (LastInputRecord->EventType == MOUSE_EVENT &&
63                 LastInputRecord->Event.MouseEvent.dwEventFlags == MOUSE_MOVED)
64             {
65                 /* Update the mouse position */
66                 LastInputRecord->Event.MouseEvent.dwMousePosition.X =
67                     InputRecord->Event.MouseEvent.dwMousePosition.X;
68                 LastInputRecord->Event.MouseEvent.dwMousePosition.Y =
69                     InputRecord->Event.MouseEvent.dwMousePosition.Y;
70 
71                 i = 1;
72                 // return STATUS_SUCCESS;
73                 Status = STATUS_SUCCESS;
74             }
75         }
76         else if (InputRecord->EventType == KEY_EVENT &&
77                  InputRecord->Event.KeyEvent.bKeyDown)
78         {
79             if (LastInputRecord->EventType == KEY_EVENT &&
80                 LastInputRecord->Event.KeyEvent.bKeyDown &&
81                 (LastInputRecord->Event.KeyEvent.wVirtualScanCode ==    // Same scancode
82                      InputRecord->Event.KeyEvent.wVirtualScanCode) &&
83                 (LastInputRecord->Event.KeyEvent.uChar.UnicodeChar ==   // Same character
84                      InputRecord->Event.KeyEvent.uChar.UnicodeChar) &&
85                 (LastInputRecord->Event.KeyEvent.dwControlKeyState ==   // Same Ctrl/Alt/Shift state
86                      InputRecord->Event.KeyEvent.dwControlKeyState) )
87             {
88                 /* Update the repeat count */
89                 LastInputRecord->Event.KeyEvent.wRepeatCount +=
90                     InputRecord->Event.KeyEvent.wRepeatCount;
91 
92                 i = 1;
93                 // return STATUS_SUCCESS;
94                 Status = STATUS_SUCCESS;
95             }
96         }
97     }
98 
99     /* If we coalesced the only one element, we can quit */
100     if (i == 1 && Status == STATUS_SUCCESS /* && NumEventsToWrite == 1 */)
101         goto Done;
102 
103     /*
104      * No event coalesced, add them in the usual way.
105      */
106 
107     if (AppendToEnd)
108     {
109         /* Go to the beginning of the list */
110         // InputRecords = InputRecords;
111     }
112     else
113     {
114         /* Go to the end of the list */
115         InputRecords = &InputRecords[NumEventsToWrite - 1];
116     }
117 
118     /* Set the event if the list is going to be non-empty */
119     if (IsListEmpty(&Console->InputBuffer.InputEvents))
120         SetWaitEvent = TRUE;
121 
122     for (i = 0; i < NumEventsToWrite && NT_SUCCESS(Status); ++i)
123     {
124         PINPUT_RECORD InputRecord;
125         ConsoleInput* ConInRec;
126 
127         if (AppendToEnd)
128         {
129             /* Select the event and go to the next one */
130             InputRecord = InputRecords++;
131         }
132         else
133         {
134             /* Select the event and go to the previous one */
135             InputRecord = InputRecords--;
136         }
137 
138         /* Add event to the queue */
139         ConInRec = ConsoleAllocHeap(0, sizeof(ConsoleInput));
140         if (ConInRec == NULL)
141         {
142             // return STATUS_INSUFFICIENT_RESOURCES;
143             Status = STATUS_INSUFFICIENT_RESOURCES;
144             continue;
145         }
146 
147         ConInRec->InputEvent = *InputRecord;
148 
149         if (AppendToEnd)
150         {
151             /* Append the event to the end of the queue */
152             InsertTailList(&Console->InputBuffer.InputEvents, &ConInRec->ListEntry);
153         }
154         else
155         {
156             /* Append the event to the beginning of the queue */
157             InsertHeadList(&Console->InputBuffer.InputEvents, &ConInRec->ListEntry);
158         }
159         _InterlockedIncrement((PLONG)&Console->InputBuffer.NumberOfEvents);
160 
161         // return STATUS_SUCCESS;
162         Status = STATUS_SUCCESS;
163     }
164 
165     if (SetWaitEvent) NtSetEvent(Console->InputBuffer.ActiveEvent, NULL);
166 
167 Done:
168     if (NumEventsWritten) *NumEventsWritten = i;
169 
170     return Status;
171 }
172 
173 static VOID
174 PurgeInputBuffer(IN PCONSOLE_INPUT_BUFFER InputBuffer)
175 {
176     PLIST_ENTRY CurrentEntry;
177     ConsoleInput* Event;
178 
179     /* Discard all entries in the input event queue */
180     _InterlockedExchange((PLONG)&InputBuffer->NumberOfEvents, 0);
181     while (!IsListEmpty(&InputBuffer->InputEvents))
182     {
183         CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
184         Event = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
185         ConsoleFreeHeap(Event);
186     }
187 
188     // NtClose(Console->InputBuffer.ActiveEvent);
189 }
190 
191 NTSTATUS NTAPI
192 ConDrvInitInputBuffer(IN PCONSOLE Console,
193                       IN ULONG InputBufferSize)
194 {
195     NTSTATUS Status;
196     OBJECT_ATTRIBUTES ObjectAttributes;
197 
198     ConSrvInitObject(&Console->InputBuffer.Header, INPUT_BUFFER, Console);
199 
200     InitializeObjectAttributes(&ObjectAttributes,
201                                NULL,
202                                OBJ_INHERIT,
203                                NULL,
204                                NULL);
205 
206     Status = NtCreateEvent(&Console->InputBuffer.ActiveEvent, EVENT_ALL_ACCESS,
207                            &ObjectAttributes, NotificationEvent, FALSE);
208     if (!NT_SUCCESS(Status))
209         return Status;
210 
211     Console->InputBuffer.InputBufferSize = InputBufferSize;
212     Console->InputBuffer.NumberOfEvents  = 0;
213     InitializeListHead(&Console->InputBuffer.InputEvents);
214     Console->InputBuffer.Mode = ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT |
215                                 ENABLE_ECHO_INPUT      | ENABLE_MOUSE_INPUT;
216 
217     return STATUS_SUCCESS;
218 }
219 
220 VOID NTAPI
221 ConDrvDeinitInputBuffer(IN PCONSOLE Console)
222 {
223     PurgeInputBuffer(&Console->InputBuffer);
224     NtClose(Console->InputBuffer.ActiveEvent);
225 }
226 
227 
228 /* PUBLIC DRIVER APIS *********************************************************/
229 
230 NTSTATUS NTAPI
231 ConDrvReadConsole(IN PCONSOLE Console,
232                   IN PCONSOLE_INPUT_BUFFER InputBuffer,
233                   IN BOOLEAN Unicode,
234                   OUT PVOID Buffer,
235                   IN OUT PCONSOLE_READCONSOLE_CONTROL ReadControl,
236                   IN PVOID Parameter OPTIONAL,
237                   IN ULONG NumCharsToRead,
238                   OUT PULONG NumCharsRead OPTIONAL)
239 {
240     // STATUS_PENDING : Wait if more to read ; STATUS_SUCCESS : Don't wait.
241     // NTSTATUS Status; = STATUS_PENDING;
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) || (Buffer == NULL && NumCharsToRead == 0));
252 
253     /* Call the line-discipline */
254     return TermReadStream(Console,
255                           Unicode,
256                           Buffer,
257                           ReadControl,
258                           Parameter,
259                           NumCharsToRead,
260                           NumCharsRead);
261 }
262 
263 NTSTATUS NTAPI
264 ConDrvGetConsoleInput(IN PCONSOLE Console,
265                       IN PCONSOLE_INPUT_BUFFER InputBuffer,
266                       IN BOOLEAN KeepEvents,
267                       IN BOOLEAN WaitForMoreEvents,
268                       OUT PINPUT_RECORD InputRecord,
269                       IN ULONG NumEventsToRead,
270                       OUT PULONG NumEventsRead OPTIONAL)
271 {
272     PLIST_ENTRY CurrentInput;
273     ConsoleInput* Input;
274     ULONG i = 0;
275 
276     if (Console == NULL || InputBuffer == NULL /* || InputRecord == NULL */)
277         return STATUS_INVALID_PARAMETER;
278 
279     /* Validity checks */
280     ASSERT(Console == InputBuffer->Header.Console);
281     ASSERT((InputRecord != NULL) || (InputRecord == NULL && NumEventsToRead == 0));
282 
283     if (NumEventsRead) *NumEventsRead = 0;
284 
285     if (IsListEmpty(&InputBuffer->InputEvents))
286     {
287         /*
288          * No input is available. Wait for more input if requested,
289          * otherwise, we don't wait, so we return success.
290          */
291         return (WaitForMoreEvents ? STATUS_PENDING : STATUS_SUCCESS);
292     }
293 
294     /* Only get input if there is any */
295     CurrentInput = InputBuffer->InputEvents.Flink;
296     i = 0;
297     while ((CurrentInput != &InputBuffer->InputEvents) && (i < NumEventsToRead))
298     {
299         Input = CONTAINING_RECORD(CurrentInput, ConsoleInput, ListEntry);
300 
301         *InputRecord = Input->InputEvent;
302 
303         ++InputRecord;
304         ++i;
305         CurrentInput = CurrentInput->Flink;
306 
307         /* Remove the events from the queue if needed */
308         if (!KeepEvents)
309         {
310             _InterlockedDecrement((PLONG)&InputBuffer->NumberOfEvents);
311             RemoveEntryList(&Input->ListEntry);
312             ConsoleFreeHeap(Input);
313         }
314     }
315 
316     if (NumEventsRead) *NumEventsRead = i;
317 
318     if (IsListEmpty(&InputBuffer->InputEvents))
319     {
320         NtClearEvent(InputBuffer->ActiveEvent);
321     }
322 
323     // FIXME: If we add back UNICODE support, it's here that we need to do the translation.
324 
325     /* We read all the inputs available, we return success */
326     return STATUS_SUCCESS;
327 }
328 
329 NTSTATUS NTAPI
330 ConDrvWriteConsoleInput(IN PCONSOLE Console,
331                         IN PCONSOLE_INPUT_BUFFER InputBuffer,
332                         IN BOOLEAN AppendToEnd,
333                         IN PINPUT_RECORD InputRecord,
334                         IN ULONG NumEventsToWrite,
335                         OUT PULONG NumEventsWritten OPTIONAL)
336 {
337     if (Console == NULL || InputBuffer == NULL /* || InputRecord == NULL */)
338         return STATUS_INVALID_PARAMETER;
339 
340     /* Validity checks */
341     ASSERT(Console == InputBuffer->Header.Console);
342     ASSERT((InputRecord != NULL) || (InputRecord == NULL && NumEventsToWrite == 0));
343 
344     /* Now, add the events */
345     if (NumEventsWritten) *NumEventsWritten = 0;
346 
347     // FIXME: If we add back UNICODE support, it's here that we need to do the translation.
348 
349     return AddInputEvents(Console,
350                           InputRecord,
351                           NumEventsToWrite,
352                           NumEventsWritten,
353                           AppendToEnd);
354 }
355 
356 NTSTATUS NTAPI
357 ConDrvFlushConsoleInputBuffer(IN PCONSOLE Console,
358                               IN PCONSOLE_INPUT_BUFFER InputBuffer)
359 {
360     if (Console == NULL || InputBuffer == NULL)
361         return STATUS_INVALID_PARAMETER;
362 
363     /* Validity check */
364     ASSERT(Console == InputBuffer->Header.Console);
365 
366     /* Discard all entries in the input event queue */
367     PurgeInputBuffer(InputBuffer);
368     NtClearEvent(InputBuffer->ActiveEvent);
369 
370     return STATUS_SUCCESS;
371 }
372 
373 NTSTATUS NTAPI
374 ConDrvGetConsoleNumberOfInputEvents(IN PCONSOLE Console,
375                                     IN PCONSOLE_INPUT_BUFFER InputBuffer,
376                                     OUT PULONG NumberOfEvents)
377 {
378     if (Console == NULL || InputBuffer == NULL || NumberOfEvents == NULL)
379         return STATUS_INVALID_PARAMETER;
380 
381     /* Validity check */
382     ASSERT(Console == InputBuffer->Header.Console);
383 
384     *NumberOfEvents = InputBuffer->NumberOfEvents;
385     return STATUS_SUCCESS;
386 }
387 
388 /* EOF */
389