xref: /reactos/dll/win32/kernel32/client/console/init.c (revision 3e44a5d7)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS system libraries
4  * FILE:            dll/win32/kernel32/client/console/init.c
5  * PURPOSE:         Console API Client Initialization
6  * PROGRAMMERS:     Alex Ionescu (alex.ionescu@reactos.org)
7  *                  Aleksey Bragin (aleksey@reactos.org)
8  *                  Hermes Belusca-Maito (hermes.belusca@sfr.fr)
9  */
10 
11 /* INCLUDES *******************************************************************/
12 
13 #include <k32.h>
14 
15 // For Control Panel Applet
16 #include <cpl.h>
17 
18 #define NDEBUG
19 #include <debug.h>
20 
21 
22 /* GLOBALS ********************************************************************/
23 
24 RTL_CRITICAL_SECTION ConsoleLock;
25 BOOLEAN ConsoleInitialized = FALSE;
26 extern HANDLE InputWaitHandle;
27 
28 static const PWSTR DefaultConsoleTitle = L"ReactOS Console";
29 
30 /* FUNCTIONS ******************************************************************/
31 
32 DWORD
33 WINAPI
PropDialogHandler(IN LPVOID lpThreadParameter)34 PropDialogHandler(IN LPVOID lpThreadParameter)
35 {
36     // NOTE: lpThreadParameter corresponds to the client shared section handle.
37 
38     NTSTATUS Status = STATUS_SUCCESS;
39     HMODULE hConsoleApplet = NULL;
40     APPLET_PROC CPlApplet;
41     static BOOL AlreadyDisplayingProps = FALSE;
42     WCHAR szBuffer[MAX_PATH];
43 
44     /*
45      * Do not launch more than once the console property dialog applet,
46      * or (albeit less probable), if we are not initialized.
47      */
48     if (!ConsoleInitialized || AlreadyDisplayingProps)
49     {
50         /* Close the associated client shared section handle if needed */
51         if (lpThreadParameter)
52             CloseHandle((HANDLE)lpThreadParameter);
53 
54         return STATUS_UNSUCCESSFUL;
55     }
56 
57     AlreadyDisplayingProps = TRUE;
58 
59     /* Load the control applet */
60     GetSystemDirectoryW(szBuffer, MAX_PATH);
61     wcscat(szBuffer, L"\\console.dll");
62     hConsoleApplet = LoadLibraryW(szBuffer);
63     if (hConsoleApplet == NULL)
64     {
65         DPRINT1("Failed to load console.dll\n");
66         Status = STATUS_UNSUCCESSFUL;
67         goto Quit;
68     }
69 
70     /* Load its main function */
71     CPlApplet = (APPLET_PROC)GetProcAddress(hConsoleApplet, "CPlApplet");
72     if (CPlApplet == NULL)
73     {
74         DPRINT1("Error: console.dll misses CPlApplet export\n");
75         Status = STATUS_UNSUCCESSFUL;
76         goto Quit;
77     }
78 
79     /* Initialize the applet */
80     if (CPlApplet(NULL, CPL_INIT, 0, 0) == FALSE)
81     {
82         DPRINT1("Error: failed to initialize console.dll\n");
83         Status = STATUS_UNSUCCESSFUL;
84         goto Quit;
85     }
86 
87     /* Check the count */
88     if (CPlApplet(NULL, CPL_GETCOUNT, 0, 0) != 1)
89     {
90         DPRINT1("Error: console.dll returned unexpected CPL count\n");
91         Status = STATUS_UNSUCCESSFUL;
92         goto Quit;
93     }
94 
95     /*
96      * Start the applet. For Windows compatibility purposes we need
97      * to pass the client shared section handle (lpThreadParameter)
98      * via the hWnd parameter of the CPlApplet function.
99      */
100     CPlApplet((HWND)lpThreadParameter, CPL_DBLCLK, 0, 0);
101 
102     /* We have finished */
103     CPlApplet(NULL, CPL_EXIT, 0, 0);
104 
105 Quit:
106     if (hConsoleApplet) FreeLibrary(hConsoleApplet);
107     AlreadyDisplayingProps = FALSE;
108     return Status;
109 }
110 
111 
112 static INT
ParseShellInfo(LPCWSTR lpszShellInfo,LPCWSTR lpszKeyword)113 ParseShellInfo(LPCWSTR lpszShellInfo,
114                LPCWSTR lpszKeyword)
115 {
116     DPRINT("ParseShellInfo is UNIMPLEMENTED\n");
117     return 0;
118 }
119 
120 
121 /*
122  * NOTE:
123  * The "LPDWORD Length" parameters point on input to the maximum size of
124  * the buffers that can hold data (if != 0), and on output they hold the
125  * real size of the data. If "Length" are == 0 on input, then on output
126  * they receive the full size of the data.
127  * The "LPWSTR* lpTitle" parameter has a double meaning:
128  * - when "CaptureTitle" is TRUE, data is copied to the buffer pointed
129  *   by the pointer (*lpTitle).
130  * - when "CaptureTitle" is FALSE, "*lpTitle" is set to the address of
131  *   the source data.
132  */
133 VOID
SetUpConsoleInfo(IN BOOLEAN CaptureTitle,IN OUT LPDWORD pTitleLength,IN OUT LPWSTR * lpTitle OPTIONAL,IN OUT LPDWORD pDesktopLength,IN OUT LPWSTR * lpDesktop OPTIONAL,IN OUT PCONSOLE_START_INFO ConsoleStartInfo)134 SetUpConsoleInfo(IN BOOLEAN CaptureTitle,
135                  IN OUT LPDWORD pTitleLength,
136                  IN OUT LPWSTR* lpTitle OPTIONAL,
137                  IN OUT LPDWORD pDesktopLength,
138                  IN OUT LPWSTR* lpDesktop OPTIONAL,
139                  IN OUT PCONSOLE_START_INFO ConsoleStartInfo)
140 {
141     PRTL_USER_PROCESS_PARAMETERS Parameters = NtCurrentPeb()->ProcessParameters;
142     DWORD Length;
143 
144     /* Initialize the fields */
145 
146     ConsoleStartInfo->IconIndex = 0;
147     ConsoleStartInfo->hIcon   = NULL;
148     ConsoleStartInfo->hIconSm = NULL;
149     ConsoleStartInfo->dwStartupFlags = Parameters->WindowFlags;
150     ConsoleStartInfo->nFont = 0;
151     ConsoleStartInfo->nInputBufferSize = 0;
152     ConsoleStartInfo->uCodePage = GetOEMCP();
153 
154     if (lpTitle)
155     {
156         LPWSTR Title;
157 
158         /* If we don't have any title, use the default one */
159         if (Parameters->WindowTitle.Buffer == NULL)
160         {
161             Title  = DefaultConsoleTitle;
162             Length = lstrlenW(DefaultConsoleTitle) * sizeof(WCHAR); // sizeof(DefaultConsoleTitle);
163         }
164         else
165         {
166             Title  = Parameters->WindowTitle.Buffer;
167             Length = Parameters->WindowTitle.Length;
168         }
169 
170         /* Retrieve the needed buffer size */
171         Length += sizeof(WCHAR);
172         if (*pTitleLength > 0) Length = min(Length, *pTitleLength);
173         *pTitleLength = Length;
174 
175         /* Capture the data if needed, or, return a pointer to it */
176         if (CaptureTitle)
177         {
178             /*
179              * Length is always >= sizeof(WCHAR). Copy everything but the
180              * possible trailing NULL character, and then NULL-terminate.
181              */
182             Length -= sizeof(WCHAR);
183             RtlCopyMemory(*lpTitle, Title, Length);
184             (*lpTitle)[Length / sizeof(WCHAR)] = UNICODE_NULL;
185         }
186         else
187         {
188             *lpTitle = Title;
189         }
190     }
191     else
192     {
193         *pTitleLength = 0;
194     }
195 
196     if (lpDesktop && Parameters->DesktopInfo.Buffer && *Parameters->DesktopInfo.Buffer)
197     {
198         /* Retrieve the needed buffer size */
199         Length = Parameters->DesktopInfo.Length + sizeof(WCHAR);
200         if (*pDesktopLength > 0) Length = min(Length, *pDesktopLength);
201         *pDesktopLength = Length;
202 
203         /* Return a pointer to the data */
204         *lpDesktop = Parameters->DesktopInfo.Buffer;
205     }
206     else
207     {
208         *pDesktopLength = 0;
209         if (lpDesktop) *lpDesktop = NULL;
210     }
211 
212     if (Parameters->WindowFlags & STARTF_USEFILLATTRIBUTE)
213     {
214         ConsoleStartInfo->wFillAttribute = (WORD)Parameters->FillAttribute;
215     }
216     if (Parameters->WindowFlags & STARTF_USECOUNTCHARS)
217     {
218         ConsoleStartInfo->dwScreenBufferSize.X = (SHORT)Parameters->CountCharsX;
219         ConsoleStartInfo->dwScreenBufferSize.Y = (SHORT)Parameters->CountCharsY;
220     }
221     if (Parameters->WindowFlags & STARTF_USESHOWWINDOW)
222     {
223         ConsoleStartInfo->wShowWindow = (WORD)Parameters->ShowWindowFlags;
224     }
225     if (Parameters->WindowFlags & STARTF_USEPOSITION)
226     {
227         ConsoleStartInfo->dwWindowOrigin.X = (SHORT)Parameters->StartingX;
228         ConsoleStartInfo->dwWindowOrigin.Y = (SHORT)Parameters->StartingY;
229     }
230     if (Parameters->WindowFlags & STARTF_USESIZE)
231     {
232         ConsoleStartInfo->dwWindowSize.X = (SHORT)Parameters->CountX;
233         ConsoleStartInfo->dwWindowSize.Y = (SHORT)Parameters->CountY;
234     }
235 
236     /* Get shell information (ShellInfo.Buffer is NULL-terminated) */
237     if (Parameters->ShellInfo.Buffer != NULL)
238     {
239         ConsoleStartInfo->IconIndex = ParseShellInfo(Parameters->ShellInfo.Buffer, L"dde.");
240 
241         if ((Parameters->WindowFlags & STARTF_USEHOTKEY) == 0)
242             ConsoleStartInfo->dwHotKey = ParseShellInfo(Parameters->ShellInfo.Buffer, L"hotkey.");
243         else
244             ConsoleStartInfo->dwHotKey = HandleToUlong(Parameters->StandardInput);
245     }
246 }
247 
248 
249 VOID
SetUpHandles(IN PCONSOLE_START_INFO ConsoleStartInfo)250 SetUpHandles(IN PCONSOLE_START_INFO ConsoleStartInfo)
251 {
252     PRTL_USER_PROCESS_PARAMETERS Parameters = NtCurrentPeb()->ProcessParameters;
253 
254     if (ConsoleStartInfo->dwStartupFlags & STARTF_USEHOTKEY)
255     {
256         Parameters->WindowFlags &= ~STARTF_USEHOTKEY;
257     }
258     if (ConsoleStartInfo->dwStartupFlags & STARTF_SHELLPRIVATE)
259     {
260         Parameters->WindowFlags &= ~STARTF_SHELLPRIVATE;
261     }
262 
263     /* We got the handles, let's set them */
264     Parameters->ConsoleHandle = ConsoleStartInfo->ConsoleHandle;
265 
266     if ((ConsoleStartInfo->dwStartupFlags & STARTF_USESTDHANDLES) == 0)
267     {
268         Parameters->StandardInput  = ConsoleStartInfo->InputHandle;
269         Parameters->StandardOutput = ConsoleStartInfo->OutputHandle;
270         Parameters->StandardError  = ConsoleStartInfo->ErrorHandle;
271     }
272 }
273 
274 
275 static BOOLEAN
IsConsoleApp(VOID)276 IsConsoleApp(VOID)
277 {
278     PIMAGE_NT_HEADERS ImageNtHeader = RtlImageNtHeader(NtCurrentPeb()->ImageBaseAddress);
279     return (ImageNtHeader && (ImageNtHeader->OptionalHeader.Subsystem ==
280                               IMAGE_SUBSYSTEM_WINDOWS_CUI));
281 }
282 
283 
284 static BOOLEAN
ConnectConsole(IN PWSTR SessionDir,IN PCONSRV_API_CONNECTINFO ConnectInfo,OUT PBOOLEAN InServerProcess)285 ConnectConsole(IN PWSTR SessionDir,
286                IN PCONSRV_API_CONNECTINFO ConnectInfo,
287                OUT PBOOLEAN InServerProcess)
288 {
289     NTSTATUS Status;
290     ULONG ConnectInfoSize = sizeof(*ConnectInfo);
291 
292     ASSERT(SessionDir);
293 
294     /* Connect to the Console Server */
295     DPRINT("Connecting to the Console Server...\n");
296     Status = CsrClientConnectToServer(SessionDir,
297                                       CONSRV_SERVERDLL_INDEX,
298                                       ConnectInfo,
299                                       &ConnectInfoSize,
300                                       InServerProcess);
301     if (!NT_SUCCESS(Status))
302     {
303         DPRINT1("Failed to connect to the Console Server (Status %lx)\n", Status);
304         return FALSE;
305     }
306 
307     /* Nothing to do for server-to-server */
308     if (*InServerProcess) return TRUE;
309 
310     /* Nothing to do if this is not a console app */
311     if (!ConnectInfo->IsConsoleApp) return TRUE;
312 
313     /* Wait for the connection to finish */
314     // Is ConnectInfo->ConsoleStartInfo.InitEvents aligned on handle boundary ????
315     Status = NtWaitForMultipleObjects(MAX_INIT_EVENTS,
316                                       ConnectInfo->ConsoleStartInfo.InitEvents,
317                                       WaitAny, FALSE, NULL);
318     if (!NT_SUCCESS(Status))
319     {
320         BaseSetLastNTError(Status);
321         return FALSE;
322     }
323 
324     NtClose(ConnectInfo->ConsoleStartInfo.InitEvents[INIT_SUCCESS]);
325     NtClose(ConnectInfo->ConsoleStartInfo.InitEvents[INIT_FAILURE]);
326     if (Status != INIT_SUCCESS)
327     {
328         NtCurrentPeb()->ProcessParameters->ConsoleHandle = NULL;
329         return FALSE;
330     }
331 
332     return TRUE;
333 }
334 
335 
336 BOOLEAN
337 WINAPI
ConDllInitialize(IN ULONG Reason,IN PWSTR SessionDir)338 ConDllInitialize(IN ULONG Reason,
339                  IN PWSTR SessionDir)
340 {
341     NTSTATUS Status;
342     PRTL_USER_PROCESS_PARAMETERS Parameters = NtCurrentPeb()->ProcessParameters;
343     BOOLEAN InServerProcess = FALSE;
344     CONSRV_API_CONNECTINFO ConnectInfo;
345 
346     if (Reason != DLL_PROCESS_ATTACH)
347     {
348         if ((Reason == DLL_THREAD_ATTACH) && IsConsoleApp())
349         {
350             /* Sync the new thread's LangId with the console's one */
351             SetTEBLangID();
352         }
353         else if (Reason == DLL_PROCESS_DETACH)
354         {
355             /* Free our resources */
356             if (ConsoleInitialized != FALSE)
357             {
358                 ConsoleInitialized = FALSE;
359                 RtlDeleteCriticalSection(&ConsoleLock);
360             }
361         }
362 
363         return TRUE;
364     }
365 
366     DPRINT("ConDllInitialize for: %wZ\n"
367            "Our current console handles are: 0x%p, 0x%p, 0x%p 0x%p\n",
368            &Parameters->ImagePathName,
369            Parameters->ConsoleHandle,
370            Parameters->StandardInput,
371            Parameters->StandardOutput,
372            Parameters->StandardError);
373 
374     /* Initialize our global console DLL lock */
375     Status = RtlInitializeCriticalSection(&ConsoleLock);
376     if (!NT_SUCCESS(Status)) return FALSE;
377     ConsoleInitialized = TRUE;
378 
379     /* Show by default the console window when applicable */
380     ConnectInfo.IsWindowVisible = TRUE;
381     /* If this is a console app, a console will be created/opened */
382     ConnectInfo.IsConsoleApp = IsConsoleApp();
383 
384     /* Do nothing if this is not a console app... */
385     if (!ConnectInfo.IsConsoleApp)
386     {
387         DPRINT("Image is not a console application\n");
388     }
389 
390     /*
391      * Handle the special flags given to us by BasePushProcessParameters.
392      */
393     if (Parameters->ConsoleHandle == HANDLE_DETACHED_PROCESS)
394     {
395         /* No console to create */
396         DPRINT("No console to create\n");
397         /*
398          * The new process does not inherit its parent's console and cannot
399          * attach to the console of its parent. The new process can call the
400          * AllocConsole function at a later time to create a console.
401          */
402         Parameters->ConsoleHandle = NULL;  // Do not inherit the parent's console.
403         ConnectInfo.IsConsoleApp  = FALSE; // Do not create any console.
404     }
405     else if (Parameters->ConsoleHandle == HANDLE_CREATE_NEW_CONSOLE)
406     {
407         /* We'll get the real one soon */
408         DPRINT("Creating a new separate console\n");
409         /*
410          * The new process has a new console, instead of inheriting
411          * its parent's console.
412          */
413         Parameters->ConsoleHandle = NULL; // Do not inherit the parent's console.
414     }
415     else if (Parameters->ConsoleHandle == HANDLE_CREATE_NO_WINDOW)
416     {
417         /* We'll get the real one soon */
418         DPRINT("Creating a new invisible console\n");
419         /*
420          * The process is a console application that is being run
421          * without a console window. Therefore, the console handle
422          * for the application is not set.
423          */
424         Parameters->ConsoleHandle   = NULL;  // Do not inherit the parent's console.
425         ConnectInfo.IsWindowVisible = FALSE; // A console is created but is not shown to the user.
426     }
427     else
428     {
429         DPRINT("Using existing console: 0x%p\n", Parameters->ConsoleHandle);
430     }
431 
432     /* Do nothing if this is not a console app... */
433     if (!ConnectInfo.IsConsoleApp)
434     {
435         /* Do not inherit the parent's console if we are not a console app */
436         Parameters->ConsoleHandle = NULL;
437     }
438 
439     /* Now use the proper console handle */
440     ConnectInfo.ConsoleStartInfo.ConsoleHandle = Parameters->ConsoleHandle;
441 
442     /* Initialize the console dispatchers */
443     ConnectInfo.CtrlRoutine = ConsoleControlDispatcher;
444     ConnectInfo.PropRoutine = PropDialogHandler;
445     // ConnectInfo.ImeRoutine  = ImeRoutine;
446 
447     /* Set up the console properties */
448     if (ConnectInfo.IsConsoleApp && Parameters->ConsoleHandle == NULL)
449     {
450         /*
451          * We can set up the console properties only if we create a new one
452          * (we do not inherit it from our parent).
453          */
454 
455         LPWSTR ConsoleTitle = ConnectInfo.ConsoleTitle;
456 
457         ConnectInfo.TitleLength   = sizeof(ConnectInfo.ConsoleTitle);
458         ConnectInfo.DesktopLength = 0; // SetUpConsoleInfo will give us the real length.
459 
460         SetUpConsoleInfo(TRUE,
461                          &ConnectInfo.TitleLength,
462                          &ConsoleTitle,
463                          &ConnectInfo.DesktopLength,
464                          &ConnectInfo.Desktop,
465                          &ConnectInfo.ConsoleStartInfo);
466         DPRINT("ConsoleTitle = '%S' - Desktop = '%S'\n",
467                ConsoleTitle, ConnectInfo.Desktop);
468     }
469     else
470     {
471         ConnectInfo.TitleLength   = 0;
472         ConnectInfo.DesktopLength = 0;
473     }
474 
475     /* Initialize the Input EXE name */
476     if (ConnectInfo.IsConsoleApp)
477     {
478         LPWSTR CurDir  = ConnectInfo.CurDir;
479         LPWSTR AppName = ConnectInfo.AppName;
480 
481         InitExeName();
482 
483         ConnectInfo.CurDirLength  = sizeof(ConnectInfo.CurDir);
484         ConnectInfo.AppNameLength = sizeof(ConnectInfo.AppName);
485 
486         SetUpAppName(TRUE,
487                      &ConnectInfo.CurDirLength,
488                      &CurDir,
489                      &ConnectInfo.AppNameLength,
490                      &AppName);
491         DPRINT("CurDir = '%S' - AppName = '%S'\n",
492                CurDir, AppName);
493     }
494     else
495     {
496         ConnectInfo.CurDirLength  = 0;
497         ConnectInfo.AppNameLength = 0;
498     }
499 
500     /*
501      * Initialize Console Ctrl Handling, that needs to be supported by
502      * all applications, especially because it is used at shutdown.
503      */
504     InitializeCtrlHandling();
505 
506     /* Connect to the Console Server */
507     if (!ConnectConsole(SessionDir,
508                         &ConnectInfo,
509                         &InServerProcess))
510     {
511         // DPRINT1("Failed to connect to the Console Server (Status %lx)\n", Status);
512         return FALSE;
513     }
514 
515     /* If we are not doing server-to-server init and if this is a console app... */
516     if (!InServerProcess && ConnectInfo.IsConsoleApp)
517     {
518         /* ... set the handles that we got */
519         if (Parameters->ConsoleHandle == NULL)
520             SetUpHandles(&ConnectInfo.ConsoleStartInfo);
521 
522         InputWaitHandle = ConnectInfo.ConsoleStartInfo.InputWaitHandle;
523 
524         /* Sync the current thread's LangId with the console's one */
525         SetTEBLangID();
526     }
527 
528     DPRINT("Console setup: 0x%p, 0x%p, 0x%p, 0x%p\n",
529            Parameters->ConsoleHandle,
530            Parameters->StandardInput,
531            Parameters->StandardOutput,
532            Parameters->StandardError);
533 
534     return TRUE;
535 }
536 
537 /* EOF */
538