Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I move Windows desktop icons using python 2.7?

I am trying to write a python routine to save and restore desktop icon positions. I am using 32-bit python 2.7 on Windows 7 x64. Using information from here (stack exchange), I am able to read the icon names and positions from the foreign process list view that Windows uses to store this info, but I fail when using LVM_SETITEMPOSITION to set new positions (or restore the positions). All of the icons end up in the same exact spot on the desktop. 'Auto arrange' and 'align to grid' are off. The relevant code is towards the bottom. WARNING: if you run this code all of your icons will be in a pile :(

import ctypes

class LVITEMW(ctypes.Structure):
    _fields_ = [
        ('mask', ctypes.c_uint32),
        ('iItem', ctypes.c_int32),
        ('iSubItem', ctypes.c_int32),
        ('state', ctypes.c_uint32),
        ('stateMask', ctypes.c_uint32),
        ('pszText', ctypes.c_uint64),
        ('cchTextMax', ctypes.c_int32),
        ('iImage', ctypes.c_int32),
        ('lParam', ctypes.c_uint64), # On 32 bit should be c_long
        ('iIndent',ctypes.c_int32),
        ('iGroupId', ctypes.c_int32),
        ('cColumns', ctypes.c_uint32),
        ('puColumns', ctypes.c_uint64),
        ('piColFmt', ctypes.c_int64),
        ('iGroup', ctypes.c_int32),
    ]

class POINT(ctypes.Structure):
    _fields_ = [('x', ctypes.c_int), ('y', ctypes.c_int)]

def icon_save_restore(savedicons=None, restore=False):
    import struct, commctrl, win32gui, win32con, win32api
    dthwnd = win32gui.FindWindow(None, 'Program Manager')
    ukhwnd = win32gui.GetWindow(dthwnd, win32con.GW_CHILD)
    slvhwnd = win32gui.GetWindow(ukhwnd, win32con.GW_CHILD)
    pid = ctypes.create_string_buffer(4)
    p_pid = ctypes.addressof(pid)
    ctypes.windll.user32.GetWindowThreadProcessId(slvhwnd, p_pid)
    hProcHnd = ctypes.windll.kernel32.OpenProcess(win32con.PROCESS_ALL_ACCESS, False, struct.unpack("i",pid)[0])
    pBuffertxt = ctypes.windll.kernel32.VirtualAllocEx(hProcHnd, 0, 4096, win32con.MEM_RESERVE|win32con.MEM_COMMIT, win32con.PAGE_READWRITE)
    copied = ctypes.create_string_buffer(4)
    p_copied = ctypes.addressof(copied)
    lvitem = LVITEMW()
    lvitem.mask = ctypes.c_uint32(commctrl.LVIF_TEXT)
    lvitem.pszText = ctypes.c_uint64(pBuffertxt)
    lvitem.cchTextMax = ctypes.c_int32(4096)
    lvitem.iSubItem = ctypes.c_int32(0)
    pLVI = ctypes.windll.kernel32.VirtualAllocEx(hProcHnd, 0, 4096, win32con.MEM_RESERVE| win32con.MEM_COMMIT,  win32con.PAGE_READWRITE)
    win32api.SetLastError(0)
    ctypes.windll.kernel32.WriteProcessMemory(hProcHnd, pLVI, ctypes.addressof(lvitem), ctypes.sizeof(lvitem), p_copied)
    num_items = win32gui.SendMessage(slvhwnd, commctrl.LVM_GETITEMCOUNT)
    if restore is False:
        p = POINT()
        pBufferpnt = ctypes.windll.kernel32.VirtualAllocEx(hProcHnd, 0, ctypes.sizeof(p), win32con.MEM_RESERVE|win32con.MEM_COMMIT, win32con.PAGE_READWRITE)
        icons = {}
        for i in xrange(num_items):
            # Get icon text
            win32gui.SendMessage(slvhwnd, commctrl.LVM_GETITEMTEXT, i, pLVI)
            target_bufftxt = ctypes.create_string_buffer(4096)
            ctypes.windll.kernel32.ReadProcessMemory(hProcHnd, pBuffertxt, ctypes.addressof(target_bufftxt), 4096, p_copied)
            key = target_bufftxt.value
            # Get icon position
            win32api.SendMessage(slvhwnd, commctrl.LVM_GETITEMPOSITION, i, pBufferpnt)
            p = POINT()
            ctypes.windll.kernel32.ReadProcessMemory(hProcHnd, pBufferpnt, ctypes.addressof(p), ctypes.sizeof(p), p_copied)
            icons[key] = (i,p)
        ctypes.windll.kernel32.VirtualFreeEx(hProcHnd, pLVI, 0, win32con.MEM_RELEASE)
        ctypes.windll.kernel32.VirtualFreeEx(hProcHnd, pBuffertxt, 0, win32con.MEM_RELEASE)
        ctypes.windll.kernel32.VirtualFreeEx(hProcHnd, pBufferpnt, 0, win32con.MEM_RELEASE)
        win32api.CloseHandle(hProcHnd)
        return icons
    else:  # RESTORE ICON POSITIONS PROBLEM IS HERE SOMEWHERE!!!
        win32gui.SendMessage(slvhwnd, win32con.WM_SETREDRAW, 0, 0)
        for i in xrange(num_items):
            # Get icon text
            win32gui.SendMessage(slvhwnd, commctrl.LVM_GETITEMTEXT, i, pLVI)
            target_bufftxt = ctypes.create_string_buffer(4096)
            ctypes.windll.kernel32.ReadProcessMemory(hProcHnd, pBuffertxt, ctypes.addressof(target_bufftxt), 4096, p_copied)
            key = target_bufftxt.value
            if key in savedicons.keys():
                # Set icon position
                p = savedicons[key][1]  # p is ctypes POINT
                p_lng = point_to_long(p)  # explicitly convert to HIWORD/LOWORD and c_long
                # Reserve space for input variable in foreign process and get pointer to it the that memory space
                pBufferpnt = ctypes.windll.kernel32.VirtualAllocEx(hProcHnd, 0, ctypes.sizeof(p_lng), win32con.MEM_RESERVE|win32con.MEM_COMMIT, win32con.PAGE_READWRITE)
                # Write the desired coordinates in to the space just created
                ret = ctypes.windll.kernel32.WriteProcessMemory(hProcHnd, pBufferpnt, ctypes.addressof(p_lng), ctypes.sizeof(p_lng), p_copied)
                if ret == 0:
                    raise WindowsError
                # Send the message to change the position for that item's index and the pointer to the new position
                ret = win32gui.SendMessage(slvhwnd, commctrl.LVM_SETITEMPOSITION, i, pBufferpnt)
                if ret == 0:
                    raise WindowsError
                # Release the reserved memory for the variable (I recognize that I probably don't need to aLloc/free this within the loop)
                ctypes.windll.kernel32.VirtualFreeEx(hProcHnd, pBufferpnt, 0, win32con.MEM_RELEASE)
        win32gui.SendMessage(slvhwnd, win32con.WM_SETREDRAW, 1, 0)
        ctypes.windll.kernel32.VirtualFreeEx(hProcHnd, pLVI, 0, win32con.MEM_RELEASE)
        ctypes.windll.kernel32.VirtualFreeEx(hProcHnd, pBuffertxt, 0, win32con.MEM_RELEASE)
        win32api.CloseHandle(hProcHnd)
        return None


def point_to_long(p):
    ret = (p.y * 0x10000) + (p.x & 0xFFFF)
    return ctypes.c_long(ret)

if __name__ == '__main__':
    mysavedicons = icon_save_restore(restore=False)
    icon_save_restore(mysavedicons, restore=True)

I think there may be a problem with either 1) something to do with 32 bit and 64 bit memory address space, but the other components where I write the LVITEM structure or read the icon text work ok or 2) there is some issue in the way I am converting the coordinate information or calling SendMessage for GETITEMPOSITION. Any insight or help would be greatly appreciated.

like image 696
KenV99 Avatar asked Nov 09 '22 19:11

KenV99


1 Answers

Turns out that there is version that uses 32-bit addresses (LVM_SETITEMPOSITION32) that I wish MSDN would have cross-referenced in their documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/bb761194(v=vs.85).aspx

This accepts the POINT structure directly so there is no need to try to convert to HIWORD/LOWORD. Before posting, I did indeed try using a 32 bit shift and longlong (64 bit version of long) and that didn't work either with LVM_SETITEMPOSITION. In any case, with the change, everything works as expected.

like image 51
KenV99 Avatar answered Nov 15 '22 09:11

KenV99