How can I detect that my window is on the current virtual desktop?


Virtual desktops are a feature added in Windows 10 blah blah exposition.

Here is how virtual desktops work, from a programmatic standpoint:

  • To switch to a virtual desktop, the system shows the windows that belong to the virtual desktop and hides the windows that do not belong to the virtual desktop. Note that the windows still all belong to the same desktop (hence the "virtual"). All we're doing is hiding and showing windows.
  • When a new window is shown, it gets placed on the current virtual desktop.
  • When a window becomes foreground, the system switches to the virtual desktop that the window belongs to.

That said, there are some guidelines that programs should follow.

  • Do not programatically change the current virtual desktop. The user should be the one to change virtual desktops, if that's what they want.
  • If your program decides to open a new window, then open a new window. It will be placed on the current virtual desktop.
  • If your program decides to reuse an existing window (for example, if you have a tabbed user interface, and you want to open the document in a new tab), then when looking for a window to reuse, limit your search to the current virtual desktop. If you cannot find a window from the current virtual desktop, then create and show a new one, which will be placed on the current virtual desktop.
  • Exception: If your program opens each document in a new window, and the user opens a document that you already have a window for, then you are allowed to switch to the virtual desktop that contains the already-open document.

Let's start with the scratch program and make these changes.

#include <shlobj.h>

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
    g_hwndChild = CreateWindow("listbox", nullptr,
       WS_VISIBLE | WS_CHILD, 0, 0, 0, 0, hwnd,
       (HMENU)IntToPtr(1), g_hinst, 0);

    return TRUE;
}

void
ProcessCommandLine(LPCSTR pszMessage)
{
    ListBox_AddString(g_hwndChild, pszMessage);
}

void
OnCopyData(HWND hwnd, HWND hwndFrom, PCOPYDATASTRUCT pcds)
{
    if (pcds->dwData == 0)
    {
      // WARNING! Parameter validation is missing!
      ProcessCommandLine(reinterpret_cast<PSTR>(pcds->lpData));
    }
}

    // Add to WndProc
    HANDLE_MSG(hwnd, WM_COPYDATA, OnCopyData);

BOOL
WindowCanBeReused(HWND hwnd)
{
    // A more realistic program would have some evaluation criteria.
    return TRUE;
}

BOOL
TryHandOffToExistingInstance(LPCSTR pszMessage)
{
    HWND hwndFound = nullptr;
    while ((hwndFound = FindWindowEx(nullptr, hwndFound,
                        "Scratch", nullptr)) != nullptr) {
      if (WindowCanBeReused(hwndFound)) {
        SetForegroundWindow(hwndFound);
        COPYDATASTRUCT cds;
        cds.dwData = 0;
        cds.cbData = lstrlen(pszMessage) + 1;
        cds.lpData = const_cast<PSTR>(pszMessage);
        FORWARD_WM_COPYDATA(hwndFound, nullptr, &cds, SendMessage);
        return TRUE;
      }
    }
    return FALSE;
}


int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                   LPSTR lpCmdLine, int nShowCmd)
{
    MSG msg;
    HWND hwnd;

    g_hinst = hinst;

    if (!InitApp()) return 0;

    if (SUCCEEDED(CoInitialize(NULL))) {/* In case we use COM */

      if (!lpCmdLine[0]) {
        lpCmdLine = const_cast<PSTR>("(empty command line)");
      }
      
      if (!TryHandOffToExistingInstance(lpCmdLine)) {

        hwnd = CreateWindow(
            TEXT("Scratch"),                /* Class Name */
            TEXT("Scratch"),                /* Title */
            WS_OVERLAPPEDWINDOW,            /* Style */
            CW_USEDEFAULT, CW_USEDEFAULT,   /* Position */
            CW_USEDEFAULT, CW_USEDEFAULT,   /* Size */
            NULL,                           /* Parent */
            NULL,                           /* No menu */
            hinst,                          /* Instance */
            0);                             /* No special parameters */

        ShowWindow(hwnd, nShowCmd);

        while (GetMessage(&msg, NULL, 0, 0)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
      }

      CoUninitialize();
    }

    return 0;
}

This is our non-virtual-desktop-aware version of the program. When it is run, it looks for an existing instance that can be reused, and if it finds one, it asks that existing instance to handle the command line.

Now let's make this program virtual-desktop-aware.

IVirtualDesktopManager* g_pvdm;

BOOL
WindowCanBeReused(HWND hwnd)
{
    BOOL isCurrent;
    if (g_pvdm &&
        SUCCEEDED(g_pvdm->IsWindowOnCurrentVirtualDesktop(hwnd,
                                          &isCurrent)) &&
        !isCurrent) {
      return FALSE;
    }

    // A more realistic program would have some evaluation criteria.
    return TRUE;
}

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                   LPSTR lpCmdLine, int nShowCmd)
{
    ...
    
    if (SUCCEEDED(CoInitialize(NULL))) {/* In case we use COM */

      // This can fail if the system does not support virtual desktops.
      CoCreateInstance(CLSID_VirtualDesktopManager,
                       nullptr, CLSCTX_ALL, IID_PPV_ARGS(&g_pvdm));

      if (!lpCmdLine[0]) {
        lpCmdLine = const_cast<PSTR>("(empty command line)");
      }
      
      if (!TryHandOffToExistingInstance(lpCmdLine)) {
         ...
      }

      if (g_pvdm) g_pvdm->Release();

      CoUninitialize();
    }

    return 0;
}

We updated the Window­Can­Be­Reused function so it takes the virtual desktop state into account. Specifically, we will not attempt to reuse windows that are not part of the current virtual desktop.

Comments (26)
  1. Tanveer Badar says:

    I always thought Windows 10 (virtual) desktops worked like Desktops utility from SysInternals.

    1. skSdnW says:

      No, the SysInternals tool uses HDESK objects but they are too much of a security/namespace boundary to really implement a work-space feature.

      1. cheong00 says:

        Oh, I thought the newer versions of SysInternals Desktops is using virtual desktop, while the older versions that I last checked around 2008 on WinXP just creates a new session of each “desktop” (so it’s impossible to move windows around different desktop sessions, but it’s possible to switch “personality” by simple shortcut-keys. I wish they had split these to different utilities as both provide convenience in different ways).

  2. BZ says:

    I don’t use virtual desktops (as a user), but suppose your app is a web browser. The default is to open any request in a new tab of the existing window if one exists. Are you saying that if a request comes in from a different desktop a new window will (or should?) be created? Isn’t that counter-intuitive and a recipe for confusion?

    1. Switching desktops is counter intuitive. Say you are working on a particular task. You open a web page and that switches you to a different task just open the web page? You’re just going to detach the tab into a new window, and then move the window to the task it was supposed to be part of.

      1. Brian_EE says:

        “You’re just going to detach the tab into a new window, and then move the window to the task it was supposed to be part of.”

        No. No I’m not. Don’t fall into the trap that what seems intuitive to you is how everyone else’s workflow or usage model is. I don’t want browser tabs detached. I want them all in one “MDI” window that I can Alt-Tab to.

        It bugs me to no end that when I click on a minimized browser button on Win-10 taskbar that I have to then click one of many “mini-fied” tabs. I just wish the window would Restore at whatever tab it was Minimized at.

        1. laonianren says:

          Are you talking about the Internet Explorer anti-feature? You can turn it off by unchecking “Show previews for individual tabs in the taskbar” in the Tab options dialog. Or just use a different browser.

          That said, your point doesn’t seem to have much to do with Raymond’s.

        2. Voo says:

          “ I don’t want browser tabs detached. I want them all in one “MDI” window that I can Alt-Tab to.”
          This is about multiple desktops, you can’t alt-tab between windows in different desktops which is why your implementation would be a horrible use experience.

          If I’m working on a task I don’t want to get thrown on a completely different setup to read some documentation.

    2. Muzer says:

      One use case for virtual desktops (I’ve never used them myself even as a long-time desktop Linux user, but this is how I hear others use them) is to divide your windows in a way that is logical to the user, perhaps into work and personal, or perhaps into different projects or things you’re working on concurrently. In either case, you will likely want different web pages open for work/personal, or different pages open for different projects. So it makes perfect sense to me that a web page requested in the context of Desktop B will open a new browser window on Desktop B (assuming one doesn’t already exist on Desktop B), even if one DOES already exist on Desktop A.

      1. cheong00 says:

        Yeah, “poor man’s alternative to have multiple monitors”.

        Yes, multi-monitor configuration is becoming more common now, but still relatively less common for servers attached to KVM boxes.

  3. Kevin Fee says:

    Interesting that you put the return type on the line before the function name, ensuring the function name is at the beginning of the line. Is that a new style at Microsoft? I kinda like it.

    1. Smithers says:

      It certainly seems to be used in many of the Windows header files, though not all (wingdi.h). Of course, I don’t know if this is official practice at MS.

      I do know that it’s mandated by GNU style, for precisely the reason that a search for /^functionname/ matches the definition of the function and not any calls to it. Makes more sense than most of GNU C style.

      1. Azarien says:

        It’s part of KNF (Kernel Normal Form) style.

  4. Killer{R} says:

    1) user started a long job* completion of which causes new windows popup on desktop A
    2) user switched to desktop A where he works on some work, thinking ‘hey when I will finish with this will switch to desktop A and continue to work there
    3) long job* completed and.. shown window at desktop B, completely disrupting planned workflow.

    *long job that shows window at the end: one example some heavy application startup, or another example – compiling project in eclipse with following perspective change to debug.

    PS MS always discourages me how they avoid to evolve existing functionality and reinvent new functionality instead that duplicates old one but have a bit more features. I understand that its harder to learn old code but.. hey, guys, you’re writing OS, why not extend exiting virtual desktop but not invent brand new ones? Of course it requires rewriting win32 heap management etc, but result will be better API and better internal architecture.

    1. I don’t understand how you don’t consider this an evolution of existing technology. The virtual desktop system evolves the existing desktop system by inferring virtual desktop partitioning from existing window behavior. There are other rules that prevent your scenario from showing up on the wrong desktop. (In this case, the rule that owned windows always appears on the same desktop as their owner.)

      1. Killer{R} says:

        Existing virtual desktops are CreateDesktop() &co. As I understand – they’re not participating in this new desktop functionality. The reason is understandable – they’re not allowing window switch from one desktop to another due to deep internal reasons – window is part of win32k desktop heap, so you cannot easily move window from one desktop to another. So MS invented brand new desktops that has this functionality but misses many features of old desktops.
        Thats my complain – why not re-engineer old win32 desktops implementation to allow dynamic _old_ desktops assignment instead of creating adding desktops kind.
        About owned windows – its example from Linux world. Eclipse goes to different workspace after long compilation because it re-creates all its windows. So not sure that owner will help here. but when I using Windows with its old good desktops utility – I’m pretty sure that nobody will popup such way from another desktop. Now from you description it looks like Windows got same desktop as Linux – with same rules, so, with same issues…

        1. It comes down to what problem you think virtual desktops is trying to solve. The problem that Windows 10 virtual desktops is trying to solve is helping people organize their windows with minimum compatibility impact. Putting different windows on different HDESKTOPs would break programs that create multiple windows and wind up with different windows on different desktops. (Since those windows wouldn’t be able to talk to each other any more.) Should we have just called it “workspaces” instead of “virtual desktops” to avoid setting false expectations?

          1. Joshua says:

            I actually had this back in the XP days with a very similar implementation to what Windows 10 ended up with. Too many applications broke anyway because they didn’t like their windows being hidden out from under them. CreateDesktop() probably would have been less compatibility headaches. At least those breakages follow a logical model where the user can predict when they break.

          2. Someone says:

            “Too many applications broke anyway because they didn’t like their windows being hidden out from under them. ”

            The Windows Manager could hide (not render) top-level windows without changing any application-visible bits. I hope, the virtual desktop feature works this way, or not?

  5. Wear says:

    “When a new window is shown, it gets placed on the current virtual desktop. ”

    This behaviour kind of bugs me, mainly because of splash screens. If I am on desktop 2 and I start program X I want program X to start on desktop 2. If I go over to Desktop 1 to read an article while program X is loading I don’t want Program X to pop-up on desktop 1 and interrupt what I’m doing. The same thing happens if you build and run in visual studio. Program X should stay in its room, I’ll be in to talk to it in a minute.

    1. torrinj says:

      I agree it’s annoying, but I just move the windows to the desktop I want it to be on. Alt+TAB will allow you to move the Windows on the current desktop to a different desktop.

      1. torrinj says:

        Oops, I said Alt+TAB when I meant WIN+TAB.

      2. Wear says:

        Yeah that’s what I do too. Shoo them off back to where they belong.

    2. Joshua Schaeffer says:

      That’s why HDESKTOP solutions are so rad. It’s like Docker for UI.

  6. Joshua Schaeffer says:

    I have never had these APIs work as advertised. They are completely falsely documented and return unreasonable error codes.

  7. Iain says:

    “______ are a _____ blah blah exposition.”

    My new standard opening for internal documents. +1

Comments are closed.

Skip to main content