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 
160         // return STATUS_SUCCESS;
161         Status = STATUS_SUCCESS;
162     }
163 
164     if (SetWaitEvent) NtSetEvent(Console->InputBuffer.ActiveEvent, NULL);
165 
166 Done:
167     if (NumEventsWritten) *NumEventsWritten = i;
168 
169     return Status;
170 }
171 
172 static VOID
173 PurgeInputBuffer(PCONSOLE Console)
174 {
175     PLIST_ENTRY CurrentEntry;
176     ConsoleInput* Event;
177 
178     while (!IsListEmpty(&Console->InputBuffer.InputEvents))
179     {
180         CurrentEntry = RemoveHeadList(&Console->InputBuffer.InputEvents);
181         Event = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
182         ConsoleFreeHeap(Event);
183     }
184 
185     // CloseHandle(Console->InputBuffer.ActiveEvent);
186 }
187 
188 NTSTATUS NTAPI
189 ConDrvInitInputBuffer(IN PCONSOLE Console,
190                       IN ULONG InputBufferSize)
191 {
192     NTSTATUS Status;
193     OBJECT_ATTRIBUTES ObjectAttributes;
194 
195     ConSrvInitObject(&Console->InputBuffer.Header, INPUT_BUFFER, Console);
196 
197     InitializeObjectAttributes(&ObjectAttributes,
198                                NULL,
199                                OBJ_INHERIT,
200                                NULL,
201                                NULL);
202 
203     Status = NtCreateEvent(&Console->InputBuffer.ActiveEvent, EVENT_ALL_ACCESS,
204                            &ObjectAttributes, NotificationEvent, FALSE);
205     if (!NT_SUCCESS(Status))
206         return Status;
207 
208     Console->InputBuffer.InputBufferSize = InputBufferSize;
209     InitializeListHead(&Console->InputBuffer.InputEvents);
210     Console->InputBuffer.Mode = ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT |
211                                 ENABLE_ECHO_INPUT      | ENABLE_MOUSE_INPUT;
212 
213     return STATUS_SUCCESS;
214 }
215 
216 VOID NTAPI
217 ConDrvDeinitInputBuffer(IN PCONSOLE Console)
218 {
219     PurgeInputBuffer(Console);
220     CloseHandle(Console->InputBuffer.ActiveEvent);
221 }
222 
223 
224 /* PUBLIC DRIVER APIS *********************************************************/
225 
226 NTSTATUS NTAPI
227 ConDrvReadConsole(IN PCONSOLE Console,
228                   IN PCONSOLE_INPUT_BUFFER InputBuffer,
229                   IN BOOLEAN Unicode,
230                   OUT PVOID Buffer,
231                   IN OUT PCONSOLE_READCONSOLE_CONTROL ReadControl,
232                   IN PVOID Parameter OPTIONAL,
233                   IN ULONG NumCharsToRead,
234                   OUT PULONG NumCharsRead OPTIONAL)
235 {
236     // STATUS_PENDING : Wait if more to read ; STATUS_SUCCESS : Don't wait.
237     // NTSTATUS Status; = STATUS_PENDING;
238 
239     if (Console == NULL || InputBuffer == NULL || /* Buffer == NULL  || */
240         ReadControl == NULL || ReadControl->nLength != sizeof(CONSOLE_READCONSOLE_CONTROL))
241     {
242         return STATUS_INVALID_PARAMETER;
243     }
244 
245     /* Validity checks */
246     ASSERT(Console == InputBuffer->Header.Console);
247     ASSERT((Buffer != NULL) || (Buffer == NULL && NumCharsToRead == 0));
248 
249     /* Call the line-discipline */
250     return TermReadStream(Console,
251                           Unicode,
252                           Buffer,
253                           ReadControl,
254                           Parameter,
255                           NumCharsToRead,
256                           NumCharsRead);
257 }
258 
259 NTSTATUS NTAPI
260 ConDrvGetConsoleInput(IN PCONSOLE Console,
261                       IN PCONSOLE_INPUT_BUFFER InputBuffer,
262                       IN BOOLEAN KeepEvents,
263                       IN BOOLEAN WaitForMoreEvents,
264                       OUT PINPUT_RECORD InputRecord,
265                       IN ULONG NumEventsToRead,
266                       OUT PULONG NumEventsRead OPTIONAL)
267 {
268     PLIST_ENTRY CurrentInput;
269     ConsoleInput* Input;
270     ULONG i = 0;
271 
272     if (Console == NULL || InputBuffer == NULL /* || InputRecord == NULL */)
273         return STATUS_INVALID_PARAMETER;
274 
275     /* Validity checks */
276     ASSERT(Console == InputBuffer->Header.Console);
277     ASSERT((InputRecord != NULL) || (InputRecord == NULL && NumEventsToRead == 0));
278 
279     if (NumEventsRead) *NumEventsRead = 0;
280 
281     if (IsListEmpty(&InputBuffer->InputEvents))
282     {
283         /*
284          * No input is available. Wait for more input if requested,
285          * otherwise, we don't wait, so we return success.
286          */
287         return (WaitForMoreEvents ? STATUS_PENDING : STATUS_SUCCESS);
288     }
289 
290     /* Only get input if there is any */
291     CurrentInput = InputBuffer->InputEvents.Flink;
292     i = 0;
293     while ((CurrentInput != &InputBuffer->InputEvents) && (i < NumEventsToRead))
294     {
295         Input = CONTAINING_RECORD(CurrentInput, ConsoleInput, ListEntry);
296 
297         *InputRecord = Input->InputEvent;
298 
299         ++InputRecord;
300         ++i;
301         CurrentInput = CurrentInput->Flink;
302 
303         /* Remove the events from the queue if needed */
304         if (!KeepEvents)
305         {
306             RemoveEntryList(&Input->ListEntry);
307             ConsoleFreeHeap(Input);
308         }
309     }
310 
311     if (NumEventsRead) *NumEventsRead = i;
312 
313     if (IsListEmpty(&InputBuffer->InputEvents))
314     {
315         ResetEvent(InputBuffer->ActiveEvent);
316     }
317 
318     // FIXME: If we add back UNICODE support, it's here that we need to do the translation.
319 
320     /* We read all the inputs available, we return success */
321     return STATUS_SUCCESS;
322 }
323 
324 NTSTATUS NTAPI
325 ConDrvWriteConsoleInput(IN PCONSOLE Console,
326                         IN PCONSOLE_INPUT_BUFFER InputBuffer,
327                         IN BOOLEAN AppendToEnd,
328                         IN PINPUT_RECORD InputRecord,
329                         IN ULONG NumEventsToWrite,
330                         OUT PULONG NumEventsWritten OPTIONAL)
331 {
332     if (Console == NULL || InputBuffer == NULL /* || InputRecord == NULL */)
333         return STATUS_INVALID_PARAMETER;
334 
335     /* Validity checks */
336     ASSERT(Console == InputBuffer->Header.Console);
337     ASSERT((InputRecord != NULL) || (InputRecord == NULL && NumEventsToWrite == 0));
338 
339     /* Now, add the events */
340     if (NumEventsWritten) *NumEventsWritten = 0;
341 
342     // FIXME: If we add back UNICODE support, it's here that we need to do the translation.
343 
344     return AddInputEvents(Console,
345                           InputRecord,
346                           NumEventsToWrite,
347                           NumEventsWritten,
348                           AppendToEnd);
349 }
350 
351 NTSTATUS NTAPI
352 ConDrvFlushConsoleInputBuffer(IN PCONSOLE Console,
353                               IN PCONSOLE_INPUT_BUFFER InputBuffer)
354 {
355     PLIST_ENTRY CurrentEntry;
356     ConsoleInput* Event;
357 
358     if (Console == NULL || InputBuffer == NULL)
359         return STATUS_INVALID_PARAMETER;
360 
361     /* Validity check */
362     ASSERT(Console == InputBuffer->Header.Console);
363 
364     /* Discard all entries in the input event queue */
365     while (!IsListEmpty(&InputBuffer->InputEvents))
366     {
367         CurrentEntry = RemoveHeadList(&InputBuffer->InputEvents);
368         Event = CONTAINING_RECORD(CurrentEntry, ConsoleInput, ListEntry);
369         ConsoleFreeHeap(Event);
370     }
371     ResetEvent(InputBuffer->ActiveEvent);
372 
373     return STATUS_SUCCESS;
374 }
375 
376 NTSTATUS NTAPI
377 ConDrvGetConsoleNumberOfInputEvents(IN PCONSOLE Console,
378                                     IN PCONSOLE_INPUT_BUFFER InputBuffer,
379                                     OUT PULONG NumberOfEvents)
380 {
381     PLIST_ENTRY CurrentInput;
382 
383     if (Console == NULL || InputBuffer == NULL || NumberOfEvents == NULL)
384         return STATUS_INVALID_PARAMETER;
385 
386     /* Validity check */
387     ASSERT(Console == InputBuffer->Header.Console);
388 
389     *NumberOfEvents = 0;
390 
391     /* If there are any events ... */
392     CurrentInput = InputBuffer->InputEvents.Flink;
393     while (CurrentInput != &InputBuffer->InputEvents)
394     {
395         CurrentInput = CurrentInput->Flink;
396         (*NumberOfEvents)++;
397     }
398 
399     return STATUS_SUCCESS;
400 }
401 
402 /* EOF */
403