File indexing completed on 2024-05-05 16:13:54
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2004 David Faure <faure@kde.org> 0004 SPDX-FileCopyrightText: 2009 Christian Ehrlicher <ch.ehrlicher@gmx.de> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "kio_trash_win.h" 0010 #include "kio/job.h" 0011 #include "kioglobal_p.h" 0012 #include "kiotrashdebug.h" 0013 0014 #include <QCoreApplication> 0015 #include <QDataStream> 0016 #include <QDateTime> 0017 0018 #include <KConfigGroup> 0019 #include <KLocalizedString> 0020 0021 #include <objbase.h> 0022 0023 // Pseudo plugin class to embed meta data 0024 class KIOPluginForMetaData : public QObject 0025 { 0026 Q_OBJECT 0027 Q_PLUGIN_METADATA(IID "org.kde.kio.slave.trash" FILE "trash.json") 0028 }; 0029 0030 extern "C" { 0031 int Q_DECL_EXPORT kdemain(int argc, char **argv) 0032 { 0033 bool bNeedsUninit = (CoInitializeEx(NULL, COINIT_MULTITHREADED) == S_OK); 0034 // necessary to use other kio slaves 0035 QCoreApplication app(argc, argv); 0036 0037 // start the slave 0038 TrashProtocol slave(argv[1], argv[2], argv[3]); 0039 slave.dispatchLoop(); 0040 0041 if (bNeedsUninit) { 0042 CoUninitialize(); 0043 } 0044 return 0; 0045 } 0046 } 0047 0048 static const qint64 KDE_SECONDS_SINCE_1601 = 11644473600LL; 0049 static const qint64 KDE_USEC_IN_SEC = 1000000LL; 0050 static const int WM_SHELLNOTIFY = (WM_USER + 42); 0051 #ifndef SHCNRF_InterruptLevel 0052 static const int SHCNRF_InterruptLevel = 0x0001; 0053 static const int SHCNRF_ShellLevel = 0x0002; 0054 static const int SHCNRF_RecursiveInterrupt = 0x1000; 0055 #endif 0056 0057 static inline time_t filetimeToTime_t(const FILETIME *time) 0058 { 0059 ULARGE_INTEGER i64; 0060 i64.LowPart = time->dwLowDateTime; 0061 i64.HighPart = time->dwHighDateTime; 0062 i64.QuadPart /= KDE_USEC_IN_SEC * 10; 0063 i64.QuadPart -= KDE_SECONDS_SINCE_1601; 0064 return i64.QuadPart; 0065 } 0066 0067 LRESULT CALLBACK trash_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp) 0068 { 0069 if (message == WM_SHELLNOTIFY) { 0070 TrashProtocol *that = (TrashProtocol *)GetWindowLongPtr(hwnd, GWLP_USERDATA); 0071 that->updateRecycleBin(); 0072 } 0073 return DefWindowProc(hwnd, message, wp, lp); 0074 } 0075 0076 TrashProtocol::TrashProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app) 0077 : SlaveBase(protocol, pool, app) 0078 , m_config(QString::fromLatin1("trashrc"), KConfig::SimpleConfig) 0079 { 0080 // create a hidden window to receive notifications through window messages 0081 const QString className = QLatin1String("TrashProtocol_Widget") + QString::number(quintptr(trash_internal_proc)); 0082 HINSTANCE hi = GetModuleHandle(nullptr); 0083 WNDCLASS wc; 0084 memset(&wc, 0, sizeof(WNDCLASS)); 0085 wc.lpfnWndProc = trash_internal_proc; 0086 wc.hInstance = hi; 0087 wc.lpszClassName = (LPCWSTR)className.utf16(); 0088 RegisterClass(&wc); 0089 m_notificationWindow = CreateWindow(wc.lpszClassName, // classname 0090 wc.lpszClassName, // window name 0091 0, // style 0092 0, 0093 0, 0094 0, 0095 0, // geometry 0096 0, // parent 0097 0, // menu handle 0098 hi, // application 0099 0); // windows creation data. 0100 SetWindowLongPtr(m_notificationWindow, GWLP_USERDATA, (LONG_PTR)this); 0101 0102 // get trash IShellFolder object 0103 LPITEMIDLIST iilTrash; 0104 IShellFolder *isfDesktop; 0105 // we assume that this will always work - if not we've a bigger problem than a kio_trash crash... 0106 SHGetFolderLocation(NULL, CSIDL_BITBUCKET, 0, 0, &iilTrash); 0107 SHGetDesktopFolder(&isfDesktop); 0108 isfDesktop->BindToObject(iilTrash, NULL, IID_IShellFolder2, (void **)&m_isfTrashFolder); 0109 isfDesktop->Release(); 0110 SHGetMalloc(&m_pMalloc); 0111 0112 // register for recycle bin notifications, have to do it for *every* single recycle bin 0113 #if 0 0114 // TODO: this does not work for devices attached after this loop here... 0115 DWORD dwSize = GetLogicalDriveStrings(0, NULL); 0116 LPWSTR pszDrives = (LPWSTR)malloc((dwSize + 2) * sizeof(WCHAR)); 0117 #endif 0118 0119 SHChangeNotifyEntry stPIDL; 0120 stPIDL.pidl = iilTrash; 0121 stPIDL.fRecursive = TRUE; 0122 m_hNotifyRBin = SHChangeNotifyRegister(m_notificationWindow, 0123 SHCNRF_InterruptLevel | SHCNRF_ShellLevel | SHCNRF_RecursiveInterrupt, 0124 SHCNE_ALLEVENTS, 0125 WM_SHELLNOTIFY, 0126 1, 0127 &stPIDL); 0128 0129 ILFree(iilTrash); 0130 0131 updateRecycleBin(); 0132 } 0133 0134 TrashProtocol::~TrashProtocol() 0135 { 0136 SHChangeNotifyDeregister(m_hNotifyRBin); 0137 const QString className = QLatin1String("TrashProtocol_Widget") + QString::number(quintptr(trash_internal_proc)); 0138 UnregisterClass((LPCWSTR)className.utf16(), GetModuleHandle(nullptr)); 0139 DestroyWindow(m_notificationWindow); 0140 0141 if (m_pMalloc) { 0142 m_pMalloc->Release(); 0143 } 0144 if (m_isfTrashFolder) { 0145 m_isfTrashFolder->Release(); 0146 } 0147 } 0148 0149 void TrashProtocol::restore(const QUrl &trashURL, const QUrl &destURL) 0150 { 0151 LPITEMIDLIST pidl = NULL; 0152 LPCONTEXTMENU pCtxMenu = NULL; 0153 0154 const QString path = trashURL.path().mid(1).replace(QLatin1Char('/'), QLatin1Char('\\')); 0155 LPWSTR lpFile = (LPWSTR)path.utf16(); 0156 HRESULT res = m_isfTrashFolder->ParseDisplayName(0, 0, lpFile, 0, &pidl, 0); 0157 bool bOk = translateError(res); 0158 if (!bOk) { 0159 return; 0160 } 0161 0162 res = m_isfTrashFolder->GetUIObjectOf(0, 1, (LPCITEMIDLIST *)&pidl, IID_IContextMenu, NULL, (LPVOID *)&pCtxMenu); 0163 bOk = translateError(res); 0164 if (!bOk) { 0165 return; 0166 } 0167 0168 // this looks hacky but it's the only solution I found so far... 0169 HMENU hmenuCtx = CreatePopupMenu(); 0170 res = pCtxMenu->QueryContextMenu(hmenuCtx, 0, 1, 0x00007FFF, CMF_NORMAL); 0171 bOk = translateError(res); 0172 if (!bOk) { 0173 return; 0174 } 0175 0176 UINT uiCommand = ~0U; 0177 char verb[MAX_PATH]; 0178 const int iMenuMax = GetMenuItemCount(hmenuCtx); 0179 for (int i = 0; i < iMenuMax; i++) { 0180 UINT uiID = GetMenuItemID(hmenuCtx, i) - 1; 0181 if ((uiID == -1) || (uiID == 0)) { 0182 continue; 0183 } 0184 res = pCtxMenu->GetCommandString(uiID, GCS_VERBA, NULL, verb, sizeof(verb)); 0185 if (FAILED(res)) { 0186 continue; 0187 } 0188 if (stricmp(verb, "undelete") == 0) { 0189 uiCommand = uiID; 0190 break; 0191 } 0192 } 0193 if (uiCommand != ~0U) { 0194 CMINVOKECOMMANDINFO cmi; 0195 0196 memset(&cmi, 0, sizeof(CMINVOKECOMMANDINFO)); 0197 cmi.cbSize = sizeof(CMINVOKECOMMANDINFO); 0198 cmi.lpVerb = MAKEINTRESOURCEA(uiCommand); 0199 cmi.fMask = CMIC_MASK_FLAG_NO_UI; 0200 res = pCtxMenu->InvokeCommand((CMINVOKECOMMANDINFO *)&cmi); 0201 0202 bOk = translateError(res); 0203 if (bOk) { 0204 finished(); 0205 } 0206 } 0207 DestroyMenu(hmenuCtx); 0208 pCtxMenu->Release(); 0209 ILFree(pidl); 0210 } 0211 0212 void TrashProtocol::clearTrash() 0213 { 0214 translateError(SHEmptyRecycleBin(0, 0, 0)); 0215 finished(); 0216 } 0217 0218 void TrashProtocol::rename(const QUrl &oldURL, const QUrl &newURL, KIO::JobFlags flags) 0219 { 0220 qCDebug(KIO_TRASH) << "TrashProtocol::rename(): old=" << oldURL << " new=" << newURL << " overwrite=" << (flags & KIO::Overwrite); 0221 0222 if (oldURL.scheme() == QLatin1String("trash") && newURL.scheme() == QLatin1String("trash")) { 0223 error(KIO::ERR_CANNOT_RENAME, oldURL.toDisplayString()); 0224 return; 0225 } 0226 0227 copyOrMove(oldURL, newURL, (flags & KIO::Overwrite), Move); 0228 } 0229 0230 void TrashProtocol::copy(const QUrl &src, const QUrl &dest, int /*permissions*/, KIO::JobFlags flags) 0231 { 0232 qCDebug(KIO_TRASH) << "TrashProtocol::copy(): " << src << " " << dest; 0233 0234 if (src.scheme() == QLatin1String("trash") && dest.scheme() == QLatin1String("trash")) { 0235 error(KIO::ERR_UNSUPPORTED_ACTION, i18n("This file is already in the trash bin.")); 0236 return; 0237 } 0238 0239 copyOrMove(src, dest, (flags & KIO::Overwrite), Copy); 0240 } 0241 0242 void TrashProtocol::copyOrMove(const QUrl &src, const QUrl &dest, bool overwrite, CopyOrMove action) 0243 { 0244 if (src.scheme() == QLatin1String("trash") && dest.isLocalFile()) { 0245 if (action == Move) { 0246 restore(src, dest); 0247 } else { 0248 error(KIO::ERR_UNSUPPORTED_ACTION, i18n("not supported")); 0249 } 0250 // Extracting (e.g. via dnd). Ignore original location stored in info file. 0251 return; 0252 } else if (src.isLocalFile() && dest.scheme() == QLatin1String("trash")) { 0253 UINT op = (action == Move) ? FO_DELETE : FO_COPY; 0254 if (!doFileOp(src, FO_DELETE, FOF_ALLOWUNDO)) { 0255 return; 0256 } 0257 finished(); 0258 return; 0259 } else { 0260 error(KIO::ERR_UNSUPPORTED_ACTION, i18n("Internal error in copyOrMove, should never happen")); 0261 } 0262 } 0263 0264 void TrashProtocol::stat(const QUrl &url) 0265 { 0266 KIO::UDSEntry entry; 0267 if (url.path() == QLatin1String("/")) { 0268 STRRET strret; 0269 IShellFolder *isfDesktop; 0270 LPITEMIDLIST iilTrash; 0271 0272 SHGetFolderLocation(NULL, CSIDL_BITBUCKET, 0, 0, &iilTrash); 0273 SHGetDesktopFolder(&isfDesktop); 0274 isfDesktop->BindToObject(iilTrash, NULL, IID_IShellFolder2, (void **)&m_isfTrashFolder); 0275 isfDesktop->GetDisplayNameOf(iilTrash, SHGDN_NORMAL, &strret); 0276 isfDesktop->Release(); 0277 ILFree(iilTrash); 0278 0279 entry.fastInsert(KIO::UDSEntry::UDS_NAME, QString::fromUtf16((const unsigned short *)strret.pOleStr)); 0280 entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); 0281 entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0700); 0282 entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("inode/directory")); 0283 m_pMalloc->Free(strret.pOleStr); 0284 } else { 0285 // TODO: when does this happen? 0286 } 0287 statEntry(entry); 0288 finished(); 0289 } 0290 0291 void TrashProtocol::del(const QUrl &url, bool /*isfile*/) 0292 { 0293 if (!doFileOp(url, FO_DELETE, 0)) { 0294 return; 0295 } 0296 finished(); 0297 } 0298 0299 void TrashProtocol::listDir(const QUrl &url) 0300 { 0301 qCDebug(KIO_TRASH) << "TrashProtocol::listDir(): " << url; 0302 // There are no subfolders in Windows Trash 0303 listRoot(); 0304 } 0305 0306 void TrashProtocol::listRoot() 0307 { 0308 IEnumIDList *l; 0309 HRESULT res = m_isfTrashFolder->EnumObjects(0, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN, &l); 0310 if (res != S_OK) { 0311 return; 0312 } 0313 0314 STRRET strret; 0315 SFGAOF attribs; 0316 KIO::UDSEntry entry; 0317 LPITEMIDLIST i; 0318 WIN32_FIND_DATAW findData; 0319 while (l->Next(1, &i, NULL) == S_OK) { 0320 m_isfTrashFolder->GetDisplayNameOf(i, SHGDN_NORMAL, &strret); 0321 entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, QString::fromUtf16((const unsigned short *)strret.pOleStr)); 0322 m_pMalloc->Free(strret.pOleStr); 0323 m_isfTrashFolder->GetDisplayNameOf(i, SHGDN_FORPARSING | SHGDN_INFOLDER, &strret); 0324 entry.fastInsert(KIO::UDSEntry::UDS_NAME, QString::fromUtf16((const unsigned short *)strret.pOleStr)); 0325 m_pMalloc->Free(strret.pOleStr); 0326 m_isfTrashFolder->GetAttributesOf(1, (LPCITEMIDLIST *)&i, &attribs); 0327 SHGetDataFromIDList(m_isfTrashFolder, i, SHGDFIL_FINDDATA, &findData, sizeof(findData)); 0328 entry.fastInsert(KIO::UDSEntry::UDS_SIZE, ((quint64)findData.nFileSizeLow) + (((quint64)findData.nFileSizeHigh) << 32)); 0329 entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, filetimeToTime_t(&findData.ftLastWriteTime)); 0330 entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, filetimeToTime_t(&findData.ftLastAccessTime)); 0331 entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, filetimeToTime_t(&findData.ftCreationTime)); 0332 entry.fastInsert(KIO::UDSEntry::UDS_EXTRA, QString::fromUtf16((const unsigned short *)strret.pOleStr)); 0333 entry.fastInsert(KIO::UDSEntry::UDS_EXTRA + 1, QDateTime().toString(Qt::ISODate)); 0334 mode_t type = QT_STAT_REG; 0335 if ((attribs & SFGAO_FOLDER) == SFGAO_FOLDER) { 0336 type = QT_STAT_DIR; 0337 } 0338 if ((attribs & SFGAO_LINK) == SFGAO_LINK) { 0339 type = QT_STAT_LNK; 0340 } 0341 entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, type); 0342 mode_t access = 0700; 0343 if ((findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY) { 0344 type = 0300; 0345 } 0346 listEntry(entry); 0347 0348 ILFree(i); 0349 } 0350 l->Release(); 0351 finished(); 0352 } 0353 0354 void TrashProtocol::special(const QByteArray &data) 0355 { 0356 QDataStream stream(data); 0357 int cmd; 0358 stream >> cmd; 0359 0360 switch (cmd) { 0361 case 1: 0362 // empty trash folder 0363 clearTrash(); 0364 break; 0365 case 2: 0366 // convert old trash folder (non-windows only) 0367 finished(); 0368 break; 0369 case 3: { 0370 QUrl url; 0371 stream >> url; 0372 restore(url, QUrl()); 0373 break; 0374 } 0375 default: 0376 qCWarning(KIO_TRASH) << "Unknown command in special(): " << cmd; 0377 error(KIO::ERR_UNSUPPORTED_ACTION, QString::number(cmd)); 0378 break; 0379 } 0380 } 0381 0382 void TrashProtocol::updateRecycleBin() 0383 { 0384 IEnumIDList *l; 0385 HRESULT res = m_isfTrashFolder->EnumObjects(0, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN, &l); 0386 if (res != S_OK) { 0387 return; 0388 } 0389 0390 bool bEmpty = true; 0391 LPITEMIDLIST i; 0392 if (l->Next(1, &i, NULL) == S_OK) { 0393 bEmpty = false; 0394 ILFree(i); 0395 } 0396 KConfigGroup group = m_config.group("Status"); 0397 group.writeEntry("Empty", bEmpty); 0398 m_config.sync(); 0399 l->Release(); 0400 } 0401 0402 void TrashProtocol::put(const QUrl &url, int /*permissions*/, KIO::JobFlags) 0403 { 0404 qCDebug(KIO_TRASH) << "put: " << url; 0405 // create deleted file. We need to get the mtime and original location from metadata... 0406 // Maybe we can find the info file for url.fileName(), in case ::rename() was called first, and failed... 0407 error(KIO::ERR_ACCESS_DENIED, url.toDisplayString()); 0408 } 0409 0410 void TrashProtocol::get(const QUrl &url) 0411 { 0412 // TODO 0413 } 0414 0415 bool TrashProtocol::doFileOp(const QUrl &url, UINT wFunc, FILEOP_FLAGS fFlags) 0416 { 0417 const QString path = url.path().replace(QLatin1Char('/'), QLatin1Char('\\')); 0418 // must be double-null terminated. 0419 QByteArray delBuf((path.length() + 2) * 2, 0); 0420 memcpy(delBuf.data(), path.utf16(), path.length() * 2); 0421 0422 SHFILEOPSTRUCTW op; 0423 memset(&op, 0, sizeof(SHFILEOPSTRUCTW)); 0424 op.wFunc = wFunc; 0425 op.pFrom = (LPCWSTR)delBuf.constData(); 0426 op.fFlags = fFlags | FOF_NOCONFIRMATION | FOF_NOERRORUI; 0427 return translateError(SHFileOperationW(&op)); 0428 } 0429 0430 bool TrashProtocol::translateError(HRESULT hRes) 0431 { 0432 // TODO! 0433 if (FAILED(hRes)) { 0434 error(KIO::ERR_DOES_NOT_EXIST, QLatin1String("fixme!")); 0435 return false; 0436 } 0437 return true; 0438 } 0439 0440 #include "kio_trash_win.moc" 0441 0442 #include "moc_kio_trash_win.cpp"