Git conversion: Make reactos the root directory, move rosapps, rostests, wallpapers...
[reactos.git] / win32ss / user / ntuser / desktop.c
diff --git a/win32ss/user/ntuser/desktop.c b/win32ss/user/ntuser/desktop.c
new file mode 100644 (file)
index 0000000..6ecf94b
--- /dev/null
@@ -0,0 +1,2558 @@
+/*
+ *  COPYRIGHT:        See COPYING in the top level directory
+ *  PROJECT:          ReactOS Win32k subsystem
+ *  PURPOSE:          Desktops
+ *  FILE:             subsystems/win32/win32k/ntuser/desktop.c
+ *  PROGRAMMER:       Casper S. Hornstrup (chorns@users.sourceforge.net)
+ */
+
+/* INCLUDES ******************************************************************/
+
+#include <win32k.h>
+DBG_DEFAULT_CHANNEL(UserDesktop);
+
+#include <reactos/buildno.h>
+
+static NTSTATUS
+UserInitializeDesktop(PDESKTOP pdesk, PUNICODE_STRING DesktopName, PWINSTATION_OBJECT pwinsta);
+
+static NTSTATUS
+IntMapDesktopView(IN PDESKTOP pdesk);
+
+static NTSTATUS
+IntUnmapDesktopView(IN PDESKTOP pdesk);
+
+static VOID
+IntFreeDesktopHeap(IN PDESKTOP pdesk);
+
+/* GLOBALS *******************************************************************/
+
+/* These can be changed via csrss startup, these are defaults */
+DWORD gdwDesktopSectionSize = 512;
+DWORD gdwNOIOSectionSize    = 128; // A guess, for one or more of the first three system desktops.
+
+/* Currently active desktop */
+PDESKTOP gpdeskInputDesktop = NULL;
+HDC ScreenDeviceContext = NULL;
+PTHREADINFO gptiDesktopThread = NULL;
+HCURSOR gDesktopCursor = NULL;
+
+/* OBJECT CALLBACKS **********************************************************/
+
+NTSTATUS
+APIENTRY
+IntDesktopObjectParse(IN PVOID ParseObject,
+                      IN PVOID ObjectType,
+                      IN OUT PACCESS_STATE AccessState,
+                      IN KPROCESSOR_MODE AccessMode,
+                      IN ULONG Attributes,
+                      IN OUT PUNICODE_STRING CompleteName,
+                      IN OUT PUNICODE_STRING RemainingName,
+                      IN OUT PVOID Context OPTIONAL,
+                      IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
+                      OUT PVOID *Object)
+{
+    NTSTATUS Status;
+    PDESKTOP Desktop;
+    OBJECT_ATTRIBUTES ObjectAttributes;
+    PLIST_ENTRY NextEntry, ListHead;
+    PWINSTATION_OBJECT WinStaObject = (PWINSTATION_OBJECT)ParseObject;
+    UNICODE_STRING DesktopName;
+    PBOOLEAN pContext = (PBOOLEAN) Context;
+
+    if (pContext)
+        *pContext = FALSE;
+
+    /* Set the list pointers and loop the window station */
+    ListHead = &WinStaObject->DesktopListHead;
+    NextEntry = ListHead->Flink;
+    while (NextEntry != ListHead)
+    {
+        /* Get the current desktop */
+        Desktop = CONTAINING_RECORD(NextEntry, DESKTOP, ListEntry);
+
+        /* Get the desktop name */
+        ASSERT(Desktop->pDeskInfo != NULL);
+        RtlInitUnicodeString(&DesktopName, Desktop->pDeskInfo->szDesktopName);
+
+        /* Compare the name */
+        if (RtlEqualUnicodeString(RemainingName,
+                                  &DesktopName,
+                                  (Attributes & OBJ_CASE_INSENSITIVE) != 0))
+        {
+            /* We found a match. Did this come from a create? */
+            if (Context)
+            {
+                /* Unless OPEN_IF was given, fail with an error */
+                if (!(Attributes & OBJ_OPENIF))
+                {
+                    /* Name collision */
+                    return STATUS_OBJECT_NAME_COLLISION;
+                }
+                else
+                {
+                    /* Otherwise, return with a warning only */
+                    Status = STATUS_OBJECT_NAME_EXISTS;
+                }
+            }
+            else
+            {
+                /* This was a real open, so this is OK */
+                Status = STATUS_SUCCESS;
+            }
+
+            /* Reference the desktop and return it */
+            ObReferenceObject(Desktop);
+            *Object = Desktop;
+            return Status;
+        }
+
+        /* Go to the next desktop */
+        NextEntry = NextEntry->Flink;
+    }
+
+    /* If we got here but this isn't a create, just fail */
+    if (!Context) return STATUS_OBJECT_NAME_NOT_FOUND;
+
+    /* Create the desktop object */
+    InitializeObjectAttributes(&ObjectAttributes, RemainingName, 0, NULL, NULL);
+    Status = ObCreateObject(KernelMode,
+                            ExDesktopObjectType,
+                            &ObjectAttributes,
+                            KernelMode,
+                            NULL,
+                            sizeof(DESKTOP),
+                            0,
+                            0,
+                            (PVOID*)&Desktop);
+    if (!NT_SUCCESS(Status)) return Status;
+
+    /* Initialize the desktop */
+    Status = UserInitializeDesktop(Desktop, RemainingName, WinStaObject);
+    if (!NT_SUCCESS(Status))
+    {
+        ObDereferenceObject(Desktop);
+        return Status;
+    }
+
+    /* Set the desktop object and return success */
+    *Object = Desktop;
+    *pContext = TRUE;
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS
+NTAPI
+IntDesktopObjectDelete(
+    _In_ PVOID Parameters)
+{
+    PWIN32_DELETEMETHOD_PARAMETERS DeleteParameters = Parameters;
+    PDESKTOP pdesk = (PDESKTOP)DeleteParameters->Object;
+
+    TRACE("Deleting desktop object 0x%p\n", pdesk);
+
+    if (pdesk->pDeskInfo &&
+        pdesk->pDeskInfo->spwnd)
+    {
+        ASSERT(pdesk->pDeskInfo->spwnd->spwndChild == NULL);
+        co_UserDestroyWindow(pdesk->pDeskInfo->spwnd);
+    }
+
+    if (pdesk->spwndMessage)
+        co_UserDestroyWindow(pdesk->spwndMessage);
+
+    /* Remove the desktop from the window station's list of associcated desktops */
+    RemoveEntryList(&pdesk->ListEntry);
+
+    /* Free the heap */
+    IntFreeDesktopHeap(pdesk);
+
+    ObDereferenceObject(pdesk->rpwinstaParent);
+
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS
+NTAPI
+IntDesktopOkToClose(
+    _In_ PVOID Parameters)
+{
+    PWIN32_OKAYTOCLOSEMETHOD_PARAMETERS OkToCloseParameters = Parameters;
+    PTHREADINFO pti = PsGetCurrentThreadWin32Thread();
+
+    if (pti == NULL)
+    {
+        /* This happens when we leak desktop handles */
+        return STATUS_SUCCESS;
+    }
+
+    /* Do not allow the current desktop or the initial desktop to be closed */
+    if (OkToCloseParameters->Handle == pti->ppi->hdeskStartup ||
+        OkToCloseParameters->Handle == pti->hdesk)
+    {
+        return STATUS_ACCESS_DENIED;
+    }
+
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS
+NTAPI
+IntDesktopObjectOpen(
+    _In_ PVOID Parameters)
+{
+    PWIN32_OPENMETHOD_PARAMETERS OpenParameters = Parameters;
+    PPROCESSINFO ppi = PsGetProcessWin32Process(OpenParameters->Process);
+    if (ppi == NULL)
+        return STATUS_SUCCESS;
+
+    return IntMapDesktopView((PDESKTOP)OpenParameters->Object);
+}
+
+NTSTATUS
+NTAPI
+IntDesktopObjectClose(
+    _In_ PVOID Parameters)
+{
+    PWIN32_CLOSEMETHOD_PARAMETERS CloseParameters = Parameters;
+    PPROCESSINFO ppi = PsGetProcessWin32Process(CloseParameters->Process);
+    if (ppi == NULL)
+    {
+        /* This happens when the process leaks desktop handles.
+         * At this point the PPROCESSINFO is already destroyed */
+        return STATUS_SUCCESS;
+    }
+
+    return IntUnmapDesktopView((PDESKTOP)CloseParameters->Object);
+}
+
+
+/* PRIVATE FUNCTIONS **********************************************************/
+
+INIT_FUNCTION
+NTSTATUS
+NTAPI
+InitDesktopImpl(VOID)
+{
+    GENERIC_MAPPING IntDesktopMapping = { DESKTOP_READ,
+                                          DESKTOP_WRITE,
+                                          DESKTOP_EXECUTE,
+                                          DESKTOP_ALL_ACCESS};
+
+    /* Set Desktop Object Attributes */
+    ExDesktopObjectType->TypeInfo.DefaultNonPagedPoolCharge = sizeof(DESKTOP);
+    ExDesktopObjectType->TypeInfo.GenericMapping = IntDesktopMapping;
+    ExDesktopObjectType->TypeInfo.ValidAccessMask = DESKTOP_ALL_ACCESS;
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS
+GetSystemVersionString(OUT PWSTR pwszzVersion,
+                       IN SIZE_T cchDest,
+                       IN BOOLEAN InSafeMode,
+                       IN BOOLEAN AppendNtSystemRoot)
+{
+    NTSTATUS Status;
+
+    RTL_OSVERSIONINFOEXW VerInfo;
+    UNICODE_STRING BuildLabString;
+    UNICODE_STRING CSDVersionString;
+    RTL_QUERY_REGISTRY_TABLE VersionConfigurationTable[] =
+    {
+        {
+            NULL,
+            RTL_QUERY_REGISTRY_DIRECT,
+            L"BuildLab",
+            &BuildLabString,
+            REG_NONE, NULL, 0
+        },
+        {
+            NULL,
+            RTL_QUERY_REGISTRY_DIRECT,
+            L"CSDVersion",
+            &CSDVersionString,
+            REG_NONE, NULL, 0
+        },
+
+        {0}
+    };
+
+    WCHAR BuildLabBuffer[256];
+    WCHAR VersionBuffer[256];
+    PWCHAR EndBuffer;
+
+    VerInfo.dwOSVersionInfoSize = sizeof(VerInfo);
+
+    /*
+     * This call is uniquely used to retrieve the current CSD numbers.
+     * All the rest (major, minor, ...) is either retrieved from the
+     * SharedUserData structure, or from the registry.
+     */
+    RtlGetVersion((PRTL_OSVERSIONINFOW)&VerInfo);
+
+    /*
+     * - Retrieve the BuildLab string from the registry (set by the kernel).
+     * - In kernel-mode, szCSDVersion is not initialized. Initialize it
+     *   and query its value from the registry.
+     */
+    RtlZeroMemory(BuildLabBuffer, sizeof(BuildLabBuffer));
+    RtlInitEmptyUnicodeString(&BuildLabString,
+                              BuildLabBuffer,
+                              sizeof(BuildLabBuffer));
+    RtlZeroMemory(VerInfo.szCSDVersion, sizeof(VerInfo.szCSDVersion));
+    RtlInitEmptyUnicodeString(&CSDVersionString,
+                              VerInfo.szCSDVersion,
+                              sizeof(VerInfo.szCSDVersion));
+    Status = RtlQueryRegistryValues(RTL_REGISTRY_WINDOWS_NT,
+                                    L"",
+                                    VersionConfigurationTable,
+                                    NULL,
+                                    NULL);
+    if (!NT_SUCCESS(Status))
+    {
+        /* Indicate nothing is there */
+        BuildLabString.Length = 0;
+        CSDVersionString.Length = 0;
+    }
+    /* NULL-terminate the strings */
+    BuildLabString.Buffer[BuildLabString.Length / sizeof(WCHAR)] = UNICODE_NULL;
+    CSDVersionString.Buffer[CSDVersionString.Length / sizeof(WCHAR)] = UNICODE_NULL;
+
+    EndBuffer = VersionBuffer;
+    if ( /* VerInfo.wServicePackMajor != 0 && */ CSDVersionString.Length)
+    {
+        /* Print the version string */
+        Status = RtlStringCbPrintfExW(VersionBuffer,
+                                      sizeof(VersionBuffer),
+                                      &EndBuffer,
+                                      NULL,
+                                      0,
+                                      L": %wZ",
+                                      &CSDVersionString);
+        if (!NT_SUCCESS(Status))
+        {
+            /* No version, NULL-terminate the string */
+            *EndBuffer = UNICODE_NULL;
+        }
+    }
+    else
+    {
+        /* No version, NULL-terminate the string */
+        *EndBuffer = UNICODE_NULL;
+    }
+
+    if (InSafeMode)
+    {
+        /* String for Safe Mode */
+        Status = RtlStringCchPrintfW(pwszzVersion,
+                                     cchDest,
+                                     L"ReactOS Version %S %wZ (NT %u.%u Build %u%s)\n",
+                                     KERNEL_VERSION_STR,
+                                     &BuildLabString,
+                                     SharedUserData->NtMajorVersion,
+                                     SharedUserData->NtMinorVersion,
+                                     (VerInfo.dwBuildNumber & 0xFFFF),
+                                     VersionBuffer);
+
+        if (AppendNtSystemRoot && NT_SUCCESS(Status))
+        {
+            Status = RtlStringCbPrintfW(VersionBuffer,
+                                        sizeof(VersionBuffer),
+                                        L" - %s\n",
+                                        SharedUserData->NtSystemRoot);
+            if (NT_SUCCESS(Status))
+            {
+                /* Replace the last newline by a NULL, before concatenating */
+                EndBuffer = wcsrchr(pwszzVersion, L'\n');
+                if (EndBuffer) *EndBuffer = UNICODE_NULL;
+
+                /* The concatenated string has a terminating newline */
+                Status = RtlStringCchCatW(pwszzVersion,
+                                          cchDest,
+                                          VersionBuffer);
+                if (!NT_SUCCESS(Status))
+                {
+                    /* Concatenation failed, put back the newline */
+                    if (EndBuffer) *EndBuffer = L'\n';
+                }
+            }
+
+            /* Override any failures as the NtSystemRoot string is optional */
+            Status = STATUS_SUCCESS;
+        }
+    }
+    else
+    {
+        /* Multi-string for Normal Mode */
+        Status = RtlStringCchPrintfW(pwszzVersion,
+                                     cchDest,
+                                     L"ReactOS Version %S\n"
+                                     L"Build %wZ\n"
+                                     L"Reporting NT %u.%u (Build %u%s)\n",
+                                     KERNEL_VERSION_STR,
+                                     &BuildLabString,
+                                     SharedUserData->NtMajorVersion,
+                                     SharedUserData->NtMinorVersion,
+                                     (VerInfo.dwBuildNumber & 0xFFFF),
+                                     VersionBuffer);
+
+        if (AppendNtSystemRoot && NT_SUCCESS(Status))
+        {
+            Status = RtlStringCbPrintfW(VersionBuffer,
+                                        sizeof(VersionBuffer),
+                                        L"%s\n",
+                                        SharedUserData->NtSystemRoot);
+            if (NT_SUCCESS(Status))
+            {
+                Status = RtlStringCchCatW(pwszzVersion,
+                                          cchDest,
+                                          VersionBuffer);
+            }
+
+            /* Override any failures as the NtSystemRoot string is optional */
+            Status = STATUS_SUCCESS;
+        }
+    }
+
+    if (!NT_SUCCESS(Status))
+    {
+        /* Fall-back string */
+        Status = RtlStringCchPrintfW(pwszzVersion,
+                                     cchDest,
+                                     L"ReactOS Version %S %wZ\n",
+                                     KERNEL_VERSION_STR,
+                                     &BuildLabString);
+        if (!NT_SUCCESS(Status))
+        {
+            /* General failure, NULL-terminate the string */
+            pwszzVersion[0] = UNICODE_NULL;
+        }
+    }
+
+    /*
+     * Convert the string separators (newlines) into NULLs
+     * and NULL-terminate the multi-string.
+     */
+    while (*pwszzVersion)
+    {
+        EndBuffer = wcschr(pwszzVersion, L'\n');
+        if (!EndBuffer) break;
+        pwszzVersion = EndBuffer;
+
+        *pwszzVersion++ = UNICODE_NULL;
+    }
+    *pwszzVersion = UNICODE_NULL;
+
+    return Status;
+}
+
+
+NTSTATUS FASTCALL
+IntParseDesktopPath(PEPROCESS Process,
+                    PUNICODE_STRING DesktopPath,
+                    HWINSTA *hWinSta,
+                    HDESK *hDesktop)
+{
+    OBJECT_ATTRIBUTES ObjectAttributes;
+    UNICODE_STRING ObjectName;
+    NTSTATUS Status;
+    WCHAR wstrWinstaFullName[MAX_PATH], *pwstrWinsta = NULL, *pwstrDesktop = NULL;
+
+    ASSERT(hWinSta);
+    ASSERT(hDesktop);
+    ASSERT(DesktopPath);
+
+    *hWinSta = NULL;
+    *hDesktop = NULL;
+
+    if (DesktopPath->Buffer != NULL && DesktopPath->Length > sizeof(WCHAR))
+    {
+        /*
+         * Parse the desktop path string which can be in the form "WinSta\Desktop"
+         * or just "Desktop". In latter case WinSta0 will be used.
+         */
+
+        pwstrDesktop = wcschr(DesktopPath->Buffer, L'\\');
+        if (pwstrDesktop != NULL)
+        {
+            *pwstrDesktop = 0;
+            pwstrDesktop++;
+            pwstrWinsta = DesktopPath->Buffer;
+        }
+        else
+        {
+            pwstrDesktop = DesktopPath->Buffer;
+            pwstrWinsta = NULL;
+        }
+
+        TRACE("IntParseDesktopPath pwstrWinsta:%S pwstrDesktop:%S\n", pwstrWinsta, pwstrDesktop);
+    }
+
+#if 0
+    /* Search the process handle table for (inherited) window station
+       handles, use a more appropriate one than WinSta0 if possible. */
+    if (!ObFindHandleForObject(Process,
+                               NULL,
+                               ExWindowStationObjectType,
+                               NULL,
+                               (PHANDLE)hWinSta))
+#endif
+    {
+        /* We had no luck searching for opened handles, use WinSta0 now */
+        if (!pwstrWinsta)
+            pwstrWinsta = L"WinSta0";
+    }
+
+#if 0
+    /* Search the process handle table for (inherited) desktop
+       handles, use a more appropriate one than Default if possible. */
+    if (!ObFindHandleForObject(Process,
+                               NULL,
+                               ExDesktopObjectType,
+                               NULL,
+                               (PHANDLE)hDesktop))
+#endif
+    {
+        /* We had no luck searching for opened handles, use Desktop now */
+        if (!pwstrDesktop)
+            pwstrDesktop = L"Default";
+    }
+
+    if (*hWinSta == NULL)
+    {
+        swprintf(wstrWinstaFullName, L"%wZ\\%ws", &gustrWindowStationsDir, pwstrWinsta);
+        RtlInitUnicodeString( &ObjectName, wstrWinstaFullName);
+
+        TRACE("parsed initial winsta: %wZ\n", &ObjectName);
+
+        /* Open the window station */
+        InitializeObjectAttributes(&ObjectAttributes,
+                                   &ObjectName,
+                                   OBJ_CASE_INSENSITIVE,
+                                   NULL,
+                                   NULL);
+
+        Status = ObOpenObjectByName(&ObjectAttributes,
+                                    ExWindowStationObjectType,
+                                    KernelMode,
+                                    NULL,
+                                    WINSTA_ACCESS_ALL,
+                                    NULL,
+                                    (HANDLE*)hWinSta);
+
+        if (!NT_SUCCESS(Status))
+        {
+            SetLastNtError(Status);
+            ERR("Failed to reference window station %wZ PID: --!\n", &ObjectName );
+            return Status;
+        }
+    }
+
+    if (*hDesktop == NULL)
+    {
+        RtlInitUnicodeString(&ObjectName, pwstrDesktop);
+
+        TRACE("parsed initial desktop: %wZ\n", &ObjectName);
+
+        /* Open the desktop object */
+        InitializeObjectAttributes(&ObjectAttributes,
+                                   &ObjectName,
+                                   OBJ_CASE_INSENSITIVE,
+                                   *hWinSta,
+                                   NULL);
+
+        Status = ObOpenObjectByName(&ObjectAttributes,
+                                    ExDesktopObjectType,
+                                    KernelMode,
+                                    NULL,
+                                    DESKTOP_ALL_ACCESS,
+                                    NULL,
+                                    (HANDLE*)hDesktop);
+
+        if (!NT_SUCCESS(Status))
+        {
+            *hDesktop = NULL;
+            NtClose(*hWinSta);
+            *hWinSta = NULL;
+            SetLastNtError(Status);
+            ERR("Failed to reference desktop %wZ PID: --!\n", &ObjectName);
+            return Status;
+        }
+    }
+    return STATUS_SUCCESS;
+}
+
+/*
+ * IntValidateDesktopHandle
+ *
+ * Validates the desktop handle.
+ *
+ * Remarks
+ *    If the function succeeds, the handle remains referenced. If the
+ *    fucntion fails, last error is set.
+ */
+
+NTSTATUS FASTCALL
+IntValidateDesktopHandle(
+    HDESK Desktop,
+    KPROCESSOR_MODE AccessMode,
+    ACCESS_MASK DesiredAccess,
+    PDESKTOP *Object)
+{
+    NTSTATUS Status;
+
+    Status = ObReferenceObjectByHandle(
+                Desktop,
+                DesiredAccess,
+                ExDesktopObjectType,
+                AccessMode,
+                (PVOID*)Object,
+                NULL);
+
+    TRACE("IntValidateDesktopHandle: handle:0x%p obj:0x%p access:0x%x Status:0x%lx\n",
+          Desktop, *Object, DesiredAccess, Status);
+
+    if (!NT_SUCCESS(Status))
+        SetLastNtError(Status);
+
+    return Status;
+}
+
+PDESKTOP FASTCALL
+IntGetActiveDesktop(VOID)
+{
+    return gpdeskInputDesktop;
+}
+
+/*
+ * Returns or creates a handle to the desktop object
+ */
+HDESK FASTCALL
+IntGetDesktopObjectHandle(PDESKTOP DesktopObject)
+{
+    NTSTATUS Status;
+    HDESK Ret;
+
+    ASSERT(DesktopObject);
+
+    if (!ObFindHandleForObject(PsGetCurrentProcess(),
+                               DesktopObject,
+                               ExDesktopObjectType,
+                               NULL,
+                               (PHANDLE)&Ret))
+    {
+        Status = ObOpenObjectByPointer(DesktopObject,
+                                       0,
+                                       NULL,
+                                       0,
+                                       ExDesktopObjectType,
+                                       UserMode,
+                                       (PHANDLE)&Ret);
+        if (!NT_SUCCESS(Status))
+        {
+            /* Unable to create a handle */
+            ERR("Unable to create a desktop handle\n");
+            return NULL;
+        }
+    }
+    else
+    {
+        TRACE("Got handle: %p\n", Ret);
+    }
+
+    return Ret;
+}
+
+PUSER_MESSAGE_QUEUE FASTCALL
+IntGetFocusMessageQueue(VOID)
+{
+    PDESKTOP pdo = IntGetActiveDesktop();
+    if (!pdo)
+    {
+        TRACE("No active desktop\n");
+        return(NULL);
+    }
+    return (PUSER_MESSAGE_QUEUE)pdo->ActiveMessageQueue;
+}
+
+VOID FASTCALL
+IntSetFocusMessageQueue(PUSER_MESSAGE_QUEUE NewQueue)
+{
+    PUSER_MESSAGE_QUEUE Old;
+    PDESKTOP pdo = IntGetActiveDesktop();
+    if (!pdo)
+    {
+        TRACE("No active desktop\n");
+        return;
+    }
+    if (NewQueue != NULL)
+    {
+        if (NewQueue->Desktop != NULL)
+        {
+            TRACE("Message Queue already attached to another desktop!\n");
+            return;
+        }
+        IntReferenceMessageQueue(NewQueue);
+        (void)InterlockedExchangePointer((PVOID*)&NewQueue->Desktop, pdo);
+    }
+    Old = (PUSER_MESSAGE_QUEUE)InterlockedExchangePointer((PVOID*)&pdo->ActiveMessageQueue, NewQueue);
+    if (Old != NULL)
+    {
+        (void)InterlockedExchangePointer((PVOID*)&Old->Desktop, 0);
+        gpqForegroundPrev = Old;
+        IntDereferenceMessageQueue(Old);
+    }
+    // Only one Q can have active foreground even when there are more than one desktop.
+    if (NewQueue)
+    {
+        gpqForeground = pdo->ActiveMessageQueue;
+    }
+    else
+    {
+        gpqForeground = NULL;
+        ERR("ptiLastInput is CLEARED!!\n");
+        ptiLastInput = NULL; // ReactOS hacks,,,, should check for process death.
+    }
+}
+
+PWND FASTCALL
+IntGetThreadDesktopWindow(PTHREADINFO pti)
+{
+    if (!pti) pti = PsGetCurrentThreadWin32Thread();
+    if (pti->pDeskInfo) return pti->pDeskInfo->spwnd;
+    return NULL;
+}
+
+PWND FASTCALL co_GetDesktopWindow(PWND pWnd)
+{
+    if (pWnd->head.rpdesk &&
+        pWnd->head.rpdesk->pDeskInfo)
+        return pWnd->head.rpdesk->pDeskInfo->spwnd;
+    return NULL;
+}
+
+HWND FASTCALL IntGetDesktopWindow(VOID)
+{
+    PDESKTOP pdo = IntGetActiveDesktop();
+    if (!pdo)
+    {
+        TRACE("No active desktop\n");
+        return NULL;
+    }
+    return pdo->DesktopWindow;
+}
+
+PWND FASTCALL UserGetDesktopWindow(VOID)
+{
+    PDESKTOP pdo = IntGetActiveDesktop();
+
+    if (!pdo)
+    {
+        TRACE("No active desktop\n");
+        return NULL;
+    }
+    // return pdo->pDeskInfo->spwnd;
+    return UserGetWindowObject(pdo->DesktopWindow);
+}
+
+HWND FASTCALL IntGetMessageWindow(VOID)
+{
+    PDESKTOP pdo = IntGetActiveDesktop();
+
+    if (!pdo)
+    {
+        TRACE("No active desktop\n");
+        return NULL;
+    }
+    return pdo->spwndMessage->head.h;
+}
+
+PWND FASTCALL UserGetMessageWindow(VOID)
+{
+    PDESKTOP pdo = IntGetActiveDesktop();
+
+    if (!pdo)
+    {
+        TRACE("No active desktop\n");
+        return NULL;
+    }
+    return pdo->spwndMessage;
+}
+
+HWND FASTCALL IntGetCurrentThreadDesktopWindow(VOID)
+{
+    PTHREADINFO pti = PsGetCurrentThreadWin32Thread();
+    PDESKTOP pdo = pti->rpdesk;
+    if (NULL == pdo)
+    {
+        ERR("Thread doesn't have a desktop\n");
+        return NULL;
+    }
+    return pdo->DesktopWindow;
+}
+
+/* PUBLIC FUNCTIONS ***********************************************************/
+
+BOOL FASTCALL
+DesktopWindowProc(PWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam, LRESULT *lResult)
+{
+    PAINTSTRUCT Ps;
+    ULONG Value;
+    //ERR("DesktopWindowProc\n");
+
+    *lResult = 0;
+
+    switch (Msg)
+    {
+        case WM_NCCREATE:
+            if (!Wnd->fnid)
+            {
+                Wnd->fnid = FNID_DESKTOP;
+            }
+            *lResult = (LRESULT)TRUE;
+            return TRUE;
+
+        case WM_CREATE:
+            Value = HandleToULong(PsGetCurrentProcessId());
+            // Save Process ID
+            co_UserSetWindowLong(UserHMGetHandle(Wnd), DT_GWL_PROCESSID, Value, FALSE);
+            Value = HandleToULong(PsGetCurrentThreadId());
+            // Save Thread ID
+            co_UserSetWindowLong(UserHMGetHandle(Wnd), DT_GWL_THREADID, Value, FALSE);
+        case WM_CLOSE:
+            return TRUE;
+
+        case WM_DISPLAYCHANGE:
+            co_WinPosSetWindowPos(Wnd, 0, 0, 0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER | SWP_NOACTIVATE);
+            return TRUE;
+
+        case WM_ERASEBKGND:
+            IntPaintDesktop((HDC)wParam);
+            *lResult = 1;
+            return TRUE;
+
+        case WM_PAINT:
+        {
+            if (IntBeginPaint(Wnd, &Ps))
+            {
+                IntEndPaint(Wnd, &Ps);
+            }
+            return TRUE;
+        }
+        case WM_SYSCOLORCHANGE:
+            co_UserRedrawWindow(Wnd, NULL, NULL, RDW_INVALIDATE|RDW_ERASE|RDW_ALLCHILDREN);
+            return TRUE;
+
+        case WM_SETCURSOR:
+        {
+            PCURICON_OBJECT pcurOld, pcurNew;
+            pcurNew = UserGetCurIconObject(gDesktopCursor);
+            if (!pcurNew)
+            {
+                return TRUE;
+            }
+
+            pcurNew->CURSORF_flags |= CURSORF_CURRENT;
+            pcurOld = UserSetCursor(pcurNew, FALSE);
+            if (pcurOld)
+            {
+                pcurOld->CURSORF_flags &= ~CURSORF_CURRENT;
+                UserDereferenceObject(pcurOld);
+            }
+            return TRUE;
+        }
+
+        case WM_WINDOWPOSCHANGING:
+        {
+            PWINDOWPOS pWindowPos = (PWINDOWPOS)lParam;
+            if ((pWindowPos->flags & SWP_SHOWWINDOW) != 0)
+            {
+                HDESK hdesk = IntGetDesktopObjectHandle(gpdeskInputDesktop);
+                IntSetThreadDesktop(hdesk, FALSE);
+            }
+            break;
+        }
+        default:
+            TRACE("DWP calling IDWP Msg %d\n",Msg);
+            //*lResult = IntDefWindowProc(Wnd, Msg, wParam, lParam, FALSE);
+    }
+    return TRUE; /* We are done. Do not do any callbacks to user mode */
+}
+
+BOOL FASTCALL
+UserMessageWindowProc(PWND pwnd, UINT Msg, WPARAM wParam, LPARAM lParam, LRESULT *lResult)
+{
+    *lResult = 0;
+
+    switch(Msg)
+    {
+    case WM_NCCREATE:
+        pwnd->fnid |= FNID_MESSAGEWND;
+        *lResult = (LRESULT)TRUE;
+        break;
+    case WM_DESTROY:
+        pwnd->fnid |= FNID_DESTROY;
+        break;
+    default:
+        ERR("UMWP calling IDWP\n");
+        *lResult = IntDefWindowProc(pwnd, Msg, wParam, lParam, FALSE);
+    }
+
+    return TRUE; /* We are done. Do not do any callbacks to user mode */
+}
+
+VOID NTAPI DesktopThreadMain(VOID)
+{
+    BOOL Ret;
+    MSG Msg;
+
+    gptiDesktopThread = PsGetCurrentThreadWin32Thread();
+
+    UserEnterExclusive();
+
+    /* Register system classes. This thread does not belong to any desktop so the
+       classes will be allocated from the shared heap */
+    UserRegisterSystemClasses();
+
+    while (TRUE)
+    {
+        Ret = co_IntGetPeekMessage(&Msg, 0, 0, 0, PM_REMOVE, TRUE);
+        if (Ret)
+        {
+            IntDispatchMessage(&Msg);
+        }
+    }
+
+    UserLeave();
+}
+
+HDC FASTCALL
+UserGetDesktopDC(ULONG DcType, BOOL EmptyDC, BOOL ValidatehWnd)
+{
+    PWND DesktopObject = 0;
+    HDC DesktopHDC = 0;
+
+    UserEnterExclusive();
+
+    if (DcType == DC_TYPE_DIRECT)
+    {
+        DesktopObject = UserGetDesktopWindow();
+        DesktopHDC = (HDC)UserGetWindowDC(DesktopObject);
+    }
+    else
+    {
+        PMONITOR pMonitor = UserGetPrimaryMonitor();
+        DesktopHDC = IntGdiCreateDisplayDC(pMonitor->hDev, DcType, EmptyDC);
+    }
+
+    UserLeave();
+
+    return DesktopHDC;
+}
+
+VOID APIENTRY
+UserRedrawDesktop(VOID)
+{
+    PWND Window = NULL;
+    PREGION Rgn;
+
+    Window = UserGetDesktopWindow();
+    Rgn = IntSysCreateRectpRgnIndirect(&Window->rcWindow);
+
+    IntInvalidateWindows( Window,
+                             Rgn,
+                       RDW_FRAME |
+                       RDW_ERASE |
+                  RDW_INVALIDATE |
+                 RDW_ALLCHILDREN);
+
+    REGION_Delete(Rgn);
+}
+
+
+NTSTATUS FASTCALL
+co_IntShowDesktop(PDESKTOP Desktop, ULONG Width, ULONG Height, BOOL bRedraw)
+{
+    PWND pwnd = Desktop->pDeskInfo->spwnd;
+    UINT flags = SWP_NOACTIVATE|SWP_NOZORDER|SWP_SHOWWINDOW;
+    ASSERT(pwnd);
+
+    if (!bRedraw)
+        flags |= SWP_NOREDRAW;
+
+    co_WinPosSetWindowPos(pwnd, NULL, 0, 0, Width, Height, flags);
+
+    if (bRedraw)
+        co_UserRedrawWindow( pwnd, NULL, 0, RDW_UPDATENOW | RDW_ALLCHILDREN | RDW_INVALIDATE );
+
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS FASTCALL
+IntHideDesktop(PDESKTOP Desktop)
+{
+    PWND DesktopWnd;
+
+    DesktopWnd = IntGetWindowObject(Desktop->DesktopWindow);
+    if (! DesktopWnd)
+    {
+        return ERROR_INVALID_WINDOW_HANDLE;
+    }
+    DesktopWnd->style &= ~WS_VISIBLE;
+
+    return STATUS_SUCCESS;
+}
+
+static
+HWND* FASTCALL
+UserBuildShellHookHwndList(PDESKTOP Desktop)
+{
+    ULONG entries=0;
+    PLIST_ENTRY ListEntry;
+    PSHELL_HOOK_WINDOW Current;
+    HWND* list;
+
+    /* FIXME: If we save nb elements in desktop, we don't have to loop to find nb entries */
+    ListEntry = Desktop->ShellHookWindows.Flink;
+    while (ListEntry != &Desktop->ShellHookWindows)
+    {
+        ListEntry = ListEntry->Flink;
+        entries++;
+    }
+
+    if (!entries) return NULL;
+
+    list = ExAllocatePoolWithTag(PagedPool, sizeof(HWND) * (entries + 1), USERTAG_WINDOWLIST); /* alloc one extra for nullterm */
+    if (list)
+    {
+        HWND* cursor = list;
+
+        ListEntry = Desktop->ShellHookWindows.Flink;
+        while (ListEntry != &Desktop->ShellHookWindows)
+        {
+            Current = CONTAINING_RECORD(ListEntry, SHELL_HOOK_WINDOW, ListEntry);
+            ListEntry = ListEntry->Flink;
+            *cursor++ = Current->hWnd;
+        }
+
+        *cursor = NULL; /* Nullterm list */
+    }
+
+    return list;
+}
+
+/*
+ * Send the Message to the windows registered for ShellHook
+ * notifications. The lParam contents depend on the Message. See
+ * MSDN for more details (RegisterShellHookWindow)
+ */
+VOID co_IntShellHookNotify(WPARAM Message, WPARAM wParam, LPARAM lParam)
+{
+    PDESKTOP Desktop = IntGetActiveDesktop();
+    HWND* HwndList;
+
+    if (!gpsi->uiShellMsg)
+    {
+        gpsi->uiShellMsg = IntAddAtom(L"SHELLHOOK");
+
+        TRACE("MsgType = %x\n", gpsi->uiShellMsg);
+        if (!gpsi->uiShellMsg)
+            ERR("LastError: %x\n", EngGetLastError());
+    }
+
+    if (!Desktop)
+    {
+        TRACE("IntShellHookNotify: No desktop!\n");
+        return;
+    }
+
+    // Allow other devices have a shot at foreground.
+    if (Message == HSHELL_APPCOMMAND) ptiLastInput = NULL;
+
+    // FIXME: System Tray Support.
+
+    HwndList = UserBuildShellHookHwndList(Desktop);
+    if (HwndList)
+    {
+        HWND* cursor = HwndList;
+
+        for (; *cursor; cursor++)
+        {
+            TRACE("Sending notify\n");
+            UserPostMessage(*cursor,
+                            gpsi->uiShellMsg,
+                            Message,
+                            (Message == HSHELL_LANGUAGE ? lParam : (LPARAM)wParam) );
+/*            co_IntPostOrSendMessage(*cursor,
+                                    gpsi->uiShellMsg,
+                                    Message,
+                                    (Message == HSHELL_LANGUAGE ? lParam : (LPARAM)wParam) );*/
+        }
+
+        ExFreePoolWithTag(HwndList, USERTAG_WINDOWLIST);
+    }
+
+    if (ISITHOOKED(WH_SHELL))
+    {
+        co_HOOK_CallHooks(WH_SHELL, Message, wParam, lParam);
+    }
+}
+
+/*
+ * Add the window to the ShellHookWindows list. The windows
+ * on that list get notifications that are important to shell
+ * type applications.
+ *
+ * TODO: Validate the window? I'm not sure if sending these messages to
+ * an unsuspecting application that is not your own is a nice thing to do.
+ */
+BOOL IntRegisterShellHookWindow(HWND hWnd)
+{
+    PTHREADINFO pti = PsGetCurrentThreadWin32Thread();
+    PDESKTOP Desktop = pti->rpdesk;
+    PSHELL_HOOK_WINDOW Entry;
+
+    TRACE("IntRegisterShellHookWindow\n");
+
+    /* First deregister the window, so we can be sure it's never twice in the
+     * list.
+     */
+    IntDeRegisterShellHookWindow(hWnd);
+
+    Entry = ExAllocatePoolWithTag(PagedPool,
+                                  sizeof(SHELL_HOOK_WINDOW),
+                                  TAG_WINSTA);
+
+    if (!Entry)
+        return FALSE;
+
+    Entry->hWnd = hWnd;
+
+    InsertTailList(&Desktop->ShellHookWindows, &Entry->ListEntry);
+
+    return TRUE;
+}
+
+/*
+ * Remove the window from the ShellHookWindows list. The windows
+ * on that list get notifications that are important to shell
+ * type applications.
+ */
+BOOL IntDeRegisterShellHookWindow(HWND hWnd)
+{
+    PTHREADINFO pti = PsGetCurrentThreadWin32Thread();
+    PDESKTOP Desktop = pti->rpdesk;
+    PLIST_ENTRY ListEntry;
+    PSHELL_HOOK_WINDOW Current;
+
+    ListEntry = Desktop->ShellHookWindows.Flink;
+    while (ListEntry != &Desktop->ShellHookWindows)
+    {
+        Current = CONTAINING_RECORD(ListEntry, SHELL_HOOK_WINDOW, ListEntry);
+        ListEntry = ListEntry->Flink;
+        if (Current->hWnd == hWnd)
+        {
+            RemoveEntryList(&Current->ListEntry);
+            ExFreePoolWithTag(Current, TAG_WINSTA);
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+static VOID
+IntFreeDesktopHeap(IN OUT PDESKTOP Desktop)
+{
+    /* FIXME: Disable until unmapping works in mm */
+#if 0
+    if (Desktop->pheapDesktop != NULL)
+    {
+        MmUnmapViewInSessionSpace(Desktop->pheapDesktop);
+        Desktop->pheapDesktop = NULL;
+    }
+
+    if (Desktop->hsectionDesktop != NULL)
+    {
+        ObDereferenceObject(Desktop->hsectionDesktop);
+        Desktop->hsectionDesktop = NULL;
+    }
+#endif
+}
+
+BOOL FASTCALL
+IntPaintDesktop(HDC hDC)
+{
+    static WCHAR s_wszSafeMode[] = L"Safe Mode"; // FIXME: Localize!
+
+    RECTL Rect;
+    HBRUSH DesktopBrush, PreviousBrush;
+    HWND hWndDesktop;
+    BOOL doPatBlt = TRUE;
+    PWND WndDesktop;
+    BOOLEAN InSafeMode;
+
+    if (GdiGetClipBox(hDC, &Rect) == ERROR)
+        return FALSE;
+
+    hWndDesktop = IntGetDesktopWindow(); // rpdesk->DesktopWindow;
+
+    WndDesktop = UserGetWindowObject(hWndDesktop); // rpdesk->pDeskInfo->spwnd;
+    if (!WndDesktop)
+        return FALSE;
+
+    /* Retrieve the current SafeMode state */
+    InSafeMode = (UserGetSystemMetrics(SM_CLEANBOOT) != 0); // gpsi->aiSysMet[SM_CLEANBOOT];
+
+    if (!InSafeMode)
+    {
+        DesktopBrush = (HBRUSH)WndDesktop->pcls->hbrBackground;
+
+        /*
+         * Paint desktop background
+         */
+        if (gspv.hbmWallpaper != NULL)
+        {
+            SIZE sz;
+            int x, y;
+            HDC hWallpaperDC;
+
+            sz.cx = WndDesktop->rcWindow.right - WndDesktop->rcWindow.left;
+            sz.cy = WndDesktop->rcWindow.bottom - WndDesktop->rcWindow.top;
+
+            if (gspv.WallpaperMode == wmStretch ||
+                gspv.WallpaperMode == wmTile)
+            {
+                x = 0;
+                y = 0;
+            }
+            else
+            {
+                /* Find the upper left corner, can be negative if the bitmap is bigger than the screen */
+                x = (sz.cx / 2) - (gspv.cxWallpaper / 2);
+                y = (sz.cy / 2) - (gspv.cyWallpaper / 2);
+            }
+
+            hWallpaperDC = NtGdiCreateCompatibleDC(hDC);
+            if (hWallpaperDC != NULL)
+            {
+                HBITMAP hOldBitmap;
+
+                /* Fill in the area that the bitmap is not going to cover */
+                if (x > 0 || y > 0)
+                {
+                    /* FIXME: Clip out the bitmap
+                       can be replaced with "NtGdiPatBlt(hDC, x, y, gspv.cxWallpaper, gspv.cyWallpaper, PATCOPY | DSTINVERT);"
+                       once we support DSTINVERT */
+                    PreviousBrush = NtGdiSelectBrush(hDC, DesktopBrush);
+                    NtGdiPatBlt(hDC, Rect.left, Rect.top, Rect.right, Rect.bottom, PATCOPY);
+                    NtGdiSelectBrush(hDC, PreviousBrush);
+                }
+
+                /* Do not fill the background after it is painted no matter the size of the picture */
+                doPatBlt = FALSE;
+
+                hOldBitmap = NtGdiSelectBitmap(hWallpaperDC, gspv.hbmWallpaper);
+
+                if (gspv.WallpaperMode == wmStretch)
+                {
+                    if (Rect.right && Rect.bottom)
+                        NtGdiStretchBlt(hDC,
+                                        x,
+                                        y,
+                                        sz.cx,
+                                        sz.cy,
+                                        hWallpaperDC,
+                                        0,
+                                        0,
+                                        gspv.cxWallpaper,
+                                        gspv.cyWallpaper,
+                                        SRCCOPY,
+                                        0);
+                }
+                else if (gspv.WallpaperMode == wmTile)
+                {
+                    /* Paint the bitmap across the screen then down */
+                    for (y = 0; y < Rect.bottom; y += gspv.cyWallpaper)
+                    {
+                        for (x = 0; x < Rect.right; x += gspv.cxWallpaper)
+                        {
+                            NtGdiBitBlt(hDC,
+                                        x,
+                                        y,
+                                        gspv.cxWallpaper,
+                                        gspv.cyWallpaper,
+                                        hWallpaperDC,
+                                        0,
+                                        0,
+                                        SRCCOPY,
+                                        0,
+                                        0);
+                        }
+                    }
+                }
+                else
+                {
+                    NtGdiBitBlt(hDC,
+                                x,
+                                y,
+                                gspv.cxWallpaper,
+                                gspv.cyWallpaper,
+                                hWallpaperDC,
+                                0,
+                                0,
+                                SRCCOPY,
+                                0,
+                                0);
+                }
+                NtGdiSelectBitmap(hWallpaperDC, hOldBitmap);
+                NtGdiDeleteObjectApp(hWallpaperDC);
+            }
+        }
+    }
+    else
+    {
+        /* Black desktop background in Safe Mode */
+        DesktopBrush = StockObjects[BLACK_BRUSH];
+    }
+
+    /* Background is set to none, clear the screen */
+    if (doPatBlt)
+    {
+        PreviousBrush = NtGdiSelectBrush(hDC, DesktopBrush);
+        NtGdiPatBlt(hDC, Rect.left, Rect.top, Rect.right, Rect.bottom, PATCOPY);
+        NtGdiSelectBrush(hDC, PreviousBrush);
+    }
+
+    /*
+     * Display the system version on the desktop background
+     */
+    if (InSafeMode || g_AlwaysDisplayVersion || g_PaintDesktopVersion)
+    {
+        NTSTATUS Status;
+        static WCHAR wszzVersion[1024] = L"\0";
+
+        /* Only used in normal mode */
+        // We expect at most 4 strings (3 for version, 1 for optional NtSystemRoot)
+        static POLYTEXTW VerStrs[4] = {{0},{0},{0},{0}};
+        INT i = 0;
+        INT len;
+
+        HFONT hFont1 = NULL, hFont2 = NULL, hOldFont = NULL;
+        COLORREF crText, color_old;
+        UINT align_old;
+        INT mode_old;
+        PDC pdc;
+
+        if (!UserSystemParametersInfo(SPI_GETWORKAREA, 0, &Rect, 0))
+        {
+            Rect.left = Rect.top = 0;
+            Rect.right  = UserGetSystemMetrics(SM_CXSCREEN);
+            Rect.bottom = UserGetSystemMetrics(SM_CYSCREEN);
+        }
+        else
+        {
+            RECTL_vOffsetRect(&Rect, -Rect.left, -Rect.top);
+        }
+
+        /*
+         * Set up the fonts (otherwise use default ones)
+         */
+
+        /* Font for the principal version string */
+        hFont1 = GreCreateFontIndirectW(&gspv.ncm.lfCaptionFont);
+        /* Font for the secondary version strings */
+        hFont2 = GreCreateFontIndirectW(&gspv.ncm.lfMenuFont);
+
+        if (hFont1)
+            hOldFont = NtGdiSelectFont(hDC, hFont1);
+
+        if (gspv.hbmWallpaper == NULL)
+        {
+            /* Retrieve the brush fill colour */
+            // TODO: The following code constitutes "GreGetBrushColor".
+            PreviousBrush = NtGdiSelectBrush(hDC, DesktopBrush);
+            pdc = DC_LockDc(hDC);
+            if (pdc)
+            {
+                crText = pdc->eboFill.ulRGBColor;
+                DC_UnlockDc(pdc);
+            }
+            else
+            {
+                crText = RGB(0, 0, 0);
+            }
+            NtGdiSelectBrush(hDC, PreviousBrush);
+
+            /* Adjust text colour according to the brush */
+            if (GetRValue(crText) + GetGValue(crText) + GetBValue(crText) > 128 * 3)
+                crText = RGB(0, 0, 0);
+            else
+                crText = RGB(255, 255, 255);
+        }
+        else
+        {
+            /* Always use white when the text is displayed on top of a wallpaper */
+            crText = RGB(255, 255, 255);
+        }
+
+        color_old = IntGdiSetTextColor(hDC, crText);
+        align_old = IntGdiSetTextAlign(hDC, TA_RIGHT);
+        mode_old  = IntGdiSetBkMode(hDC, TRANSPARENT);
+
+        /* Display the system version information */
+        if (!*wszzVersion)
+        {
+            Status = GetSystemVersionString(wszzVersion,
+                                            ARRAYSIZE(wszzVersion),
+                                            InSafeMode,
+                                            g_AlwaysDisplayVersion);
+            if (!InSafeMode && NT_SUCCESS(Status) && *wszzVersion)
+            {
+                PWCHAR pstr = wszzVersion;
+                for (i = 0; (i < ARRAYSIZE(VerStrs)) && *pstr; ++i)
+                {
+                    VerStrs[i].n = wcslen(pstr);
+                    VerStrs[i].lpstr = pstr;
+                    pstr += (VerStrs[i].n + 1);
+                }
+            }
+        }
+        else
+        {
+            Status = STATUS_SUCCESS;
+        }
+        if (NT_SUCCESS(Status) && *wszzVersion)
+        {
+            if (!InSafeMode)
+            {
+                SIZE Size = {0, 0};
+                LONG TotalHeight = 0;
+
+                /* Normal Mode: multiple version information text separated by newlines */
+                IntGdiSetTextAlign(hDC, TA_RIGHT | TA_BOTTOM);
+
+                /* Compute the heights of the strings */
+                if (hFont1) NtGdiSelectFont(hDC, hFont1);
+                for (i = 0; i < ARRAYSIZE(VerStrs); ++i)
+                {
+                    if (!VerStrs[i].lpstr || !*VerStrs[i].lpstr || (VerStrs[i].n == 0))
+                        break;
+
+                    GreGetTextExtentW(hDC, VerStrs[i].lpstr, VerStrs[i].n, &Size, 1);
+                    VerStrs[i].y = Size.cy; // Store the string height
+                    TotalHeight += Size.cy;
+
+                    /* While the first string was using hFont1, all the others use hFont2 */
+                    if (hFont2) NtGdiSelectFont(hDC, hFont2);
+                }
+                /* The total height must not exceed the screen height */
+                TotalHeight = min(TotalHeight, Rect.bottom);
+
+                /* Display the strings */
+                if (hFont1) NtGdiSelectFont(hDC, hFont1);
+                for (i = 0; i < ARRAYSIZE(VerStrs); ++i)
+                {
+                    if (!VerStrs[i].lpstr || !*VerStrs[i].lpstr || (VerStrs[i].n == 0))
+                        break;
+
+                    TotalHeight -= VerStrs[i].y;
+                    GreExtTextOutW(hDC,
+                                   Rect.right - 5,
+                                   Rect.bottom - TotalHeight - 5,
+                                   0, NULL,
+                                   VerStrs[i].lpstr,
+                                   VerStrs[i].n,
+                                   NULL, 0);
+
+                    /* While the first string was using hFont1, all the others use hFont2 */
+                    if (hFont2) NtGdiSelectFont(hDC, hFont2);
+                }
+            }
+            else
+            {
+                if (hFont1) NtGdiSelectFont(hDC, hFont1);
+
+                /* Safe Mode: single version information text in top center */
+                len = wcslen(wszzVersion);
+
+                IntGdiSetTextAlign(hDC, TA_CENTER | TA_TOP);
+                GreExtTextOutW(hDC, (Rect.right + Rect.left)/2, Rect.top + 3, 0, NULL, wszzVersion, len, NULL, 0);
+            }
+        }
+
+        if (InSafeMode)
+        {
+            if (hFont1) NtGdiSelectFont(hDC, hFont1);
+
+            /* Print Safe Mode text in corners */
+            len = wcslen(s_wszSafeMode);
+
+            IntGdiSetTextAlign(hDC, TA_LEFT | TA_TOP);
+            GreExtTextOutW(hDC, Rect.left, Rect.top + 3, 0, NULL, s_wszSafeMode, len, NULL, 0);
+            IntGdiSetTextAlign(hDC, TA_RIGHT | TA_TOP);
+            GreExtTextOutW(hDC, Rect.right, Rect.top + 3, 0, NULL, s_wszSafeMode, len, NULL, 0);
+            IntGdiSetTextAlign(hDC, TA_LEFT | TA_BOTTOM);
+            GreExtTextOutW(hDC, Rect.left, Rect.bottom - 5, 0, NULL, s_wszSafeMode, len, NULL, 0);
+            IntGdiSetTextAlign(hDC, TA_RIGHT | TA_BOTTOM);
+            GreExtTextOutW(hDC, Rect.right, Rect.bottom - 5, 0, NULL, s_wszSafeMode, len, NULL, 0);
+        }
+
+        IntGdiSetBkMode(hDC, mode_old);
+        IntGdiSetTextAlign(hDC, align_old);
+        IntGdiSetTextColor(hDC, color_old);
+
+        if (hFont2)
+            GreDeleteObject(hFont2);
+
+        if (hFont1)
+        {
+            NtGdiSelectFont(hDC, hOldFont);
+            GreDeleteObject(hFont1);
+        }
+    }
+
+    return TRUE;
+}
+
+static NTSTATUS
+UserInitializeDesktop(PDESKTOP pdesk, PUNICODE_STRING DesktopName, PWINSTATION_OBJECT pwinsta)
+{
+    PVOID DesktopHeapSystemBase = NULL;
+    ULONG_PTR HeapSize = gdwDesktopSectionSize * 1024;
+    SIZE_T DesktopInfoSize;
+    ULONG i;
+
+    TRACE("UserInitializeDesktop desktop 0x%p with name %wZ\n", pdesk, DesktopName);
+
+    RtlZeroMemory(pdesk, sizeof(DESKTOP));
+
+    /* Link the desktop with the parent window station */
+    ObReferenceObject(pwinsta);
+    pdesk->rpwinstaParent = pwinsta;
+    InsertTailList(&pwinsta->DesktopListHead, &pdesk->ListEntry);
+
+    /* Create the desktop heap */
+    pdesk->hsectionDesktop = NULL;
+    pdesk->pheapDesktop = UserCreateHeap(&pdesk->hsectionDesktop,
+                                         &DesktopHeapSystemBase,
+                                         HeapSize);
+    if (pdesk->pheapDesktop == NULL)
+    {
+        ERR("Failed to create desktop heap!\n");
+        return STATUS_NO_MEMORY;
+    }
+
+    /* Create DESKTOPINFO */
+    DesktopInfoSize = sizeof(DESKTOPINFO) + DesktopName->Length + sizeof(WCHAR);
+    pdesk->pDeskInfo = RtlAllocateHeap(pdesk->pheapDesktop,
+                                       HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY,
+                                       DesktopInfoSize);
+    if (pdesk->pDeskInfo == NULL)
+    {
+        ERR("Failed to create the DESKTOP structure!\n");
+        return STATUS_NO_MEMORY;
+    }
+
+    /* Initialize the DESKTOPINFO */
+    pdesk->pDeskInfo->pvDesktopBase = DesktopHeapSystemBase;
+    pdesk->pDeskInfo->pvDesktopLimit = (PVOID)((ULONG_PTR)DesktopHeapSystemBase + HeapSize);
+    RtlCopyMemory(pdesk->pDeskInfo->szDesktopName,
+                  DesktopName->Buffer,
+                  DesktopName->Length + sizeof(WCHAR));
+    for (i = 0; i < NB_HOOKS; i++)
+    {
+        InitializeListHead(&pdesk->pDeskInfo->aphkStart[i]);
+    }
+
+    InitializeListHead(&pdesk->ShellHookWindows);
+    InitializeListHead(&pdesk->PtiList);
+
+    return STATUS_SUCCESS;
+}
+
+/* SYSCALLS *******************************************************************/
+
+/*
+ * NtUserCreateDesktop
+ *
+ * Creates a new desktop.
+ *
+ * Parameters
+ *    poaAttribs
+ *       Object Attributes.
+ *
+ *    lpszDesktopDevice
+ *       Name of the device.
+ *
+ *    pDeviceMode
+ *       Device Mode.
+ *
+ *    dwFlags
+ *       Interaction flags.
+ *
+ *    dwDesiredAccess
+ *       Requested type of access.
+ *
+ *
+ * Return Value
+ *    If the function succeeds, the return value is a handle to the newly
+ *    created desktop. If the specified desktop already exists, the function
+ *    succeeds and returns a handle to the existing desktop. When you are
+ *    finished using the handle, call the CloseDesktop function to close it.
+ *    If the function fails, the return value is NULL.
+ *
+ * Status
+ *    @implemented
+ */
+
+HDESK APIENTRY
+NtUserCreateDesktop(
+    POBJECT_ATTRIBUTES ObjectAttributes,
+    PUNICODE_STRING lpszDesktopDevice,
+    LPDEVMODEW lpdmw,
+    DWORD dwFlags,
+    ACCESS_MASK dwDesiredAccess)
+{
+    PDESKTOP pdesk = NULL;
+    NTSTATUS Status = STATUS_SUCCESS;
+    HDESK hdesk;
+    BOOLEAN Context = FALSE;
+    UNICODE_STRING ClassName;
+    LARGE_STRING WindowName;
+    BOOL NoHooks = FALSE;
+    PWND pWnd = NULL;
+    CREATESTRUCTW Cs;
+    PTHREADINFO ptiCurrent;
+    PCLS pcls;
+
+    DECLARE_RETURN(HDESK);
+
+    TRACE("Enter NtUserCreateDesktop\n");
+    UserEnterExclusive();
+
+    ptiCurrent = PsGetCurrentThreadWin32Thread();
+    ASSERT(ptiCurrent);
+    ASSERT(gptiDesktopThread);
+
+    /* Turn off hooks when calling any CreateWindowEx from inside win32k. */
+    NoHooks = (ptiCurrent->TIF_flags & TIF_DISABLEHOOKS);
+    ptiCurrent->TIF_flags |= TIF_DISABLEHOOKS;
+    ptiCurrent->pClientInfo->dwTIFlags = ptiCurrent->TIF_flags;
+
+    /*
+     * Try to open already existing desktop
+     */
+    Status = ObOpenObjectByName(
+                 ObjectAttributes,
+                 ExDesktopObjectType,
+                 UserMode,
+                 NULL,
+                 dwDesiredAccess,
+                 (PVOID)&Context,
+                 (HANDLE*)&hdesk);
+    if (!NT_SUCCESS(Status))
+    {
+        ERR("ObOpenObjectByName failed to open/create desktop\n");
+        SetLastNtError(Status);
+        RETURN(NULL);
+    }
+
+    /* In case the object was not created (eg if it existed), return now */
+    if (Context == FALSE)
+    {
+        TRACE("NtUserCreateDesktop opened desktop %wZ\n", ObjectAttributes->ObjectName);
+        RETURN( hdesk);
+    }
+
+    /* Reference the desktop */
+    Status = ObReferenceObjectByHandle(hdesk,
+                                       0,
+                                       ExDesktopObjectType,
+                                       KernelMode,
+                                       (PVOID*)&pdesk,
+                                       NULL);
+    if (!NT_SUCCESS(Status))
+    {
+        ERR("Failed to reference desktop object\n");
+        SetLastNtError(Status);
+        RETURN(NULL);
+    }
+
+    /* Get the desktop window class. The thread desktop does not belong to any desktop
+     * so the classes created there (including the desktop class) are allocated in the shared heap
+     * It would cause problems if we used a class that belongs to the caller
+     */
+    ClassName.Buffer = WC_DESKTOP;
+    ClassName.Length = 0;
+    pcls = IntGetAndReferenceClass(&ClassName, 0, TRUE);
+    if (pcls == NULL)
+    {
+        ASSERT(FALSE);
+        RETURN(NULL);
+    }
+
+    RtlZeroMemory(&WindowName, sizeof(WindowName));
+    RtlZeroMemory(&Cs, sizeof(Cs));
+    Cs.x = UserGetSystemMetrics(SM_XVIRTUALSCREEN),
+    Cs.y = UserGetSystemMetrics(SM_YVIRTUALSCREEN),
+    Cs.cx = UserGetSystemMetrics(SM_CXVIRTUALSCREEN),
+    Cs.cy = UserGetSystemMetrics(SM_CYVIRTUALSCREEN),
+    Cs.style = WS_POPUP|WS_CLIPCHILDREN;
+    Cs.hInstance = hModClient; // hModuleWin; // Server side winproc!
+    Cs.lpszName = (LPCWSTR) &WindowName;
+    Cs.lpszClass = (LPCWSTR) &ClassName;
+
+    /* Use IntCreateWindow instead of co_UserCreateWindowEx cause the later expects a thread with a desktop */
+    pWnd = IntCreateWindow(&Cs, &WindowName, pcls, NULL, NULL, NULL, pdesk);
+    if (pWnd == NULL)
+    {
+        ERR("Failed to create desktop window for the new desktop\n");
+        RETURN(NULL);
+    }
+
+    pdesk->dwSessionId = PsGetCurrentProcessSessionId();
+    pdesk->DesktopWindow = pWnd->head.h;
+    pdesk->pDeskInfo->spwnd = pWnd;
+    pWnd->fnid = FNID_DESKTOP;
+
+    ClassName.Buffer = MAKEINTATOM(gpsi->atomSysClass[ICLS_HWNDMESSAGE]);
+    ClassName.Length = 0;
+    pcls = IntGetAndReferenceClass(&ClassName, 0, TRUE);
+    if (pcls == NULL)
+    {
+        ASSERT(FALSE);
+        RETURN(NULL);
+    }
+
+    RtlZeroMemory(&WindowName, sizeof(WindowName));
+    RtlZeroMemory(&Cs, sizeof(Cs));
+    Cs.cx = Cs.cy = 100;
+    Cs.style = WS_POPUP|WS_CLIPCHILDREN;
+    Cs.hInstance = hModClient; // hModuleWin; // Server side winproc!
+    Cs.lpszName = (LPCWSTR) &WindowName;
+    Cs.lpszClass = (LPCWSTR) &ClassName;
+    pWnd = IntCreateWindow(&Cs, &WindowName, pcls, NULL, NULL, NULL, pdesk);
+    if (pWnd == NULL)
+    {
+        ERR("Failed to create message window for the new desktop\n");
+        RETURN(NULL);
+    }
+
+    pdesk->spwndMessage = pWnd;
+    pWnd->fnid = FNID_MESSAGEWND;
+
+    /* Now,,,
+       if !(WinStaObject->Flags & WSF_NOIO) is (not set) for desktop input output mode (see wiki)
+       Create Tooltip. Saved in DesktopObject->spwndTooltip.
+       Tooltip dwExStyle: WS_EX_TOOLWINDOW|WS_EX_TOPMOST
+       hWndParent are spwndMessage. Use hModuleWin for server side winproc!
+       The rest is same as message window.
+       http://msdn.microsoft.com/en-us/library/bb760250(VS.85).aspx
+    */
+    RETURN( hdesk);
+
+CLEANUP:
+    if (pdesk != NULL)
+    {
+        ObDereferenceObject(pdesk);
+    }
+    if (_ret_ == NULL && hdesk != NULL)
+    {
+        ObCloseHandle(hdesk, UserMode);
+    }
+    if (!NoHooks)
+    {
+        ptiCurrent->TIF_flags &= ~TIF_DISABLEHOOKS;
+        ptiCurrent->pClientInfo->dwTIFlags = ptiCurrent->TIF_flags;
+    }
+    TRACE("Leave NtUserCreateDesktop, ret=%p\n",_ret_);
+    UserLeave();
+    END_CLEANUP;
+}
+
+/*
+ * NtUserOpenDesktop
+ *
+ * Opens an existing desktop.
+ *
+ * Parameters
+ *    lpszDesktopName
+ *       Name of the existing desktop.
+ *
+ *    dwFlags
+ *       Interaction flags.
+ *
+ *    dwDesiredAccess
+ *       Requested type of access.
+ *
+ * Return Value
+ *    Handle to the desktop or zero on failure.
+ *
+ * Status
+ *    @implemented
+ */
+
+HDESK APIENTRY
+NtUserOpenDesktop(
+    POBJECT_ATTRIBUTES ObjectAttributes,
+    DWORD dwFlags,
+    ACCESS_MASK dwDesiredAccess)
+{
+    NTSTATUS Status;
+    HDESK Desktop;
+
+    Status = ObOpenObjectByName(
+                 ObjectAttributes,
+                 ExDesktopObjectType,
+                 UserMode,
+                 NULL,
+                 dwDesiredAccess,
+                 NULL,
+                 (HANDLE*)&Desktop);
+
+    if (!NT_SUCCESS(Status))
+    {
+        ERR("Failed to open desktop\n");
+        SetLastNtError(Status);
+        return NULL;
+    }
+
+    TRACE("Opened desktop %S with handle 0x%p\n", ObjectAttributes->ObjectName->Buffer, Desktop);
+
+    return Desktop;
+}
+
+/*
+ * NtUserOpenInputDesktop
+ *
+ * Opens the input (interactive) desktop.
+ *
+ * Parameters
+ *    dwFlags
+ *       Interaction flags.
+ *
+ *    fInherit
+ *       Inheritance option.
+ *
+ *    dwDesiredAccess
+ *       Requested type of access.
+ *
+ * Return Value
+ *    Handle to the input desktop or zero on failure.
+ *
+ * Status
+ *    @implemented
+ */
+
+HDESK APIENTRY
+NtUserOpenInputDesktop(
+    DWORD dwFlags,
+    BOOL fInherit,
+    ACCESS_MASK dwDesiredAccess)
+{
+    NTSTATUS Status;
+    HDESK hdesk = NULL;
+    ULONG HandleAttributes = 0;
+
+    UserEnterExclusive();
+    TRACE("Enter NtUserOpenInputDesktop gpdeskInputDesktop 0x%p\n",gpdeskInputDesktop);
+
+    if (fInherit) HandleAttributes = OBJ_INHERIT;
+
+    /* Create a new handle to the object */
+    Status = ObOpenObjectByPointer(
+                 gpdeskInputDesktop,
+                 HandleAttributes,
+                 NULL,
+                 dwDesiredAccess,
+                 ExDesktopObjectType,
+                 UserMode,
+                 (PHANDLE)&hdesk);
+
+    if (!NT_SUCCESS(Status))
+    {
+        ERR("Failed to open input desktop object\n");
+        SetLastNtError(Status);
+    }
+
+    TRACE("NtUserOpenInputDesktop returning 0x%p\n",hdesk);
+    UserLeave();
+    return hdesk;
+}
+
+/*
+ * NtUserCloseDesktop
+ *
+ * Closes a desktop handle.
+ *
+ * Parameters
+ *    hDesktop
+ *       Handle to the desktop.
+ *
+ * Return Value
+ *   Status
+ *
+ * Remarks
+ *   The desktop handle can be created with NtUserCreateDesktop or
+ *   NtUserOpenDesktop. This function will fail if any thread in the calling
+ *   process is using the specified desktop handle or if the handle refers
+ *   to the initial desktop of the calling process.
+ *
+ * Status
+ *    @implemented
+ */
+
+BOOL APIENTRY
+NtUserCloseDesktop(HDESK hDesktop)
+{
+    PDESKTOP pdesk;
+    NTSTATUS Status;
+    DECLARE_RETURN(BOOL);
+
+    TRACE("NtUserCloseDesktop called (0x%p)\n", hDesktop);
+    UserEnterExclusive();
+
+    if (hDesktop == gptiCurrent->hdesk || hDesktop == gptiCurrent->ppi->hdeskStartup)
+    {
+        ERR("Attempted to close thread desktop\n");
+        EngSetLastError(ERROR_BUSY);
+        RETURN(FALSE);
+    }
+
+    Status = IntValidateDesktopHandle( hDesktop, UserMode, 0, &pdesk);
+    if (!NT_SUCCESS(Status))
+    {
+        ERR("Validation of desktop handle (0x%p) failed\n", hDesktop);
+        RETURN(FALSE);
+    }
+
+    ObDereferenceObject(pdesk);
+
+    Status = ZwClose(hDesktop);
+    if (!NT_SUCCESS(Status))
+    {
+        ERR("Failed to close desktop handle 0x%p\n", hDesktop);
+        SetLastNtError(Status);
+        RETURN(FALSE);
+    }
+
+    RETURN(TRUE);
+
+CLEANUP:
+    TRACE("Leave NtUserCloseDesktop, ret=%i\n",_ret_);
+    UserLeave();
+    END_CLEANUP;
+}
+
+/*
+ * NtUserPaintDesktop
+ *
+ * The NtUserPaintDesktop function fills the clipping region in the
+ * specified device context with the desktop pattern or wallpaper. The
+ * function is provided primarily for shell desktops.
+ *
+ * Parameters
+ *    hDC
+ *       Handle to the device context.
+ *
+ * Status
+ *    @implemented
+ */
+
+BOOL APIENTRY
+NtUserPaintDesktop(HDC hDC)
+{
+    BOOL Ret;
+    UserEnterExclusive();
+    TRACE("Enter NtUserPaintDesktop\n");
+    Ret = IntPaintDesktop(hDC);
+    TRACE("Leave NtUserPaintDesktop, ret=%i\n",Ret);
+    UserLeave();
+    return Ret;
+}
+
+/*
+ * NtUserResolveDesktop
+ *
+ * The NtUserResolveDesktop function retrieves handles to the desktop and
+ * the window station specified by the desktop path string.
+ *
+ * Parameters
+ *    ProcessHandle
+ *       Handle to a user process.
+ *
+ *    DesktopPath
+ *       The desktop path string.
+ *
+ * Return Value
+ *    Handle to the desktop (direct return value) and
+ *    handle to the associated window station (by pointer).
+ *    NULL in case of failure.
+ *
+ * Remarks
+ *    Callable by CSRSS only.
+ *
+ * Status
+ *    @implemented
+ */
+
+HDESK
+APIENTRY
+NtUserResolveDesktop(
+    IN HANDLE ProcessHandle,
+    IN PUNICODE_STRING DesktopPath,
+    DWORD dwUnknown,
+    OUT HWINSTA* phWinSta)
+{
+    NTSTATUS Status;
+    PEPROCESS Process = NULL;
+    HWINSTA hWinSta = NULL;
+    HDESK hDesktop  = NULL;
+
+    /* Allow only the Console Server to perform this operation (via CSRSS) */
+    if (PsGetCurrentProcess() != gpepCSRSS)
+        return NULL;
+
+    /* Get the process object the user handle was referencing */
+    Status = ObReferenceObjectByHandle(ProcessHandle,
+                                       PROCESS_QUERY_INFORMATION,
+                                       *PsProcessType,
+                                       UserMode,
+                                       (PVOID*)&Process,
+                                       NULL);
+    if (!NT_SUCCESS(Status)) return NULL;
+
+    // UserEnterShared();
+
+    _SEH2_TRY
+    {
+        UNICODE_STRING CapturedDesktopPath;
+
+        /* Capture the user desktop path string */
+        Status = IntSafeCopyUnicodeStringTerminateNULL(&CapturedDesktopPath,
+                                                       DesktopPath);
+        if (!NT_SUCCESS(Status)) _SEH2_YIELD(goto Quit);
+
+        /* Call the internal function */
+        Status = IntParseDesktopPath(Process,
+                                     &CapturedDesktopPath,
+                                     &hWinSta,
+                                     &hDesktop);
+        if (!NT_SUCCESS(Status))
+        {
+            ERR("IntParseDesktopPath failed, Status = 0x%08lx\n", Status);
+            hWinSta  = NULL;
+            hDesktop = NULL;
+        }
+
+        /* Return the window station handle */
+        *phWinSta = hWinSta;
+
+        /* Free the captured string */
+        if (CapturedDesktopPath.Buffer)
+            ExFreePoolWithTag(CapturedDesktopPath.Buffer, TAG_STRING);
+    }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+    {
+        Status = _SEH2_GetExceptionCode();
+    }
+    _SEH2_END;
+
+Quit:
+    // UserLeave();
+
+    /* Dereference the process object */
+    ObDereferenceObject(Process);
+
+    /* Return the desktop handle */
+    return hDesktop;
+}
+
+/*
+ * NtUserSwitchDesktop
+ *
+ * Sets the current input (interactive) desktop.
+ *
+ * Parameters
+ *    hDesktop
+ *       Handle to desktop.
+ *
+ * Return Value
+ *    Status
+ *
+ * Status
+ *    @unimplemented
+ */
+
+BOOL APIENTRY
+NtUserSwitchDesktop(HDESK hdesk)
+{
+    PDESKTOP pdesk;
+    NTSTATUS Status;
+    BOOL bRedrawDesktop;
+    DECLARE_RETURN(BOOL);
+
+    UserEnterExclusive();
+    TRACE("Enter NtUserSwitchDesktop(0x%p)\n", hdesk);
+
+    Status = IntValidateDesktopHandle( hdesk, UserMode, 0, &pdesk);
+    if (!NT_SUCCESS(Status))
+    {
+        ERR("Validation of desktop handle (0x%p) failed\n", hdesk);
+        RETURN(FALSE);
+    }
+
+    if (PsGetCurrentProcessSessionId() != pdesk->rpwinstaParent->dwSessionId)
+    {
+        ObDereferenceObject(pdesk);
+        ERR("NtUserSwitchDesktop called for a desktop of a different session\n");
+        RETURN(FALSE);
+    }
+
+    if (pdesk == gpdeskInputDesktop)
+    {
+        ObDereferenceObject(pdesk);
+        WARN("NtUserSwitchDesktop called for active desktop\n");
+        RETURN(TRUE);
+    }
+
+    /*
+     * Don't allow applications switch the desktop if it's locked, unless the caller
+     * is the logon application itself
+     */
+    if ((pdesk->rpwinstaParent->Flags & WSS_LOCKED) &&
+        gpidLogon != PsGetCurrentProcessId())
+    {
+        ObDereferenceObject(pdesk);
+        ERR("Switching desktop 0x%p denied because the window station is locked!\n", hdesk);
+        RETURN(FALSE);
+    }
+
+    if (pdesk->rpwinstaParent != InputWindowStation)
+    {
+        ObDereferenceObject(pdesk);
+        ERR("Switching desktop 0x%p denied because desktop doesn't belong to the interactive winsta!\n", hdesk);
+        RETURN(FALSE);
+    }
+
+    /* FIXME: Fail if the process is associated with a secured
+              desktop such as Winlogon or Screen-Saver */
+    /* FIXME: Connect to input device */
+
+    TRACE("Switching from desktop 0x%p to 0x%p\n", gpdeskInputDesktop, pdesk);
+
+    bRedrawDesktop = FALSE;
+
+    /* The first time SwitchDesktop is called, gpdeskInputDesktop is NULL */
+    if (gpdeskInputDesktop != NULL)
+    {
+        if ((gpdeskInputDesktop->pDeskInfo->spwnd->style & WS_VISIBLE) == WS_VISIBLE)
+            bRedrawDesktop = TRUE;
+
+        /* Hide the previous desktop window */
+        IntHideDesktop(gpdeskInputDesktop);
+    }
+
+    /* Set the active desktop in the desktop's window station. */
+    InputWindowStation->ActiveDesktop = pdesk;
+
+    /* Set the global state. */
+    gpdeskInputDesktop = pdesk;
+
+    /* Show the new desktop window */
+    co_IntShowDesktop(pdesk, UserGetSystemMetrics(SM_CXSCREEN), UserGetSystemMetrics(SM_CYSCREEN), bRedrawDesktop);
+
+    TRACE("SwitchDesktop gpdeskInputDesktop 0x%p\n",gpdeskInputDesktop);
+    ObDereferenceObject(pdesk);
+
+    RETURN(TRUE);
+
+CLEANUP:
+    TRACE("Leave NtUserSwitchDesktop, ret=%i\n",_ret_);
+    UserLeave();
+    END_CLEANUP;
+}
+
+/*
+ * NtUserGetThreadDesktop
+ *
+ * Status
+ *    @implemented
+ */
+
+HDESK APIENTRY
+NtUserGetThreadDesktop(DWORD dwThreadId, DWORD Unknown1)
+{
+    NTSTATUS Status;
+    PETHREAD Thread;
+    PDESKTOP DesktopObject;
+    HDESK Ret, hThreadDesktop;
+    OBJECT_HANDLE_INFORMATION HandleInformation;
+    DECLARE_RETURN(HDESK);
+
+    UserEnterExclusive();
+    TRACE("Enter NtUserGetThreadDesktop\n");
+
+    if (!dwThreadId)
+    {
+        EngSetLastError(ERROR_INVALID_PARAMETER);
+        RETURN(0);
+    }
+
+    Status = PsLookupThreadByThreadId((HANDLE)(DWORD_PTR)dwThreadId, &Thread);
+    if (!NT_SUCCESS(Status))
+    {
+        EngSetLastError(ERROR_INVALID_PARAMETER);
+        RETURN(0);
+    }
+
+    if (Thread->ThreadsProcess == PsGetCurrentProcess())
+    {
+        /* Just return the handle, we queried the desktop handle of a thread running
+           in the same context */
+        Ret = ((PTHREADINFO)Thread->Tcb.Win32Thread)->hdesk;
+        ObDereferenceObject(Thread);
+        RETURN(Ret);
+    }
+
+    /* Get the desktop handle and the desktop of the thread */
+    if (!(hThreadDesktop = ((PTHREADINFO)Thread->Tcb.Win32Thread)->hdesk) ||
+        !(DesktopObject = ((PTHREADINFO)Thread->Tcb.Win32Thread)->rpdesk))
+    {
+        ObDereferenceObject(Thread);
+        ERR("Desktop information of thread 0x%x broken!?\n", dwThreadId);
+        RETURN(NULL);
+    }
+
+    /* We could just use DesktopObject instead of looking up the handle, but latter
+       may be a bit safer (e.g. when the desktop is being destroyed */
+    /* Switch into the context of the thread we're trying to get the desktop from,
+       so we can use the handle */
+    KeAttachProcess(&Thread->ThreadsProcess->Pcb);
+    Status = ObReferenceObjectByHandle(hThreadDesktop,
+                                       GENERIC_ALL,
+                                       ExDesktopObjectType,
+                                       UserMode,
+                                       (PVOID*)&DesktopObject,
+                                       &HandleInformation);
+    KeDetachProcess();
+
+    /* The handle couldn't be found, there's nothing to get... */
+    if (!NT_SUCCESS(Status))
+    {
+        ObDereferenceObject(Thread);
+        RETURN(NULL);
+    }
+
+    /* Lookup our handle table if we can find a handle to the desktop object,
+       if not, create one */
+    Ret = IntGetDesktopObjectHandle(DesktopObject);
+
+    /* All done, we got a valid handle to the desktop */
+    ObDereferenceObject(DesktopObject);
+    ObDereferenceObject(Thread);
+    RETURN(Ret);
+
+CLEANUP:
+    TRACE("Leave NtUserGetThreadDesktop, ret=%p\n",_ret_);
+    UserLeave();
+    END_CLEANUP;
+}
+
+static NTSTATUS
+IntUnmapDesktopView(IN PDESKTOP pdesk)
+{
+    PPROCESSINFO ppi;
+    PW32HEAP_USER_MAPPING HeapMapping, *PrevLink;
+    NTSTATUS Status = STATUS_SUCCESS;
+
+    TRACE("IntUnmapDesktopView called for desktop object %p\n", pdesk);
+
+    ppi = PsGetCurrentProcessWin32Process();
+
+    /*
+     * Unmap if we're the last thread using the desktop.
+     * Start the search at the next mapping: skip the first entry
+     * as it must be the global user heap mapping.
+     */
+    PrevLink = &ppi->HeapMappings.Next;
+    HeapMapping = *PrevLink;
+    while (HeapMapping != NULL)
+    {
+        if (HeapMapping->KernelMapping == (PVOID)pdesk->pheapDesktop)
+        {
+            if (--HeapMapping->Count == 0)
+            {
+                *PrevLink = HeapMapping->Next;
+
+                TRACE("ppi 0x%p unmapped heap of desktop 0x%p\n", ppi, pdesk);
+                Status = MmUnmapViewOfSection(PsGetCurrentProcess(),
+                                              HeapMapping->UserMapping);
+
+                ObDereferenceObject(pdesk);
+
+                UserHeapFree(HeapMapping);
+                break;
+            }
+        }
+
+        PrevLink = &HeapMapping->Next;
+        HeapMapping = HeapMapping->Next;
+    }
+
+    return Status;
+}
+
+static NTSTATUS
+IntMapDesktopView(IN PDESKTOP pdesk)
+{
+    PPROCESSINFO ppi;
+    PW32HEAP_USER_MAPPING HeapMapping, *PrevLink;
+    PVOID UserBase = NULL;
+    SIZE_T ViewSize = 0;
+    LARGE_INTEGER Offset;
+    NTSTATUS Status;
+
+    TRACE("IntMapDesktopView called for desktop object 0x%p\n", pdesk);
+
+    ppi = PsGetCurrentProcessWin32Process();
+
+    /*
+     * Find out if another thread already mapped the desktop heap.
+     * Start the search at the next mapping: skip the first entry
+     * as it must be the global user heap mapping.
+     */
+    PrevLink = &ppi->HeapMappings.Next;
+    HeapMapping = *PrevLink;
+    while (HeapMapping != NULL)
+    {
+        if (HeapMapping->KernelMapping == (PVOID)pdesk->pheapDesktop)
+        {
+            HeapMapping->Count++;
+            return STATUS_SUCCESS;
+        }
+
+        PrevLink = &HeapMapping->Next;
+        HeapMapping = HeapMapping->Next;
+    }
+
+    /* We're the first, map the heap */
+    Offset.QuadPart = 0;
+    Status = MmMapViewOfSection(pdesk->hsectionDesktop,
+                                PsGetCurrentProcess(),
+                                &UserBase,
+                                0,
+                                0,
+                                &Offset,
+                                &ViewSize,
+                                ViewUnmap,
+                                SEC_NO_CHANGE,
+                                PAGE_EXECUTE_READ); /* Would prefer PAGE_READONLY, but thanks to RTL heaps... */
+    if (!NT_SUCCESS(Status))
+    {
+        ERR("Failed to map desktop\n");
+        return Status;
+    }
+
+    TRACE("ppi 0x%p mapped heap of desktop 0x%p\n", ppi, pdesk);
+
+    /* Add the mapping */
+    HeapMapping = UserHeapAlloc(sizeof(*HeapMapping));
+    if (HeapMapping == NULL)
+    {
+        MmUnmapViewOfSection(PsGetCurrentProcess(), UserBase);
+        ERR("UserHeapAlloc() failed!\n");
+        return STATUS_NO_MEMORY;
+    }
+
+    HeapMapping->Next = NULL;
+    HeapMapping->KernelMapping = (PVOID)pdesk->pheapDesktop;
+    HeapMapping->UserMapping = UserBase;
+    HeapMapping->Limit = ViewSize;
+    HeapMapping->Count = 1;
+    *PrevLink = HeapMapping;
+
+    ObReferenceObject(pdesk);
+
+    return STATUS_SUCCESS;
+}
+
+BOOL
+IntSetThreadDesktop(IN HDESK hDesktop,
+                    IN BOOL FreeOnFailure)
+{
+    PDESKTOP pdesk = NULL, pdeskOld;
+    PTHREADINFO pti;
+    NTSTATUS Status;
+    PCLIENTTHREADINFO pctiOld, pctiNew = NULL;
+    PCLIENTINFO pci;
+
+    ASSERT(NtCurrentTeb());
+
+    TRACE("IntSetThreadDesktop hDesktop:0x%p, FOF:%i\n",hDesktop, FreeOnFailure);
+
+    pti = PsGetCurrentThreadWin32Thread();
+    pci = pti->pClientInfo;
+
+    /* If the caller gave us a desktop, ensure it is valid */
+    if (hDesktop != NULL)
+    {
+        /* Validate the new desktop. */
+        Status = IntValidateDesktopHandle( hDesktop, UserMode, 0, &pdesk);
+        if (!NT_SUCCESS(Status))
+        {
+            ERR("Validation of desktop handle (0x%p) failed\n", hDesktop);
+            return FALSE;
+        }
+
+        if (pti->rpdesk == pdesk)
+        {
+            /* Nothing to do */
+            ObDereferenceObject(pdesk);
+            return TRUE;
+        }
+    }
+
+    /* Make sure that we don't own any window in the current desktop */
+    if (!IsListEmpty(&pti->WindowListHead))
+    {
+        if (pdesk)
+            ObDereferenceObject(pdesk);
+        ERR("Attempted to change thread desktop although the thread has windows!\n");
+        EngSetLastError(ERROR_BUSY);
+        return FALSE;
+    }
+
+    /* Desktop is being re-set so clear out foreground. */
+    if (pti->rpdesk != pdesk && pti->MessageQueue == gpqForeground)
+    {
+        // Like above, there shouldn't be any windows, hooks or anything active on this threads desktop!
+        IntSetFocusMessageQueue(NULL);
+    }
+
+    /* Before doing the switch, map the new desktop heap and allocate the new pcti */
+    if (pdesk != NULL)
+    {
+        Status = IntMapDesktopView(pdesk);
+        if (!NT_SUCCESS(Status))
+        {
+            ERR("Failed to map desktop heap!\n");
+            ObDereferenceObject(pdesk);
+            SetLastNtError(Status);
+            return FALSE;
+        }
+
+        pctiNew = DesktopHeapAlloc( pdesk, sizeof(CLIENTTHREADINFO));
+        if (pctiNew == NULL)
+        {
+            ERR("Failed to allocate new pcti\n");
+            IntUnmapDesktopView(pdesk);
+            ObDereferenceObject(pdesk);
+            EngSetLastError(ERROR_NOT_ENOUGH_MEMORY);
+            return FALSE;
+        }
+    }
+
+    /* free all classes or move them to the shared heap */
+    if (pti->rpdesk != NULL)
+    {
+        if (!IntCheckProcessDesktopClasses(pti->rpdesk, FreeOnFailure))
+        {
+            ERR("Failed to move process classes to shared heap!\n");
+            if (pdesk)
+            {
+                DesktopHeapFree(pdesk, pctiNew);
+                IntUnmapDesktopView(pdesk);
+                ObDereferenceObject(pdesk);
+            }
+            return FALSE;
+        }
+    }
+
+    pdeskOld = pti->rpdesk;
+    if (pti->pcti != &pti->cti)
+        pctiOld = pti->pcti;
+    else
+        pctiOld = NULL;
+
+    /* do the switch */
+    if (pdesk != NULL)
+    {
+        pti->rpdesk = pdesk;
+        pti->hdesk = hDesktop;
+        pti->pDeskInfo = pti->rpdesk->pDeskInfo;
+        pti->pcti = pctiNew;
+
+        pci->ulClientDelta = DesktopHeapGetUserDelta();
+        pci->pDeskInfo = (PVOID)((ULONG_PTR)pti->pDeskInfo - pci->ulClientDelta);
+        pci->pClientThreadInfo = (PVOID)((ULONG_PTR)pti->pcti - pci->ulClientDelta);
+
+        /* initialize the new pcti */
+        if (pctiOld != NULL)
+        {
+            RtlCopyMemory(pctiNew, pctiOld, sizeof(CLIENTTHREADINFO));
+        }
+        else
+        {
+            RtlZeroMemory(pctiNew, sizeof(CLIENTTHREADINFO));
+            pci->fsHooks = pti->fsHooks;
+            pci->dwTIFlags = pti->TIF_flags;
+        }
+    }
+    else
+    {
+        pti->rpdesk = NULL;
+        pti->hdesk = NULL;
+        pti->pDeskInfo = NULL;
+        pti->pcti = &pti->cti; // Always point inside so there will be no crash when posting or sending msg's!
+        pci->ulClientDelta = 0;
+        pci->pDeskInfo = NULL;
+        pci->pClientThreadInfo = NULL;
+    }
+
+    /* clean up the old desktop */
+    if (pdeskOld != NULL)
+    {
+        RemoveEntryList(&pti->PtiLink);
+        if (pctiOld) DesktopHeapFree(pdeskOld, pctiOld);
+        IntUnmapDesktopView(pdeskOld);
+        ObDereferenceObject(pdeskOld);
+    }
+
+    if (pdesk)
+    {
+        InsertTailList(&pdesk->PtiList, &pti->PtiLink);
+    }
+
+    TRACE("IntSetThreadDesktop: pti 0x%p ppi 0x%p switched from object 0x%p to 0x%p\n", pti, pti->ppi, pdeskOld, pdesk);
+
+    return TRUE;
+}
+
+/*
+ * NtUserSetThreadDesktop
+ *
+ * Status
+ *    @implemented
+ */
+
+BOOL APIENTRY
+NtUserSetThreadDesktop(HDESK hDesktop)
+{
+    BOOL ret = FALSE;
+
+    UserEnterExclusive();
+
+    // FIXME: IntSetThreadDesktop validates the desktop handle, it should happen
+    // here too and set the NT error level. Q. Is it necessary to have the validation
+    // in IntSetThreadDesktop? Is it needed there too?
+    if (hDesktop || (!hDesktop && PsGetCurrentProcess() == gpepCSRSS))
+        ret = IntSetThreadDesktop(hDesktop, FALSE);
+
+    UserLeave();
+
+    return ret;
+}
+
+/* EOF */