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  */
10 
11 /* INCLUDES *******************************************************************/
12 
13 #include <consrv.h>
14 
15 #include <coninput.h>
16 
17 #define NDEBUG
18 #include <debug.h>
19 
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     /* Initialize a new text-mode screen buffer with default settings */
224     ScreenBufferInfo.ScreenBufferSize = ConsoleInfo->ScreenBufferSize;
225     ScreenBufferInfo.ScreenAttrib     = ConsoleInfo->ScreenAttrib;
226     ScreenBufferInfo.PopupAttrib      = ConsoleInfo->PopupAttrib;
227     ScreenBufferInfo.IsCursorVisible  = TRUE;
228     ScreenBufferInfo.CursorSize       = ConsoleInfo->CursorSize;
229 
230     InitializeListHead(&Console->BufferList);
231     Status = ConDrvCreateScreenBuffer(&NewBuffer,
232                                       Console,
233                                       NULL,
234                                       CONSOLE_TEXTMODE_BUFFER,
235                                       &ScreenBufferInfo);
236     if (!NT_SUCCESS(Status))
237     {
238         DPRINT1("ConDrvCreateScreenBuffer: failed, Status = 0x%08lx\n", Status);
239         ConDrvDeinitInputBuffer(Console);
240         DeleteCriticalSection(&Console->Lock);
241         ConsoleFreeHeap(Console);
242         return Status;
243     }
244     /* Make the new screen buffer active */
245     Console->ActiveBuffer = NewBuffer;
246     Console->UnpauseEvent = NULL;
247 
248     DPRINT("Console initialized\n");
249 
250     /* All went right, so add the console to the list */
251     Status = ConDrvInsertConsole(Console);
252     if (!NT_SUCCESS(Status))
253     {
254         /* Fail */
255         ConDrvDeleteConsole(Console);
256         return Status;
257     }
258 
259     /* The initialization is finished */
260     DPRINT("Change state\n");
261     Console->State = CONSOLE_RUNNING;
262 
263     /* Return the newly created console to the caller and a success code too */
264     *NewConsole = Console;
265     return STATUS_SUCCESS;
266 }
267 
268 NTSTATUS NTAPI
269 ConDrvAttachTerminal(IN PCONSOLE Console,
270                      IN PTERMINAL Terminal)
271 {
272     NTSTATUS Status;
273 
274     if (Console == NULL || Terminal == NULL)
275         return STATUS_INVALID_PARAMETER;
276 
277     /* FIXME: Lock the console before ?? */
278 
279     /*
280      * Attach the terminal to the console. Use now the TermIFace of the console,
281      * and not the user-defined temporary Terminal pointer.
282      */
283     Console->TermIFace = *Terminal;
284     Console->TermIFace.Console = Console;
285 
286     /* Initialize the terminal AFTER having attached it to the console */
287     DPRINT("Finish initialization of terminal\n");
288     Status = Console->TermIFace.Vtbl->InitTerminal(&Console->TermIFace, Console);
289     if (!NT_SUCCESS(Status))
290     {
291         DPRINT1("Terminal initialization failed, Status = 0x%08lx\n", Status);
292 
293         /* We failed, detach the terminal from the console */
294         Terminal->Console = NULL; // For the caller
295         ResetTerminal(Console);
296         return Status;
297     }
298 
299     /* Copy buffer contents to screen */
300     // Terminal.Draw();
301 
302     DPRINT("Terminal initialization done\n");
303     return STATUS_SUCCESS;
304 }
305 
306 NTSTATUS NTAPI
307 ConDrvDetachTerminal(IN PCONSOLE Console)
308 {
309     if (Console == NULL) return STATUS_INVALID_PARAMETER;
310 
311     /* FIXME: Lock the console before ?? */
312 
313     /* Deinitialize the terminal BEFORE detaching it from the console */
314     Console->TermIFace.Vtbl->DeinitTerminal(&Console->TermIFace/*, Console*/);
315 
316     /*
317      * Detach the terminal from the console:
318      * reinitialize the terminal interface.
319      */
320     ResetTerminal(Console);
321 
322     DPRINT("Terminal unregistered\n");
323     return STATUS_SUCCESS;
324 }
325 
326 VOID NTAPI
327 ConDrvDeleteConsole(IN PCONSOLE Console)
328 {
329     DPRINT("ConDrvDeleteConsole(0x%p)\n", Console);
330 
331     /*
332      * Forbid validation of any console by other threads
333      * during the deletion of this console.
334      */
335     ConDrvLockConsoleListExclusive();
336 
337     /*
338      * If the console is already being destroyed, i.e. not running
339      * or finishing to be initialized, just return.
340      */
341     if (!ConDrvValidateConsoleUnsafe(Console, CONSOLE_RUNNING, TRUE) &&
342         !ConDrvValidateConsoleUnsafe(Console, CONSOLE_INITIALIZING, TRUE))
343     {
344         /* Unlock the console list and return */
345         ConDrvUnlockConsoleList();
346         return;
347     }
348 
349     /*
350      * We are about to be destroyed. Signal it to other people
351      * so that they can terminate what they are doing, and that
352      * they cannot longer validate the console.
353      */
354     Console->State = CONSOLE_TERMINATING;
355 
356     /*
357      * Allow other threads to finish their job: basically, unlock
358      * all other calls to EnterCriticalSection(&Console->Lock); by
359      * ConDrvValidateConsoleUnsafe functions so that they just see
360      * that we are not in CONSOLE_RUNNING state anymore, or unlock
361      * other concurrent calls to ConDrvDeleteConsole so that they
362      * can see that we are in fact already deleting the console.
363      */
364     LeaveCriticalSection(&Console->Lock);
365     ConDrvUnlockConsoleList();
366 
367     /* Deregister the terminal */
368     DPRINT("Deregister terminal\n");
369     ConDrvDetachTerminal(Console);
370     DPRINT("Terminal deregistered\n");
371 
372     /***
373      * Check that the console is in terminating state before continuing
374      * (the cleanup code must not change the state of the console...
375      * ...unless to cancel console deletion ?).
376      ***/
377 
378     ConDrvLockConsoleListExclusive();
379 
380     if (!ConDrvValidateConsoleUnsafe(Console, CONSOLE_TERMINATING, TRUE))
381     {
382         ConDrvUnlockConsoleList();
383         return;
384     }
385 
386     /* We are now in destruction */
387     Console->State = CONSOLE_IN_DESTRUCTION;
388 
389     /* We really delete the console. Reset the count to be sure. */
390     Console->ReferenceCount = 0;
391 
392     /* Remove the console from the list */
393     RemoveConsole(Console);
394 
395     /* Delete the last screen buffer */
396     ConDrvDeleteScreenBuffer(Console->ActiveBuffer);
397     Console->ActiveBuffer = NULL;
398     if (!IsListEmpty(&Console->BufferList))
399     {
400         /***ConDrvUnlockConsoleList();***/
401         ASSERTMSG("BUGBUGBUG!! screen buffer list not empty\n", FALSE);
402     }
403 
404     /* Deinitialize the input buffer */
405     ConDrvDeinitInputBuffer(Console);
406 
407     if (Console->UnpauseEvent) CloseHandle(Console->UnpauseEvent);
408 
409     DPRINT("ConDrvDeleteConsole - Unlocking\n");
410     LeaveCriticalSection(&Console->Lock);
411     DPRINT("ConDrvDeleteConsole - Destroying lock\n");
412     DeleteCriticalSection(&Console->Lock);
413     DPRINT("ConDrvDeleteConsole - Lock destroyed ; freeing console\n");
414 
415     ConsoleFreeHeap(Console);
416     DPRINT("ConDrvDeleteConsole - Console destroyed\n");
417 
418     /* Unlock the console list and return */
419     ConDrvUnlockConsoleList();
420 }
421 
422 
423 /* PUBLIC DRIVER APIS *********************************************************/
424 
425 NTSTATUS NTAPI
426 ConDrvGetConsoleMode(IN PCONSOLE Console,
427                      IN PCONSOLE_IO_OBJECT Object,
428                      OUT PULONG ConsoleMode)
429 {
430     NTSTATUS Status = STATUS_SUCCESS;
431 
432     if (Console == NULL || Object == NULL || ConsoleMode == NULL)
433         return STATUS_INVALID_PARAMETER;
434 
435     /* Validity check */
436     ASSERT(Console == Object->Console);
437 
438     /*** FIXME: */ *ConsoleMode = 0; /***/
439 
440     if (INPUT_BUFFER == Object->Type)
441     {
442         PCONSOLE_INPUT_BUFFER InputBuffer = (PCONSOLE_INPUT_BUFFER)Object;
443         *ConsoleMode = InputBuffer->Mode;
444     }
445     else if (TEXTMODE_BUFFER == Object->Type || GRAPHICS_BUFFER == Object->Type)
446     {
447         PCONSOLE_SCREEN_BUFFER Buffer = (PCONSOLE_SCREEN_BUFFER)Object;
448         *ConsoleMode = Buffer->Mode;
449     }
450     else
451     {
452         Status = STATUS_INVALID_HANDLE;
453     }
454 
455     return Status;
456 }
457 
458 NTSTATUS NTAPI
459 ConDrvSetConsoleMode(IN PCONSOLE Console,
460                      IN PCONSOLE_IO_OBJECT Object,
461                      IN ULONG ConsoleMode)
462 {
463 #define CONSOLE_VALID_INPUT_MODES   ( ENABLE_PROCESSED_INPUT  | ENABLE_LINE_INPUT   | \
464                                       ENABLE_ECHO_INPUT       | ENABLE_WINDOW_INPUT | \
465                                       ENABLE_MOUSE_INPUT )
466 #define CONSOLE_VALID_OUTPUT_MODES  ( ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT )
467 
468     NTSTATUS Status = STATUS_SUCCESS;
469 
470     if (Console == NULL || Object == NULL)
471         return STATUS_INVALID_PARAMETER;
472 
473     /* Validity check */
474     ASSERT(Console == Object->Console);
475 
476     if (INPUT_BUFFER == Object->Type)
477     {
478         PCONSOLE_INPUT_BUFFER InputBuffer = (PCONSOLE_INPUT_BUFFER)Object;
479 
480         /* Only the presence of valid mode flags is allowed */
481         if (ConsoleMode & ~CONSOLE_VALID_INPUT_MODES)
482         {
483             Status = STATUS_INVALID_PARAMETER;
484         }
485         else
486         {
487             InputBuffer->Mode = (ConsoleMode & CONSOLE_VALID_INPUT_MODES);
488         }
489     }
490     else if (TEXTMODE_BUFFER == Object->Type || GRAPHICS_BUFFER == Object->Type)
491     {
492         PCONSOLE_SCREEN_BUFFER Buffer = (PCONSOLE_SCREEN_BUFFER)Object;
493 
494         /* Only the presence of valid mode flags is allowed */
495         if (ConsoleMode & ~CONSOLE_VALID_OUTPUT_MODES)
496         {
497             Status = STATUS_INVALID_PARAMETER;
498         }
499         else
500         {
501             Buffer->Mode = (ConsoleMode & CONSOLE_VALID_OUTPUT_MODES);
502         }
503     }
504     else
505     {
506         Status = STATUS_INVALID_HANDLE;
507     }
508 
509     return Status;
510 }
511 
512 NTSTATUS NTAPI
513 ConDrvGetConsoleCP(IN PCONSOLE Console,
514                    OUT PUINT CodePage,
515                    IN BOOLEAN OutputCP)
516 {
517     if (Console == NULL || CodePage == NULL)
518         return STATUS_INVALID_PARAMETER;
519 
520     *CodePage = (OutputCP ? Console->OutputCodePage : Console->InputCodePage);
521 
522     return STATUS_SUCCESS;
523 }
524 
525 NTSTATUS NTAPI
526 ConDrvSetConsoleCP(IN PCONSOLE Console,
527                    IN UINT CodePage,
528                    IN BOOLEAN OutputCP)
529 {
530     if (Console == NULL || !IsValidCodePage(CodePage))
531         return STATUS_INVALID_PARAMETER;
532 
533     if (OutputCP)
534         Console->OutputCodePage = CodePage;
535     else
536         Console->InputCodePage = CodePage;
537 
538     return STATUS_SUCCESS;
539 }
540 
541 /* EOF */
542