diff --git a/src/ioslaves/trash/kio_trash_win.cpp b/src/ioslaves/trash/kio_trash_win.cpp index 67e06543..b277f4e6 100644 --- a/src/ioslaves/trash/kio_trash_win.cpp +++ b/src/ioslaves/trash/kio_trash_win.cpp @@ -1,454 +1,461 @@ /* This file is part of the KDE project Copyright (C) 2004 David Faure Copyright (C) 2009 Christian Ehrlicher This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #define QT_NO_CAST_FROM_ASCII #include "kio_trash_win.h" #include "kiotrashdebug.h" #include "kioglobal_p.h" #include "kio/job.h" #include #include #include #include #include #include +// Pseudo plugin class to embed meta data +class KIOPluginForMetaData : public QObject +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.kde.kio.slave.trash" FILE "trash.json") +}; + extern "C" { int Q_DECL_EXPORT kdemain(int argc, char **argv) { bool bNeedsUninit = (CoInitializeEx(NULL, COINIT_MULTITHREADED) == S_OK); // necessary to use other kio slaves QCoreApplication app(argc, argv); // start the slave TrashProtocol slave(argv[1], argv[2], argv[3]); slave.dispatchLoop(); if (bNeedsUninit) { CoUninitialize(); } return 0; } } static const qint64 KDE_SECONDS_SINCE_1601 = 11644473600LL; static const qint64 KDE_USEC_IN_SEC = 1000000LL; static const int WM_SHELLNOTIFY = (WM_USER + 42); #ifndef SHCNRF_InterruptLevel static const int SHCNRF_InterruptLevel = 0x0001; static const int SHCNRF_ShellLevel = 0x0002; static const int SHCNRF_RecursiveInterrupt = 0x1000; #endif static inline time_t filetimeToTime_t(const FILETIME *time) { ULARGE_INTEGER i64; i64.LowPart = time->dwLowDateTime; i64.HighPart = time->dwHighDateTime; i64.QuadPart /= KDE_USEC_IN_SEC * 10; i64.QuadPart -= KDE_SECONDS_SINCE_1601; return i64.QuadPart; } LRESULT CALLBACK trash_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp) { if (message == WM_SHELLNOTIFY) { TrashProtocol *that = (TrashProtocol *)GetWindowLongPtr(hwnd, GWLP_USERDATA); that->updateRecycleBin(); } return DefWindowProc(hwnd, message, wp, lp); } TrashProtocol::TrashProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app) : SlaveBase(protocol, pool, app) , m_config(QString::fromLatin1("trashrc"), KConfig::SimpleConfig) { // create a hidden window to receive notifications through window messages const QString className = QLatin1String("TrashProtocol_Widget") + QString::number(quintptr(trash_internal_proc)); HINSTANCE hi = GetModuleHandle(nullptr); WNDCLASS wc; memset(&wc, 0, sizeof(WNDCLASS)); wc.lpfnWndProc = trash_internal_proc; wc.hInstance = hi; wc.lpszClassName = (LPCWSTR)className.utf16(); RegisterClass(&wc); m_notificationWindow = CreateWindow(wc.lpszClassName, // classname wc.lpszClassName, // window name 0, // style 0, 0, 0, 0, // geometry 0, // parent 0, // menu handle hi, // application 0); // windows creation data. SetWindowLongPtr(m_notificationWindow, GWLP_USERDATA, (LONG_PTR)this); // get trash IShellFolder object LPITEMIDLIST iilTrash; IShellFolder *isfDesktop; // we assume that this will always work - if not we've a bigger problem than a kio_trash crash... SHGetFolderLocation(NULL, CSIDL_BITBUCKET, 0, 0, &iilTrash); SHGetDesktopFolder(&isfDesktop); isfDesktop->BindToObject(iilTrash, NULL, IID_IShellFolder2, (void **)&m_isfTrashFolder); isfDesktop->Release(); SHGetMalloc(&m_pMalloc); // register for recycle bin notifications, have to do it for *every* single recycle bin #if 0 // TODO: this does not work for devices attached after this loop here... DWORD dwSize = GetLogicalDriveStrings(0, NULL); LPWSTR pszDrives = (LPWSTR)malloc((dwSize + 2) * sizeof(WCHAR)); #endif SHChangeNotifyEntry stPIDL; stPIDL.pidl = iilTrash; stPIDL.fRecursive = TRUE; m_hNotifyRBin = SHChangeNotifyRegister(m_notificationWindow, SHCNRF_InterruptLevel | SHCNRF_ShellLevel | SHCNRF_RecursiveInterrupt, SHCNE_ALLEVENTS, WM_SHELLNOTIFY, 1, &stPIDL); ILFree(iilTrash); updateRecycleBin(); } TrashProtocol::~TrashProtocol() { SHChangeNotifyDeregister(m_hNotifyRBin); const QString className = QLatin1String("TrashProtocol_Widget") + QString::number(quintptr(trash_internal_proc)); UnregisterClass((LPCWSTR)className.utf16(), GetModuleHandle(nullptr)); DestroyWindow(m_notificationWindow); if (m_pMalloc) { m_pMalloc->Release(); } if (m_isfTrashFolder) { m_isfTrashFolder->Release(); } } void TrashProtocol::restore(const QUrl &trashURL, const QUrl &destURL) { LPITEMIDLIST pidl = NULL; LPCONTEXTMENU pCtxMenu = NULL; const QString path = trashURL.path().mid(1).replace(QLatin1Char('/'), QLatin1Char('\\')); LPWSTR lpFile = (LPWSTR)path.utf16(); HRESULT res = m_isfTrashFolder->ParseDisplayName(0, 0, lpFile, 0, &pidl, 0); bool bOk = translateError(res); if (!bOk) { return; } res = m_isfTrashFolder->GetUIObjectOf(0, 1, (LPCITEMIDLIST *)&pidl, IID_IContextMenu, NULL, (LPVOID *)&pCtxMenu); bOk = translateError(res); if (!bOk) { return; } // this looks hacky but it's the only solution I found so far... HMENU hmenuCtx = CreatePopupMenu(); res = pCtxMenu->QueryContextMenu(hmenuCtx, 0, 1, 0x00007FFF, CMF_NORMAL); bOk = translateError(res); if (!bOk) { return; } UINT uiCommand = ~0U; char verb[MAX_PATH]; const int iMenuMax = GetMenuItemCount(hmenuCtx); for (int i = 0; i < iMenuMax; i++) { UINT uiID = GetMenuItemID(hmenuCtx, i) - 1; if ((uiID == -1) || (uiID == 0)) { continue; } res = pCtxMenu->GetCommandString(uiID, GCS_VERBA, NULL, verb, sizeof(verb)); if (FAILED(res)) { continue; } if (stricmp(verb, "undelete") == 0) { uiCommand = uiID; break; } } if (uiCommand != ~0U) { CMINVOKECOMMANDINFO cmi; memset(&cmi, 0, sizeof(CMINVOKECOMMANDINFO)); cmi.cbSize = sizeof(CMINVOKECOMMANDINFO); cmi.lpVerb = MAKEINTRESOURCEA(uiCommand); cmi.fMask = CMIC_MASK_FLAG_NO_UI; res = pCtxMenu->InvokeCommand((CMINVOKECOMMANDINFO *)&cmi); bOk = translateError(res); if (bOk) { finished(); } } DestroyMenu(hmenuCtx); pCtxMenu->Release(); ILFree(pidl); } void TrashProtocol::clearTrash() { translateError(SHEmptyRecycleBin(0, 0, 0)); finished(); } void TrashProtocol::rename(const QUrl &oldURL, const QUrl &newURL, KIO::JobFlags flags) { qCDebug(KIO_TRASH) << "TrashProtocol::rename(): old=" << oldURL << " new=" << newURL << " overwrite=" << (flags & KIO::Overwrite); if (oldURL.scheme() == QLatin1String("trash") && newURL.scheme() == QLatin1String("trash")) { error(KIO::ERR_CANNOT_RENAME, oldURL.toDisplayString()); return; } copyOrMove(oldURL, newURL, (flags & KIO::Overwrite), Move); } void TrashProtocol::copy(const QUrl &src, const QUrl &dest, int /*permissions*/, KIO::JobFlags flags) { qCDebug(KIO_TRASH) << "TrashProtocol::copy(): " << src << " " << dest; if (src.scheme() == QLatin1String("trash") && dest.scheme() == QLatin1String("trash")) { error(KIO::ERR_UNSUPPORTED_ACTION, i18n("This file is already in the trash bin.")); return; } copyOrMove(src, dest, (flags & KIO::Overwrite), Copy); } void TrashProtocol::copyOrMove(const QUrl &src, const QUrl &dest, bool overwrite, CopyOrMove action) { if (src.scheme() == QLatin1String("trash") && dest.isLocalFile()) { if (action == Move) { restore(src, dest); } else { error(KIO::ERR_UNSUPPORTED_ACTION, i18n("not supported")); } // Extracting (e.g. via dnd). Ignore original location stored in info file. return; } else if (src.isLocalFile() && dest.scheme() == QLatin1String("trash")) { UINT op = (action == Move) ? FO_DELETE : FO_COPY; if (!doFileOp(src, FO_DELETE, FOF_ALLOWUNDO)) { return; } finished(); return; } else { error(KIO::ERR_UNSUPPORTED_ACTION, i18n("Internal error in copyOrMove, should never happen")); } } void TrashProtocol::stat(const QUrl &url) { KIO::UDSEntry entry; if (url.path() == QLatin1String("/")) { STRRET strret; IShellFolder *isfDesktop; LPITEMIDLIST iilTrash; SHGetFolderLocation(NULL, CSIDL_BITBUCKET, 0, 0, &iilTrash); SHGetDesktopFolder(&isfDesktop); isfDesktop->BindToObject(iilTrash, NULL, IID_IShellFolder2, (void **)&m_isfTrashFolder); isfDesktop->GetDisplayNameOf(iilTrash, SHGDN_NORMAL, &strret); isfDesktop->Release(); ILFree(iilTrash); entry.insert(KIO::UDSEntry::UDS_NAME, QString::fromUtf16((const unsigned short *) strret.pOleStr)); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(KIO::UDSEntry::UDS_ACCESS, 0700); entry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("inode/directory")); m_pMalloc->Free(strret.pOleStr); } else { // TODO: when does this happen? } statEntry(entry); finished(); } void TrashProtocol::del(const QUrl &url, bool /*isfile*/) { if (!doFileOp(url, FO_DELETE, 0)) { return; } finished(); } void TrashProtocol::listDir(const QUrl &url) { qCDebug(KIO_TRASH) << "TrashProtocol::listDir(): " << url; // There are no subfolders in Windows Trash listRoot(); } void TrashProtocol::listRoot() { IEnumIDList *l; HRESULT res = m_isfTrashFolder->EnumObjects(0, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN, &l); if (res != S_OK) { return; } STRRET strret; SFGAOF attribs; KIO::UDSEntry entry; LPITEMIDLIST i; WIN32_FIND_DATAW findData; while (l->Next(1, &i, NULL) == S_OK) { m_isfTrashFolder->GetDisplayNameOf(i, SHGDN_NORMAL, &strret); entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, QString::fromUtf16((const unsigned short *)strret.pOleStr)); m_pMalloc->Free(strret.pOleStr); m_isfTrashFolder->GetDisplayNameOf(i, SHGDN_FORPARSING | SHGDN_INFOLDER, &strret); entry.insert(KIO::UDSEntry::UDS_NAME, QString::fromUtf16((const unsigned short *)strret.pOleStr)); m_pMalloc->Free(strret.pOleStr); m_isfTrashFolder->GetAttributesOf(1, (LPCITEMIDLIST *)&i, &attribs); SHGetDataFromIDList(m_isfTrashFolder, i, SHGDFIL_FINDDATA, &findData, sizeof(findData)); entry.insert(KIO::UDSEntry::UDS_SIZE, ((quint64)findData.nFileSizeLow) + (((quint64)findData.nFileSizeHigh) << 32)); entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, filetimeToTime_t(&findData.ftLastWriteTime)); entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, filetimeToTime_t(&findData.ftLastAccessTime)); entry.insert(KIO::UDSEntry::UDS_CREATION_TIME, filetimeToTime_t(&findData.ftCreationTime)); entry.insert(KIO::UDSEntry::UDS_EXTRA, QString::fromUtf16((const unsigned short *)strret.pOleStr)); entry.insert(KIO::UDSEntry::UDS_EXTRA + 1, QDateTime().toString(Qt::ISODate)); mode_t type = QT_STAT_REG; if ((attribs & SFGAO_FOLDER) == SFGAO_FOLDER) { type = QT_STAT_DIR; } if ((attribs & SFGAO_LINK) == SFGAO_LINK) { type = QT_STAT_LNK; } entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, type); mode_t access = 0700; if ((findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY) { type = 0300; } listEntry(entry, false); ILFree(i); } l->Release(); entry.clear(); listEntry(entry, true); finished(); } void TrashProtocol::special(const QByteArray &data) { QDataStream stream(data); int cmd; stream >> cmd; switch (cmd) { case 1: // empty trash folder clearTrash(); break; case 2: // convert old trash folder (non-windows only) finished(); break; case 3: { QUrl url; stream >> url; restore(url, QUrl()); break; } default: qCWarning(KIO_TRASH) << "Unknown command in special(): " << cmd; error(KIO::ERR_UNSUPPORTED_ACTION, QString::number(cmd)); break; } } void TrashProtocol::updateRecycleBin() { IEnumIDList *l; HRESULT res = m_isfTrashFolder->EnumObjects(0, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN, &l); if (res != S_OK) { return; } bool bEmpty = true; LPITEMIDLIST i; if (l->Next(1, &i, NULL) == S_OK) { bEmpty = false; ILFree(i); } KConfigGroup group = m_config.group("Status"); group.writeEntry("Empty", bEmpty); m_config.sync(); l->Release(); } void TrashProtocol::put(const QUrl &url, int /*permissions*/, KIO::JobFlags) { qCDebug(KIO_TRASH) << "put: " << url; // create deleted file. We need to get the mtime and original location from metadata... // Maybe we can find the info file for url.fileName(), in case ::rename() was called first, and failed... error(KIO::ERR_ACCESS_DENIED, url.toDisplayString()); } void TrashProtocol::get(const QUrl &url) { // TODO } bool TrashProtocol::doFileOp(const QUrl &url, UINT wFunc, FILEOP_FLAGS fFlags) { const QString path = url.path().replace(QLatin1Char('/'), QLatin1Char('\\')); // must be double-null terminated. QByteArray delBuf((path.length() + 2) * 2, 0); memcpy(delBuf.data(), path.utf16(), path.length() * 2); SHFILEOPSTRUCTW op; memset(&op, 0, sizeof(SHFILEOPSTRUCTW)); op.wFunc = wFunc; op.pFrom = (LPCWSTR)delBuf.constData(); op.fFlags = fFlags | FOF_NOCONFIRMATION | FOF_NOERRORUI; return translateError(SHFileOperationW(&op)); } bool TrashProtocol::translateError(HRESULT hRes) { // TODO! if (FAILED(hRes)) { error(KIO::ERR_DOES_NOT_EXIST, QLatin1String("fixme!")); return false; } return true; } -#include "moc_kio_trash_win.cpp" +#include "kio_trash_win.moc"