From 091e43d21ac735a4bbe6dcbf5f539656f2e19241 Mon Sep 17 00:00:00 2001 From: Antoine Beauchamp Date: Sun, 7 Jan 2024 17:34:16 -0500 Subject: [PATCH] Updated code for refresh_file_explorer.exe. Now getting paths from all File Explorer windows. #115. --- src/refresh_file_explorer/CMakeLists.txt | 4 + src/refresh_file_explorer/file_explorer.cpp | 208 ++++++++++++++++++++ src/refresh_file_explorer/file_explorer.h | 26 +++ src/refresh_file_explorer/main.cpp | 87 +++----- src/refresh_file_explorer/user.cpp | 55 ++++++ src/refresh_file_explorer/user.h | 19 ++ 6 files changed, 336 insertions(+), 63 deletions(-) create mode 100644 src/refresh_file_explorer/file_explorer.cpp create mode 100644 src/refresh_file_explorer/file_explorer.h create mode 100644 src/refresh_file_explorer/user.cpp create mode 100644 src/refresh_file_explorer/user.h diff --git a/src/refresh_file_explorer/CMakeLists.txt b/src/refresh_file_explorer/CMakeLists.txt index 3e02b105..a53995c8 100644 --- a/src/refresh_file_explorer/CMakeLists.txt +++ b/src/refresh_file_explorer/CMakeLists.txt @@ -4,7 +4,11 @@ add_executable(refresh_file_explorer WIN32 ${SHELLANYTHING_EXPORT_HEADER} ${SHELLANYTHING_VERSION_HEADER} ${SHELLANYTHING_CONFIG_HEADER} + file_explorer.cpp + file_explorer.h main.cpp + user.cpp + user.h ) # Force UNICODE for target diff --git a/src/refresh_file_explorer/file_explorer.cpp b/src/refresh_file_explorer/file_explorer.cpp new file mode 100644 index 00000000..171d2341 --- /dev/null +++ b/src/refresh_file_explorer/file_explorer.cpp @@ -0,0 +1,208 @@ +// https://learn.microsoft.com/en-us/cpp/cpp/how-to-create-and-use-ccomptr-and-ccomqiptr-instances?view=msvc-170 +// https://forums.codeguru.com/showthread.php?345012-Access-violation-using-CComPtr-lt-gt + +#include "file_explorer.h" +#include "user.h" + +#include +#include +#include +#include +#include + +#include "rapidassist/undef_windows_macros.h" +#include "rapidassist/unicode.h" +#include "rapidassist/errors.h" +#include "rapidassist/filesystem_utf8.h" +#include "rapidassist/environment_utf8.h" + +bool GetFileExplorerWindowPaths(Utf8FileList& files) +{ + files.clear(); + + CoInitialize(NULL); + + HRESULT hr; + wchar_t szPath[50 * MAX_PATH]; + DWORD size_in_characters = sizeof(szPath) / 2; + + CComPtr pShellWindows; + hr = pShellWindows.CoCreateInstance( + CLSID_ShellWindows, + NULL, + CLSCTX_ALL + ); + if (FAILED(hr)) + { + CoUninitialize(); + return false; + } + + // Browse shell windows one by one + bool has_more = true; + VARIANT index; + V_VT(&index) = VT_I4; + V_I4(&index) = 0; + while (has_more) + { + // Check for a shell window at index + IDispatch* pDisp; + hr = pShellWindows->Item(index, &pDisp); + if (hr == S_FALSE) + { + has_more = false; + } + else if (hr == S_OK) // Warning, do not check 'hr' with SUCCEEDED() macro since S_FALSE is considered a success. + { + // Handle IDispatch* graceful destruction + CComPtr pItem = pDisp; + + // Get IWebBrowserApp from Item + CComPtr pWebApp; + hr = pItem->QueryInterface(IID_PPV_ARGS(&pWebApp)); + if (SUCCEEDED(hr)) + { + // Get IServiceProvider from IWebBrowserApp + CComPtr pServiceProvider; + hr = pWebApp->QueryInterface(IID_PPV_ARGS(&pServiceProvider)); + if (SUCCEEDED(hr)) + { + // Get IShellBrowser from IServiceProvider + CComPtr pShellBrowser; + hr = pServiceProvider->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&pShellBrowser)); + if (SUCCEEDED(hr)) + { + IShellView* psv; + hr = pShellBrowser->QueryActiveShellView(&psv); + if (SUCCEEDED(hr)) + { + // Handle IShellView* graceful destruction + CComPtr pShellView = psv; + + // Get IFolderView from IShellView + CComPtr pFolderView; + hr = pShellView->QueryInterface(IID_PPV_ARGS(&pFolderView)); + if (SUCCEEDED(hr)) + { + IPersistFolder2* ppf2; + hr = pFolderView->GetFolder(IID_IPersistFolder2, (void**)&ppf2); + if (SUCCEEDED(hr)) + { + // Handle IPersistFolder2* graceful destruction + CComPtr pPersistFolder2 = ppf2; + + LPITEMIDLIST pidlFolder; + if (SUCCEEDED(ppf2->GetCurFolder(&pidlFolder))) + { + szPath[0] = '\0'; + if (SHGetPathFromIDListEx(pidlFolder, szPath, size_in_characters, GPFIDL_DEFAULT)) + { + // Convert from WIDE to UTF8 + std::string str_utf8 = ra::unicode::UnicodeToUtf8(szPath); + files.push_back(str_utf8); + } + else + { + //not a directory + } + CoTaskMemFree(pidlFolder); + } + } + } + } + } + } + } + } + + // Next Item + V_I4(&index)++; + } + + CoUninitialize(); + + return true; +} + +bool OpenFileExplorerWindow(const std::string& path) +{ + // Find the absolute path of explorer.exe + std::string explorer_path_utf8 = ra::filesystem::FindFileFromPathsUtf8("explorer.exe"); + if (explorer_path_utf8.empty()) + return false; + std::wstring explorer_path_wide = ra::unicode::Utf8ToUnicode(explorer_path_utf8); + const wchar_t * lpApplicationName = explorer_path_wide.c_str(); + + // Convert the given path to wide string + std::wstring path_wide = ra::unicode::Utf8ToUnicode(path); + + // Build a command line (which is including the path to the executable) + std::wstring command_line; + command_line += explorer_path_wide; + + // Add the given path as an argument + // Wrap the path in double quotes to handle spaces. + // And make a copy of the string for stupid WIN32 api... + command_line += L" "; + command_line += L"\""; + command_line += path_wide; + command_line += L"\""; + wchar_t * lpCommandLine = new wchar_t[command_line.size() + 1]; + StrCpyW(lpCommandLine, command_line.c_str()); + + //launch a new process with the command line + PROCESS_INFORMATION pi = { 0 }; + STARTUPINFOW si = { 0 }; + si.cb = sizeof(STARTUPINFO); + BOOL created = CreateProcessW( + lpApplicationName, // Using module name + lpCommandLine, // Command line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + 0, // No creation flags + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi // Pointer to PROCESS_INFORMATION structure + ); + if (created != 0) + { + //Wait for the application to initialize properly + WaitForInputIdle(pi.hProcess, INFINITE); + + //Extract the program id + DWORD process_id = pi.dwProcessId; + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + delete[] lpCommandLine; + return true; + } + + delete[] lpCommandLine; + return false; +} + +void TestOpenFolderUnicode() +{ + // Test: + const std::string test_path = "D:\\" "\xCE" "\xA8"; // psi + //const std::string test_path = "C:\\temp"; + bool opened = OpenFileExplorerWindow(test_path); + if (!opened) + { + ra::errors::errorcode_t error_code = ra::errors::GetLastErrorCode(); + std::string error_desc = ra::errors::GetErrorCodeDescription(error_code); + + std::wstring message; + message += L"Failed to open directory:\n"; + message += ra::unicode::Utf8ToUnicode(test_path); + message += L"\n"; + message += L"\n"; + message += ra::unicode::Utf8ToUnicode(error_desc); + + ShowErrorMessage(message); + } +} diff --git a/src/refresh_file_explorer/file_explorer.h b/src/refresh_file_explorer/file_explorer.h new file mode 100644 index 00000000..0ee862d5 --- /dev/null +++ b/src/refresh_file_explorer/file_explorer.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +typedef std::vector Utf8FileList; + +/// +/// Get the list of paths for all opened File Explorer windows. +/// The paths are returned in utf8 format. +/// +/// The output file path list in utf8 format. +/// Returns true when the function has succeeded. Returns false otherwise. +bool GetFileExplorerWindowPaths(Utf8FileList& files); + +/// +/// Open File Explorer to the given location. +/// +/// The path location in utf8 format. +/// Returns true when the function has succeeded. Returns false otherwise. +bool OpenFileExplorerWindow(const std::string& path); + +/// +/// Test to open directory unicode directory `D:\Ψ`. +/// +void TestOpenFolderUnicode(); diff --git a/src/refresh_file_explorer/main.cpp b/src/refresh_file_explorer/main.cpp index e9fbf655..31b35a16 100644 --- a/src/refresh_file_explorer/main.cpp +++ b/src/refresh_file_explorer/main.cpp @@ -1,74 +1,35 @@ #include +#include "file_explorer.h" +#include "user.h" -const TCHAR g_szClassName[] = L"myWindowClass"; - -// Step 4: the Window Procedure -LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) - { - case WM_CLOSE: - DestroyWindow(hwnd); - break; - case WM_DESTROY: - PostQuitMessage(0); - break; - default: - return DefWindowProc(hwnd, msg, wParam, lParam); - } - return 0; -} +#include "rapidassist/process_utf8.h" +#include "rapidassist/filesystem_utf8.h" +#include "rapidassist/unicode.h" +#include "rapidassist/errors.h" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { - WNDCLASSEX wc; - HWND hwnd; - MSG msg; - - //Step 1: Registering the Window Class - wc.cbSize = sizeof(WNDCLASSEX); - wc.style = 0; - wc.lpfnWndProc = WndProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - wc.hInstance = hInstance; - wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); - wc.lpszMenuName = NULL; - wc.lpszClassName = g_szClassName; - wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); - - if (!RegisterClassEx(&wc)) + // Get prompt confirmation + bool confirmed = GetUserConfirmation(); + if (!confirmed) + return 1; + + TestOpenFolderUnicode(); + return 9; + + // Get all the paths from File Explorer windows + Utf8FileList files; + bool success = GetFileExplorerWindowPaths(files); + if (!success) { - MessageBox(NULL, L"Window Registration Failed!", L"Error!", MB_ICONEXCLAMATION | MB_OK); - return 0; + std::wstring message; + message += L"ERROR!\n"; + message += L"Failed getting paths from File Explorer windows."; + ShowErrorMessage(message); + return 2; } - // Step 2: Creating the Window - hwnd = CreateWindowEx( - WS_EX_CLIENTEDGE, - g_szClassName, - L"The title of my window", - WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, - NULL, NULL, hInstance, NULL); - if (hwnd == NULL) - { - MessageBox(NULL, L"Window Creation Failed!", L"Error!", MB_ICONEXCLAMATION | MB_OK); - return 0; - } - - ShowWindow(hwnd, nCmdShow); - UpdateWindow(hwnd); - - // Step 3: The Message Loop - while (GetMessage(&msg, NULL, 0, 0) > 0) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - return (int)msg.wParam; + return 0; } diff --git a/src/refresh_file_explorer/user.cpp b/src/refresh_file_explorer/user.cpp new file mode 100644 index 00000000..6ca2af69 --- /dev/null +++ b/src/refresh_file_explorer/user.cpp @@ -0,0 +1,55 @@ +#include +#include "user.h" + +#include "rapidassist/process_utf8.h" +#include "rapidassist/filesystem_utf8.h" +#include "rapidassist/unicode.h" +#include "rapidassist/errors.h" + +void ShowErrorMessageUtf8(const std::string& message) +{ + std::wstring message_wide = ra::unicode::Utf8ToUnicode(message); + ShowErrorMessage(message_wide); +} + +void ShowErrorMessage(const std::wstring& message) +{ + std::string path = ra::process::GetCurrentProcessPathUtf8(); + std::string filename = ra::filesystem::GetFilenameWithoutExtension(path.c_str()); + std::wstring filename_wide = ra::unicode::Utf8ToUnicode(filename); + + std::wstring title; + title += filename_wide; + title += L" ERROR!"; + + HWND hWnd = GetForegroundWindow(); + MessageBoxW(hWnd, message.c_str(), title.c_str(), MB_OK | MB_ICONERROR); +} + +bool GetUserConfirmation() +{ + std::string path = ra::process::GetCurrentProcessPathUtf8(); + std::string filename = ra::filesystem::GetFilenameWithoutExtension(path.c_str()); + std::wstring filename_wide = ra::unicode::Utf8ToUnicode(filename); + + std::wstring title; + title += filename_wide; + + std::wstring message; + message += + L"The following application restart all File Explorer windows. " + L"Windows will be closed and then reopened to the same location. " + L"This restart cycle will release all unused DLL."; + message += L"\n\n"; + message += + L"This is required since File Explorer do not automatically release Shell Extensions DLL that have been unregistered from the system. " + L"A system reboot is usually required to release unregistered DLL."; + message += L"\n\n"; + message += L"Do you want to continue?"; + + HWND hWnd = GetForegroundWindow(); + int result = MessageBoxW(hWnd, message.c_str(), title.c_str(), MB_YESNO | MB_ICONQUESTION); + if (result == IDYES) + return true; + return false; +} diff --git a/src/refresh_file_explorer/user.h b/src/refresh_file_explorer/user.h new file mode 100644 index 00000000..ef2b05ca --- /dev/null +++ b/src/refresh_file_explorer/user.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +/// +/// Show an error message to the user. +/// +void ShowErrorMessageUtf8(const std::string& message); + +/// +/// Show an error message to the user. +/// +void ShowErrorMessage(const std::wstring& message); + +/// +/// Ask for the user's confirmation before proceeding. +/// +/// Returns true when the user has agreed to proceed. Returns false otherwise. +bool GetUserConfirmation();