dllmain.cpp
1 // dllmain.cpp : Defines the entry point for the DLL application. 2 #include "pch.h" 3 4 #include <atlfile.h> 5 #include <atlstr.h> 6 #include <Shlwapi.h> 7 #include <shobjidl_core.h> 8 #include <string> 9 10 #include <common/telemetry/EtwTrace/EtwTrace.h> 11 #include <common/utils/elevation.h> 12 #include <common/utils/process_path.h> 13 #include <common/utils/resources.h> 14 #include <Settings.h> 15 #include <trace.h> 16 17 #include <wil/win32_helpers.h> 18 #include <wrl/module.h> 19 #include "Generated Files/resource.h" 20 21 using namespace Microsoft::WRL; 22 23 HINSTANCE g_hInst = 0; 24 Shared::Trace::ETWTrace trace(L"ImageResizerContextMenu"); 25 26 #define BUFSIZE 4096 * 4 27 28 BOOL APIENTRY DllMain( HMODULE hModule, 29 DWORD ul_reason_for_call, 30 LPVOID lpReserved 31 ) 32 { 33 switch (ul_reason_for_call) 34 { 35 case DLL_PROCESS_ATTACH: 36 g_hInst = hModule; 37 Trace::RegisterProvider(); 38 break; 39 case DLL_PROCESS_DETACH: 40 Trace::UnregisterProvider(); 41 break; 42 } 43 return TRUE; 44 } 45 46 class __declspec(uuid("8F491918-259F-451A-950F-8C3EBF4864AF")) ImageResizerContextMenuCommand final : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IExplorerCommand, IObjectWithSite> 47 { 48 public: 49 virtual const wchar_t* Title() { return L"Image Resizer"; } 50 virtual const EXPCMDFLAGS Flags() { return ECF_DEFAULT; } 51 virtual const EXPCMDSTATE State(_In_opt_ IShellItemArray* selection) { return ECS_ENABLED; } 52 53 // IExplorerCommand 54 IFACEMETHODIMP GetTitle(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* name) 55 { 56 return SHStrDup(context_menu_caption.c_str(), name); 57 } 58 59 IFACEMETHODIMP GetIcon(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* icon) 60 { 61 std::wstring iconResourcePath = get_module_folderpath(g_hInst); 62 iconResourcePath += L"\\Assets\\ImageResizer\\"; 63 iconResourcePath += L"ImageResizer.ico"; 64 return SHStrDup(iconResourcePath.c_str(), icon); 65 } 66 67 IFACEMETHODIMP GetToolTip(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* infoTip) 68 { 69 *infoTip = nullptr; 70 return E_NOTIMPL; 71 } 72 73 IFACEMETHODIMP GetCanonicalName(_Out_ GUID* guidCommandName) 74 { 75 *guidCommandName = __uuidof(this); 76 return S_OK; 77 } 78 79 IFACEMETHODIMP GetState(_In_opt_ IShellItemArray* selection, _In_ BOOL okToBeSlow, _Out_ EXPCMDSTATE* cmdState) 80 { 81 if (nullptr == selection) { 82 // We've observed that it's possible that a null gets passed instead of an empty array. Just don't show the context menu in this case. 83 *cmdState = ECS_HIDDEN; 84 return S_OK; 85 } 86 87 if (!CSettingsInstance().GetEnabled()) 88 { 89 *cmdState = ECS_HIDDEN; 90 return S_OK; 91 } 92 // Hide if the file is not an image 93 *cmdState = ECS_HIDDEN; 94 // Suppressing C26812 warning as the issue is in the shtypes.h library 95 #pragma warning(suppress : 26812) 96 PERCEIVED type; 97 PERCEIVEDFLAG flag; 98 IShellItem* shellItem=nullptr; 99 //Check extension of first item in the list (the item which is right-clicked on) 100 HRESULT getItemResult = selection->GetItemAt(0, &shellItem); 101 if (S_OK != getItemResult || nullptr == shellItem) { 102 // Some safeguards to avoid runtime errors. 103 *cmdState = ECS_HIDDEN; 104 return S_OK; 105 } 106 LPTSTR pszPath; 107 // Retrieves the entire file system path of the file from its shell item 108 HRESULT getDisplayResult = shellItem->GetDisplayName(SIGDN_FILESYSPATH, &pszPath); 109 if (S_OK != getDisplayResult || nullptr == pszPath) 110 { 111 // Avoid crashes in the following code. 112 return E_FAIL; 113 } 114 115 LPTSTR pszExt = PathFindExtension(pszPath); 116 if (nullptr == pszExt) 117 { 118 CoTaskMemFree(pszPath); 119 // Avoid crashes in the following code. 120 return E_FAIL; 121 } 122 123 // TODO: Instead, detect whether there's a WIC codec installed that can handle this file 124 AssocGetPerceivedType(pszExt, &type, &flag, NULL); 125 126 CoTaskMemFree(pszPath); 127 // If selected file is an image... 128 129 if (type == PERCEIVED_TYPE_IMAGE) 130 { 131 *cmdState = ECS_ENABLED; 132 } 133 return S_OK; 134 } 135 136 IFACEMETHODIMP Invoke(_In_opt_ IShellItemArray* selection, _In_opt_ IBindCtx*) noexcept 137 try 138 { 139 trace.UpdateState(true); 140 141 Trace::Invoked(); 142 HRESULT hr = S_OK; 143 144 if (selection) 145 { 146 hr = ResizePictures(selection); 147 } 148 149 Trace::InvokedRet(hr); 150 151 trace.UpdateState(false); 152 trace.Flush(); 153 154 return hr; 155 } 156 CATCH_RETURN(); 157 158 IFACEMETHODIMP GetFlags(_Out_ EXPCMDFLAGS* flags) 159 { 160 *flags = Flags(); 161 return S_OK; 162 } 163 IFACEMETHODIMP EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** enumCommands) 164 { 165 *enumCommands = nullptr; 166 return E_NOTIMPL; 167 } 168 169 // IObjectWithSite 170 IFACEMETHODIMP SetSite(_In_ IUnknown* site) noexcept 171 { 172 m_site = site; 173 return S_OK; 174 } 175 IFACEMETHODIMP GetSite(_In_ REFIID riid, _COM_Outptr_ void** site) noexcept { return m_site.CopyTo(riid, site); } 176 177 protected: 178 ComPtr<IUnknown> m_site; 179 180 private: 181 HRESULT StartNamedPipeServerAndSendData(std::wstring pipe_name) 182 { 183 hPipe = CreateNamedPipe( 184 pipe_name.c_str(), 185 PIPE_ACCESS_DUPLEX | 186 WRITE_DAC, 187 PIPE_TYPE_MESSAGE | 188 PIPE_READMODE_MESSAGE | 189 PIPE_WAIT, 190 PIPE_UNLIMITED_INSTANCES, 191 BUFSIZE, 192 BUFSIZE, 193 0, 194 NULL); 195 196 if (hPipe == NULL || hPipe == INVALID_HANDLE_VALUE) 197 { 198 return E_FAIL; 199 } 200 201 // This call blocks until a client process connects to the pipe 202 BOOL connected = ConnectNamedPipe(hPipe, NULL); 203 if (!connected) 204 { 205 if (GetLastError() == ERROR_PIPE_CONNECTED) 206 { 207 return S_OK; 208 } 209 else 210 { 211 CloseHandle(hPipe); 212 } 213 return E_FAIL; 214 } 215 216 return S_OK; 217 } 218 219 HRESULT ResizePictures(IShellItemArray* psiItemArray) 220 { 221 // Set the application path based on the location of the dll 222 std::wstring path = get_module_folderpath(g_hInst); 223 path = path + L"\\PowerToys.ImageResizer.exe"; 224 225 std::wstring pipe_name(L"\\\\.\\pipe\\powertoys_imageresizerinput_"); 226 UUID temp_uuid; 227 wchar_t* uuid_chars = nullptr; 228 if (UuidCreate(&temp_uuid) == RPC_S_UUID_NO_ADDRESS) 229 { 230 auto val = get_last_error_message(GetLastError()); 231 Logger::warn(L"UuidCreate cannot create guid. {}", val.has_value() ? val.value() : L""); 232 } 233 else if (UuidToString(&temp_uuid, reinterpret_cast<RPC_WSTR*>(& uuid_chars)) != RPC_S_OK) 234 { 235 auto val = get_last_error_message(GetLastError()); 236 Logger::warn(L"UuidToString cannot convert to string. {}", val.has_value() ? val.value() : L""); 237 } 238 239 if (uuid_chars != nullptr) 240 { 241 pipe_name += std::wstring(uuid_chars); 242 RpcStringFree(reinterpret_cast<RPC_WSTR*>(&uuid_chars)); 243 uuid_chars = nullptr; 244 } 245 create_pipe_thread = std::thread(&ImageResizerContextMenuCommand::StartNamedPipeServerAndSendData, this, pipe_name); 246 RunNonElevatedEx(path.c_str(), pipe_name, get_module_folderpath(g_hInst)); 247 create_pipe_thread.join(); 248 249 if (hPipe != INVALID_HANDLE_VALUE) 250 { 251 CAtlFile writePipe(hPipe); 252 253 //m_pdtobj will be NULL when invoked from the MSIX build as Initialize is never called (IShellExtInit functions aren't called in case of MSIX). 254 DWORD fileCount = 0; 255 // Gets the list of files currently selected using the IShellItemArray 256 psiItemArray->GetCount(&fileCount); 257 // Iterate over the list of files 258 for (DWORD i = 0; i < fileCount; i++) 259 { 260 IShellItem* shellItem; 261 HRESULT getItemAtResult = psiItemArray->GetItemAt(i, &shellItem); 262 if (SUCCEEDED(getItemAtResult)) 263 { 264 LPWSTR itemName; 265 // Retrieves the entire file system path of the file from its shell item 266 HRESULT getDisplayResult = shellItem->GetDisplayName(SIGDN_FILESYSPATH, &itemName); 267 if (SUCCEEDED(getDisplayResult)) 268 { 269 CString fileName(itemName); 270 fileName.Append(_T("\r\n")); 271 // Write the file path into the input stream for image resizer 272 writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR)); 273 } 274 } 275 } 276 writePipe.Close(); 277 } 278 279 return S_OK; 280 } 281 282 std::thread create_pipe_thread; 283 HANDLE hPipe = INVALID_HANDLE_VALUE; 284 std::wstring context_menu_caption = GET_RESOURCE_STRING_FALLBACK(IDS_IMAGERESIZER_CONTEXT_MENU_ENTRY, L"Resize with Image Resizer"); 285 }; 286 287 CoCreatableClass(ImageResizerContextMenuCommand) 288 CoCreatableClassWrlCreatorMapInclude(ImageResizerContextMenuCommand) 289 290 291 STDAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ IActivationFactory** factory) 292 { 293 return Module<ModuleType::InProc>::GetModule().GetActivationFactory(activatableClassId, factory); 294 } 295 296 STDAPI DllCanUnloadNow() 297 { 298 return Module<InProc>::GetModule().GetObjectCount() == 0 ? S_OK : S_FALSE; 299 } 300 301 STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _COM_Outptr_ void** instance) 302 { 303 return Module<InProc>::GetModule().GetClassObject(rclsid, riid, instance); 304 }