0

I have referenced these 2 following threads:

Since they mentioned that the desktop itself was a ListView, I attempted to send the LVM_SETITEMPOSITION message directly to the Desktop HWND as such:

#include <windows.h>
#include <stdio.h>
#include <CommCtrl.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
    {
      HWND MYHWND=NULL;
      MYHWND=GetDesktopWindow();
      SendMessage(MYHWND, LVM_SETITEMPOSITION, 2, MAKELPARAM(100, 100));
      return 1;
    }

However, it didn't work, and I'm not sure why, so I tried to convert their C++ code into C by doing the following:

#include <windows.h>
#include <stdio.h>
#include <CommCtrl.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
    {
      HWND desktopHandle=NULL;
      desktopHandle= FindWindowEx(NULL, NULL, "Progman", "Program Manager");
      desktopHandle = FindWindowEx(desktopHandle, NULL, "SHELLDLL_DefView", NULL);
      desktopHandle = FindWindowEx(desktopHandle, NULL, "SysListView32", "FolderView");
      SendMessage(desktopHandle, LVM_SETITEMPOSITION, 2, MAKELPARAM(500, 500));
      return 1;
    }

Which still doesn't work. Could someone kindly help me figure out what is wrong? Thanks!

bugger
  • 137
  • 5
  • Are you checking the errors returned by `FindWindow`/`FindWindowEx`? Try using Spy++ to help debug, for me I noticed that SHELLDLL_DefView is a child of WorkerW. I suspect you might have a similar problem. – jacob Apr 15 '23 at 04:40

3 Answers3

2

As you have discovered already, the popular approach1, 2 to reach right into the Shell's private parts and do whatever is not a sustainable solution3. This had never been supported, and starting with Windows 10 1809 Microsoft sent out a message that's harder to ignore than recommendations: Implementation details changed, rendering solutions based on those implementation details dysfunctional.

Luckily, the Shell has a supported programming interface, that—among others—allows manipulating the positions of items on the desktop. The interface is exposed using the Component Object Model (COM), the foundational technology that underpins the vast majority of system services offered by the operating system.

While built on simple, easy to understand principles, one of COM's major feats (strict separation between interfaces and implementations, and loose coupling) poses a specific challenge: Its decoupled nature is reflected in the documentation by a prevalent lack of structural information. Interfaces are predominatly documented in isolation, with nearly no mention of where they fit in the broader scheme of things. Using a COM interface is easy. Knowing how to get to an interface of interest is the tricky part.

We are fortunate that Raymond Chen has covered the tricky part in his article Manipulating the positions of desktop icons, allowing us to keep our sanity. The following is an approximate transliteration of his code into C4:

Prelude:

// We need the COM interfaces declared as C-compatible structs
#define CINTERFACE
// Opt in to convenience macros (not strictly required, but it
// saves us from having to repeat ourselves)
#define COBJMACROS

#include <ExDisp.h>
#include <ShObjIdl.h>
#include <ShlObj.h>
#include <objbase.h>

The first function is used to get a COM interface that represents the desktop's folder view. It starts from a magic place (CLSID_ShellWindows), taking a detour through a number of hard to guess avenues, to finally arrive at the interface we are interested in obtaining.

FindDesktopFolderView:

/// @brief Finds the folder view that represents the desktop
///
/// @param riid The interface ID of the requested interface. This
///             is commonly `IID_IFolderView`.
/// @param ppv  The address of a pointer that receives the
///             requested interface on success. Callers need to
///             make sure that the pointer type matches `riid`.
///
/// @return Returns a success code on success, a failure code
///         otherwise.
///         If this function call succeeds, `*ppv` contains a
///         valid pointer. If this function call fails, `*ppv`
///         holds a null pointer.
///
HRESULT FindDesktopFolderView(REFIID riid, void **ppv) {
  // Argument validation
  if (!ppv)
    return E_POINTER;
  *ppv = NULL;

  // Instantiate an object that provides access to the collection
  // of Shell windows (Explorer, Internet Explorer, ...)
  IShellWindows *pShellWindows = NULL;
  HRESULT hr = CoCreateInstance(&CLSID_ShellWindows, NULL, CLSCTX_ALL,
                                &IID_IShellWindows, &pShellWindows);

  // Find the specific Explorer window that represents the desktop
  IDispatch *pDispatch = NULL;
  if (SUCCEEDED(hr)) {
    VARIANT vtLoc = {.vt = VT_I4, .lVal = CSIDL_DESKTOP};
    VARIANT vtEmpty = {.vt = VT_EMPTY};
    long lhwnd = 0;
    hr = IShellWindows_FindWindowSW(pShellWindows, &vtLoc, &vtEmpty, SWC_DESKTOP,
                                    &lhwnd, SWFO_NEEDDISPATCH, &pDispatch);
  }

  // Request the service provider interface...
  IServiceProvider *pServiceProvider = NULL;
  if (SUCCEEDED(hr)) {
    hr = IDispatch_QueryInterface(pDispatch, &IID_IServiceProvider,
                                  &pServiceProvider);
  }

  // ... so that we can get to the top Shell browser. You can
  // think of this as the frame window that hosts one or more
  // Shell views
  IShellBrowser *pBrowser = NULL;
  if (SUCCEEDED(hr)) {
    hr = IServiceProvider_QueryService(pServiceProvider, &SID_STopLevelBrowser,
                                       &IID_IShellBrowser, &pBrowser);
  }

  // Request the active Shell view. This may seem needless with
  // the desktop having a single view, but File Explorer windows
  // typically have more than one, and the generality of the API
  // shows
  IShellView *pShellView = NULL;
  if (SUCCEEDED(hr)) {
    hr = IShellBrowser_QueryActiveShellView(pBrowser, &pShellView);
  }

  // Request the interface specified by callers of this function
  if (SUCCEEDED(hr)) {
    hr = IShellView_QueryInterface(pShellView, riid, ppv);
  }

  // We're done with all the interfaces, so let's clean them up.
  // The only interface we care about is stored in `*ppv`,
  // assuming that all calls succeeded
  if (pShellView)
    IShellView_Release(pShellView);
  if (pBrowser)
    IShellBrowser_Release(pBrowser);
  if (pServiceProvider)
    IServiceProvider_Release(pServiceProvider);
  if (pDispatch)
    IDispatch_Release(pDispatch);
  if (pShellWindows)
    IShellWindows_Release(pShellWindows);

  return hr;
}

When called with an riid of IID_IFolderView this gets us an IFolderView interface. We can use that to obtain an iterator, find the item at a given index, and manipulate properties. That's what the following function does.

RepositionItemByIndex:

/// @brief Moves a desktop item to a new location
///
/// @param index The zero-based index of the item to be moved.
/// @param pos   The desired new position of the item. The
///              position is subject to adjustments performed by
///              the Shell (e.g. when the *"Align icons to grid"*
///              option is enabled).
///
/// @return Returns a success code on success, a failure code
///         otherwise
///
HRESULT RepositionItemByIndex(int index, POINT pos) {
  HRESULT hr = S_OK;

  // Lower bounds check
  if (index < 0)
    hr = DISP_E_BADINDEX;

  IFolderView *pView = NULL;
  if (SUCCEEDED(hr)) {
    hr = FindDesktopFolderView(&IID_IFolderView, &pView);
  }

  int count = 0;
  if (SUCCEEDED(hr)) {
    hr = IFolderView_ItemCount(pView, SVGIO_ALLVIEW, &count);
    // Upper bounds check
    if (SUCCEEDED(hr) && (index >= count))
      hr = DISP_E_BADINDEX;
  }

  // Get an enumerator for items in view order
  IEnumIDList *pIDList = NULL;
  if (SUCCEEDED(hr)) {
    hr = IFolderView_Items(pView, SVGIO_ALLVIEW | SVGIO_FLAG_VIEWORDER,
                           &IID_IEnumIDList, &pIDList);
  }

  // Extract `index + 1` items (ideally we'd `Skip()` over the
  // first `index` items, but that returns `E_NOTIMPL` for this
  // interface)
  ITEMIDLIST *pIDL = NULL;
  int current_idx = -1;
  while (SUCCEEDED(hr) && current_idx < index) {
    // Clean up ID list allocated in previous iteration;
    // `CoTaskMemFree` can be called on a null pointer, so the
    // first iteration is fine
    CoTaskMemFree(pIDL);
    pIDL = NULL;

    hr = IEnumIDList_Next(pIDList, 1, &pIDL, NULL);
    // Exit on error, or enumerator depletion (`S_FALSE`)
    if (hr != S_OK)
      break;

    ++current_idx;
  }

  // With the ID list identifying the item we can finally
  // request to have its position updated
  if (SUCCEEDED(hr) && (index == current_idx)) {
    PCITEMID_CHILD apidl[1] = {pIDL};
    hr = IFolderView_SelectAndPositionItems(pView, 1, apidl, &pos,
                                            SVSI_POSITIONITEM);
  }

  // Cleanup
  CoTaskMemFree(pIDL);
  if (pIDList)
    IEnumIDList_Release(pIDList);
  if (pView)
    IFolderView_Release(pView);

  return hr;
}

Shell items are identified by Item ID Lists, an opaque binary blob containing data private to the folder that contains the item. Functions typically accept a pointer to an item ID lists (PIDLs); this indirection allows passing item ID lists of arbitrary size by way of a fixed-size pointer value. PCITEMID_CHILD above expands to ITEMIDLIST const*, meaning that apidl is an array of length 1 containing a single PIDL (ITEMIDLIST const*).

All of this appears to be excruciatingly complex, and there is a whole book chapter's worth of text that could be said still. But, that would be crazy. We're not doing that, and instead appreciate how easy it is to use the above.

main:

int main(int argc, char **argv) {
  HRESULT hr = CoInitialize(NULL);

  if (SUCCEEDED(hr)) {
    POINT pos = {.x = 500, .y = 500};
    hr = RepositionItemByIndex(0, pos);
  }

  return hr;
}

That's all there is to it.

Keep in mind that an item's position affects where it gets sorted in the item list. As a consequence, calling RepositionItemByIndex twice with the same index can result in moving two different items.

A better interface could be RepositionItemByDisplayName, or RepositionItemByPIDL, keeping the operation independent of the items' sorting predicate. If there is a demand for those more predictable implementations I can add them in a future update.


1 How to programmatically move icons' location from Windows Desktop?
2 How can I programmatically manipulate Windows desktop icon locations?
3 A reminder about the correct way of accessing and manipulating the position of icons on the desktop
4 I'm not a C expert, though I believe the implementation to be sound, with all errors handled and all acquired resources released.

IInspectable
  • 46,945
  • 8
  • 85
  • 181
0

Did more research, apparently the exact method of moving Desktop icons which I had posted does not work anymore, as per Raymond Chen:

A reminder about the correct way of accessing and manipulating the position of icons on the desktop

It stopped working because it never was guaranteed to work in the first place.

Starting in Windows 10 version 1809, there were changes to the way that the positions of the desktop icons are managed, and one of the consequences is that if you try to manipulate the icon positions by talking directly to the ListView control, those changes aren’t taken into account by the icon management code, and your changes are lost.

The solution is to stop sending messages to an undocumented window to take advantage of internal implementation details, because the nature of implementation details is that they change when the implementation changes.

The supported API for manipulating desktop icons is IFolder­View::Select­And­Position­Items, and that API still works. (As it should, because it’s the supported API.) You can look at some code I wrote many years ago for manipulating the position of desktop icons to see how it’s done.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
bugger
  • 137
  • 5
  • This is essentially a link-only answer. With the unfortunate property of being more useful than the accepted and upvoted [answer](https://stackoverflow.com/a/76020405/1889329). Why not turn this into a real answer? – IInspectable Apr 15 '23 at 06:51
  • @IInspectable I hope this would be useful to others who may come across a similar issue. Unfortunately I have absolutely 0 idea what is going on in the code (It appears to be using concepts/cpp? that I am unfamiliar with), but when I finally do figure it out, I'd look to update this answer! – bugger Apr 15 '23 at 07:07
  • The code (and the entire Shell API) is based on [COM](https://learn.microsoft.com/en-us/windows/win32/com/the-component-object-model). The code is using C++ smart pointers for automatic resource management. Everything could be implemented in C (with manual resource management) just as well. – IInspectable Apr 15 '23 at 07:14
  • @bugger link-only answers are discouraged, because links break over time. StackOverflow posts are expected to be self-contained. Posting links is fine, but you should include the relevant information directly in your answer, too. – Remy Lebeau Apr 15 '23 at 08:37
  • @RemyLebeau in that case, would u recommend me to delete this answer? I have never touched cpp before and am trying, but would need a significant amount of time to understand the code. The link that I have posted is also already available in a comment by IInspectable – bugger Apr 15 '23 at 12:14
0

Here's a solution that worked for me, using what I wrote in the comments.

Your solution for me was failing on finding SHELLDLL_DefView, I used Spy++ to find SHELLDLL_DefView and it was a child of WorkerW you'll have to iterate through the various WorkerW's to find the correct one containing the SHELLDLL_DefView.

#define UNICODE

#include <windows.h>

#include <CommCtrl.h>
#include <stdio.h>

int main(int argc, char **argv) {
    HWND hWorkerW         = NULL;
    HWND hShellDLLDefView = NULL;
    HWND hListView        = NULL;

    do {
        hWorkerW = FindWindowEx(NULL, hWorkerW, L"WorkerW", NULL);
        hShellDLLDefView =
            FindWindowEx(hWorkerW, NULL, L"SHELLDLL_DefView", NULL);
    } while (hShellDLLDefView == NULL && hWorkerW != NULL);

    hListView =
        FindWindowEx(hShellDLLDefView, NULL, L"SysListView32", L"FolderView");

    if (!hListView) {
        puts("Failed to find SysListView32 window.");
        return EXIT_FAILURE;
    }

    int nIcons = ListView_GetItemCount(hListView);
    printf("Found %d items.", nIcons);

    SendMessage(hListView, LVM_SETITEMPOSITION, 2, MAKELPARAM(500, 500));
    return EXIT_SUCCESS;
}
jacob
  • 4,656
  • 1
  • 23
  • 32
  • 1
    The Shell has a programming interface. [Manipulating the positions of desktop icons](https://devblogs.microsoft.com/oldnewthing/20130318-00/?p=4933) explains how to do the task without resorting to implementation details. – IInspectable Apr 15 '23 at 06:49
  • @IInspectable Thanks, yeah what you linked is probably preferable as indicated by Raymond Chen, the above code is not stable nor guaranteed to work between versions of Windows. Thanks for sharing the article. – jacob Apr 15 '23 at 08:28