AyuGramDesktop/Telegram/SourceFiles/platform/win/file_utilities_win.cpp
John Preston 08167a6a91 Removed #include "stdafx.h" from all files.
Currently the build without implicitly included precompiled header
is not supported anyway (because Qt MOC source files do not include
stdafx.h, they include plain headers).

So when we decide to support building without implicitly included
precompiled headers we'll have to fix all the headers anyway.
2017-03-04 12:27:52 +03:00

420 lines
No EOL
13 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop 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.
It 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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "platform/win/file_utilities_win.h"
#include "mainwindow.h"
#include "localstorage.h"
#include "platform/win/windows_dlls.h"
#include "lang.h"
#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(App::pixmapFromImageInPlace(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);
}
} // 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 | OAIF_FILE_IS_URI | 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, QPoint menuPosition) {
auto window = App::wnd();
if (!window) return false;
auto parentHWND = window->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();
std::sort(handlers.begin(), handlers.end(), [](const OpenWithApp &a, const OpenWithApp &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 = lang(lng_wnd_choose_program_menu);
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);
int sel = TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD, menuPosition.x(), menuPosition.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 UnsafeShowInFolder(const QString &filepath) {
auto pathEscaped = QDir::toNativeSeparators(filepath).replace('"', qsl("\"\""));
auto wstringParam = (qstr("/select,") + pathEscaped).toStdWString();
ShellExecute(0, 0, L"explorer", wstringParam.c_str(), 0, SW_SHOWNORMAL);
}
void PostprocessDownloaded(const QString &filepath) {
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(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(App::wnd() ? App::wnd()->filedialogParent() : 0, 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);
// We use "obsolete" value ::DirectoryOnly instead of ::Directory + ::ShowDirsOnly
// because in Windows XP native dialog this one works, while the "preferred" one
// shows a native file choose dialog where you can't choose a directory, only open one.
dialog.setFileMode(QFileDialog::DirectoryOnly);
dialog.setOption(QFileDialog::ShowDirsOnly);
} else { // save file
dialog.setFileMode(QFileDialog::AnyFile);
dialog.setAcceptMode(QFileDialog::AcceptSave);
}
dialog.show();
auto realLastPath = ([startFile] {
// 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);
if (type == Type::WriteFile) {
auto toSelect = startFile;
auto lastSlash = toSelect.lastIndexOf('/');
if (lastSlash >= 0) {
toSelect = toSelect.mid(lastSlash + 1);
}
auto lastBackSlash = toSelect.lastIndexOf('\\');
if (lastBackSlash >= 0) {
toSelect = toSelect.mid(lastBackSlash + 1);
}
dialog.selectFile(toSelect);
}
int res = dialog.exec();
if (type != Type::ReadFolder) {
// Save last used directory for all queries except directory choosing.
auto path = dialog.directory().absolutePath();
if (path != cDialogLastPath()) {
cSetDialogLastPath(path);
Local::writeUserSettings();
}
}
if (res == 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