1 /*
2  * PROJECT:     ReactOS shell32
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Copy as Path Menu implementation
5  * COPYRIGHT:   Copyright 2024 Whindmar Saksit <whindsaks@proton.me>
6  *              Copyright 2024 Thamatip Chitpong <thamatip.chitpong@reactos.org>
7  */
8 
9 #include "precomp.h"
10 
11 WINE_DEFAULT_DEBUG_CHANNEL(shell);
12 
13 #define IDC_COPYASPATH 0
14 
CCopyAsPathMenu()15 CCopyAsPathMenu::CCopyAsPathMenu()
16 {
17 }
18 
~CCopyAsPathMenu()19 CCopyAsPathMenu::~CCopyAsPathMenu()
20 {
21 }
22 
23 static DWORD
SetClipboard(UINT cf,const void * data,SIZE_T size)24 SetClipboard(UINT cf, const void* data, SIZE_T size)
25 {
26     BOOL succ = FALSE;
27     HGLOBAL handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, size);
28     if (handle)
29     {
30         LPVOID clipdata = GlobalLock(handle);
31         if (clipdata)
32         {
33             CopyMemory(clipdata, data, size);
34             GlobalUnlock(handle);
35             if (OpenClipboard(NULL))
36             {
37                 EmptyClipboard();
38                 succ = SetClipboardData(cf, handle) != NULL;
39                 CloseClipboard();
40             }
41         }
42         if (!succ)
43         {
44             GlobalFree(handle);
45         }
46     }
47     return succ ? ERROR_SUCCESS : GetLastError();
48 }
49 
50 static DWORD
SetClipboardFromString(LPCWSTR str)51 SetClipboardFromString(LPCWSTR str)
52 {
53     SIZE_T cch = lstrlenW(str) + 1, size = cch * sizeof(WCHAR);
54     if (size > cch)
55         return SetClipboard(CF_UNICODETEXT, str, size);
56     else
57         return ERROR_BUFFER_OVERFLOW;
58 }
59 
60 static void
AppendToPathList(CStringW & paths,LPCWSTR path,DWORD index)61 AppendToPathList(CStringW &paths, LPCWSTR path, DWORD index)
62 {
63     if (index)
64         paths += L"\r\n";
65     LPCWSTR quote = StrChrW(path, L' ');
66     if (quote)
67         paths += L'\"';
68     paths += path;
69     if (quote)
70         paths += L'\"';
71 }
72 
73 HRESULT
DoCopyAsPath(IDataObject * pdto)74 CCopyAsPathMenu::DoCopyAsPath(IDataObject *pdto)
75 {
76     CStringW paths;
77     DWORD i, count;
78     CComPtr<IShellItemArray> array;
79     HRESULT hr = SHCreateShellItemArrayFromDataObject(pdto, IID_PPV_ARG(IShellItemArray, &array));
80     if (SUCCEEDED(hr))
81     {
82         for (i = 0, array->GetCount(&count); i < count && SUCCEEDED(hr); ++i)
83         {
84             CComPtr<IShellItem> item;
85             hr = array->GetItemAt(i, &item);
86             if (SUCCEEDED(hr))
87             {
88                 CComHeapPtr<WCHAR> path;
89                 hr = item->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &path);
90                 if (SUCCEEDED(hr))
91                 {
92                     AppendToPathList(paths, path, i);
93                 }
94             }
95         }
96     }
97     if (SUCCEEDED(hr))
98     {
99         DWORD err = SetClipboardFromString(paths);
100         hr = HRESULT_FROM_WIN32(err);
101     }
102 
103     return hr;
104 }
105 
106 static const CMVERBMAP g_VerbMap[] =
107 {
108     { "copyaspath", IDC_COPYASPATH },
109     { NULL }
110 };
111 
112 STDMETHODIMP
QueryContextMenu(HMENU hMenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags)113 CCopyAsPathMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
114 {
115     MENUITEMINFOW mii;
116 
117     TRACE("CCopyAsPathMenu::QueryContextMenu(%p %p %u %u %u %u)\n", this,
118           hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
119 
120     if ((uFlags & CMF_NOVERBS) || !(uFlags & CMF_EXTENDEDVERBS))
121         return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 0);
122 
123     // Insert "Copy as path"
124     CStringW strText(MAKEINTRESOURCEW(IDS_COPYASPATHMENU));
125     ZeroMemory(&mii, sizeof(mii));
126     mii.cbSize = sizeof(mii);
127     mii.fMask = MIIM_ID | MIIM_TYPE;
128     mii.fType = MFT_STRING;
129     mii.wID = idCmdFirst + IDC_COPYASPATH;
130     mii.dwTypeData = strText.GetBuffer();
131     if (InsertMenuItemW(hMenu, indexMenu, TRUE, &mii))
132         return MAKE_HRESULT(SEVERITY_SUCCESS, 0, mii.wID - idCmdFirst + 1);
133 
134     return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 0);
135 }
136 
137 STDMETHODIMP
InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)138 CCopyAsPathMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
139 {
140     TRACE("CCopyAsPathMenu::InvokeCommand(%p %p)\n", this, lpcmi);
141 
142     int CmdId = SHELL_MapContextMenuVerbToCmdId(lpcmi, g_VerbMap);
143     if (CmdId == IDC_COPYASPATH)
144         return DoCopyAsPath(m_pDataObject);
145 
146     return E_FAIL;
147 }
148 
149 STDMETHODIMP
GetCommandString(UINT_PTR idCommand,UINT uFlags,UINT * lpReserved,LPSTR lpszName,UINT uMaxNameLen)150 CCopyAsPathMenu::GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen)
151 {
152     TRACE("CCopyAsPathMenu::GetCommandString(%p %lu %u %p %p %u)\n", this,
153           idCommand, uFlags, lpReserved, lpszName, uMaxNameLen);
154     return SHELL_GetCommandStringImpl(idCommand, uFlags, lpszName, uMaxNameLen, g_VerbMap);
155 }
156 
157 STDMETHODIMP
Initialize(PCIDLIST_ABSOLUTE pidlFolder,IDataObject * pdtobj,HKEY hkeyProgID)158 CCopyAsPathMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
159 {
160     m_pDataObject = pdtobj;
161     return S_OK;
162 }
163