2025-11-28 00:35:46 +09:00

493 lines
17 KiB
C++

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
#define NTDDI_VERSION NTDDI_WIN7 // Specifies that the minimum required platform is Windows 7.
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#define STRICT_TYPED_ITEMIDS // Utilize strictly typed IDLists
// Windows Header Files:
#include <windows.h>
#include <psapi.h>
#include <shlwapi.h>
#include <strsafe.h>
// Header Files for Jump List features
#include <objectarray.h>
#include <shobjidl.h>
#include <propkey.h>
#include <propvarutil.h>
#include <knownfolders.h>
#include <shlobj.h>
#include "resource.h"
#include "FileRegistrations.h"
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
HINSTANCE g_hInst = NULL;
WCHAR const c_szTitle[] = L"Custom Jump List Sample";
WCHAR const c_szWindowClass[] = L"CUSTOMJUMPLISTSAMPLE";
PCWSTR const c_rgpszFiles[] =
{
L"Microsoft_Sample_1.txt",
L"Microsoft_Sample_2.txt",
L"Microsoft_Sample_3.doc",
L"Microsoft_Sample_4.doc"
};
// Creates a set of sample files in the current user's Documents directory to use as items in the
// custom category inserted into the Jump List.
HRESULT CreateSampleFiles()
{
PWSTR pszPathDocuments;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_Documents, KF_FLAG_CREATE, NULL, &pszPathDocuments);
if (SUCCEEDED(hr))
{
for (UINT i = 0; SUCCEEDED(hr) && i < ARRAYSIZE(c_rgpszFiles); i++)
{
WCHAR szPathSample[MAX_PATH];
hr = PathCombine(szPathSample, pszPathDocuments, c_rgpszFiles[i]) ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
IStream *pstm;
hr = SHCreateStreamOnFileEx(szPathSample, (STGM_WRITE | STGM_FAILIFTHERE), FILE_ATTRIBUTE_NORMAL, TRUE, NULL, &pstm);
if (SUCCEEDED(hr))
{
PCWSTR pszText = L"This is a sample file for the CustomJumpListSample.\r\n";
ULONG cb = (sizeof(pszText[0]) * (lstrlen(pszText) + 1));
hr = IStream_Write(pstm, pszText, cb);
pstm->Release();
}
else if (HRESULT_FROM_WIN32(ERROR_FILE_EXISTS) == hr)
{
// If the file exists, we're ok, we'll just reuse it
hr = S_OK;
}
}
}
CoTaskMemFree(pszPathDocuments);
}
return hr;
}
// Cleans up the sample files that were created in the current user's Documents directory
void CleanupSampleFiles()
{
PWSTR pszPathDocuments;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_Documents, KF_FLAG_CREATE, NULL, &pszPathDocuments);
if (SUCCEEDED(hr))
{
// Don't abort the loop if we fail to cleanup a file, we still want to try to clean up the rest
for (UINT i = 0; i < ARRAYSIZE(c_rgpszFiles); i++)
{
WCHAR szPathSample[MAX_PATH];
hr = PathCombine(szPathSample, pszPathDocuments, c_rgpszFiles[i]) ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
DeleteFile(szPathSample);
}
}
CoTaskMemFree(pszPathDocuments);
}
}
// Creates a CLSID_ShellLink to insert into the Tasks section of the Jump List. This type of Jump
// List item allows the specification of an explicit command line to execute the task.
HRESULT _CreateShellLink(PCWSTR pszArguments, PCWSTR pszTitle, IShellLink **ppsl)
{
IShellLink *psl;
HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&psl));
if (SUCCEEDED(hr))
{
// Determine our executable's file path so the task will execute this application
WCHAR szAppPath[MAX_PATH];
if (GetModuleFileName(NULL, szAppPath, ARRAYSIZE(szAppPath)))
{
hr = psl->SetPath(szAppPath);
if (SUCCEEDED(hr))
{
hr = psl->SetArguments(pszArguments);
if (SUCCEEDED(hr))
{
// The title property is required on Jump List items provided as an IShellLink
// instance. This value is used as the display name in the Jump List.
IPropertyStore *pps;
hr = psl->QueryInterface(IID_PPV_ARGS(&pps));
if (SUCCEEDED(hr))
{
PROPVARIANT propvar;
hr = InitPropVariantFromString(pszTitle, &propvar);
if (SUCCEEDED(hr))
{
hr = pps->SetValue(PKEY_Title, propvar);
if (SUCCEEDED(hr))
{
hr = pps->Commit();
if (SUCCEEDED(hr))
{
hr = psl->QueryInterface(IID_PPV_ARGS(ppsl));
}
}
PropVariantClear(&propvar);
}
pps->Release();
}
}
}
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
psl->Release();
}
return hr;
}
// The Tasks category of Jump Lists supports separator items. These are simply IShellLink instances
// that have the PKEY_AppUserModel_IsDestListSeparator property set to TRUE. All other values are
// ignored when this property is set.
HRESULT _CreateSeparatorLink(IShellLink **ppsl)
{
IPropertyStore *pps;
HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pps));
if (SUCCEEDED(hr))
{
PROPVARIANT propvar;
hr = InitPropVariantFromBoolean(TRUE, &propvar);
if (SUCCEEDED(hr))
{
hr = pps->SetValue(PKEY_AppUserModel_IsDestListSeparator, propvar);
if (SUCCEEDED(hr))
{
hr = pps->Commit();
if (SUCCEEDED(hr))
{
hr = pps->QueryInterface(IID_PPV_ARGS(ppsl));
}
}
PropVariantClear(&propvar);
}
pps->Release();
}
return hr;
}
// Builds the collection of task items and adds them to the Task section of the Jump List. All tasks
// should be added to the canonical "Tasks" category by calling ICustomDestinationList::AddUserTasks.
HRESULT _AddTasksToList(ICustomDestinationList *pcdl)
{
IObjectCollection *poc;
HRESULT hr = CoCreateInstance(CLSID_EnumerableObjectCollection, NULL, CLSCTX_INPROC, IID_PPV_ARGS(&poc));
if (SUCCEEDED(hr))
{
IShellLink * psl;
hr = _CreateShellLink(L"/Task1", L"Task 1", &psl);
if (SUCCEEDED(hr))
{
hr = poc->AddObject(psl);
psl->Release();
}
if (SUCCEEDED(hr))
{
hr = _CreateShellLink(L"/Task2", L"Second Task", &psl);
if (SUCCEEDED(hr))
{
hr = poc->AddObject(psl);
psl->Release();
}
}
if (SUCCEEDED(hr))
{
hr = _CreateSeparatorLink(&psl);
if (SUCCEEDED(hr))
{
hr = poc->AddObject(psl);
psl->Release();
}
}
if (SUCCEEDED(hr))
{
hr = _CreateShellLink(L"/Task3", L"Task 3", &psl);
if (SUCCEEDED(hr))
{
hr = poc->AddObject(psl);
psl->Release();
}
}
if (SUCCEEDED(hr))
{
IObjectArray * poa;
hr = poc->QueryInterface(IID_PPV_ARGS(&poa));
if (SUCCEEDED(hr))
{
// Add the tasks to the Jump List. Tasks always appear in the canonical "Tasks"
// category that is displayed at the bottom of the Jump List, after all other
// categories.
hr = pcdl->AddUserTasks(poa);
poa->Release();
}
}
poc->Release();
}
return hr;
}
// Determines if the provided IShellItem is listed in the array of items that the user has removed
bool _IsItemInArray(IShellItem *psi, IObjectArray *poaRemoved)
{
bool fRet = false;
UINT cItems;
if (SUCCEEDED(poaRemoved->GetCount(&cItems)))
{
IShellItem *psiCompare;
for (UINT i = 0; !fRet && i < cItems; i++)
{
if (SUCCEEDED(poaRemoved->GetAt(i, IID_PPV_ARGS(&psiCompare))))
{
int iOrder;
fRet = SUCCEEDED(psiCompare->Compare(psi, SICHINT_CANONICAL, &iOrder)) && (0 == iOrder);
psiCompare->Release();
}
}
}
return fRet;
}
// Adds a custom category to the Jump List. Each item that should be in the category is added to
// an ordered collection, and then the category is appended to the Jump List as a whole.
HRESULT _AddCategoryToList(ICustomDestinationList *pcdl, IObjectArray *poaRemoved)
{
IObjectCollection *poc;
HRESULT hr = CoCreateInstance(CLSID_EnumerableObjectCollection, NULL, CLSCTX_INPROC, IID_PPV_ARGS(&poc));
if (SUCCEEDED(hr))
{
for (UINT i = 0; i < ARRAYSIZE(c_rgpszFiles); i++)
{
IShellItem *psi;
if (SUCCEEDED(SHCreateItemInKnownFolder(FOLDERID_Documents, KF_FLAG_DEFAULT, c_rgpszFiles[i], IID_PPV_ARGS(&psi))))
{
// Items listed in the removed list may not be re-added to the Jump List during this
// list-building transaction. They should not be re-added to the Jump List until
// the user has used the item again. The AppendCategory call below will fail if
// an attempt to add an item in the removed list is made.
if (!_IsItemInArray(psi, poaRemoved))
{
poc->AddObject(psi);
}
psi->Release();
}
}
IObjectArray *poa;
hr = poc->QueryInterface(IID_PPV_ARGS(&poa));
if (SUCCEEDED(hr))
{
// Add the category to the Jump List. If there were more categories, they would appear
// from top to bottom in the order they were appended.
hr = pcdl->AppendCategory(L"Custom Category", poa);
poa->Release();
}
poc->Release();
}
return hr;
}
// Builds a new custom Jump List for this application.
void CreateJumpList()
{
// Create the custom Jump List object.
ICustomDestinationList *pcdl;
HRESULT hr = CoCreateInstance(CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pcdl));
if (SUCCEEDED(hr))
{
// Custom Jump Lists follow a push model - applications are responsible for providing an updated
// list anytime the contents should be changed. Lists are generated in a list-building
// transaction that starts by calling BeginList. Until the list is committed, Windows will
// display the previous version of the list, if available.
//
// The cMinSlots out parameter indicates the minimum number of items that the Jump List UI is
// guaranteed to display. Applications can provide more items when building a custom Jump List,
// but the extra items may not be displayed. The number is dependant upon a number of factors,
// such as screen resolution and the "Number of recent items to display in Jump Lists" user setting.
// See the MSDN documentation on BeginList for more information.
//
// The IObjectArray returned from BeginList contains a list of items the user has chosen to remove
// from their Jump List. Applications must respect the user's removal of an item and not re-add any
// item in the removed list during this list-building transaction. Applications should also clear any
// persited usage-tracking data for any item in the removed list. If the user begins using a
// previously removed item in the future, it may be re-added to the list.
UINT cMinSlots;
IObjectArray *poaRemoved;
hr = pcdl->BeginList(&cMinSlots, IID_PPV_ARGS(&poaRemoved));
if (SUCCEEDED(hr))
{
// Add content to the Jump List.
hr = _AddCategoryToList(pcdl, poaRemoved);
if (SUCCEEDED(hr))
{
hr = _AddTasksToList(pcdl);
if (SUCCEEDED(hr))
{
// Commit the list-building transaction.
hr = pcdl->CommitList();
}
}
poaRemoved->Release();
}
pcdl->Release();
}
}
// Removes that existing custom Jump List for this application.
void DeleteJumpList()
{
ICustomDestinationList *pcdl;
HRESULT hr = CoCreateInstance(CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pcdl));
if (SUCCEEDED(hr))
{
hr = pcdl->DeleteList(NULL);
pcdl->Release();
}
}
// Window proc for this application.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// Parse the menu selections
switch (wmId)
{
case IDM_EXIT:
DestroyWindow(hWnd);
break;
case IDM_FILE_CREATECUSTOMJUMPLIST:
CreateJumpList();
break;
case IDM_FILE_DELETECUSTOMJUMPLIST:
DeleteJumpList();
break;
case IDM_FILE_DEREGISTERFILETYPES:
{
CleanupSampleFiles();
PCWSTR pszMessage;
HRESULT hr = UnRegisterFileTypeHandlers();
if (E_ACCESSDENIED == hr)
{
pszMessage = L"Please run this application as an administrator to remove file type registrations.";
}
else if (FAILED(hr))
{
pszMessage = L"Unable to remove file type registrations.";
}
else
{
pszMessage = L"File type registrations were successfully removed.";
}
MessageBox(hWnd, pszMessage, c_szTitle, MB_OK);
break;
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pszCmdLine, int nCmdShow)
{
g_hInst = hInstance;
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr))
{
if (!AreFileTypesRegistered())
{
PCWSTR pszMessage = NULL;
hr = RegisterToHandleFileTypes();
if (E_ACCESSDENIED == hr)
{
pszMessage = L"Please relaunch this application as an administrator to register for the required file types.";
}
else if (FAILED(hr))
{
pszMessage = L"Unable to register the required file types.";
}
else
{
pszMessage = L"The required file types were successfully registered.";
}
MessageBox(NULL, pszMessage, c_szTitle, MB_OK);
}
if (SUCCEEDED(hr))
{
if (FAILED(CreateSampleFiles()))
{
MessageBox(NULL, L"Unable to create the sample files.", c_szTitle, MB_OK);
}
if (pszCmdLine && *pszCmdLine)
{
MessageBox(NULL, pszCmdLine, c_szTitle, MB_OK);
}
WNDCLASSEX wcex = {sizeof(wcex)};
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CUSTOMJUMPLISTSAMPLE));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_CUSTOMJUMPLISTSAMPLE);
wcex.lpszClassName = c_szWindowClass;
RegisterClassEx(&wcex);
HWND hWnd = CreateWindow(c_szWindowClass, c_szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 300, 200, NULL, NULL, hInstance, NULL);
if (hWnd)
{
ShowWindow(hWnd, nCmdShow);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
CoUninitialize();
}
return 0;
}