1 /*
2  * The MIT License (MIT)
3  *
4  * Copyright (c) 2018 Nathan Osman
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to
8  * deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22  * IN THE SOFTWARE.
23  **/
24 
25 #include <strsafe.h>
26 #include <windows.h>
27 #include <shellapi.h>
28 
29 #include <nitroshare/apiutil.h>
30 
31 #include "contextmenu.h"
32 
ContextMenu()33 ContextMenu::ContextMenu()
34     : mRefCount(1)
35 {
36 }
37 
QueryInterface(REFIID riid,LPVOID * ppvObject)38 STDMETHODIMP ContextMenu::QueryInterface(REFIID riid, LPVOID *ppvObject)
39 {
40     if (!ppvObject) {
41         return E_INVALIDARG;
42     }
43 
44     *ppvObject = NULL;
45 
46     if (IsEqualIID(riid, IID_IUnknown)) {
47         OutputDebugString(TEXT("ContextMenu queried for IUnknown"));
48         *ppvObject = this;
49     } else if (IsEqualIID(riid, IID_IShellExtInit)) {
50         OutputDebugString(TEXT("ContextMenu queried for IShellExtInit"));
51         *ppvObject = (IShellExtInit*) this;
52     } else if (IsEqualIID(riid, IID_IContextMenu)) {
53         OutputDebugString(TEXT("ContextMenu queried for IContextMenu"));
54         *ppvObject = (IContextMenu*) this;
55     } else {
56         return E_NOINTERFACE;
57     }
58 
59     AddRef();
60     return S_OK;
61 }
62 
STDMETHODIMP_(ULONG)63 STDMETHODIMP_(ULONG) ContextMenu::AddRef()
64 {
65     InterlockedIncrement(&mRefCount);
66     return mRefCount;
67 }
68 
STDMETHODIMP_(ULONG)69 STDMETHODIMP_(ULONG) ContextMenu::Release()
70 {
71     ULONG refCount = InterlockedDecrement(&mRefCount);
72     if (!mRefCount) {
73         delete this;
74     }
75     return refCount;
76 }
77 
Initialize(PCIDLIST_ABSOLUTE pidlFolder,IDataObject * pdtobj,HKEY hkeyProgID)78 STDMETHODIMP ContextMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
79 {
80     FORMATETC formatetc = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
81     STGMEDIUM medium;
82 
83     HRESULT hResult = pdtobj->GetData(&formatetc, &medium);
84     if (hResult != S_OK) {
85         return hResult;
86     }
87 
88     // Lock the memory for reading the list of files
89     HDROP hDrop = (HDROP) GlobalLock(medium.hGlobal);
90     if (hDrop == NULL) {
91         return E_FAIL;
92     }
93 
94     // Read the filenames into the list
95     mFilenames.clear();
96     UINT nFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
97     for (UINT i = 0; i < nFiles; ++i) {
98         WCHAR filename[MAX_PATH];
99         if (!DragQueryFileW(hDrop, i, filename, MAX_PATH)) {
100             hResult = E_FAIL;
101         }
102         mFilenames.append(QString::fromWCharArray(filename));
103     }
104 
105     // Free the memory
106     GlobalUnlock(medium.hGlobal);
107 
108     return hResult;
109 }
110 
GetCommandString(UINT_PTR idCmd,UINT uFlags,UINT * pwReserved,LPSTR pszName,UINT cchMax)111 STDMETHODIMP ContextMenu::GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT *pwReserved, LPSTR pszName, UINT cchMax)
112 {
113     switch (uFlags) {
114     case GCS_HELPTEXTA:
115         StringCchCopyA(pszName, cchMax, "Send the selected items with NitroShare");
116         break;
117     case GCS_HELPTEXTW:
118         StringCchCopyW((LPWSTR) pszName, cchMax, L"Send the selected items with NitroShare");
119         break;
120     case GCS_VALIDATEA:
121     case GCS_VALIDATEW:
122         break;
123     case GCS_VERBA:
124         StringCchCopyA(pszName, cchMax, "Send");
125         break;
126     case GCS_VERBW:
127         StringCchCopyW((LPWSTR) pszName, cchMax, L"Send");
128         break;
129     }
130 
131     return S_OK;
132 }
133 
InvokeCommand(LPCMINVOKECOMMANDINFO pici)134 STDMETHODIMP ContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
135 {
136     if (!IS_INTRESOURCE(pici->lpVerb)) {
137         return E_INVALIDARG;
138     }
139 
140     // Only index 0 is valid
141     if (LOWORD(pici->lpVerb)) {
142         return E_INVALIDARG;
143     }
144 
145     // Attempt to send the items
146     QVariantMap params{
147         { "items", mFilenames }
148     };
149     QVariant ret;
150     if (!ApiUtil::sendRequest("senditemsui", params, ret, nullptr)) {
151         MessageBox(NULL, TEXT("Unable to send the selected items."), TEXT("Error"), MB_ICONERROR);
152         return E_FAIL;
153     }
154 
155     return S_OK;
156 }
157 
QueryContextMenu(HMENU hmenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags)158 STDMETHODIMP ContextMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
159 {
160     if (uFlags & CMF_DEFAULTONLY) {
161         return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
162     }
163 
164     UINT uMenuFlags = MF_BYPOSITION;
165     TCHAR caption[2048];
166 
167     // Set the flags and caption according to the current status
168     QVariant ret;
169     if (!ApiUtil::sendRequest("version", QVariantMap(), ret, nullptr)) {
170         uMenuFlags |= MF_GRAYED;
171         StringCchCopy(caption, sizeof(caption), TEXT("NitroShare is not running"));
172     } else if (mFilenames.size() == 1) {
173         StringCchCopy(caption, sizeof(caption), TEXT("Send item with NitroShare"));
174     } else {
175         StringCbPrintf(caption, sizeof(caption), TEXT("Send %d items with NitroShare"), mFilenames.size());
176     }
177 
178     // Insert the new menu item
179     if (!InsertMenu(hmenu, indexMenu, uMenuFlags, idCmdFirst, caption)) {
180         return E_FAIL;
181     }
182 
183     return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1);
184 }
185