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 
15 CCopyAsPathMenu::CCopyAsPathMenu()
16 {
17 }
18 
19 CCopyAsPathMenu::~CCopyAsPathMenu()
20 {
21 }
22 
23 static DWORD
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
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
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
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 STDMETHODIMP
107 CCopyAsPathMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
108 {
109     MENUITEMINFOW mii;
110 
111     TRACE("CCopyAsPathMenu::QueryContextMenu(%p %p %u %u %u %u)\n", this,
112           hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
113 
114     if ((uFlags & CMF_NOVERBS) || !(uFlags & CMF_EXTENDEDVERBS))
115         return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 0);
116 
117     // Insert "Copy as path"
118     CStringW strText(MAKEINTRESOURCEW(IDS_COPYASPATHMENU));
119     ZeroMemory(&mii, sizeof(mii));
120     mii.cbSize = sizeof(mii);
121     mii.fMask = MIIM_ID | MIIM_TYPE;
122     mii.fType = MFT_STRING;
123     mii.wID = idCmdFirst + IDC_COPYASPATH;
124     mii.dwTypeData = strText.GetBuffer();
125     if (InsertMenuItemW(hMenu, indexMenu, TRUE, &mii))
126         return MAKE_HRESULT(SEVERITY_SUCCESS, 0, mii.wID - idCmdFirst + 1);
127 
128     return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 0);
129 }
130 
131 STDMETHODIMP
132 CCopyAsPathMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
133 {
134     TRACE("CCopyAsPathMenu::InvokeCommand(%p %p)\n", this, lpcmi);
135 
136     if (IS_INTRESOURCE(lpcmi->lpVerb) && LOWORD(lpcmi->lpVerb) == IDC_COPYASPATH)
137         return DoCopyAsPath(m_pDataObject);
138 
139     return E_FAIL;
140 }
141 
142 STDMETHODIMP
143 CCopyAsPathMenu::GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen)
144 {
145     FIXME("CCopyAsPathMenu::GetCommandString(%p %lu %u %p %p %u)\n", this,
146           idCommand, uFlags, lpReserved, lpszName, uMaxNameLen);
147 
148     return E_NOTIMPL;
149 }
150 
151 STDMETHODIMP
152 CCopyAsPathMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
153 {
154     m_pDataObject = pdtobj;
155     return S_OK;
156 }
157