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 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 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 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 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 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 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 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