From 81b58282290ec920c01d0a150a9100a8c8badda7 Mon Sep 17 00:00:00 2001 From: ema Date: Wed, 22 Apr 2026 23:12:49 +0800 Subject: [PATCH] Add IDM support (Internet Download Manager) --- .../QuickLook.Native32/DllExport.cpp | 1 + QuickLook.Native/QuickLook.Native32/IDMan.cpp | 268 ++++++++++++++++++ QuickLook.Native/QuickLook.Native32/IDMan.h | 30 ++ .../QuickLook.Native32.vcxproj | 2 + .../QuickLook.Native32/Shell32.cpp | 11 + QuickLook.Native/QuickLook.Native32/Shell32.h | 1 + .../QuickLook.Native64.vcxproj | 1 + .../QuickLook.NativeArm64.vcxproj | 1 + QuickLook/NativeMethods/QuickLook.cs | 3 +- 9 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 QuickLook.Native/QuickLook.Native32/IDMan.cpp create mode 100644 QuickLook.Native/QuickLook.Native32/IDMan.h diff --git a/QuickLook.Native/QuickLook.Native32/DllExport.cpp b/QuickLook.Native/QuickLook.Native32/DllExport.cpp index 29c669b..aab249b 100644 --- a/QuickLook.Native/QuickLook.Native32/DllExport.cpp +++ b/QuickLook.Native/QuickLook.Native32/DllExport.cpp @@ -21,6 +21,7 @@ #include "WoW64HookHelper.h" #include "DOpus.h" #include "MultiCommander.h" +#include "IDMan.h" #define EXPORT extern "C" __declspec(dllexport) diff --git a/QuickLook.Native/QuickLook.Native32/IDMan.cpp b/QuickLook.Native/QuickLook.Native32/IDMan.cpp new file mode 100644 index 0000000..801996a --- /dev/null +++ b/QuickLook.Native/QuickLook.Native32/IDMan.cpp @@ -0,0 +1,268 @@ +// Copyright © 2017-2026 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "stdafx.h" +#include "IDMan.h" +#include + +#pragma comment(lib, "UIAutomationCore.lib") + +void IDMan::GetSelected(PWCHAR buffer) +{ + // Step 1: Get the selected item name from the IDM list via UIAutomation + WCHAR selectedName[MAX_PATH] = { L'\0' }; + if (!GetSelectedItemName(selectedName)) + return; + + // Step 2: Resolve the file path from the IDM registry + GetFilePath(selectedName, buffer); +} + +bool IDMan::GetSelectedItemName(PWCHAR nameBuffer) +{ + HWND hwnd = GetForegroundWindow(); + if (hwnd == nullptr) + return false; + + IUIAutomation* pAutomation = nullptr; + HRESULT hr = CoCreateInstance( + __uuidof(CUIAutomation8), + nullptr, + CLSCTX_INPROC_SERVER, + __uuidof(IUIAutomation), + reinterpret_cast(&pAutomation)); + + if (FAILED(hr)) + { + // Fallback to older interface + hr = CoCreateInstance( + __uuidof(CUIAutomation), + nullptr, + CLSCTX_INPROC_SERVER, + __uuidof(IUIAutomation), + reinterpret_cast(&pAutomation)); + if (FAILED(hr)) + return false; + } + + // Get UIAutomation element from the IDM window handle + IUIAutomationElement* pIDMWindow = nullptr; + hr = pAutomation->ElementFromHandle(hwnd, &pIDMWindow); + if (FAILED(hr) || pIDMWindow == nullptr) + { + pAutomation->Release(); + return false; + } + + // Build OR condition: DataGrid || List + IUIAutomationCondition* pDataGridCond = nullptr; + VARIANT varDataGrid; + VariantInit(&varDataGrid); + varDataGrid.vt = VT_I4; + varDataGrid.intVal = UIA_DataGridControlTypeId; + pAutomation->CreatePropertyCondition(UIA_ControlTypePropertyId, varDataGrid, &pDataGridCond); + + IUIAutomationCondition* pListCond = nullptr; + VARIANT varList; + VariantInit(&varList); + varList.vt = VT_I4; + varList.intVal = UIA_ListControlTypeId; + pAutomation->CreatePropertyCondition(UIA_ControlTypePropertyId, varList, &pListCond); + + IUIAutomationCondition* pOrCond = nullptr; + pAutomation->CreateOrCondition(pDataGridCond, pListCond, &pOrCond); + pDataGridCond->Release(); + pListCond->Release(); + + // Find the list view (DataGrid or List) inside the IDM window + IUIAutomationElement* pListView = nullptr; + pIDMWindow->FindFirst(TreeScope_Descendants, pOrCond, &pListView); + pOrCond->Release(); + pIDMWindow->Release(); + + if (pListView == nullptr) + { + pAutomation->Release(); + return false; + } + + // Enumerate all children and find the selected item + IUIAutomationCondition* pTrueCond = nullptr; + pAutomation->CreateTrueCondition(&pTrueCond); + + IUIAutomationElementArray* pChildren = nullptr; + pListView->FindAll(TreeScope_Children, pTrueCond, &pChildren); + pTrueCond->Release(); + pListView->Release(); + + if (pChildren == nullptr) + { + pAutomation->Release(); + return false; + } + + bool found = false; + int count = 0; + pChildren->get_Length(&count); + + for (int i = 0; i < count && !found; i++) + { + IUIAutomationElement* pItem = nullptr; + if (SUCCEEDED(pChildren->GetElement(i, &pItem)) && pItem != nullptr) + { + IUIAutomationSelectionItemPattern* pSelPattern = nullptr; + if (SUCCEEDED(pItem->GetCurrentPatternAs( + UIA_SelectionItemPatternId, + __uuidof(IUIAutomationSelectionItemPattern), + reinterpret_cast(&pSelPattern))) && + pSelPattern != nullptr) + { + BOOL isSelected = FALSE; + pSelPattern->get_CurrentIsSelected(&isSelected); + pSelPattern->Release(); + + if (isSelected) + { + BSTR name = nullptr; + if (SUCCEEDED(pItem->get_CurrentName(&name)) && name != nullptr) + { + wcscpy_s(nameBuffer, MAX_PATH, name); + SysFreeString(name); + found = true; + } + } + } + pItem->Release(); + } + } + + pChildren->Release(); + pAutomation->Release(); + return found; +} + +void IDMan::GetFilePath(PCWSTR name, PWCHAR buffer) +{ + // Extract the file extension (e.g. ".mp4") + WCHAR ext[64] = { L'\0' }; + const WCHAR* dot = wcsrchr(name, L'.'); + if (dot != nullptr) + wcscpy_s(ext, dot); + + // Read the default download path from LocalPathW (stored as raw Unicode bytes) + WCHAR defaultPath[MAX_PATH] = { L'\0' }; + { + HKEY hBase = nullptr; + if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\DownloadManager", 0, KEY_READ, &hBase) == ERROR_SUCCESS) + { + BYTE data[MAX_PATH * sizeof(WCHAR)] = {}; + DWORD dataSize = sizeof(data); + DWORD dataType = 0; + if (RegQueryValueExW(hBase, L"LocalPathW", nullptr, &dataType, data, &dataSize) == ERROR_SUCCESS) + { + if (dataType == REG_BINARY) + wcsncpy_s(defaultPath, MAX_PATH, reinterpret_cast(data), dataSize / sizeof(WCHAR)); + else if (dataType == REG_SZ) + wcsncpy_s(defaultPath, MAX_PATH, reinterpret_cast(data), MAX_PATH - 1); + } + RegCloseKey(hBase); + } + } + + // Try to match extension against FoldersTree entries + if (ext[0] != L'\0') + { + HKEY hFolders = nullptr; + if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\DownloadManager\\FoldersTree", 0, KEY_READ, &hFolders) == ERROR_SUCCESS) + { + WCHAR subKeyName[MAX_PATH] = { L'\0' }; + DWORD subKeyIdx = 0; + while (RegEnumKeyW(hFolders, subKeyIdx++, subKeyName, MAX_PATH) == ERROR_SUCCESS) + { + HKEY hSub = nullptr; + if (RegOpenKeyExW(hFolders, subKeyName, 0, KEY_READ, &hSub) == ERROR_SUCCESS) + { + WCHAR maskValue[512] = { L'\0' }; + DWORD maskSize = sizeof(maskValue); + if (RegQueryValueExW(hSub, L"mask", nullptr, nullptr, reinterpret_cast(maskValue), &maskSize) == ERROR_SUCCESS) + { + // maskValue is space-delimited list of extensions without dots (e.g. "mp4 mkv avi") + WCHAR* ctx = nullptr; + WCHAR* tok = wcstok_s(maskValue, L" ", &ctx); + while (tok != nullptr) + { + WCHAR tokExt[64] = L"."; + wcscat_s(tokExt, tok); + if (_wcsicmp(tokExt, ext) == 0) + { + // Compose: defaultPath\subKeyName\filename + swprintf_s(buffer, MAX_PATH_EX, L"%s\\%s\\%s", defaultPath, subKeyName, name); + RegCloseKey(hSub); + RegCloseKey(hFolders); + return; + } + tok = wcstok_s(nullptr, L" ", &ctx); + } + } + RegCloseKey(hSub); + } + } + RegCloseKey(hFolders); + } + + // Extension not in FoldersTree — use the default download folder + if (defaultPath[0] != L'\0') + { + swprintf_s(buffer, MAX_PATH_EX, L"%s\\%s", defaultPath, name); + return; + } + } + + // Fallback: search individual download entries in the registry for FR_FNCD match + HKEY hBase = nullptr; + if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\DownloadManager", 0, KEY_READ, &hBase) == ERROR_SUCCESS) + { + WCHAR subKeyName[MAX_PATH] = { L'\0' }; + DWORD subKeyIdx = 0; + while (RegEnumKeyW(hBase, subKeyIdx++, subKeyName, MAX_PATH) == ERROR_SUCCESS) + { + HKEY hSub = nullptr; + if (RegOpenKeyExW(hBase, subKeyName, 0, KEY_READ, &hSub) == ERROR_SUCCESS) + { + WCHAR frFncd[MAX_PATH] = { L'\0' }; + DWORD frSize = sizeof(frFncd); + if (RegQueryValueExW(hSub, L"FR_FNCD", nullptr, nullptr, reinterpret_cast(frFncd), &frSize) == ERROR_SUCCESS) + { + if (_wcsicmp(frFncd, name) == 0) + { + WCHAR localFileName[MAX_PATH_EX] = { L'\0' }; + DWORD lfSize = sizeof(localFileName); + if (RegQueryValueExW(hSub, L"LocalFileName", nullptr, nullptr, reinterpret_cast(localFileName), &lfSize) == ERROR_SUCCESS) + { + wcscpy_s(buffer, MAX_PATH_EX, localFileName); + RegCloseKey(hSub); + RegCloseKey(hBase); + return; + } + } + } + RegCloseKey(hSub); + } + } + RegCloseKey(hBase); + } +} diff --git a/QuickLook.Native/QuickLook.Native32/IDMan.h b/QuickLook.Native/QuickLook.Native32/IDMan.h new file mode 100644 index 0000000..529420b --- /dev/null +++ b/QuickLook.Native/QuickLook.Native32/IDMan.h @@ -0,0 +1,30 @@ +// Copyright © 2017-2026 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "stdafx.h" + +class IDMan +{ +public: + static void GetSelected(PWCHAR buffer); + +private: + static bool GetSelectedItemName(PWCHAR nameBuffer); + static void GetFilePath(PCWSTR name, PWCHAR buffer); +}; diff --git a/QuickLook.Native/QuickLook.Native32/QuickLook.Native32.vcxproj b/QuickLook.Native/QuickLook.Native32/QuickLook.Native32.vcxproj index b2484e9..96c595b 100644 --- a/QuickLook.Native/QuickLook.Native32/QuickLook.Native32.vcxproj +++ b/QuickLook.Native/QuickLook.Native32/QuickLook.Native32.vcxproj @@ -218,6 +218,7 @@ + @@ -251,6 +252,7 @@ + diff --git a/QuickLook.Native/QuickLook.Native32/Shell32.cpp b/QuickLook.Native/QuickLook.Native32/Shell32.cpp index dfbebd3..88b1e81 100644 --- a/QuickLook.Native/QuickLook.Native32/Shell32.cpp +++ b/QuickLook.Native/QuickLook.Native32/Shell32.cpp @@ -23,6 +23,7 @@ #include "Everything.h" #include "DOpus.h" #include "MultiCommander.h" +#include "IDMan.h" using namespace std; @@ -65,6 +66,13 @@ Shell32::FocusedWindowType Shell32::GetFocusedWindowType() } if (wcscmp(classBuffer, L"#32770") == 0) { + WCHAR titleBuffer[512] = { L'\0' }; + GetWindowText(hwndfg, titleBuffer, 512); + if (wcsncmp(titleBuffer, L"Internet Download Manager", 25) == 0) + { + return IDM; + } + if (FindWindowEx(hwndfg, nullptr, L"DUIViewWndClassName", nullptr) != nullptr) { if (!HelperMethods::IsExplorerSearchBoxFocused()) @@ -99,6 +107,9 @@ void Shell32::GetCurrentSelection(PWCHAR buffer) case MULTICOMMANDER: MultiCommander::GetSelected(buffer); break; + case IDM: + IDMan::GetSelected(buffer); + break; default: break; } diff --git a/QuickLook.Native/QuickLook.Native32/Shell32.h b/QuickLook.Native/QuickLook.Native32/Shell32.h index 82bf1d4..857bc83 100644 --- a/QuickLook.Native/QuickLook.Native32/Shell32.h +++ b/QuickLook.Native/QuickLook.Native32/Shell32.h @@ -31,6 +31,7 @@ public: EVERYTHING, DOPUS, MULTICOMMANDER, + IDM, }; static FocusedWindowType GetFocusedWindowType(); diff --git a/QuickLook.Native/QuickLook.Native64/QuickLook.Native64.vcxproj b/QuickLook.Native/QuickLook.Native64/QuickLook.Native64.vcxproj index b39735d..4398e41 100644 --- a/QuickLook.Native/QuickLook.Native64/QuickLook.Native64.vcxproj +++ b/QuickLook.Native/QuickLook.Native64/QuickLook.Native64.vcxproj @@ -52,6 +52,7 @@ + diff --git a/QuickLook.Native/QuickLook.NativeArm64/QuickLook.NativeArm64.vcxproj b/QuickLook.Native/QuickLook.NativeArm64/QuickLook.NativeArm64.vcxproj index 9f864e8..5f47059 100644 --- a/QuickLook.Native/QuickLook.NativeArm64/QuickLook.NativeArm64.vcxproj +++ b/QuickLook.Native/QuickLook.NativeArm64/QuickLook.NativeArm64.vcxproj @@ -38,6 +38,7 @@ + diff --git a/QuickLook/NativeMethods/QuickLook.cs b/QuickLook/NativeMethods/QuickLook.cs index 7e5a22b..614bd16 100644 --- a/QuickLook/NativeMethods/QuickLook.cs +++ b/QuickLook/NativeMethods/QuickLook.cs @@ -151,6 +151,7 @@ internal static class QuickLook Dialog, Everything, DOpus, - MultiCommander + MultiCommander, + IDM, } }