1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS Console Driver DLL
4  * FILE:            win32ss/user/winsrv/consrv/condrv/console.c
5  * PURPOSE:         Console Management Functions
6  * PROGRAMMERS:     G� van Geldorp
7  *                  Jeffrey Morlan
8  *                  Hermes Belusca-Maito (hermes.belusca@sfr.fr)
9  *                  Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
10  */
11 
12 /* INCLUDES *******************************************************************/
13 
14 #include <consrv.h>
15 #include <coninput.h>
16 #include "../../concfg/font.h"
17 
18 #define NDEBUG
19 #include <debug.h>
20 
21 /* GLOBALS ********************************************************************/
22 
23 static ULONG CurrentConsoleID = 0;
24 
25 /* Linked list of consoles */
26 static LIST_ENTRY ConDrvConsoleList;
27 static RTL_RESOURCE ListLock;
28 
29 #define ConDrvLockConsoleListExclusive()    \
30     RtlAcquireResourceExclusive(&ListLock, TRUE)
31 
32 #define ConDrvLockConsoleListShared()       \
33     RtlAcquireResourceShared(&ListLock, TRUE)
34 
35 #define ConDrvUnlockConsoleList()           \
36     RtlReleaseResource(&ListLock)
37 
38 
39 static NTSTATUS
40 ConDrvInsertConsole(IN PCONSOLE Console)
41 {
42     ASSERT(Console);
43 
44     /* All went right, so add the console to the list */
45     ConDrvLockConsoleListExclusive();
46 
47     DPRINT("Insert in the list\n");
48     InsertTailList(&ConDrvConsoleList, &Console->ListEntry);
49 
50     // FIXME: Move this code to the caller function!!
51     /* Get a new console ID */
52     _InterlockedExchange((PLONG)&Console->ConsoleID, CurrentConsoleID);
53     _InterlockedIncrement((PLONG)&CurrentConsoleID);
54 
55     /* Unlock the console list and return success */
56     ConDrvUnlockConsoleList();
57     return STATUS_SUCCESS;
58 }
59 
60 static NTSTATUS
61 RemoveConsole(IN PCONSOLE Console)
62 {
63     // ASSERT(Console);
64     if (!Console) return STATUS_INVALID_PARAMETER;
65 
66     /* Remove the console from the list */
67     ConDrvLockConsoleListExclusive();
68 
69     RemoveEntryList(&Console->ListEntry);
70 
71     /* Unlock the console list and return success */
72     ConDrvUnlockConsoleList();
73     return STATUS_SUCCESS;
74 }
75 
76 
77 /* PRIVATE FUNCTIONS **********************************************************/
78 
79 VOID NTAPI
80 ConDrvPause(PCONSOLE Console)
81 {
82     /* In case we already have a pause event, just exit... */
83     if (Console->UnpauseEvent) return;
84 
85     /* ... otherwise create it */
86     NtCreateEvent(&Console->UnpauseEvent, EVENT_ALL_ACCESS,
87                   NULL, NotificationEvent, FALSE);
88 }
89 
90 VOID NTAPI
91 ConDrvUnpause(PCONSOLE Console)
92 {
93     /* In case we already freed the event, just exit... */
94     if (!Console->UnpauseEvent) return;
95 
96     /* ... otherwise set and free it */
97     NtSetEvent(Console->UnpauseEvent, NULL);
98     NtClose(Console->UnpauseEvent);
99     Console->UnpauseEvent = NULL;
100 }
101 
102 
103 /*
104  * Console accessibility check helpers
105  */
106 
107 BOOLEAN NTAPI
108 ConDrvValidateConsoleState(IN PCONSOLE Console,
109                            IN CONSOLE_STATE ExpectedState)
110 {
111     // if (!Console) return FALSE;
112 
113     /* The console must be locked */
114     // ASSERT(Console_locked);
115 
116     return (Console->State == ExpectedState);
117 }
118 
119 BOOLEAN NTAPI
120 ConDrvValidateConsoleUnsafe(IN PCONSOLE Console,
121                             IN CONSOLE_STATE ExpectedState,
122                             IN BOOLEAN LockConsole)
123 {
124     if (!Console) return FALSE;
125 
126     /*
127      * Lock the console to forbid possible console's state changes
128      * (which must be done when the console is already locked).
129      * If we don't want to lock it, it's because the lock is already
130      * held. So there must be no problems.
131      */
132     if (LockConsole) EnterCriticalSection(&Console->Lock);
133 
134     // ASSERT(Console_locked);
135 
136     /* Check whether the console's state is what we expect */
137     if (!ConDrvValidateConsoleState(Console, ExpectedState))
138     {
139         if (LockConsole) LeaveCriticalSection(&Console->Lock);
140         return FALSE;
141     }
142 
143     return TRUE;
144 }
145 
146 
147 /* CONSOLE INITIALIZATION FUNCTIONS *******************************************/
148 
149 VOID NTAPI
150 ConDrvInitConsoleSupport(VOID)
151 {
152     DPRINT("CONSRV: ConDrvInitConsoleSupport()\n");
153 
154     /* Initialize the console list and its lock */
155     InitializeListHead(&ConDrvConsoleList);
156     RtlInitializeResource(&ListLock);
157 }
158 
159 /* For resetting the terminal - defined in dummyterm.c */
160 VOID ResetTerminal(IN PCONSOLE Console);
161 
162 NTSTATUS NTAPI
163 ConDrvInitConsole(OUT PCONSOLE* NewConsole,
164                   IN PCONSOLE_INFO ConsoleInfo)
165 {
166     NTSTATUS Status;
167     // CONSOLE_INFO CapturedConsoleInfo;
168     TEXTMODE_BUFFER_INFO ScreenBufferInfo;
169     PCONSOLE Console;
170     PCONSOLE_SCREEN_BUFFER NewBuffer;
171 
172     if (NewConsole == NULL || ConsoleInfo == NULL)
173         return STATUS_INVALID_PARAMETER;
174 
175     *NewConsole = NULL;
176 
177     /*
178      * Allocate a new console
179      */
180     Console = ConsoleAllocHeap(HEAP_ZERO_MEMORY, sizeof(*Console));
181     if (NULL == Console)
182     {
183         DPRINT1("Not enough memory for console creation.\n");
184         return STATUS_NO_MEMORY;
185     }
186 
187     /*
188      * Fix the screen buffer size if needed. The rule is:
189      * ScreenBufferSize >= ConsoleSize
190      */
191     if (ConsoleInfo->ScreenBufferSize.X < ConsoleInfo->ConsoleSize.X)
192         ConsoleInfo->ScreenBufferSize.X = ConsoleInfo->ConsoleSize.X;
193     if (ConsoleInfo->ScreenBufferSize.Y < ConsoleInfo->ConsoleSize.Y)
194         ConsoleInfo->ScreenBufferSize.Y = ConsoleInfo->ConsoleSize.Y;
195 
196     /*
197      * Initialize the console
198      */
199     Console->State = CONSOLE_INITIALIZING;
200     Console->ReferenceCount = 0;
201     InitializeCriticalSection(&Console->Lock);
202 
203     /* Initialize the terminal interface */
204     ResetTerminal(Console);
205 
206     Console->ConsoleSize = ConsoleInfo->ConsoleSize;
207     Console->FixedSize   = FALSE; // Value by default; is reseted by the terminals if needed.
208 
209     /* Initialize the input buffer */
210     Status = ConDrvInitInputBuffer(Console, 0 /* ConsoleInfo->InputBufferSize */);
211     if (!NT_SUCCESS(Status))
212     {
213         DPRINT1("ConDrvInitInputBuffer: failed, Status = 0x%08lx\n", Status);
214         DeleteCriticalSection(&Console->Lock);
215         ConsoleFreeHeap(Console);
216         return Status;
217     }
218 
219     /* Set-up the code page */
220     if (IsValidCodePage(ConsoleInfo->CodePage))
221         Console->InputCodePage = Console->OutputCodePage = ConsoleInfo->CodePage;
222 
223     Console->IsCJK = IsCJKCodePage(Console->OutputCodePage);
224 
225     /* Initialize a new text-mode screen buffer with default settings */
226     ScreenBufferInfo.ScreenBufferSize = ConsoleInfo->ScreenBufferSize;
227     ScreenBufferInfo.ScreenAttrib     = ConsoleInfo->ScreenAttrib;
228     ScreenBufferInfo.PopupAttrib      = ConsoleInfo->PopupAttrib;
229     ScreenBufferInfo.IsCursorVisible  = TRUE;
230     ScreenBufferInfo.CursorSize       = ConsoleInfo->CursorSize;
231 
232     InitializeListHead(&Console->BufferList);
233     Status = ConDrvCreateScreenBuffer(&NewBuffer,
234                                       Console,
235                                       NULL,
236                                       CONSOLE_TEXTMODE_BUFFER,
237                                       &ScreenBufferInfo);
238     if (!NT_SUCCESS(Status))
239     {
240         DPRINT1("ConDrvCreateScreenBuffer: failed, Status = 0x%08lx\n", Status);
241         ConDrvDeinitInputBuffer(Console);
242         DeleteCriticalSection(&Console->Lock);
243         ConsoleFreeHeap(Console);
244         return Status;
245     }
246     /* Make the new screen buffer active */
247     Console->ActiveBuffer = NewBuffer;
248     Console->UnpauseEvent = NULL;
249 
250     DPRINT("Console initialized\n");
251 
252     /* All went right, so add the console to the list */
253     Status = ConDrvInsertConsole(Console);
254     if (!NT_SUCCESS(Status))
255     {
256         /* Fail */
257         ConDrvDeleteConsole(Console);
258         return Status;
259     }
260 
261     /* The initialization is finished */
262     DPRINT("Change state\n");
263     Console->State = CONSOLE_RUNNING;
264 
265     /* Return the newly created console to the caller and a success code too */
266     *NewConsole = Console;
267     return STATUS_SUCCESS;
268 }
269 
270 NTSTATUS NTAPI
271 ConDrvAttachTerminal(IN PCONSOLE Console,
272                      IN PTERMINAL Terminal)
273 {
274     NTSTATUS Status;
275 
276     if (Console == NULL || Terminal == NULL)
277         return STATUS_INVALID_PARAMETER;
278 
279     /* FIXME: Lock the console before ?? */
280 
281     /*
282      * Attach the terminal to the console. Use now the TermIFace of the console,
283      * and not the user-defined temporary Terminal pointer.
284      */
285     Console->TermIFace = *Terminal;
286     Console->TermIFace.Console = Console;
287 
288     /* Initialize the terminal AFTER having attached it to the console */
289     DPRINT("Finish initialization of terminal\n");
290     Status = Console->TermIFace.Vtbl->InitTerminal(&Console->TermIFace, Console);
291     if (!NT_SUCCESS(Status))
292     {
293         DPRINT1("Terminal initialization failed, Status = 0x%08lx\n", Status);
294 
295         /* We failed, detach the terminal from the console */
296         Terminal->Console = NULL; // For the caller
297         ResetTerminal(Console);
298         return Status;
299     }
300 
301     /* Copy buffer contents to screen */
302     // Terminal.Draw();
303 
304     DPRINT("Terminal initialization done\n");
305     return STATUS_SUCCESS;
306 }
307 
308 NTSTATUS NTAPI
309 ConDrvDetachTerminal(IN PCONSOLE Console)
310 {
311     if (Console == NULL) return STATUS_INVALID_PARAMETER;
312 
313     /* FIXME: Lock the console before ?? */
314 
315     /* Deinitialize the terminal BEFORE detaching it from the console */
316     Console->TermIFace.Vtbl->DeinitTerminal(&Console->TermIFace/*, Console*/);
317 
318     /*
319      * Detach the terminal from the console:
320      * reinitialize the terminal interface.
321      */
322     ResetTerminal(Console);
323 
324     DPRINT("Terminal unregistered\n");
325     return STATUS_SUCCESS;
326 }
327 
328 VOID NTAPI
329 ConDrvDeleteConsole(IN PCONSOLE Console)
330 {
331     DPRINT("ConDrvDeleteConsole(0x%p)\n", Console);
332 
333     /*
334      * Forbid validation of any console by other threads
335      * during the deletion of this console.
336      */
337     ConDrvLockConsoleListExclusive();
338 
339     /*
340      * If the console is already being destroyed, i.e. not running
341      * or finishing to be initialized, just return.
342      */
343     if (!ConDrvValidateConsoleUnsafe(Console, CONSOLE_RUNNING, TRUE) &&
344         !ConDrvValidateConsoleUnsafe(Console, CONSOLE_INITIALIZING, TRUE))
345     {
346         /* Unlock the console list and return */
347         ConDrvUnlockConsoleList();
348         return;
349     }
350 
351     /*
352      * We are about to be destroyed. Signal it to other people
353      * so that they can terminate what they are doing, and that
354      * they cannot longer validate the console.
355      */
356     Console->State = CONSOLE_TERMINATING;
357 
358     /*
359      * Allow other threads to finish their job: basically, unlock
360      * all other calls to EnterCriticalSection(&Console->Lock); by
361      * ConDrvValidateConsoleUnsafe functions so that they just see
362      * that we are not in CONSOLE_RUNNING state anymore, or unlock
363      * other concurrent calls to ConDrvDeleteConsole so that they
364      * can see that we are in fact already deleting the console.
365      */
366     LeaveCriticalSection(&Console->Lock);
367     ConDrvUnlockConsoleList();
368 
369     /* Deregister the terminal */
370     DPRINT("Deregister terminal\n");
371     ConDrvDetachTerminal(Console);
372     DPRINT("Terminal deregistered\n");
373 
374     /***
375      * Check that the console is in terminating state before continuing
376      * (the cleanup code must not change the state of the console...
377      * ...unless to cancel console deletion ?).
378      ***/
379 
380     ConDrvLockConsoleListExclusive();
381 
382     if (!ConDrvValidateConsoleUnsafe(Console, CONSOLE_TERMINATING, TRUE))
383     {
384         ConDrvUnlockConsoleList();
385         return;
386     }
387 
388     /* We are now in destruction */
389     Console->State = CONSOLE_IN_DESTRUCTION;
390 
391     /* We really delete the console. Reset the count to be sure. */
392     Console->ReferenceCount = 0;
393 
394     /* Remove the console from the list */
395     RemoveConsole(Console);
396 
397     /* Delete the last screen buffer */
398     ConDrvDeleteScreenBuffer(Console->ActiveBuffer);
399     Console->ActiveBuffer = NULL;
400     if (!IsListEmpty(&Console->BufferList))
401     {
402         /***ConDrvUnlockConsoleList();***/
403         ASSERTMSG("BUGBUGBUG!! screen buffer list not empty\n", FALSE);
404     }
405 
406     /* Deinitialize the input buffer */
407     ConDrvDeinitInputBuffer(Console);
408 
409     if (Console->UnpauseEvent) CloseHandle(Console->UnpauseEvent);
410 
411     DPRINT("ConDrvDeleteConsole - Unlocking\n");
412     LeaveCriticalSection(&Console->Lock);
413     DPRINT("ConDrvDeleteConsole - Destroying lock\n");
414     DeleteCriticalSection(&Console->Lock);
415     DPRINT("ConDrvDeleteConsole - Lock destroyed ; freeing console\n");
416 
417     ConsoleFreeHeap(Console);
418     DPRINT("ConDrvDeleteConsole - Console destroyed\n");
419 
420     /* Unlock the console list and return */
421     ConDrvUnlockConsoleList();
422 }
423 
424 
425 /* PUBLIC DRIVER APIS *********************************************************/
426 
427 NTSTATUS NTAPI
428 ConDrvGetConsoleMode(IN PCONSOLE Console,
429                      IN PCONSOLE_IO_OBJECT Object,
430                      OUT PULONG ConsoleMode)
431 {
432     NTSTATUS Status = STATUS_SUCCESS;
433 
434     if (Console == NULL || Object == NULL || ConsoleMode == NULL)
435         return STATUS_INVALID_PARAMETER;
436 
437     /* Validity check */
438     ASSERT(Console == Object->Console);
439 
440     /*** FIXME: */ *ConsoleMode = 0; /***/
441 
442     if (INPUT_BUFFER == Object->Type)
443     {
444         PCONSOLE_INPUT_BUFFER InputBuffer = (PCONSOLE_INPUT_BUFFER)Object;
445         *ConsoleMode = InputBuffer->Mode;
446     }
447     else if (TEXTMODE_BUFFER == Object->Type || GRAPHICS_BUFFER == Object->Type)
448     {
449         PCONSOLE_SCREEN_BUFFER Buffer = (PCONSOLE_SCREEN_BUFFER)Object;
450         *ConsoleMode = Buffer->Mode;
451     }
452     else
453     {
454         Status = STATUS_INVALID_HANDLE;
455     }
456 
457     return Status;
458 }
459 
460 NTSTATUS NTAPI
461 ConDrvSetConsoleMode(IN PCONSOLE Console,
462                      IN PCONSOLE_IO_OBJECT Object,
463                      IN ULONG ConsoleMode)
464 {
465 #define CONSOLE_VALID_INPUT_MODES   ( ENABLE_PROCESSED_INPUT  | ENABLE_LINE_INPUT   | \
466                                       ENABLE_ECHO_INPUT       | ENABLE_WINDOW_INPUT | \
467                                       ENABLE_MOUSE_INPUT )
468 #define CONSOLE_VALID_OUTPUT_MODES  ( ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT )
469 
470     NTSTATUS Status = STATUS_SUCCESS;
471 
472     if (Console == NULL || Object == NULL)
473         return STATUS_INVALID_PARAMETER;
474 
475     /* Validity check */
476     ASSERT(Console == Object->Console);
477 
478     if (INPUT_BUFFER == Object->Type)
479     {
480         PCONSOLE_INPUT_BUFFER InputBuffer = (PCONSOLE_INPUT_BUFFER)Object;
481 
482         /* Only the presence of valid mode flags is allowed */
483         if (ConsoleMode & ~CONSOLE_VALID_INPUT_MODES)
484         {
485             Status = STATUS_INVALID_PARAMETER;
486         }
487         else
488         {
489             InputBuffer->Mode = (ConsoleMode & CONSOLE_VALID_INPUT_MODES);
490         }
491     }
492     else if (TEXTMODE_BUFFER == Object->Type || GRAPHICS_BUFFER == Object->Type)
493     {
494         PCONSOLE_SCREEN_BUFFER Buffer = (PCONSOLE_SCREEN_BUFFER)Object;
495 
496         /* Only the presence of valid mode flags is allowed */
497         if (ConsoleMode & ~CONSOLE_VALID_OUTPUT_MODES)
498         {
499             Status = STATUS_INVALID_PARAMETER;
500         }
501         else
502         {
503             Buffer->Mode = (ConsoleMode & CONSOLE_VALID_OUTPUT_MODES);
504         }
505     }
506     else
507     {
508         Status = STATUS_INVALID_HANDLE;
509     }
510 
511     return Status;
512 }
513 
514 NTSTATUS NTAPI
515 ConDrvGetConsoleCP(IN PCONSOLE Console,
516                    OUT PUINT CodePage,
517                    IN BOOLEAN OutputCP)
518 {
519     if (Console == NULL || CodePage == NULL)
520         return STATUS_INVALID_PARAMETER;
521 
522     *CodePage = (OutputCP ? Console->OutputCodePage : Console->InputCodePage);
523 
524     return STATUS_SUCCESS;
525 }
526 
527 NTSTATUS NTAPI
528 ConDrvSetConsoleCP(IN PCONSOLE Console,
529                    IN UINT CodePage,
530                    IN BOOLEAN OutputCP)
531 {
532     if (Console == NULL || !IsValidCodePage(CodePage))
533         return STATUS_INVALID_PARAMETER;
534 
535     if (OutputCP)
536     {
537         Console->OutputCodePage = CodePage;
538         Console->IsCJK = IsCJKCodePage(CodePage);
539     }
540     else
541     {
542         Console->InputCodePage = CodePage;
543     }
544 
545     return STATUS_SUCCESS;
546 }
547 
548 /* EOF */
549