How to switch a process between default desktop and Winlogon desktop?
Asked Answered
C

2

10

I am writing a remote desktop application like TeamViewer in C++ on Windows 7 (x64) and Windows 8 (x64).

1. What made me stuck

I have implemented the mouse input and keyboard input by using SendInput(). I found SendInput() worked perfectly when the process ran under winsta0\desktop. But after the user locked the computer or the screensaver launched, it didn’t work.

If I run the process under winsta0\winlogon, SendInput() doesn’t work under winsta0\default.

2. What I have tried

I have tried using SetThreadDesktop() to switch the process from winsta0\desktop to winsta0\winlogon, but I got error 170: "The requested resource is in use" and I stucked.

3. What I want to know

I noticed that TeamViewer has a process named TeamViewer_Desktop.exe which can control mouse and keyboard under Winlogon, Default and Screensaver. How does it do it?

Can you provide the code to help me understand how to solve my question?

I want to know** how I can make my application switch between the default desktop and Winlogon desktop. So I can control the mouse and keyboard on a secured desktop without creating another process running under winlogon.exe.

Chrisom answered 15/4, 2013 at 8:22 Comment(2)
If you want sample code, use one of the open source VNC projects. Asking for demo code here is asking too much. This question is way too broad.Helmut
@DavidHeffernan Thank you for your advice. I will try reading and finding out the code for my question from the open source VNC projects. And I edited my question and made it clearly.Chrisom
J
8

You did the right thing: SetThreadDesktop is correct. The error is telling you that you have some resources open on the current desktop though, such as a window, and that prevents you from switching. If you had tried to produce a minimal test-case (as you are meant to do when asking questions here!) you would have found that out.

Cut out parts of your program until you find the chunk that's preventing you switching desktop. Some Windows APIs are nasty and prevent you switching desktop, so need to be called in a dedicated thread.

Jeaz answered 15/4, 2013 at 9:19 Comment(3)
You are right! Thanks a lot! I have tried and produced a minimal test-case. I create a console project and test the SetThreadDesktop() code. It works! Next step I will find out what prevents SetThreadDesktop() from succeeding in winform project.Chrisom
@Leon, I have tried this in Delphi ( question here ), but without success :-(. Some suggestion?Amylene
Don't use anything from user32.dll before you call SetThreadDesktop.Heartbroken
C
3

As @NicholasWilson said, SetThreadDesktop() is the correct way to switch a process between default desktop and winlogon desktop.

The error 170, "The requested resource is in use", occurred because I called MessageBox() before SetThreadDesktop() called. Also calling CreateWindow() can cause the error.

I think any function associated with GUI creation called before SetThreadDesktop() called can cause the error. So if you want to invoke SetThreadDesktop() successfully, you must be sure not to call any GUI creation function before you invoke SetThreadDesktop().

Code

Code here is how to switch the process to the specified desktop.

Usage: SetWinSta0Desktop(TEXT("winlogon")), SetWinSta0Desktop(TEXT("default"))

SetWinSta0Desktop() function:

BOOL SetWinSta0Desktop(TCHAR *szDesktopName)
{
    BOOL bSuccess = FALSE;

    HWINSTA hWinSta0 = OpenWindowStation(TEXT("WinSta0"), FALSE, MAXIMUM_ALLOWED);
    if (NULL == hWinSta0) { ShowLastErrorMessage(GetLastError(), TEXT("OpenWindowStation")); }

    bSuccess = SetProcessWindowStation(hWinSta0);
    if (!bSuccess) { ShowLastErrorMessage(GetLastError(), TEXT("SetProcessWindowStation")); }

    HDESK hDesk = OpenDesktop(szDesktopName, 0, FALSE, MAXIMUM_ALLOWED);
    if (NULL == hDesk) { ShowLastErrorMessage(GetLastError(), TEXT("OpenDesktop")); }

    bSuccess = SetThreadDesktop(hDesk);
    if (!bSuccess) { ShowLastErrorMessage(GetLastError(), TEXT("SetThreadDesktop")); }

    if (hDesk != NULL) { CloseDesktop(hDesk); }
    if (hWinSta0 != NULL) { CloseWindowStation(hWinSta0); }

    return bSuccess;
}

ShowLastErrorMessage() function:

void ShowLastErrorMessage(DWORD errCode, LPTSTR errTitle)
{
    LPTSTR errorText = NULL;

    FormatMessage(
       FORMAT_MESSAGE_FROM_SYSTEM |
       FORMAT_MESSAGE_ALLOCATE_BUFFER |
       FORMAT_MESSAGE_IGNORE_INSERTS,  
       NULL,
       errCode,
       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
       (LPTSTR)&errorText,
       0,
       NULL);

    if ( NULL != errorText )
    {
        WCHAR msg[512] = {0};
        wsprintf(msg, TEXT("%s:\nError Code: %u\n%s\n"), errTitle, errCode, errorText);

        LocalFree(errorText);
        errorText = NULL;

        OutputDebugString(msg);
    }
}
Chrisom answered 17/4, 2013 at 1:35 Comment(1)
That is the case when "The devil is in the details" - SetThreadDesktop doesn't change desktop for the PROCESS but for the THREAD! That is important distinction that should be considered. Having SetThreadDesktop being called for one thread doesn't make any subsequent new threads being switched as well.Patrinapatriot

© 2022 - 2024 — McMap. All rights reserved.