# This is a port of the Vista SDK "FolderView" sample, and associated # notes at http://shellrevealed.com/blogs/shellblog/archive/2007/03/15/Shell-Namespace-Extension_3A00_-Creating-and-Using-the-System-Folder-View-Object.aspx # A key difference to shell_view.py is that this version uses the default # IShellView provided by the shell (via SHCreateShellFolderView) rather # than our own. # XXX - sadly, it doesn't work quite like the original sample. Oh well, # another day... import sys import os import pickle import random import win32api import winxpgui as win32gui # the needs vista, let alone xp! import win32con import winerror import commctrl import pythoncom from win32com.util import IIDToInterfaceName from win32com.server.exception import COMException from win32com.server.util import wrap as _wrap from win32com.server.util import NewEnum as _NewEnum from win32com.shell import shell, shellcon from win32com.axcontrol import axcontrol # IObjectWithSite from win32com.propsys import propsys GUID=pythoncom.MakeIID # If set, output spews to the win32traceutil collector... debug=0 # wrap a python object in a COM pointer def wrap(ob, iid=None): return _wrap(ob, iid, useDispatcher=(debug>0)) def NewEnum(seq, iid): return _NewEnum(seq, iid=iid, useDispatcher=(debug>0)) # The sample makes heavy use of "string ids" (ie, integer IDs defined in .h # files, loaded at runtime from a (presumably localized) DLL. We cheat. _sids = {} # strings, indexed bystring_id, def LoadString(sid): return _sids[sid] # fn to create a unique string ID _last_ids = 0 def _make_ids(s): global _last_ids _last_ids += 1 _sids[_last_ids] = s return _last_ids # These strings are what the user sees and would be localized. # XXX - its possible that the shell might persist these values, so # this scheme wouldn't really be suitable in a real ap. IDS_UNSPECIFIED = _make_ids("unspecified") IDS_SMALL = _make_ids("small") IDS_MEDIUM = _make_ids("medium") IDS_LARGE = _make_ids("large") IDS_CIRCLE = _make_ids("circle") IDS_TRIANGLE = _make_ids("triangle") IDS_RECTANGLE = _make_ids("rectangle") IDS_POLYGON = _make_ids("polygon") IDS_DISPLAY = _make_ids("Display") IDS_DISPLAY_TT = _make_ids("Display the item.") IDS_SETTINGS = _make_ids("Settings") IDS_SETTING1 = _make_ids("Setting 1") IDS_SETTING2 = _make_ids("Setting 2") IDS_SETTING3 = _make_ids("Setting 3") IDS_SETTINGS_TT = _make_ids("Modify settings.") IDS_SETTING1_TT = _make_ids("Modify setting 1.") IDS_SETTING2_TT = _make_ids("Modify setting 2.") IDS_SETTING3_TT = _make_ids("Modify setting 3.") IDS_LESSTHAN5 = _make_ids("Less Than 5") IDS_5ORGREATER = _make_ids("Five or Greater") del _make_ids, _last_ids # Other misc resource stuff IDI_ICON1 = 100 IDI_SETTINGS = 101 # The sample defines a number of "category ids". Each one gets # its own GUID. CAT_GUID_NAME=GUID("{de094c9d-c65a-11dc-ba21-005056c00008}") CAT_GUID_SIZE=GUID("{de094c9e-c65a-11dc-ba21-005056c00008}") CAT_GUID_SIDES=GUID("{de094c9f-c65a-11dc-ba21-005056c00008}") CAT_GUID_LEVEL=GUID("{de094ca0-c65a-11dc-ba21-005056c00008}") # The next category guid is NOT based on a column (see # ViewCategoryProvider::EnumCategories()...) CAT_GUID_VALUE="{de094ca1-c65a-11dc-ba21-005056c00008}" GUID_Display=GUID("{4d6c2fdd-c689-11dc-ba21-005056c00008}") GUID_Settings=GUID("{4d6c2fde-c689-11dc-ba21-005056c00008}") GUID_Setting1=GUID("{4d6c2fdf-c689-11dc-ba21-005056c00008}") GUID_Setting2=GUID("{4d6c2fe0-c689-11dc-ba21-005056c00008}") GUID_Setting3=GUID("{4d6c2fe1-c689-11dc-ba21-005056c00008}") # Hrm - not sure what to do about the std keys. # Probably need a simple parser for propkey.h PKEY_ItemNameDisplay = ("{B725F130-47EF-101A-A5F1-02608C9EEBAC}", 10) PKEY_PropList_PreviewDetails = ("{C9944A21-A406-48FE-8225-AEC7E24C211B}", 8) # Not sure what the "3" here refers to - docs say PID_FIRST_USABLE (2) be # used. Presumably it is the 'propID' value in the .propdesc file! # note that the following GUIDs are also references in the .propdesc file PID_SOMETHING=3 # These are our 'private' PKEYs # Col 2, name="Sample.AreaSize" PKEY_Sample_AreaSize=("{d6f5e341-c65c-11dc-ba21-005056c00008}", PID_SOMETHING) # Col 3, name="Sample.NumberOfSides" PKEY_Sample_NumberOfSides = ("{d6f5e342-c65c-11dc-ba21-005056c00008}", PID_SOMETHING) # Col 4, name="Sample.DirectoryLevel" PKEY_Sample_DirectoryLevel = ("{d6f5e343-c65c-11dc-ba21-005056c00008}", PID_SOMETHING) # We construct a PIDL from a pickle of a dict - turn it back into a # dict (we should *never* be called with a PIDL that the last elt is not # ours, so it is safe to assume we created it (assume->"ass" = "u" + "me" :) def pidl_to_item(pidl): # Note that only the *last* elt in the PIDL is certainly ours, # but it contains everything we need encoded as a dict. return pickle.loads(pidl[-1]) # Start of msdn sample port... # make_item_enum replaces the sample's entire EnumIDList.cpp :) def make_item_enum(level, flags): pidls = [] nums = """zero one two three four five size seven eight nine ten""".split() for i, name in enumerate(nums): size = random.randint(0,255) sides = 1 while sides in [1,2]: sides = random.randint(0,5) is_folder = (i % 2) != 0 # check the flags say to include it. # (This seems strange; if you ask the same folder for, but appear skip = False if not (flags & shellcon.SHCONTF_STORAGE): if is_folder: skip = not (flags & shellcon.SHCONTF_FOLDERS) else: skip = not (flags & shellcon.SHCONTF_NONFOLDERS) if not skip: data = dict(name=name, size=size, sides=sides, level=level, is_folder=is_folder) pidls.append([pickle.dumps(data)]) return NewEnum(pidls, shell.IID_IEnumIDList) # start of Utils.cpp port def DisplayItem(shell_item_array, hwnd_parent=0): # Get the first ShellItem and display its name if shell_item_array is None: msg = "You must select something!" else: si = shell_item_array.GetItemAt(0) name = si.GetDisplayName(shellcon.SIGDN_NORMALDISPLAY) msg = "%d items selected, first is %r" % (shell_item_array.GetCount(), name) win32gui.MessageBox(hwnd_parent, msg, "Hello", win32con.MB_OK) # end of Utils.cpp port # start of sample's FVCommands.cpp port class Command: def __init__(self, guid, ids, ids_tt, idi, flags, callback, children): self.guid = guid; self.ids = ids; self.ids_tt = ids_tt self.idi = idi; self.flags = flags; self.callback = callback; self.children = children assert not children or isinstance(children[0], Command) def tuple(self): return self.guid, self.ids, self.ids_tt, self.idi, self.flags, self.callback, self.children # command callbacks - called back directly by us - see ExplorerCommand.Invoke def onDisplay(items, bindctx): DisplayItem(items) def onSetting1(items, bindctx): win32gui.MessageBox(0, LoadString(IDS_SETTING1), "Hello", win32con.MB_OK) def onSetting2(items, bindctx): win32gui.MessageBox(0, LoadString(IDS_SETTING2), "Hello", win32con.MB_OK) def onSetting3(items, bindctx): win32gui.MessageBox(0, LoadString(IDS_SETTING3), "Hello", win32con.MB_OK) taskSettings = [ Command(GUID_Setting1, IDS_SETTING1, IDS_SETTING1_TT, IDI_SETTINGS, 0, onSetting1, None), Command(GUID_Setting2, IDS_SETTING2, IDS_SETTING2_TT, IDI_SETTINGS, 0, onSetting2, None), Command(GUID_Setting3, IDS_SETTING3, IDS_SETTING3_TT, IDI_SETTINGS, 0, onSetting3, None), ] tasks = [ Command(GUID_Display, IDS_DISPLAY, IDS_DISPLAY_TT, IDI_ICON1, 0, onDisplay, None ), Command(GUID_Settings, IDS_SETTINGS, IDS_SETTINGS_TT, IDI_SETTINGS, shellcon.ECF_HASSUBCOMMANDS, None, taskSettings), ] class ExplorerCommandProvider: _com_interfaces_ = [shell.IID_IExplorerCommandProvider] _public_methods_ = shellcon.IExplorerCommandProvider_Methods def GetCommands(self, site, iid): items = [wrap(ExplorerCommand(t)) for t in tasks] return NewEnum(items, shell.IID_IEnumExplorerCommand) class ExplorerCommand: _com_interfaces_ = [shell.IID_IExplorerCommand] _public_methods_ = shellcon.IExplorerCommand_Methods def __init__(self, cmd): self.cmd = cmd # The sample also appears to ignore the pidl args!? def GetTitle(self, pidl): return LoadString(self.cmd.ids) def GetToolTip(self, pidl): return LoadString(self.cmd.ids_tt) def GetIcon(self, pidl): # Return a string of the usual "dll,resource_id" format # todo - just return any ".ico that comes with python" + ",0" :) raise COMException(hresult=winerror.E_NOTIMPL) def GetState(self, shell_items, slow_ok): return shellcon.ECS_ENABLED def GetFlags(self): return self.cmd.flags def GetCanonicalName(self): return self.cmd.guid def Invoke(self, items, bind_ctx): # If no function defined - just return S_OK if self.cmd.callback: self.cmd.callback(items, bind_ctx) else: print("No callback for command ", LoadString(self.cmd.ids)) def EnumSubCommands(self): if not self.cmd.children: return None items = [wrap(ExplorerCommand(c)) for c in self.cmd.children] return NewEnum(items, shell.IID_IEnumExplorerCommand) # end of sample's FVCommands.cpp port # start of sample's Category.cpp port class FolderViewCategorizer: _com_interfaces_ = [shell.IID_ICategorizer] _public_methods_ = shellcon.ICategorizer_Methods description = None # subclasses should set their own def __init__(self, shell_folder): self.sf = shell_folder # Determines the relative order of two items in their item identifier lists. def CompareCategory(self, flags, cat1, cat2): return cat1-cat2 # Retrieves the name of a categorizer, such as "Group By Device # Type", that can be displayed in the user interface. def GetDescription(self, cch): return self.description # Retrieves information about a category, such as the default # display and the text to display in the user interface. def GetCategoryInfo(self, catid): # Note: this isn't always appropriate! See overrides below return 0, str(catid) # ???? class FolderViewCategorizer_Name(FolderViewCategorizer): description = "Alphabetical" def GetCategory(self, pidls): ret = [] for pidl in pidls: val = self.sf.GetDetailsEx(pidl, PKEY_ItemNameDisplay) ret.append(val) return ret class FolderViewCategorizer_Size(FolderViewCategorizer): description = "Group By Size" def GetCategory(self, pidls): ret = [] for pidl in pidls: # Why don't we just get the size of the PIDL? val = self.sf.GetDetailsEx(pidl, PKEY_Sample_AreaSize) val = int(val) # it probably came in a VT_BSTR variant if val < 255//3: cid = IDS_SMALL elif val < 2 * 255 // 3: cid = IDS_MEDIUM else: cid = IDS_LARGE ret.append(cid) return ret def GetCategoryInfo(self, catid): return 0, LoadString(catid) class FolderViewCategorizer_Sides(FolderViewCategorizer): description = "Group By Sides" def GetCategory(self, pidls): ret = [] for pidl in pidls: val = self.sf.GetDetailsEx(pidl, PKEY_ItemNameDisplay) if val==0: cid = IDS_CIRCLE elif val==3: cid = IDS_TRIANGLE elif val==4: cid = IDS_RECTANGLE elif val==5: cid = IDS_POLYGON else: cid = IDS_UNSPECIFIED ret.append(cid) return ret def GetCategoryInfo(self, catid): return 0, LoadString(catid) class FolderViewCategorizer_Value(FolderViewCategorizer): description = "Group By Value" def GetCategory(self, pidls): ret = [] for pidl in pidls: val = self.sf.GetDetailsEx(pidl, PKEY_ItemNameDisplay) if val in "one two three four".split(): ret.append(IDS_LESSTHAN5) else: ret.append(IDS_5ORGREATER) return ret def GetCategoryInfo(self, catid): return 0, LoadString(catid) class FolderViewCategorizer_Level(FolderViewCategorizer): description = "Group By Value" def GetCategory(self, pidls): return [self.sf.GetDetailsEx(pidl, PKEY_Sample_DirectoryLevel) for pidl in pidls] class ViewCategoryProvider: _com_interfaces_ = [shell.IID_ICategoryProvider] _public_methods_ = shellcon.ICategoryProvider_Methods def __init__(self, shell_folder): self.shell_folder = shell_folder def CanCategorizeOnSCID(self, pkey): return pkey in [PKEY_ItemNameDisplay, PKEY_Sample_AreaSize, PKEY_Sample_NumberOfSides, PKEY_Sample_DirectoryLevel] # Creates a category object. def CreateCategory(self, guid, iid): if iid == shell.IID_ICategorizer: if guid == CAT_GUID_NAME: klass = FolderViewCategorizer_Name elif guid == CAT_GUID_SIDES: klass = FolderViewCategorizer_Sides elif guid == CAT_GUID_SIZE: klass = FolderViewCategorizer_Size elif guid == CAT_GUID_VALUE: klass = FolderViewCategorizer_Value elif guid == CAT_GUID_LEVEL: klass = FolderViewCategorizer_Level else: raise COMException(hresult=winerror.E_INVALIDARG) return wrap(klass(self.shell_folder)) raise COMException(hresult=winerror.E_NOINTERFACE) # Retrieves the enumerator for the categories. def EnumCategories(self): # These are additional categories beyond the columns seq = [CAT_GUID_VALUE] return NewEnum(seq, pythoncom.IID_IEnumGUID) # Retrieves a globally unique identifier (GUID) that represents # the categorizer to use for the specified Shell column. def GetCategoryForSCID(self, scid): if scid==PKEY_ItemNameDisplay: guid = CAT_GUID_NAME elif scid == PKEY_Sample_AreaSize: guid = CAT_GUID_SIZE elif scid == PKEY_Sample_NumberOfSides: guid = CAT_GUID_SIDES elif scid == PKEY_Sample_DirectoryLevel: guid = CAT_GUID_LEVEL elif scid == pythoncom.IID_NULL: # This can be called with a NULL # format ID. This will happen if you have a category, # not based on a column, that gets stored in the # property bag. When a return is made to this item, # it will call this function with a NULL format id. guid = CAT_GUID_VALUE else: raise COMException(hresult=winerror.E_INVALIDARG) return guid # Retrieves the name of the specified category. This is where # additional categories that appear under the column # related categories in the UI, get their display names. def GetCategoryName(self, guid, cch): if guid == CAT_GUID_VALUE: return "Value" raise COMException(hresult=winerror.E_FAIL) # Enables the folder to override the default grouping. def GetDefaultCategory(self): return CAT_GUID_LEVEL, (pythoncom.IID_NULL, 0) # end of sample's Category.cpp port # start of sample's ContextMenu.cpp port MENUVERB_DISPLAY = 0 folderViewImplContextMenuIDs = [ ("display", MENUVERB_DISPLAY, 0, ), ] class ContextMenu: _reg_progid_ = "Python.ShellFolderSample.ContextMenu" _reg_desc_ = "Python FolderView Context Menu" _reg_clsid_ = "{fed40039-021f-4011-87c5-6188b9979764}" _com_interfaces_ = [shell.IID_IShellExtInit, shell.IID_IContextMenu, axcontrol.IID_IObjectWithSite] _public_methods_ = shellcon.IContextMenu_Methods + shellcon.IShellExtInit_Methods + ["GetSite", "SetSite"] _context_menu_type_ = "PythonFolderViewSampleType" def __init__(self): self.site = None self.dataobj = None def Initialize(self, folder, dataobj, hkey): self.dataobj = dataobj def QueryContextMenu(self, hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags): s = LoadString(IDS_DISPLAY); win32gui.InsertMenu(hMenu, indexMenu, win32con.MF_BYPOSITION, idCmdFirst + MENUVERB_DISPLAY, s); indexMenu += 1 # other verbs could go here... # indicate that we added one verb. return 1 def InvokeCommand(self, ci): mask, hwnd, verb, params, dir, nShow, hotkey, hicon = ci # this seems very convuluted, but its what the sample does :) for verb_name, verb_id, flag in folderViewImplContextMenuIDs: if isinstance(verb, int): matches = verb==verb_id else: matches = verb==verb_name if matches: break else: assert False, ci # failed to find our ID if verb_id == MENUVERB_DISPLAY: sia = shell.SHCreateShellItemArrayFromDataObject(self.dataobj) DisplayItem(hwnd, sia) else: assert False, ci # Got some verb we weren't expecting? def GetCommandString(self, cmd, typ): raise COMException(hresult=winerror.E_NOTIMPL) def SetSite(self, site): self.site = site def GetSite(self, iid): return self.site # end of sample's ContextMenu.cpp port # start of sample's ShellFolder.cpp port class ShellFolder: _com_interfaces_ = [shell.IID_IBrowserFrameOptions, pythoncom.IID_IPersist, shell.IID_IPersistFolder, shell.IID_IPersistFolder2, shell.IID_IShellFolder, shell.IID_IShellFolder2, ] _public_methods_ = shellcon.IBrowserFrame_Methods + \ shellcon.IPersistFolder2_Methods + \ shellcon.IShellFolder2_Methods _reg_progid_ = "Python.ShellFolderSample.Folder2" _reg_desc_ = "Python FolderView sample" _reg_clsid_ = "{bb8c24ad-6aaa-4cec-ac5e-c429d5f57627}" max_levels = 5 def __init__(self, level=0): self.current_level = level self.pidl = None # set when Initialize is called def ParseDisplayName(self, hwnd, reserved, displayName, attr): #print "ParseDisplayName", displayName raise COMException(hresult=winerror.E_NOTIMPL) def EnumObjects(self, hwndOwner, flags): if self.current_level >= self.max_levels: return None return make_item_enum(self.current_level+1, flags) def BindToObject(self, pidl, bc, iid): tail = pidl_to_item(pidl) # assert tail['is_folder'], "BindToObject should only be called on folders?" # *sob* # No point creating object just to have QI fail. if iid not in ShellFolder._com_interfaces_: raise COMException(hresult=winerror.E_NOTIMPL) child = ShellFolder(self.current_level+1) # hrmph - not sure what multiple PIDLs here mean? # assert len(pidl)==1, pidl # expecting just relative child PIDL child.Initialize(self.pidl + pidl) return wrap(child, iid) def BindToStorage(self, pidl, bc, iid): return self.BindToObject(pidl, bc, iid) def CompareIDs(self, param, id1, id2): return 0 # XXX - todo - implement this! def CreateViewObject(self, hwnd, iid): if iid == shell.IID_IShellView: com_folder = wrap(self) return shell.SHCreateShellFolderView(com_folder) elif iid == shell.IID_ICategoryProvider: return wrap(ViewCategoryProvider(self)) elif iid == shell.IID_IContextMenu: ws = wrap(self) dcm = (hwnd, None, self.pidl, ws, None) return shell.SHCreateDefaultContextMenu(dcm, iid) elif iid == shell.IID_IExplorerCommandProvider: return wrap(ExplorerCommandProvider()) else: raise COMException(hresult=winerror.E_NOINTERFACE) def GetAttributesOf(self, pidls, attrFlags): assert len(pidls)==1, "sample only expects 1 too!" assert len(pidls[0])==1, "expect relative pidls!" item = pidl_to_item(pidls[0]) flags = 0 if item['is_folder']: flags |= shellcon.SFGAO_FOLDER if item['level'] < self.max_levels: flags |= shellcon.SFGAO_HASSUBFOLDER return flags # Retrieves an OLE interface that can be used to carry out # actions on the specified file objects or folders. def GetUIObjectOf(self, hwndOwner, pidls, iid, inout): assert len(pidls)==1, "oops - arent expecting more than one!" assert len(pidls[0])==1, "assuming relative pidls!" item = pidl_to_item(pidls[0]) if iid == shell.IID_IContextMenu: ws = wrap(self) dcm = (hwndOwner, None, self.pidl, ws, pidls) return shell.SHCreateDefaultContextMenu(dcm, iid) elif iid == shell.IID_IExtractIconW: dxi = shell.SHCreateDefaultExtractIcon() # dxi is IDefaultExtractIconInit if item['is_folder']: dxi.SetNormalIcon("shell32.dll", 4) else: dxi.SetNormalIcon("shell32.dll", 1) # just return the dxi - let Python QI for IID_IExtractIconW return dxi elif iid == pythoncom.IID_IDataObject: return shell.SHCreateDataObject(self.pidl, pidls, None, iid); elif iid == shell.IID_IQueryAssociations: elts = [] if item['is_folder']: elts.append((shellcon.ASSOCCLASS_FOLDER, None, None)) elts.append((shellcon.ASSOCCLASS_PROGID_STR, None, ContextMenu._context_menu_type_)) return shell.AssocCreateForClasses(elts, iid) raise COMException(hresult=winerror.E_NOINTERFACE) # Retrieves the display name for the specified file object or subfolder. def GetDisplayNameOf(self, pidl, flags): item = pidl_to_item(pidl) if flags & shellcon.SHGDN_FORPARSING: if flags & shellcon.SHGDN_INFOLDER: return item['name'] else: if flags & shellcon.SHGDN_FORADDRESSBAR: sigdn = shellcon.SIGDN_DESKTOPABSOLUTEEDITING else: sigdn = shellcon.SIGDN_DESKTOPABSOLUTEPARSING parent = shell.SHGetNameFromIDList(self.pidl, sigdn) return parent + "\\" + item['name'] else: return item['name'] def SetNameOf(self, hwndOwner, pidl, new_name, flags): raise COMException(hresult=winerror.E_NOTIMPL) def GetClassID(self): return self._reg_clsid_ # IPersistFolder method def Initialize(self, pidl): self.pidl = pidl # IShellFolder2 methods def EnumSearches(self): raise COMException(hresult=winerror.E_NOINTERFACE) # Retrieves the default sorting and display columns. def GetDefaultColumn(self, dwres): # result is (sort, display) return 0, 0 # Retrieves the default state for a specified column. def GetDefaultColumnState(self, iCol): if iCol < 3: return shellcon.SHCOLSTATE_ONBYDEFAULT | shellcon.SHCOLSTATE_TYPE_STR raise COMException(hresult=winerror.E_INVALIDARG) # Requests the GUID of the default search object for the folder. def GetDefaultSearchGUID(self): raise COMException(hresult=winerror.E_NOTIMPL) # Helper function for getting the display name for a column. def _GetColumnDisplayName(self, pidl, pkey): item = pidl_to_item(pidl) is_folder = item['is_folder'] if pkey == PKEY_ItemNameDisplay: val = item['name'] elif pkey == PKEY_Sample_AreaSize and not is_folder: val = "%d Sq. Ft." % item['size'] elif pkey == PKEY_Sample_NumberOfSides and not is_folder: val = str(item['sides']) # not sure why str() elif pkey == PKEY_Sample_DirectoryLevel: val = str(item['level']) else: val = '' return val # Retrieves detailed information, identified by a # property set ID (FMTID) and property ID (PID), # on an item in a Shell folder. def GetDetailsEx(self, pidl, pkey): item = pidl_to_item(pidl) is_folder = item['is_folder'] if not is_folder and pkey == PKEY_PropList_PreviewDetails: return "prop:Sample.AreaSize;Sample.NumberOfSides;Sample.DirectoryLevel" return self._GetColumnDisplayName(pidl, pkey) # Retrieves detailed information, identified by a # column index, on an item in a Shell folder. def GetDetailsOf(self, pidl, iCol): key = self.MapColumnToSCID(iCol); if pidl is None: data = [(commctrl.LVCFMT_LEFT, "Name"), (commctrl.LVCFMT_CENTER, "Size"), (commctrl.LVCFMT_CENTER, "Sides"), (commctrl.LVCFMT_CENTER, "Level"),] if iCol >= len(data): raise COMException(hresult=winerror.E_FAIL) fmt, val = data[iCol] else: fmt = 0 # ? val = self._GetColumnDisplayName(pidl, key) cxChar = 24 return fmt, cxChar, val # Converts a column name to the appropriate # property set ID (FMTID) and property ID (PID). def MapColumnToSCID(self, iCol): data = [PKEY_ItemNameDisplay, PKEY_Sample_AreaSize, PKEY_Sample_NumberOfSides, PKEY_Sample_DirectoryLevel] if iCol >= len(data): raise COMException(hresult=winerror.E_FAIL) return data[iCol] # IPersistFolder2 methods # Retrieves the PIDLIST_ABSOLUTE for the folder object. def GetCurFolder(self): # The docs say this is OK, but I suspect its a problem in this case :) #assert self.pidl, "haven't been initialized?" return self.pidl # end of sample's ShellFolder.cpp port def get_schema_fname(): me = win32api.GetFullPathName(__file__) sc = os.path.splitext(me)[0] + ".propdesc" assert os.path.isfile(sc), sc return sc def DllRegisterServer(): import winreg if sys.getwindowsversion()[0] < 6: print("This sample only works on Vista") sys.exit(1) key = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\" \ "Explorer\\Desktop\\Namespace\\" + \ ShellFolder._reg_clsid_) winreg.SetValueEx(key, None, 0, winreg.REG_SZ, ShellFolder._reg_desc_) # And special shell keys under our CLSID key = winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, "CLSID\\" + ShellFolder._reg_clsid_ + "\\ShellFolder") # 'Attributes' is an int stored as a binary! use struct attr = shellcon.SFGAO_FOLDER | shellcon.SFGAO_HASSUBFOLDER | \ shellcon.SFGAO_BROWSABLE import struct s = struct.pack("i", attr) winreg.SetValueEx(key, "Attributes", 0, winreg.REG_BINARY, s) # register the context menu handler under the FolderViewSampleType type. keypath = "%s\\shellex\\ContextMenuHandlers\\%s" % (ContextMenu._context_menu_type_, ContextMenu._reg_desc_) key = winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, keypath) winreg.SetValueEx(key, None, 0, winreg.REG_SZ, ContextMenu._reg_clsid_) propsys.PSRegisterPropertySchema(get_schema_fname()) print(ShellFolder._reg_desc_, "registration complete.") def DllUnregisterServer(): import winreg paths = [ "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\Namespace\\" + ShellFolder._reg_clsid_, "%s\\shellex\\ContextMenuHandlers\\%s" % (ContextMenu._context_menu_type_, ContextMenu._reg_desc_), ] for path in paths: try: winreg.DeleteKey(winreg.HKEY_LOCAL_MACHINE, path) except WindowsError as details: import errno if details.errno != errno.ENOENT: print("FAILED to remove %s: %s" % (path, details)) propsys.PSUnregisterPropertySchema(get_schema_fname()) print(ShellFolder._reg_desc_, "unregistration complete.") if __name__=='__main__': from win32com.server import register register.UseCommandLine(ShellFolder, ContextMenu, debug = debug, finalize_register = DllRegisterServer, finalize_unregister = DllUnregisterServer)