* Reorganize the whole ReactOS codebase into a new layout. Discussing it will only...
[reactos.git] / reactos / win32ss / user / ntuser / main.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS Win32k subsystem
4 * PURPOSE: Driver entry and initialization of win32k
5 * FILE: subsystems/win32/win32k/main/main.c
6 * PROGRAMER:
7 */
8
9 #include <win32k.h>
10 #include <napi.h>
11
12 #define NDEBUG
13 #include <debug.h>
14
15 HANDLE hModuleWin;
16
17 PGDI_HANDLE_TABLE NTAPI GDIOBJ_iAllocHandleTable(OUT PSECTION_OBJECT *SectionObject);
18 BOOL NTAPI GDI_CleanupForProcess (struct _EPROCESS *Process);
19 NTSTATUS NTAPI UserDestroyThreadInfo(struct _ETHREAD *Thread);
20
21 HANDLE GlobalUserHeap = NULL;
22 PSECTION_OBJECT GlobalUserHeapSection = NULL;
23
24 PSERVERINFO gpsi = NULL; // Global User Server Information.
25
26 SHORT gusLanguageID;
27 PPROCESSINFO ppiScrnSaver;
28
29 extern ULONG_PTR Win32kSSDT[];
30 extern UCHAR Win32kSSPT[];
31 extern ULONG Win32kNumberOfSysCalls;
32
33 #if DBG
34 void
35 NTAPI
36 DbgPreServiceHook(ULONG ulSyscallId, PULONG_PTR pulArguments)
37 {
38 GdiDbgPreServiceHook(ulSyscallId, pulArguments);
39 UserDbgPreServiceHook(ulSyscallId, pulArguments);
40 }
41
42 ULONG_PTR
43 NTAPI
44 DbgPostServiceHook(ULONG ulSyscallId, ULONG_PTR ulResult)
45 {
46 ulResult = GdiDbgPostServiceHook(ulSyscallId, ulResult);
47 ulResult = UserDbgPostServiceHook(ulSyscallId, ulResult);
48 return ulResult;
49 }
50 #endif
51
52 NTSTATUS
53 APIENTRY
54 Win32kProcessCallback(struct _EPROCESS *Process,
55 BOOLEAN Create)
56 {
57 PPROCESSINFO ppiCurrent;
58 DECLARE_RETURN(NTSTATUS);
59
60 ASSERT(Process->Peb);
61
62 UserEnterExclusive();
63
64 if (Create)
65 {
66 SIZE_T ViewSize = 0;
67 LARGE_INTEGER Offset;
68 PVOID UserBase = NULL;
69 PRTL_USER_PROCESS_PARAMETERS pParams = Process->Peb->ProcessParameters;
70 NTSTATUS Status;
71
72 ASSERT(PsGetProcessWin32Process(Process) == NULL);
73
74 ppiCurrent = ExAllocatePoolWithTag(NonPagedPool,
75 sizeof(PROCESSINFO),
76 USERTAG_PROCESSINFO);
77
78 if (ppiCurrent == NULL)
79 {
80 ERR_CH(UserProcess, "Failed to allocate ppi for PID:%d\n", Process->UniqueProcessId);
81 RETURN( STATUS_NO_MEMORY);
82 }
83
84 RtlZeroMemory(ppiCurrent, sizeof(PROCESSINFO));
85
86 PsSetProcessWin32Process(Process, ppiCurrent);
87
88 #if DBG
89 DbgInitDebugChannels();
90 #endif
91
92 TRACE_CH(UserProcess,"Allocated ppi 0x%x for PID:%d\n", ppiCurrent, Process->UniqueProcessId);
93
94 /* map the global heap into the process */
95 Offset.QuadPart = 0;
96 Status = MmMapViewOfSection(GlobalUserHeapSection,
97 PsGetCurrentProcess(),
98 &UserBase,
99 0,
100 0,
101 &Offset,
102 &ViewSize,
103 ViewUnmap,
104 SEC_NO_CHANGE,
105 PAGE_EXECUTE_READ); /* would prefer PAGE_READONLY, but thanks to RTL heaps... */
106 if (!NT_SUCCESS(Status))
107 {
108 TRACE_CH(UserProcess,"Failed to map the global heap! 0x%x\n", Status);
109 RETURN(Status);
110 }
111 ppiCurrent->HeapMappings.Next = NULL;
112 ppiCurrent->HeapMappings.KernelMapping = (PVOID)GlobalUserHeap;
113 ppiCurrent->HeapMappings.UserMapping = UserBase;
114 ppiCurrent->HeapMappings.Count = 1;
115
116 InitializeListHead(&ppiCurrent->MenuListHead);
117
118 InitializeListHead(&ppiCurrent->GDIBrushAttrFreeList);
119 InitializeListHead(&ppiCurrent->GDIDcAttrFreeList);
120
121 InitializeListHead(&ppiCurrent->PrivateFontListHead);
122 ExInitializeFastMutex(&ppiCurrent->PrivateFontListLock);
123
124 InitializeListHead(&ppiCurrent->DriverObjListHead);
125 ExInitializeFastMutex(&ppiCurrent->DriverObjListLock);
126
127 ppiCurrent->KeyboardLayout = W32kGetDefaultKeyLayout();
128 EngCreateEvent((PEVENT *)&ppiCurrent->InputIdleEvent);
129 KeInitializeEvent(ppiCurrent->InputIdleEvent, NotificationEvent, FALSE);
130
131
132 /* map the gdi handle table to user land */
133 Process->Peb->GdiSharedHandleTable = GDI_MapHandleTable(Process);
134 Process->Peb->GdiDCAttributeList = GDI_BATCH_LIMIT;
135 pParams = Process->Peb->ProcessParameters;
136
137 ppiCurrent->peProcess = Process;
138 /* setup process flags */
139 ppiCurrent->W32PF_flags = W32PF_THREADCONNECTED;
140
141 if ( pParams &&
142 pParams->WindowFlags & STARTF_SCRNSAVER )
143 {
144 ppiScrnSaver = ppiCurrent;
145 ppiCurrent->W32PF_flags |= W32PF_SCREENSAVER;
146 }
147
148 /* Create pools for GDI object attributes */
149 ppiCurrent->pPoolDcAttr = GdiPoolCreate(sizeof(DC_ATTR), 'acdG');
150 ppiCurrent->pPoolBrushAttr = GdiPoolCreate(sizeof(BRUSH_ATTR), 'arbG');
151 ppiCurrent->pPoolRgnAttr = GdiPoolCreate(sizeof(RGN_ATTR), 'agrG');
152 ASSERT(ppiCurrent->pPoolDcAttr);
153 ASSERT(ppiCurrent->pPoolBrushAttr);
154 ASSERT(ppiCurrent->pPoolRgnAttr);
155 }
156 else
157 {
158 /* Get the Win32 Process */
159 ppiCurrent = PsGetProcessWin32Process(Process);
160
161 ASSERT(ppiCurrent);
162
163 TRACE_CH(UserProcess, "Destroying ppi 0x%x\n", ppiCurrent);
164 ppiCurrent->W32PF_flags |= W32PF_TERMINATED;
165
166 if (ppiScrnSaver == ppiCurrent)
167 ppiScrnSaver = NULL;
168
169 if (ppiCurrent->InputIdleEvent)
170 {
171 EngFreeMem(ppiCurrent->InputIdleEvent);
172 ppiCurrent->InputIdleEvent = NULL;
173 }
174
175 IntCleanupMenus(Process, ppiCurrent);
176 IntCleanupCurIcons(Process, ppiCurrent);
177
178 /* no process windows should exist at this point, or the function will assert! */
179 DestroyProcessClasses(ppiCurrent);
180 ppiCurrent->W32PF_flags &= ~W32PF_CLASSESREGISTERED;
181
182 GDI_CleanupForProcess(Process);
183
184 co_IntGraphicsCheck(FALSE);
185
186 /*
187 * Deregister logon application automatically
188 */
189 if(LogonProcess == ppiCurrent)
190 {
191 LogonProcess = NULL;
192 }
193
194 /* Close the startup desktop */
195 if(ppiCurrent->rpdeskStartup)
196 ObDereferenceObject(ppiCurrent->rpdeskStartup);
197 if(ppiCurrent->hdeskStartup)
198 ZwClose(ppiCurrent->hdeskStartup);
199
200 /* Close the current window station */
201 UserSetProcessWindowStation(NULL);
202
203 /* Destroy GDI pools */
204 GdiPoolDestroy(ppiCurrent->pPoolDcAttr);
205 GdiPoolDestroy(ppiCurrent->pPoolBrushAttr);
206 GdiPoolDestroy(ppiCurrent->pPoolRgnAttr);
207
208 TRACE_CH(UserProcess,"Freeing ppi 0x%x\n", ppiCurrent);
209
210 /* Ftee the PROCESSINFO */
211 PsSetProcessWin32Process(Process, NULL);
212 ExFreePoolWithTag(ppiCurrent, USERTAG_PROCESSINFO);
213 }
214
215 RETURN( STATUS_SUCCESS);
216
217 CLEANUP:
218 UserLeave();
219 END_CLEANUP;
220 }
221
222 NTSTATUS NTAPI
223 UserCreateThreadInfo(struct _ETHREAD *Thread)
224 {
225 struct _EPROCESS *Process;
226 PCLIENTINFO pci;
227 PTHREADINFO ptiCurrent;
228 int i;
229 NTSTATUS Status = STATUS_SUCCESS;
230 PTEB pTeb;
231
232 Process = Thread->ThreadsProcess;
233
234 pTeb = NtCurrentTeb();
235
236 ASSERT(pTeb);
237
238 ptiCurrent = ExAllocatePoolWithTag(NonPagedPool,
239 sizeof(THREADINFO),
240 USERTAG_THREADINFO);
241 if (ptiCurrent == NULL)
242 {
243 ERR_CH(UserThread, "Failed to allocate pti for TID %d\n", Thread->Cid.UniqueThread);
244 return STATUS_NO_MEMORY;
245 }
246
247 RtlZeroMemory(ptiCurrent, sizeof(THREADINFO));
248
249 PsSetThreadWin32Thread(Thread, ptiCurrent);
250 pTeb->Win32ThreadInfo = ptiCurrent;
251 ptiCurrent->pClientInfo = (PCLIENTINFO)pTeb->Win32ClientInfo;
252
253 TRACE_CH(UserThread, "Allocated pti 0x%x for TID %d\n", ptiCurrent, Thread->Cid.UniqueThread);
254
255 /* Initialize the THREADINFO */
256 InitializeListHead(&ptiCurrent->WindowListHead);
257 InitializeListHead(&ptiCurrent->W32CallbackListHead);
258 InitializeListHead(&ptiCurrent->PtiLink);
259 for (i = 0; i < NB_HOOKS; i++)
260 {
261 InitializeListHead(&ptiCurrent->aphkStart[i]);
262 }
263 ptiCurrent->pEThread = Thread;
264 ptiCurrent->ppi = PsGetCurrentProcessWin32Process();
265 ptiCurrent->ptiSibling = ptiCurrent->ppi->ptiList;
266 ptiCurrent->ppi->ptiList = ptiCurrent;
267 ptiCurrent->ppi->cThreads++;
268 ptiCurrent->MessageQueue = MsqCreateMessageQueue(Thread);
269 if(ptiCurrent->MessageQueue == NULL)
270 {
271 ERR_CH(UserThread,"Failed to allocate message loop\n");
272 Status = STATUS_NO_MEMORY;
273 goto error;
274 }
275 ptiCurrent->KeyboardLayout = W32kGetDefaultKeyLayout();
276 if (ptiCurrent->KeyboardLayout)
277 UserReferenceObject(ptiCurrent->KeyboardLayout);
278 ptiCurrent->TIF_flags &= ~TIF_INCLEANUP;
279
280 /* Initialize the CLIENTINFO */
281 pci = (PCLIENTINFO)pTeb->Win32ClientInfo;
282 RtlZeroMemory(pci, sizeof(CLIENTINFO));
283 pci->ppi = ptiCurrent->ppi;
284 pci->fsHooks = ptiCurrent->fsHooks;
285 pci->dwTIFlags = ptiCurrent->TIF_flags;
286 if (ptiCurrent->KeyboardLayout)
287 pci->hKL = ptiCurrent->KeyboardLayout->hkl;
288
289 /* Assign a default window station and desktop to the process */
290 /* Do not try to open a desktop or window station before winlogon initializes */
291 if(ptiCurrent->ppi->hdeskStartup == NULL && LogonProcess != NULL)
292 {
293 HWINSTA hWinSta = NULL;
294 HDESK hDesk = NULL;
295 UNICODE_STRING DesktopPath;
296 PDESKTOP pdesk;
297 PRTL_USER_PROCESS_PARAMETERS ProcessParams;
298
299 /*
300 * inherit the thread desktop and process window station (if not yet inherited) from the process startup
301 * info structure. See documentation of CreateProcess()
302 */
303 ProcessParams = pTeb->ProcessEnvironmentBlock->ProcessParameters;
304
305 Status = STATUS_UNSUCCESSFUL;
306 if(ProcessParams && ProcessParams->DesktopInfo.Length > 0)
307 {
308 Status = IntSafeCopyUnicodeStringTerminateNULL(&DesktopPath, &ProcessParams->DesktopInfo);
309 }
310 if(!NT_SUCCESS(Status))
311 {
312 RtlInitUnicodeString(&DesktopPath, NULL);
313 }
314
315 Status = IntParseDesktopPath(Process,
316 &DesktopPath,
317 &hWinSta,
318 &hDesk);
319
320 if (DesktopPath.Buffer)
321 ExFreePoolWithTag(DesktopPath.Buffer, TAG_STRING);
322
323 if(!NT_SUCCESS(Status))
324 {
325 ERR_CH(UserThread, "Failed to assign default dekstop and winsta to process\n");
326 goto error;
327 }
328
329 if(!UserSetProcessWindowStation(hWinSta))
330 {
331 Status = STATUS_UNSUCCESSFUL;
332 ERR_CH(UserThread,"Failed to set initial process winsta\n");
333 goto error;
334 }
335
336 /* Validate the new desktop. */
337 Status = IntValidateDesktopHandle(hDesk, UserMode, 0, &pdesk);
338 if(!NT_SUCCESS(Status))
339 {
340 ERR_CH(UserThread,"Failed to validate initial desktop handle\n");
341 goto error;
342 }
343
344 /* Store the parsed desktop as the initial desktop */
345 ptiCurrent->ppi->hdeskStartup = hDesk;
346 ptiCurrent->ppi->rpdeskStartup = pdesk;
347 }
348
349 if (ptiCurrent->ppi->hdeskStartup != NULL)
350 {
351 if (!IntSetThreadDesktop(ptiCurrent->ppi->hdeskStartup, FALSE))
352 {
353 ERR_CH(UserThread,"Failed to set thread desktop\n");
354 Status = STATUS_UNSUCCESSFUL;
355 goto error;
356 }
357 }
358
359 /* mark the thread as fully initialized */
360 ptiCurrent->TIF_flags |= TIF_GUITHREADINITIALIZED;
361 ptiCurrent->pClientInfo->dwTIFlags = ptiCurrent->TIF_flags;
362
363 return STATUS_SUCCESS;
364
365 error:
366 ERR_CH(UserThread,"UserCreateThreadInfo failed! Freeing pti 0x%x for TID %d\n", ptiCurrent, Thread->Cid.UniqueThread);
367 UserDestroyThreadInfo(Thread);
368 return Status;
369 }
370
371 NTSTATUS
372 NTAPI
373 UserDestroyThreadInfo(struct _ETHREAD *Thread)
374 {
375 PTHREADINFO *ppti;
376 PSINGLE_LIST_ENTRY psle;
377 PPROCESSINFO ppiCurrent;
378 struct _EPROCESS *Process;
379 PTHREADINFO ptiCurrent;
380
381 Process = Thread->ThreadsProcess;
382
383 /* Get the Win32 Thread */
384 ptiCurrent = PsGetThreadWin32Thread(Thread);
385
386 ASSERT(ptiCurrent);
387
388 TRACE_CH(UserThread,"Destroying pti 0x%x\n", ptiCurrent);
389
390 ppiCurrent = ptiCurrent->ppi;
391 ptiCurrent->TIF_flags |= TIF_INCLEANUP;
392 ptiCurrent->pClientInfo->dwTIFlags = ptiCurrent->TIF_flags;
393
394
395 /* Decrement thread count and check if its 0 */
396 ppiCurrent->cThreads--;
397
398 if(ptiCurrent->TIF_flags & TIF_GUITHREADINITIALIZED)
399 {
400 /* Do now some process cleanup that requires a valid win32 thread */
401 if(ptiCurrent->ppi->cThreads == 0)
402 {
403 /* Check if we have registered the user api hook */
404 if(ptiCurrent->ppi == ppiUahServer)
405 {
406 /* Unregister the api hook */
407 UserUnregisterUserApiHook();
408 }
409
410 /* Notify logon application to restart shell if needed */
411 if(ptiCurrent->pDeskInfo)
412 {
413 if(ptiCurrent->pDeskInfo->ppiShellProcess == ppiCurrent)
414 {
415 DWORD ExitCode = PsGetProcessExitStatus(Process);
416
417 TRACE_CH(UserProcess, "Shell process is exiting (%d)\n", ExitCode);
418
419 UserPostMessage(hwndSAS,
420 WM_LOGONNOTIFY,
421 LN_SHELL_EXITED,
422 ExitCode);
423
424 ptiCurrent->pDeskInfo->ppiShellProcess = NULL;
425 }
426 }
427 }
428
429 DceFreeThreadDCE(ptiCurrent);
430 HOOK_DestroyThreadHooks(Thread);
431 EVENT_DestroyThreadEvents(Thread);
432 DestroyTimersForThread(ptiCurrent);
433 KeSetEvent(ptiCurrent->MessageQueue->NewMessages, IO_NO_INCREMENT, FALSE);
434 UnregisterThreadHotKeys(Thread);
435 co_DestroyThreadWindows(Thread);
436 IntBlockInput(ptiCurrent, FALSE);
437 IntCleanupThreadCallbacks(ptiCurrent);
438
439 /* cleanup user object references stack */
440 psle = PopEntryList(&ptiCurrent->ReferencesList);
441 while (psle)
442 {
443 PUSER_REFERENCE_ENTRY ref = CONTAINING_RECORD(psle, USER_REFERENCE_ENTRY, Entry);
444 TRACE_CH(UserThread,"thread clean: remove reference obj 0x%x\n",ref->obj);
445 UserDereferenceObject(ref->obj);
446
447 psle = PopEntryList(&ptiCurrent->ReferencesList);
448 }
449 }
450
451 /* Free the message queue */
452 if(ptiCurrent->MessageQueue)
453 MsqDestroyMessageQueue(ptiCurrent->MessageQueue);
454
455 /* Find the THREADINFO in the PROCESSINFO's list */
456 ppti = &ppiCurrent->ptiList;
457 while (*ppti != NULL && *ppti != ptiCurrent)
458 {
459 ppti = &((*ppti)->ptiSibling);
460 }
461
462 /* we must have found it */
463 ASSERT(*ppti == ptiCurrent);
464
465 /* Remove it from the list */
466 *ppti = ptiCurrent->ptiSibling;
467
468 if (ptiCurrent->KeyboardLayout)
469 UserDereferenceObject(ptiCurrent->KeyboardLayout);
470
471 IntSetThreadDesktop(NULL, TRUE);
472
473 TRACE_CH(UserThread,"Freeing pti 0x%x\n", ptiCurrent);
474
475 /* Free the THREADINFO */
476 PsSetThreadWin32Thread(Thread, NULL);
477 ExFreePoolWithTag(ptiCurrent, USERTAG_THREADINFO);
478
479 return STATUS_SUCCESS;
480 }
481
482 NTSTATUS
483 APIENTRY
484 Win32kThreadCallback(struct _ETHREAD *Thread,
485 PSW32THREADCALLOUTTYPE Type)
486 {
487 NTSTATUS Status;
488
489 UserEnterExclusive();
490
491 ASSERT(NtCurrentTeb());
492
493 if (Type == PsW32ThreadCalloutInitialize)
494 {
495 ASSERT(PsGetThreadWin32Thread(Thread) == NULL);
496 Status = UserCreateThreadInfo(Thread);
497 }
498 else
499 {
500 ASSERT(PsGetThreadWin32Thread(Thread) != NULL);
501 Status = UserDestroyThreadInfo(Thread);
502 }
503
504 UserLeave();
505
506 return Status;
507 }
508
509 #ifdef _M_IX86
510 C_ASSERT(sizeof(SERVERINFO) <= PAGE_SIZE);
511 #endif
512
513 // Return on failure
514 #define NT_ROF(x) \
515 Status = (x); \
516 if (!NT_SUCCESS(Status)) \
517 { \
518 DPRINT1("Failed '%s' (0x%lx)\n", #x, Status); \
519 return Status; \
520 }
521
522 /*
523 * This definition doesn't work
524 */
525 INIT_FUNCTION
526 NTSTATUS
527 APIENTRY
528 DriverEntry(
529 IN PDRIVER_OBJECT DriverObject,
530 IN PUNICODE_STRING RegistryPath)
531 {
532 NTSTATUS Status;
533 BOOLEAN Result;
534 WIN32_CALLOUTS_FPNS CalloutData = {0};
535 PVOID GlobalUserHeapBase = NULL;
536
537 /*
538 * Register user mode call interface
539 * (system service table index = 1)
540 */
541 Result = KeAddSystemServiceTable(Win32kSSDT,
542 NULL,
543 Win32kNumberOfSysCalls,
544 Win32kSSPT,
545 1);
546 if (Result == FALSE)
547 {
548 DPRINT1("Adding system services failed!\n");
549 return STATUS_UNSUCCESSFUL;
550 }
551
552 hModuleWin = MmPageEntireDriver(DriverEntry);
553 DPRINT("Win32k hInstance 0x%x!\n",hModuleWin);
554
555 /* Register Object Manager Callbacks */
556 CalloutData.WindowStationParseProcedure = IntWinStaObjectParse;
557 CalloutData.WindowStationDeleteProcedure = IntWinStaObjectDelete;
558 CalloutData.DesktopDeleteProcedure = IntDesktopObjectDelete;
559 CalloutData.ProcessCallout = Win32kProcessCallback;
560 CalloutData.ThreadCallout = Win32kThreadCallback;
561 CalloutData.BatchFlushRoutine = NtGdiFlushUserBatch;
562 CalloutData.DesktopOkToCloseProcedure = IntDesktopOkToClose;
563 CalloutData.WindowStationOkToCloseProcedure = IntWinstaOkToClose;
564
565 /* Register our per-process and per-thread structures. */
566 PsEstablishWin32Callouts((PWIN32_CALLOUTS_FPNS)&CalloutData);
567
568 /* Register service hook callbacks */
569 #if DBG
570 KdSystemDebugControl('CsoR', DbgPreServiceHook, ID_Win32PreServiceHook, 0, 0, 0, 0);
571 KdSystemDebugControl('CsoR', DbgPostServiceHook, ID_Win32PostServiceHook, 0, 0, 0, 0);
572 #endif
573
574 /* Create the global USER heap */
575 GlobalUserHeap = UserCreateHeap(&GlobalUserHeapSection,
576 &GlobalUserHeapBase,
577 1 * 1024 * 1024); /* FIXME: 1 MB for now... */
578 if (GlobalUserHeap == NULL)
579 {
580 DPRINT1("Failed to initialize the global heap!\n");
581 return STATUS_UNSUCCESSFUL;
582 }
583
584 /* Allocate global server info structure */
585 gpsi = UserHeapAlloc(sizeof(SERVERINFO));
586 if (!gpsi)
587 {
588 DPRINT1("Failed allocate server info structure!\n");
589 return STATUS_UNSUCCESSFUL;
590 }
591
592 RtlZeroMemory(gpsi, sizeof(SERVERINFO));
593 DPRINT("Global Server Data -> %x\n", gpsi);
594
595 NT_ROF(InitGdiHandleTable());
596 NT_ROF(InitPaletteImpl());
597
598 /* Create stock objects, ie. precreated objects commonly
599 used by win32 applications */
600 CreateStockObjects();
601 CreateSysColorObjects();
602
603 NT_ROF(InitPDEVImpl());
604 NT_ROF(InitLDEVImpl());
605 NT_ROF(InitDeviceImpl());
606 NT_ROF(InitDcImpl());
607 NT_ROF(InitUserImpl());
608 NT_ROF(InitWindowStationImpl());
609 NT_ROF(InitDesktopImpl());
610 NT_ROF(InitInputImpl());
611 NT_ROF(InitKeyboardImpl());
612 NT_ROF(MsqInitializeImpl());
613 NT_ROF(InitTimerImpl());
614 NT_ROF(InitDCEImpl());
615
616 /* Initialize FreeType library */
617 if (!InitFontSupport())
618 {
619 DPRINT1("Unable to initialize font support\n");
620 return Status;
621 }
622
623 gusLanguageID = UserGetLanguageID();
624
625 return STATUS_SUCCESS;
626 }
627
628 /* EOF */