From e79979ba40992d5ae8e12084f91c0d06f158a4df Mon Sep 17 00:00:00 2001 From: Volker Hilsheimer Date: Thu, 6 Feb 2020 10:17:52 +0100 Subject: [PATCH] On Windows 7, fall back to SHFileOperation to avoid confirmation dialogs Moving a file to the trash should preferably done via IFileOperation. However, the implementation on Windows 7 ignores the operation flags that request the shell not to show any confirmation dialogs or other UI elements. SHFileOperation is an old API that doesn't show any UI, but has the limitation that it doesn't report the location of the file in the trash after the move. So an application cannot restore the file, but the user can do so via Explorer. Overall, the better compromise is to not have dialogs at the expense of not being able to report the new path. This allows us to run the unit test on Windows 7 as well. Change-Id: Ib8e651a69e2c6750f668b52d2a70925d156cc8ae Fixes: QTBUG-81927 Reviewed-by: Vitaly Fanaskov --- src/corelib/io/qfilesystemengine_win.cpp | 136 +++++++++++----------- tests/auto/corelib/io/qfile/tst_qfile.cpp | 3 - 2 files changed, 71 insertions(+), 68 deletions(-) diff --git a/src/corelib/io/qfilesystemengine_win.cpp b/src/corelib/io/qfilesystemengine_win.cpp index 71a0e36693..36d43e9cb7 100644 --- a/src/corelib/io/qfilesystemengine_win.cpp +++ b/src/corelib/io/qfilesystemengine_win.cpp @@ -1546,73 +1546,79 @@ bool QFileSystemEngine::moveFileToTrash(const QFileSystemEntry &source, // we need the "display name" of the file, so can't use nativeFilePath const QString sourcePath = QDir::toNativeSeparators(source.filePath()); -# if defined(__IFileOperation_INTERFACE_DEFINED__) - CoInitialize(NULL); - IFileOperation *pfo = nullptr; - IShellItem *deleteItem = nullptr; - FileOperationProgressSink *sink = nullptr; - HRESULT hres = E_FAIL; - - auto coUninitialize = qScopeGuard([&](){ - if (sink) - sink->Release(); - if (deleteItem) - deleteItem->Release(); - if (pfo) - pfo->Release(); - CoUninitialize(); - if (!SUCCEEDED(hres)) - error = QSystemError(hres, QSystemError::NativeError); - }); - - hres = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pfo)); - if (!pfo) - return false; - pfo->SetOperationFlags(FOF_ALLOWUNDO | FOFX_RECYCLEONDELETE | FOF_NOCONFIRMATION - | FOF_SILENT | FOF_NOERRORUI); - hres = SHCreateItemFromParsingName(reinterpret_cast(sourcePath.utf16()), - nullptr, IID_PPV_ARGS(&deleteItem)); - if (!deleteItem) - return false; - sink = new FileOperationProgressSink; - hres = pfo->DeleteItem(deleteItem, static_cast(sink)); - if (!SUCCEEDED(hres)) - return false; - hres = pfo->PerformOperations(); - if (!SUCCEEDED(hres)) - return false; - newLocation = QFileSystemEntry(sink->targetPath); - -# else // no IFileOperation in SDK (mingw, likely) - fall back to SHFileOperation - - // double null termination needed, so can't use QString::utf16 - QVarLengthArray winFile(sourcePath.length() + 2); - sourcePath.toWCharArray(winFile.data()); - winFile[sourcePath.length()] = wchar_t{}; - winFile[sourcePath.length() + 1] = wchar_t{}; - - SHFILEOPSTRUCTW operation; - operation.hwnd = nullptr; - operation.wFunc = FO_DELETE; - operation.pFrom = winFile.constData(); - operation.pTo = nullptr; - operation.fFlags = FOF_ALLOWUNDO | FOF_NO_UI; - operation.fAnyOperationsAborted = FALSE; - operation.hNameMappings = nullptr; - operation.lpszProgressTitle = nullptr; - - int result = SHFileOperation(&operation); - if (result != 0) { - error = QSystemError(result, QSystemError::NativeError); - return false; - } /* - This implementation doesn't let us know where the file ended up, even if - we would specify FOF_WANTMAPPINGHANDLE | FOF_RENAMEONCOLLISION, as - FOF_RENAMEONCOLLISION has no effect unless files are moved, copied, or renamed. + Windows 7 insists on showing confirmation dialogs and ignores the respective + flags set on IFileOperation. Fall back to SHFileOperation, even if it doesn't + give us the new location of the file. */ - Q_UNUSED(newLocation); -# endif // IFileOperation fallback + if (QOperatingSystemVersion::current() > QOperatingSystemVersion::Windows7) { +# if defined(__IFileOperation_INTERFACE_DEFINED__) + CoInitialize(NULL); + IFileOperation *pfo = nullptr; + IShellItem *deleteItem = nullptr; + FileOperationProgressSink *sink = nullptr; + HRESULT hres = E_FAIL; + + auto coUninitialize = qScopeGuard([&](){ + if (sink) + sink->Release(); + if (deleteItem) + deleteItem->Release(); + if (pfo) + pfo->Release(); + CoUninitialize(); + if (!SUCCEEDED(hres)) + error = QSystemError(hres, QSystemError::NativeError); + }); + + hres = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pfo)); + if (!pfo) + return false; + pfo->SetOperationFlags(FOF_ALLOWUNDO | FOFX_RECYCLEONDELETE | FOF_NOCONFIRMATION + | FOF_SILENT | FOF_NOERRORUI); + hres = SHCreateItemFromParsingName(reinterpret_cast(sourcePath.utf16()), + nullptr, IID_PPV_ARGS(&deleteItem)); + if (!deleteItem) + return false; + sink = new FileOperationProgressSink; + hres = pfo->DeleteItem(deleteItem, static_cast(sink)); + if (!SUCCEEDED(hres)) + return false; + hres = pfo->PerformOperations(); + if (!SUCCEEDED(hres)) + return false; + newLocation = QFileSystemEntry(sink->targetPath); + +# endif // no IFileOperation in SDK (mingw, likely) - fall back to SHFileOperation + } else { + // double null termination needed, so can't use QString::utf16 + QVarLengthArray winFile(sourcePath.length() + 2); + sourcePath.toWCharArray(winFile.data()); + winFile[sourcePath.length()] = wchar_t{}; + winFile[sourcePath.length() + 1] = wchar_t{}; + + SHFILEOPSTRUCTW operation; + operation.hwnd = nullptr; + operation.wFunc = FO_DELETE; + operation.pFrom = winFile.constData(); + operation.pTo = nullptr; + operation.fFlags = FOF_ALLOWUNDO | FOF_NO_UI; + operation.fAnyOperationsAborted = FALSE; + operation.hNameMappings = nullptr; + operation.lpszProgressTitle = nullptr; + + int result = SHFileOperation(&operation); + if (result != 0) { + error = QSystemError(result, QSystemError::NativeError); + return false; + } + /* + This implementation doesn't let us know where the file ended up, even if + we would specify FOF_WANTMAPPINGHANDLE | FOF_RENAMEONCOLLISION, as + FOF_RENAMEONCOLLISION has no effect unless files are moved, copied, or renamed. + */ + Q_UNUSED(newLocation); + } return true; #else // Q_OS_WINRT diff --git a/tests/auto/corelib/io/qfile/tst_qfile.cpp b/tests/auto/corelib/io/qfile/tst_qfile.cpp index b065e1f10f..350bff0652 100644 --- a/tests/auto/corelib/io/qfile/tst_qfile.cpp +++ b/tests/auto/corelib/io/qfile/tst_qfile.cpp @@ -3681,9 +3681,6 @@ void tst_QFile::moveToTrash_data() QTest::addColumn("create"); QTest::addColumn("result"); - if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::Windows7) - QSKIP("Windows 7 insists on showing a confirmation dialog", SkipAll); - // success cases { QTemporaryFile temp;