// Classic Shell (c) 2009-2017, Ivo Beltchev // Open-Shell (c) 2017-2018, The Open-Shell Team // Confidential information of Ivo Beltchev. Not for disclosure or distribution without prior written consent from the author #include "stdafx.h" #include "JumpLists.h" #include "ItemManager.h" #include "ResourceHelper.h" #include "Translations.h" #include "FNVHash.h" #include "LogManager.h" #include #include static const CLSID CLSID_AutomaticDestinationList={0xf0ae1542, 0xf497, 0x484b, {0xa1, 0x75, 0xa2, 0x0d, 0xb0, 0x91, 0x44, 0xba}}; struct APPDESTCATEGORY { int type; union { wchar_t *name; int subType; }; int count; int pad[10]; // just in case }; static const GUID IID_IDestinationList={0x03f1eed2, 0x8676, 0x430b, {0xab, 0xe1, 0x76, 0x5c, 0x1d, 0x8f, 0xe1, 0x47}}; static const GUID IID_IDestinationList10a={0xfebd543d, 0x1f7b, 0x4b38, {0x94, 0x0b, 0x59, 0x33, 0xbd, 0x2c, 0xb2, 0x1b}}; // 10240 static const GUID IID_IDestinationList10b={0x507101cd, 0xf6ad, 0x46c8, {0x8e, 0x20, 0xee, 0xb9, 0xe6, 0xba, 0xc4, 0x7f}}; // 10547 interface IDestinationList: public IUnknown { public: STDMETHOD(SetMinItems)(); virtual HRESULT STDMETHODCALLTYPE SetApplicationID( LPCWSTR appUserModelId ) = 0; STDMETHOD(GetSlotCount)(); virtual HRESULT STDMETHODCALLTYPE GetCategoryCount( UINT *pCount ) = 0; virtual HRESULT STDMETHODCALLTYPE GetCategory( UINT index, int getCatFlags, APPDESTCATEGORY *pCategory ) = 0; STDMETHOD(DeleteCategory)(); virtual HRESULT STDMETHODCALLTYPE EnumerateCategoryDestinations( UINT index, REFIID riid, void **ppvObject ) = 0; STDMETHOD(RemoveDestination)( IUnknown *pItem ); STDMETHOD(ResolveDestination)(); }; static const GUID IID_IAutomaticDestinationList={0xbc10dce3, 0x62f2, 0x4bc6, {0xaf, 0x37, 0xdb, 0x46, 0xed, 0x78, 0x73, 0xc4}}; static const GUID IID_IAutomaticDestinationList10b={0xe9c5ef8d, 0xfd41, 0x4f72, {0xba, 0x87, 0xeb, 0x03 ,0xba, 0xd5, 0x81, 0x7c}}; // 10547 interface IAutomaticDestinationList: public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE Initialize( LPCWSTR appUserModelId, LPCWSTR lnkPath, LPCWSTR ) = 0; virtual HRESULT STDMETHODCALLTYPE HasList( BOOL *pHasList ) = 0; virtual HRESULT STDMETHODCALLTYPE GetList( int listType, unsigned int maxCount, REFIID riid, void **ppvObject ) = 0; STDMETHOD(AddUsagePoint)(); virtual HRESULT STDMETHODCALLTYPE PinItem( IUnknown *pItem, int pinIndex ) = 0; // -1 - pin, -2 - unpin STDMETHOD(IsPinned)(); virtual HRESULT STDMETHODCALLTYPE RemoveDestination( IUnknown *pItem ) = 0; STDMETHOD(SetUsageData)(); STDMETHOD(GetUsageData)(); STDMETHOD(ResolveDestination)(); virtual HRESULT STDMETHODCALLTYPE ClearList( int listType ) = 0; }; interface IAutomaticDestinationList10b: public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE Initialize( LPCWSTR appUserModelId, LPCWSTR lnkPath, LPCWSTR ) = 0; virtual HRESULT STDMETHODCALLTYPE HasList( BOOL *pHasList ) = 0; virtual HRESULT STDMETHODCALLTYPE GetList( int listType, unsigned int maxCount, unsigned int flags, REFIID riid, void **ppvObject ) = 0; STDMETHOD(AddUsagePoint)(); virtual HRESULT STDMETHODCALLTYPE PinItem( IUnknown *pItem, int pinIndex ) = 0; // -1 - pin, -2 - unpin STDMETHOD(IsPinned)(); virtual HRESULT STDMETHODCALLTYPE RemoveDestination( IUnknown *pItem ) = 0; STDMETHOD(SetUsageData)(); STDMETHOD(GetUsageData)(); STDMETHOD(ResolveDestination)(); virtual HRESULT STDMETHODCALLTYPE ClearList( int listType ) = 0; }; class CAutomaticList { public: CAutomaticList( const wchar_t *appid ); bool HasList( void ); CComPtr GetList( int listType, unsigned int maxCount ); void PinItem( IUnknown *pItem, int pinIndex ); bool RemoveDestination( IUnknown *pItem ); private: CComPtr m_pAutoList; CComPtr m_pAutoList10b; }; CAutomaticList::CAutomaticList( const wchar_t *appid ) { CComPtr pAutoListUnk; if (SUCCEEDED(pAutoListUnk.CoCreateInstance(CLSID_AutomaticDestinationList))) { pAutoListUnk->QueryInterface(IID_IAutomaticDestinationList,(void**)&m_pAutoList); if (m_pAutoList) { if (FAILED(m_pAutoList->Initialize(appid,NULL,NULL))) m_pAutoList=NULL; } else if (GetWinVersion()>=WIN_VER_WIN10) { pAutoListUnk->QueryInterface(IID_IAutomaticDestinationList10b,(void**)&m_pAutoList10b); if (m_pAutoList10b) { if (FAILED(m_pAutoList10b->Initialize(appid,NULL,NULL))) m_pAutoList10b=NULL; } } } } bool CAutomaticList::HasList( void ) { BOOL hasList; if (m_pAutoList) { if (FAILED(m_pAutoList->HasList(&hasList)) || !hasList) return false; } else if (m_pAutoList10b) { if (FAILED(m_pAutoList10b->HasList(&hasList)) || !hasList) return false; } else return false; CComPtr pCollection; UINT count; pCollection=GetList(1,1); if (pCollection && SUCCEEDED(pCollection->GetCount(&count)) && count>0) return true; pCollection=GetList(0,1); if (pCollection && SUCCEEDED(pCollection->GetCount(&count)) && count>0) return true; return false; } CComPtr CAutomaticList::GetList( int listType, unsigned int maxCount ) { CComPtr pCollection; if (m_pAutoList) m_pAutoList->GetList(listType,maxCount,IID_IObjectCollection,(void**)&pCollection); else if (m_pAutoList10b) m_pAutoList10b->GetList(listType,maxCount,1,IID_IObjectCollection,(void**)&pCollection); return pCollection; } void CAutomaticList::PinItem( IUnknown *pItem, int pinIndex ) { if (m_pAutoList) m_pAutoList->PinItem(pItem,pinIndex); else if (m_pAutoList10b) m_pAutoList10b->PinItem(pItem,pinIndex); } bool CAutomaticList::RemoveDestination( IUnknown *pItem ) { if (m_pAutoList) return SUCCEEDED(m_pAutoList->RemoveDestination(pItem)); else if (m_pAutoList10b) return SUCCEEDED(m_pAutoList10b->RemoveDestination(pItem)); return false; } static CComPtr GetCustomList( const wchar_t *appid ) { CComPtr pCustomListUnk; if (SUCCEEDED(pCustomListUnk.CoCreateInstance(CLSID_DestinationList))) { CComPtr pCustomList; if (GetWinVersion()QueryInterface(IID_IDestinationList,(void**)&pCustomList); else { if (FAILED(pCustomListUnk->QueryInterface(IID_IDestinationList10a,(void**)&pCustomList))) pCustomListUnk->QueryInterface(IID_IDestinationList10b,(void**)&pCustomList); } if (pCustomList && SUCCEEDED(pCustomList->SetApplicationID(appid))) return pCustomList; } return CComPtr(); } // Returns true if the given app has a non-empty jumplist bool HasJumplist( const wchar_t *appid ) { Assert(GetWinVersion()>=WIN_VER_WIN7); CComPtr pCustomList=GetCustomList(appid); if (pCustomList) { UINT count; if (SUCCEEDED(pCustomList->GetCategoryCount(&count)) && count>0) { // skip Settings app (it reports one category with unsupported type, thus jump-list will be empty) if (wcscmp(appid, L"windows.immersivecontrolpanel_cw5n1h2txyewy!microsoft.windows.immersivecontrolpanel") == 0) return false; return true; } } if (CAutomaticList(appid).HasList()) return true; return false; } static unsigned int CalcLinkHash( IShellLink *pLink ) { CAbsolutePidl pidl; if (FAILED(pLink->GetIDList(&pidl))) return 0; unsigned int hash=FNV_HASH0; CComString pName; if (SUCCEEDED(SHGetNameFromIDList(pidl,SIGDN_DESKTOPABSOLUTEPARSING,&pName))) { pName.MakeUpper(); hash=CalcFNVHash(pName); } CComQIPtr pStore=pLink; if (pStore) hash=CalcFNVHash(GetPropertyStoreString(pStore,PKEY_Link_Arguments),hash); return hash; } static void AddJumpItem( CJumpGroup &group, IUnknown *pUnknown, std::vector> &ignoreItems, std::vector &ignoreLinks ) { CJumpItem item; item.type=CJumpItem::TYPE_UNKNOWN; item.pItem=pUnknown; item.hash=0; item.bHidden=false; item.bHasArguments=false; CComQIPtr pItem=pUnknown; if (pItem) { for (std::vector>::const_iterator it=ignoreItems.begin();it!=ignoreItems.end();++it) { int order; if (SUCCEEDED(pItem->Compare(*it,SICHINT_CANONICAL|SICHINT_TEST_FILESYSPATH_IF_NOT_EQUAL,&order)) && order==0) return; } item.type=CJumpItem::TYPE_ITEM; CComString pName; if (FAILED(pItem->GetDisplayName(SIGDN_NORMALDISPLAY,&pName))) return; item.name=pName; pName.Clear(); if (SUCCEEDED(pItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING,&pName))) { LOG_MENU(LOG_OPEN,L"Jumplist Item Path: %s",(const wchar_t*)pName); pName.MakeUpper(); item.hash=CalcFNVHash(pName); } LOG_MENU(LOG_OPEN,L"Jumplist Item Name: %s",item.name); group.items.push_back(item); return; } CComQIPtr pLink=pUnknown; if (pLink) { unsigned int hash=CalcLinkHash(pLink); for (std::vector::const_iterator it=ignoreLinks.begin();it!=ignoreLinks.end();++it) { if (hash==*it) return; } item.type=CJumpItem::TYPE_LINK; CComQIPtr pStore(pLink); if (pStore) { PROPVARIANT val; PropVariantInit(&val); if (group.type==CJumpGroup::TYPE_TASKS && SUCCEEDED(pStore->GetValue(PKEY_AppUserModel_IsDestListSeparator,&val)) && val.vt==VT_BOOL && val.boolVal) { item.type=CJumpItem::TYPE_SEPARATOR; PropVariantClear(&val); } else { CString str=GetPropertyStoreString(pStore,PKEY_Title); if (!str.IsEmpty()) { wchar_t name[256]; SHLoadIndirectString(str,name,_countof(name),NULL); item.name=name; } } } CAbsolutePidl pidl; if (SUCCEEDED(pLink->GetIDList(&pidl))) { CComString pName; if (item.name.IsEmpty()) { if (SUCCEEDED(SHGetNameFromIDList(pidl,SIGDN_NORMALDISPLAY,&pName))) { item.name=pName; } } pName.Clear(); if (SUCCEEDED(SHGetNameFromIDList(pidl,SIGDN_DESKTOPABSOLUTEPARSING,&pName))) { LOG_MENU(LOG_OPEN,L"Jumplist Link Path: %s",(const wchar_t*)pName); pName.MakeUpper(); item.hash=CalcFNVHash(pName); } CComQIPtr pStore(pLink); if (pStore) { CString args=GetPropertyStoreString(pStore,PKEY_Link_Arguments); if (!args.IsEmpty()) { LOG_MENU(LOG_OPEN,L"Jumplist Link Args: %s",args); item.hash=CalcFNVHash(args,item.hash); item.bHasArguments=true; } } } LOG_MENU(LOG_OPEN,L"Jumplist Link Name: %s",item.name); #ifdef _DEBUG LogPropertyStore(LOG_OPEN, pStore); #endif if (!item.name.IsEmpty()) group.items.push_back(item); return; } } static void AddJumpCollection( CJumpGroup &group, IObjectCollection *pCollection, std::vector> &ignoreItems, std::vector &ignoreLinks ) { UINT count; if (SUCCEEDED(pCollection->GetCount(&count))) { for (UINT i=0;i pUnknown; if (SUCCEEDED(pCollection->GetAt(i,IID_IUnknown,(void**)&pUnknown)) && pUnknown) AddJumpItem(group,pUnknown,ignoreItems,ignoreLinks); } } } // Returns the jumplist for the given shortcut bool GetJumplist( const wchar_t *appid, CJumpList &list, int maxCount, int maxHeight, int sepHeight, int itemHeight ) { Assert(GetWinVersion()>=WIN_VER_WIN7); list.Clear(); UINT categoryCount=0; CComPtr pCustomList=GetCustomList(appid); if (pCustomList) { if (FAILED(pCustomList->GetCategoryCount(&categoryCount))) categoryCount=0; } list.groups.reserve(categoryCount+2); std::vector> ignoreItems; std::vector ignoreLinks; CAutomaticList autoList(appid); { // add pinned CComPtr pPinnedList=autoList.GetList(0,maxCount); if (pPinnedList) { Assert(list.groups.empty()); list.groups.resize(list.groups.size()+1); CJumpGroup &group=*list.groups.rbegin(); group.type=CJumpGroup::TYPE_PINNED; group.name=FindTranslation(L"JumpList.Pinned",L"Pinned"); AddJumpCollection(group,pPinnedList,ignoreItems,ignoreLinks); for (std::vector::const_iterator it=group.items.begin();it!=group.items.end();++it) { CComQIPtr pShellItem(it->pItem); if (pShellItem) ignoreItems.push_back(pShellItem); else { CComQIPtr pLink(it->pItem); if (pLink) { unsigned int hash=CalcLinkHash(pLink); if (hash) ignoreLinks.push_back(hash); } } } } } int taskIndex=-1; for (UINT catIndex=0;catIndexGetCategory(catIndex,1,&category))) { if (category.type==0) { // custom group if (category.name) { wchar_t name[256]; SHLoadIndirectString(category.name,name,_countof(name),NULL); CoTaskMemFree(category.name); CComPtr pCollection; if (SUCCEEDED(pCustomList->EnumerateCategoryDestinations(catIndex,IID_IObjectCollection,(void**)&pCollection))) { list.groups.resize(list.groups.size()+1); CJumpGroup &group=*list.groups.rbegin(); group.name=name; group.type=CJumpGroup::TYPE_CUSTOM; AddJumpCollection(group,pCollection,ignoreItems,ignoreLinks); } } } else if (category.type==1) { // standard group if (category.subType==1 || category.subType==2) { CComPtr pCollection=autoList.GetList(3-category.subType,maxCount); if (pCollection) { list.groups.resize(list.groups.size()+1); CJumpGroup &group=*list.groups.rbegin(); if (category.subType==1) { group.type=CJumpGroup::TYPE_FREQUENT; group.name=FindTranslation(L"JumpList.Frequent",L"Frequent"); } else { group.type=CJumpGroup::TYPE_RECENT; group.name=FindTranslation(L"JumpList.Recent",L"Recent"); } AddJumpCollection(group,pCollection,ignoreItems,ignoreLinks); } } } else if (category.type==2 && taskIndex==-1) { taskIndex=catIndex; } } } if (taskIndex!=-1) { // add tasks CComPtr pCollection; if (SUCCEEDED(pCustomList->EnumerateCategoryDestinations(taskIndex,IID_IObjectCollection,(void**)&pCollection))) { list.groups.resize(list.groups.size()+1); CJumpGroup &group=*list.groups.rbegin(); group.name=FindTranslation(L"JumpList.Tasks",L"Tasks"); group.type=CJumpGroup::TYPE_TASKS; AddJumpCollection(group,pCollection,ignoreItems,ignoreLinks); } } if (categoryCount==0) { // add recent CComPtr pRecentList=autoList.GetList(1,maxCount); if (pRecentList) { list.groups.resize(list.groups.size()+1); CJumpGroup &group=*list.groups.rbegin(); group.type=CJumpGroup::TYPE_RECENT; group.name=FindTranslation(L"JumpList.Recent",L"Recent"); AddJumpCollection(group,pRecentList,ignoreItems,ignoreLinks); } } // limit the item count (not tasks or pinned) for (std::vector::iterator it=list.groups.begin();it!=list.groups.end();++it) { CJumpGroup &group=*it; if (group.type==CJumpGroup::TYPE_TASKS || group.type==CJumpGroup::TYPE_PINNED) maxHeight-=sepHeight+(int)group.items.size()*itemHeight; } for (std::vector::iterator it=list.groups.begin();it!=list.groups.end();++it) { CJumpGroup &group=*it; if (group.type!=CJumpGroup::TYPE_TASKS && group.type!=CJumpGroup::TYPE_PINNED) { maxHeight-=sepHeight; for (std::vector::iterator it2=group.items.begin();it2!=group.items.end();++it2) if (!it2->bHidden) { it2->bHidden=(maxCount<=0 || maxHeight::iterator it=list.groups.begin();it!=list.groups.end();++it) { CJumpGroup &group=*it; group.bHidden=true; for (std::vector::const_iterator it2=group.items.begin();it2!=group.items.end();++it2) if (!it2->bHidden) { group.bHidden=false; break; } } return true; } // Executes the given item using the correct application bool ExecuteJumpItem( const CItemManager::ItemInfo *pAppInfo, const CJumpItem &item, HWND hwnd ) { Assert(GetWinVersion()>=WIN_VER_WIN7); if (!item.pItem) return false; if (item.type==CJumpItem::TYPE_ITEM) { CComQIPtr pItem(item.pItem); if (!pItem) return false; SHELLEXECUTEINFO execute={sizeof(execute),SEE_MASK_IDLIST|SEE_MASK_FLAG_LOG_USAGE}; execute.nShow=SW_SHOWNORMAL; CAbsolutePidl pidl; if (SUCCEEDED(SHGetIDListFromObject(pItem,&pidl))) { execute.lpIDList=pidl; ShellExecuteEx(&execute); } return true; } if (item.type==CJumpItem::TYPE_LINK) { // Name: System.AppUserModel.HostEnvironment -- PKEY_AppUserModel_HostEnvironment // Type: UInt32 -- VT_UI4 // FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 14 static const PROPERTYKEY PKEY_AppUserModel_HostEnvironment = { {0x9F4C2855, 0x9F79, 0x4B39, {0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3}}, 14 }; // Name: System.AppUserModel.ActivationContext -- PKEY_AppUserModel_ActivationContext // Type: String -- VT_LPWSTR // FormatID: {9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}, 20 static const PROPERTYKEY PKEY_AppUserModel_ActivationContext = { {0x9F4C2855, 0x9F79, 0x4B39, {0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3}}, 20 }; CComQIPtr pMenu(item.pItem); CStringA params; CComQIPtr pLink(item.pItem); if (pLink) { CComQIPtr store(pLink); if (store) { auto appId = GetPropertyStoreString(store, PKEY_AppUserModel_ID); if (!appId.IsEmpty()) { CComPtr target; if (SUCCEEDED(SHCreateItemInKnownFolder(FOLDERID_AppsFolder, 0, appId, IID_PPV_ARGS(&target)))) { ULONG modern = 0; if (SUCCEEDED(target->GetUInt32(PKEY_AppUserModel_HostEnvironment, &modern)) && modern) { CComQIPtr targetMenu; if (SUCCEEDED(target->BindToHandler(nullptr, BHID_SFUIObject, IID_PPV_ARGS(&targetMenu)))) { pMenu = targetMenu; params = CT2CA(GetPropertyStoreString(store, PKEY_AppUserModel_ActivationContext)); } } } } } } // invoke the link through its context menu if (!pMenu) return false; HRESULT hr; HMENU menu=CreatePopupMenu(); hr=pMenu->QueryContextMenu(menu,0,1,1000,CMF_DEFAULTONLY); if (FAILED(hr)) { DestroyMenu(menu); return false; } int id=GetMenuDefaultItem(menu,FALSE,0); if (id>0) { CMINVOKECOMMANDINFO command={sizeof(command),CMIC_MASK_FLAG_LOG_USAGE}; command.lpVerb=MAKEINTRESOURCEA(id-1); if (!params.IsEmpty()) command.lpParameters = params; wchar_t path[_MAX_PATH]; GetModuleFileName(NULL,path,_countof(path)); if (_wcsicmp(PathFindFileName(path),L"explorer.exe")==0) command.fMask|=CMIC_MASK_ASYNCOK; command.hwnd=hwnd; command.nShow=SW_SHOWNORMAL; hr=pMenu->InvokeCommand(&command); } DestroyMenu(menu); } return true; } // Removes the given item from the jumplist void RemoveJumpItem( const CItemManager::ItemInfo *pAppInfo, CJumpList &list, int groupIdx, int itemIdx ) { CString appid; { CItemManager::RWLock lock(&g_ItemManager,false,CItemManager::RWLOCK_ITEMS); appid=pAppInfo->GetAppid(); } CJumpGroup &group=list.groups[groupIdx]; if (group.type==CJumpGroup::TYPE_FREQUENT || group.type==CJumpGroup::TYPE_RECENT) { if (CAutomaticList(appid).RemoveDestination(group.items[itemIdx].pItem)) group.items.erase(group.items.begin()+itemIdx); } else { CComPtr pCustomList=GetCustomList(appid); if (pCustomList) { if (SUCCEEDED(pCustomList->RemoveDestination(group.items[itemIdx].pItem))) group.items.erase(group.items.begin()+itemIdx); } } } // Pins or unpins the given item from the jumplist void PinJumpItem( const CItemManager::ItemInfo *pAppInfo, const CJumpList &list, int groupIdx, int itemIdx, bool bPin, int pinIndex ) { CString appid; { CItemManager::RWLock lock(&g_ItemManager,false,CItemManager::RWLOCK_ITEMS); appid=pAppInfo->GetAppid(); } const CJumpGroup &group=list.groups[groupIdx]; CAutomaticList(appid).PinItem(group.items[itemIdx].pItem,bPin?pinIndex:-2); }