Obtaining the position of icons on your (WinNT-based) desktop
Environment: Windows XP Pro, Visual C++.NET 2002
I have a huge monitor at home, 22″. And I also have an ADSL-connection. So, when I’m at work, I’d like to connect to my home PC using Remote Desktop and do some stuff remotely. However, for some reason, the computers I connect from to my own computer never have a monitor that’s the same size as mine and, thus, never the same desktop resolution. Remote Desktop then just displays my home desktop at a lower resolution when connected.
Having aligned my icons around the edges of my desktop, this causes them to be ‘reorganized’ so they’re all messed up. Now, this isn’t a problem per se, but when I get home, the icons remain at their messed-up positions, requiring me to reposition them every time I connected with Remote Desktop. Which, in my case, is daily.
Time to fire up the old development tools and write a tool that finds out the position of each icon on my desktop and save it somewhere so they can be restored at their positions at the click of a button.
This has proven to be more difficult then it seemed. Below it a step-by-step walkthrough of what I did (roughly), what problems I encountered, and how I overcame the problems. All in all, this little project took two to three hours out of my life.
The first step in getting the positions of the icons is to be able to ask something for them. And whadya know? The desktop, as you see it, is just a big ListView. So, we can use the FindWindow() and FindWindowEx() APIs to get the handle of the ListView. First, we use FindWindow() to find the window with the title “Program Manager;” then, we use FindWindowEx() to find the child window (of the program manager) with the class name “SHELLDLL_DefView” (it doesn’t have a window title). Then we use FindWindowEx() again to find the child window of the SHELLDLL_DefView, with the name “FolderView.” This is the ListView. So, now we have a HWND to the ListView that shows you the icons.
Next up is to ask the ListView for the icons and their positions.
We can get the number of icons pretty simply. Just SendMessage() it the LVM_GETITEMCOUNT message and it will return you the number of icons. So far, so good. Now, we can get the position of each icon by using the message LVM_GETITEMPOSITION. We need to send this message for every icon on the desktop. The WPARAM of the message is the index (which icon do we want to query?) and the LPARAM is a pointer to a POINT structure, that will receive the X and Y positions.
I did all of this, but the POINT structure never got filled. A very strange problem, indeed. The function returns TRUE, indicating success, but I get no info. After some thinking about the problem, it occurred to me. I’m sending the pointer to a place in the memory of my process, to another process. That pointer is not valid because each process has its own memory space. In other words, the pointer I send to the ListView isn’t valid in its address space. So, now I need to have a pointer that is valid in the process space of the ListView, but I need to read it as well. So it’s gonna need to be valid in both processes. To do this, you need to create shared memory. This seemed horribly complex to me, it was late (2:00 AM), and I wanted this small tool finished before I could go to bed. So, why not do something completely different? Why not allocate memory in the address space of the ListView and read from that? I know how to do that.
Allocating memory in the process space of another process is very easy. Just use VirtualAllocEx(). And, reading from and writing and to it is easy, as well; we have ReadProcessMemory() and WriteProcessMemory() for that. The problem I faced, however, was getting the process handle to the process that is hosting the ListView. In this case, Windows Explorer.
So, how do we find the process handle of explorer? As it turns out, there’s a very simple API call for this; it’s simple because we already know the handle of a window that belongs to the process we need: GetWindowThreadProcessId(). With this API, we can get the PID of the process and then use OpenProcess() to get a handle to the process.
So, now we have a process handle, we have the window handle to the ListView and we can use VirtualAllocEx(), ReadProcessMemory(), WriteProcessMemory() and VirtualFeeEx() to deal with the out-of-process-pointer issue. Combine these things, and you can get the positions of each icon on your desktop.
One final note: Because I use VirtualAllocEx() and VirtualFreeEx(), this program only works in NT-based systems (Windows NT/2000/XP), which isn’t a problem for me. If you want to make this work for Win9x-based systems, you can easily do so by altering two functions in VirtualMem.cpp; I kept them nicely abstracted.
- Rewrote the entire program to be totally independent of MFC.
- Used the comments made by [email protected] and [email protected] to improve the program.