| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "platform/win/file_utilities_win.h"
- #include "mainwindow.h"
- #include "storage/localstorage.h"
- #include "platform/win/windows_dlls.h"
- #include "lang/lang_keys.h"
- #include "core/application.h"
- #include "core/crash_reports.h"
- #include "window/window_controller.h"
- #include "ui/ui_utility.h"
- #include <QtWidgets/QFileDialog>
- #include <QtGui/QDesktopServices>
- #include <QtCore/QSettings>
- #include <QtCore/QStandardPaths>
- #include <Shlwapi.h>
- #include <Windowsx.h>
- HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &, int hbitmapFormat);
- namespace Platform {
- namespace File {
- namespace {
- class OpenWithApp {
- public:
- OpenWithApp(const QString &name, IAssocHandler *handler, HBITMAP icon = nullptr)
- : _name(name)
- , _handler(handler)
- , _icon(icon) {
- }
- OpenWithApp(OpenWithApp &&other)
- : _name(base::take(other._name))
- , _handler(base::take(other._handler))
- , _icon(base::take(other._icon)) {
- }
- OpenWithApp &operator=(OpenWithApp &&other) {
- _name = base::take(other._name);
- _icon = base::take(other._icon);
- _handler = base::take(other._handler);
- return (*this);
- }
- OpenWithApp(const OpenWithApp &other) = delete;
- OpenWithApp &operator=(const OpenWithApp &other) = delete;
- ~OpenWithApp() {
- if (_icon) {
- DeleteBitmap(_icon);
- }
- if (_handler) {
- _handler->Release();
- }
- }
- const QString &name() const {
- return _name;
- }
- HBITMAP icon() const {
- return _icon;
- }
- IAssocHandler *handler() const {
- return _handler;
- }
- private:
- QString _name;
- IAssocHandler *_handler = nullptr;
- HBITMAP _icon = nullptr;
- };
- HBITMAP IconToBitmap(LPWSTR icon, int iconindex) {
- if (!icon) return 0;
- WCHAR tmpIcon[4096];
- if (icon[0] == L'@' && SUCCEEDED(SHLoadIndirectString(icon, tmpIcon, 4096, 0))) {
- icon = tmpIcon;
- }
- int32 w = GetSystemMetrics(SM_CXSMICON), h = GetSystemMetrics(SM_CYSMICON);
- HICON ico = ExtractIcon(0, icon, iconindex);
- if (!ico) {
- if (!iconindex) { // try to read image
- QImage img(QString::fromWCharArray(icon));
- if (!img.isNull()) {
- return qt_pixmapToWinHBITMAP(
- Ui::PixmapFromImage(
- img.scaled(
- w,
- h,
- Qt::IgnoreAspectRatio,
- Qt::SmoothTransformation)),
- /* HBitmapAlpha */ 2);
- }
- }
- return 0;
- }
- HDC screenDC = GetDC(0), hdc = CreateCompatibleDC(screenDC);
- HBITMAP result = CreateCompatibleBitmap(screenDC, w, h);
- HGDIOBJ was = SelectObject(hdc, result);
- DrawIconEx(hdc, 0, 0, ico, w, h, 0, NULL, DI_NORMAL);
- SelectObject(hdc, was);
- DeleteDC(hdc);
- ReleaseDC(0, screenDC);
- DestroyIcon(ico);
- return (HBITMAP)CopyImage(result, IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_CREATEDIBSECTION);
- }
- bool ShouldSaveZoneInformation() {
- // Check if the "Do not preserve zone information in file attachments" policy is enabled.
- const auto keyName = L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Attachments";
- const auto valueName = L"SaveZoneInformation";
- auto key = HKEY();
- auto result = RegOpenKeyEx(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &key);
- if (result != ERROR_SUCCESS) {
- // If the registry key cannot be opened, assume the default behavior:
- // Windows preserves zone information for downloaded files.
- return true;
- }
- DWORD value = 0, type = 0, size = sizeof(value);
- result = RegQueryValueEx(key, valueName, 0, &type, (LPBYTE)&value, &size);
- RegCloseKey(key);
- if (result != ERROR_SUCCESS || type != REG_DWORD) {
- return true;
- }
- return (value != 1);
- }
- } // namespace
- void UnsafeOpenEmailLink(const QString &email) {
- auto url = QUrl(qstr("mailto:") + email);
- if (!QDesktopServices::openUrl(url)) {
- auto wstringUrl = url.toString(QUrl::FullyEncoded).toStdWString();
- if (Dlls::SHOpenWithDialog) {
- OPENASINFO info;
- info.oaifInFlags = OAIF_ALLOW_REGISTRATION
- | OAIF_REGISTER_EXT
- | OAIF_EXEC
- #if WINVER >= 0x0602
- | OAIF_FILE_IS_URI
- #endif // WINVER >= 0x602
- | OAIF_URL_PROTOCOL;
- info.pcszClass = NULL;
- info.pcszFile = wstringUrl.c_str();
- Dlls::SHOpenWithDialog(0, &info);
- } else if (Dlls::OpenAs_RunDLL) {
- Dlls::OpenAs_RunDLL(0, 0, wstringUrl.c_str(), SW_SHOWNORMAL);
- } else {
- ShellExecute(0, L"open", wstringUrl.c_str(), 0, 0, SW_SHOWNORMAL);
- }
- }
- }
- bool UnsafeShowOpenWithDropdown(const QString &filepath) {
- if (!Dlls::SHAssocEnumHandlers || !Dlls::SHCreateItemFromParsingName) {
- return false;
- }
- auto window = Core::App().activeWindow();
- if (!window) {
- return false;
- }
- auto parentHWND = window->widget()->psHwnd();
- auto wstringPath = QDir::toNativeSeparators(filepath).toStdWString();
- auto result = false;
- std::vector<OpenWithApp> handlers;
- IShellItem* pItem = nullptr;
- if (SUCCEEDED(Dlls::SHCreateItemFromParsingName(wstringPath.c_str(), nullptr, IID_PPV_ARGS(&pItem)))) {
- IEnumAssocHandlers *assocHandlers = nullptr;
- if (SUCCEEDED(pItem->BindToHandler(nullptr, BHID_EnumAssocHandlers, IID_PPV_ARGS(&assocHandlers)))) {
- HRESULT hr = S_FALSE;
- do {
- IAssocHandler *handler = nullptr;
- ULONG ulFetched = 0;
- hr = assocHandlers->Next(1, &handler, &ulFetched);
- if (FAILED(hr) || hr == S_FALSE || !ulFetched) break;
- LPWSTR name = 0;
- if (SUCCEEDED(handler->GetUIName(&name))) {
- LPWSTR icon = 0;
- int iconindex = 0;
- if (SUCCEEDED(handler->GetIconLocation(&icon, &iconindex)) && icon) {
- handlers.push_back(OpenWithApp(QString::fromWCharArray(name), handler, IconToBitmap(icon, iconindex)));
- CoTaskMemFree(icon);
- } else {
- handlers.push_back(OpenWithApp(QString::fromWCharArray(name), handler));
- }
- CoTaskMemFree(name);
- } else {
- handler->Release();
- }
- } while (hr != S_FALSE);
- assocHandlers->Release();
- }
- if (!handlers.empty()) {
- HMENU menu = CreatePopupMenu();
- ranges::sort(handlers, [](const OpenWithApp &a, auto &b) {
- return a.name() < b.name();
- });
- for (int32 i = 0, l = handlers.size(); i < l; ++i) {
- MENUITEMINFO menuInfo = { 0 };
- menuInfo.cbSize = sizeof(menuInfo);
- menuInfo.fMask = MIIM_STRING | MIIM_DATA | MIIM_ID;
- menuInfo.fType = MFT_STRING;
- menuInfo.wID = i + 1;
- if (auto icon = handlers[i].icon()) {
- menuInfo.fMask |= MIIM_BITMAP;
- menuInfo.hbmpItem = icon;
- }
- auto name = handlers[i].name();
- if (name.size() > 512) name = name.mid(0, 512);
- WCHAR nameArr[1024];
- name.toWCharArray(nameArr);
- nameArr[name.size()] = 0;
- menuInfo.dwTypeData = nameArr;
- InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &menuInfo);
- }
- MENUITEMINFO sepInfo = { 0 };
- sepInfo.cbSize = sizeof(sepInfo);
- sepInfo.fMask = MIIM_STRING | MIIM_DATA;
- sepInfo.fType = MFT_SEPARATOR;
- InsertMenuItem(menu, GetMenuItemCount(menu), true, &sepInfo);
- MENUITEMINFO menuInfo = { 0 };
- menuInfo.cbSize = sizeof(menuInfo);
- menuInfo.fMask = MIIM_STRING | MIIM_DATA | MIIM_ID;
- menuInfo.fType = MFT_STRING;
- menuInfo.wID = handlers.size() + 1;
- QString name = tr::lng_wnd_choose_program_menu(tr::now);
- if (name.size() > 512) name = name.mid(0, 512);
- WCHAR nameArr[1024];
- name.toWCharArray(nameArr);
- nameArr[name.size()] = 0;
- menuInfo.dwTypeData = nameArr;
- InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &menuInfo);
- POINT position;
- GetCursorPos(&position);
- int sel = TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD, position.x, position.y, 0, parentHWND, 0);
- DestroyMenu(menu);
- if (sel > 0) {
- if (sel <= handlers.size()) {
- IDataObject *dataObj = 0;
- if (SUCCEEDED(pItem->BindToHandler(nullptr, BHID_DataObject, IID_PPV_ARGS(&dataObj))) && dataObj) {
- handlers[sel - 1].handler()->Invoke(dataObj);
- dataObj->Release();
- result = true;
- }
- }
- } else {
- result = true;
- }
- }
- pItem->Release();
- }
- return result;
- }
- bool UnsafeShowOpenWith(const QString &filepath) {
- auto wstringPath = QDir::toNativeSeparators(filepath).toStdWString();
- if (Dlls::SHOpenWithDialog) {
- OPENASINFO info;
- info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC;
- info.pcszClass = NULL;
- info.pcszFile = wstringPath.c_str();
- Dlls::SHOpenWithDialog(0, &info);
- return true;
- } else if (Dlls::OpenAs_RunDLL) {
- Dlls::OpenAs_RunDLL(0, 0, wstringPath.c_str(), SW_SHOWNORMAL);
- return true;
- }
- return false;
- }
- void UnsafeLaunch(const QString &filepath) {
- auto wstringPath = QDir::toNativeSeparators(filepath).toStdWString();
- ShellExecute(0, L"open", wstringPath.c_str(), 0, 0, SW_SHOWNORMAL);
- }
- void PostprocessDownloaded(const QString &filepath) {
- // Mark file saved to the NTFS file system as originating from the Internet security zone
- // unless this feature is disabled by Group Policy.
- if (!ShouldSaveZoneInformation()) {
- return;
- }
- auto wstringZoneFile = QDir::toNativeSeparators(filepath).toStdWString() + L":Zone.Identifier";
- auto f = CreateFile(wstringZoneFile.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
- if (f == INVALID_HANDLE_VALUE) { // :(
- return;
- }
- const char data[] = "[ZoneTransfer]\r\nZoneId=3\r\n";
- DWORD written = 0;
- BOOL result = WriteFile(f, data, sizeof(data), &written, NULL);
- CloseHandle(f);
- if (!result || written != sizeof(data)) { // :(
- return;
- }
- }
- } // namespace File
- namespace FileDialog {
- namespace {
- using Type = ::FileDialog::internal::Type;
- } // namespace
- void InitLastPath() {
- // hack to restore previous dir without hurting performance
- QSettings settings(QSettings::UserScope, qstr("QtProject"));
- settings.beginGroup(qstr("Qt"));
- QByteArray sd = settings.value(qstr("filedialog")).toByteArray();
- QDataStream stream(&sd, QIODevice::ReadOnly);
- if (!stream.atEnd()) {
- int version = 3, _QFileDialogMagic = 190;
- QByteArray splitterState;
- QByteArray headerData;
- QList<QUrl> bookmarks;
- QStringList history;
- QString currentDirectory;
- qint32 marker;
- qint32 v;
- qint32 viewMode;
- stream >> marker;
- stream >> v;
- if (marker == _QFileDialogMagic && v == version) {
- stream >> splitterState
- >> bookmarks
- >> history
- >> currentDirectory
- >> headerData
- >> viewMode;
- cSetDialogLastPath(currentDirectory);
- }
- }
- if (cDialogHelperPath().isEmpty()) {
- QDir temppath(cWorkingDir() + "tdata/tdummy/");
- if (!temppath.exists()) {
- temppath.mkpath(temppath.absolutePath());
- }
- if (temppath.exists()) {
- cSetDialogHelperPath(temppath.absolutePath());
- }
- }
- }
- bool Get(
- QPointer<QWidget> parent,
- QStringList &files,
- QByteArray &remoteContent,
- const QString &caption,
- const QString &filter,
- ::FileDialog::internal::Type type,
- QString startFile) {
- if (cDialogLastPath().isEmpty()) {
- Platform::FileDialog::InitLastPath();
- }
- // A hack for fast dialog create. There was some huge performance problem
- // if we open a file dialog in some folder with a large amount of files.
- // Some internal Qt watcher iterated over all of them, querying some information
- // that forced file icon and maybe other properties being resolved and this was
- // a blocking operation.
- auto helperPath = cDialogHelperPathFinal();
- QFileDialog dialog(parent, caption, helperPath, filter);
- dialog.setModal(true);
- if (type == Type::ReadFile || type == Type::ReadFiles) {
- dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile);
- dialog.setAcceptMode(QFileDialog::AcceptOpen);
- } else if (type == Type::ReadFolder) { // save dir
- dialog.setAcceptMode(QFileDialog::AcceptOpen);
- dialog.setFileMode(QFileDialog::Directory);
- dialog.setOption(QFileDialog::ShowDirsOnly);
- } else { // save file
- dialog.setFileMode(QFileDialog::AnyFile);
- dialog.setAcceptMode(QFileDialog::AcceptSave);
- }
- #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
- dialog.show();
- #endif // Qt < 6.0.0
- auto realLastPath = [=] {
- // If we're given some non empty path containing a folder - use it.
- if (!startFile.isEmpty() && (startFile.indexOf('/') >= 0 || startFile.indexOf('\\') >= 0)) {
- return QFileInfo(startFile).dir().absolutePath();
- }
- return cDialogLastPath();
- }();
- if (realLastPath.isEmpty() || realLastPath.endsWith(qstr("/tdummy"))) {
- realLastPath = QStandardPaths::writableLocation(
- QStandardPaths::DownloadLocation);
- }
- dialog.setDirectory(realLastPath);
- auto toSelect = startFile;
- if (type == Type::WriteFile) {
- const auto lastSlash = toSelect.lastIndexOf('/');
- if (lastSlash >= 0) {
- toSelect = toSelect.mid(lastSlash + 1);
- }
- const auto lastBackSlash = toSelect.lastIndexOf('\\');
- if (lastBackSlash >= 0) {
- toSelect = toSelect.mid(lastBackSlash + 1);
- }
- dialog.selectFile(toSelect);
- }
- CrashReports::SetAnnotation(
- "file_dialog",
- QString("caption:%1;helper:%2;filter:%3;real:%4;select:%5"
- ).arg(caption
- ).arg(helperPath
- ).arg(filter
- ).arg(realLastPath
- ).arg(toSelect));
- const auto result = dialog.exec();
- CrashReports::ClearAnnotation("file_dialog");
- if (type != Type::ReadFolder) {
- // Save last used directory for all queries except directory choosing.
- const auto path = dialog.directory().absolutePath();
- if (path != cDialogLastPath()) {
- cSetDialogLastPath(path);
- Local::writeSettings();
- }
- }
- if (result == QDialog::Accepted) {
- if (type == Type::ReadFiles) {
- files = dialog.selectedFiles();
- } else {
- files = dialog.selectedFiles().mid(0, 1);
- }
- //if (type == Type::ReadFile || type == Type::ReadFiles) {
- // remoteContent = dialog.selectedRemoteContent();
- //}
- return true;
- }
- files = QStringList();
- remoteContent = QByteArray();
- return false;
- }
- } // namespace FileDialog
- } // namespace Platform
|