file_utilities_win.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "platform/win/file_utilities_win.h"
  8. #include "mainwindow.h"
  9. #include "storage/localstorage.h"
  10. #include "platform/win/windows_dlls.h"
  11. #include "lang/lang_keys.h"
  12. #include "core/application.h"
  13. #include "core/crash_reports.h"
  14. #include "window/window_controller.h"
  15. #include "ui/ui_utility.h"
  16. #include <QtWidgets/QFileDialog>
  17. #include <QtGui/QDesktopServices>
  18. #include <QtCore/QSettings>
  19. #include <QtCore/QStandardPaths>
  20. #include <Shlwapi.h>
  21. #include <Windowsx.h>
  22. HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &, int hbitmapFormat);
  23. namespace Platform {
  24. namespace File {
  25. namespace {
  26. class OpenWithApp {
  27. public:
  28. OpenWithApp(const QString &name, IAssocHandler *handler, HBITMAP icon = nullptr)
  29. : _name(name)
  30. , _handler(handler)
  31. , _icon(icon) {
  32. }
  33. OpenWithApp(OpenWithApp &&other)
  34. : _name(base::take(other._name))
  35. , _handler(base::take(other._handler))
  36. , _icon(base::take(other._icon)) {
  37. }
  38. OpenWithApp &operator=(OpenWithApp &&other) {
  39. _name = base::take(other._name);
  40. _icon = base::take(other._icon);
  41. _handler = base::take(other._handler);
  42. return (*this);
  43. }
  44. OpenWithApp(const OpenWithApp &other) = delete;
  45. OpenWithApp &operator=(const OpenWithApp &other) = delete;
  46. ~OpenWithApp() {
  47. if (_icon) {
  48. DeleteBitmap(_icon);
  49. }
  50. if (_handler) {
  51. _handler->Release();
  52. }
  53. }
  54. const QString &name() const {
  55. return _name;
  56. }
  57. HBITMAP icon() const {
  58. return _icon;
  59. }
  60. IAssocHandler *handler() const {
  61. return _handler;
  62. }
  63. private:
  64. QString _name;
  65. IAssocHandler *_handler = nullptr;
  66. HBITMAP _icon = nullptr;
  67. };
  68. HBITMAP IconToBitmap(LPWSTR icon, int iconindex) {
  69. if (!icon) return 0;
  70. WCHAR tmpIcon[4096];
  71. if (icon[0] == L'@' && SUCCEEDED(SHLoadIndirectString(icon, tmpIcon, 4096, 0))) {
  72. icon = tmpIcon;
  73. }
  74. int32 w = GetSystemMetrics(SM_CXSMICON), h = GetSystemMetrics(SM_CYSMICON);
  75. HICON ico = ExtractIcon(0, icon, iconindex);
  76. if (!ico) {
  77. if (!iconindex) { // try to read image
  78. QImage img(QString::fromWCharArray(icon));
  79. if (!img.isNull()) {
  80. return qt_pixmapToWinHBITMAP(
  81. Ui::PixmapFromImage(
  82. img.scaled(
  83. w,
  84. h,
  85. Qt::IgnoreAspectRatio,
  86. Qt::SmoothTransformation)),
  87. /* HBitmapAlpha */ 2);
  88. }
  89. }
  90. return 0;
  91. }
  92. HDC screenDC = GetDC(0), hdc = CreateCompatibleDC(screenDC);
  93. HBITMAP result = CreateCompatibleBitmap(screenDC, w, h);
  94. HGDIOBJ was = SelectObject(hdc, result);
  95. DrawIconEx(hdc, 0, 0, ico, w, h, 0, NULL, DI_NORMAL);
  96. SelectObject(hdc, was);
  97. DeleteDC(hdc);
  98. ReleaseDC(0, screenDC);
  99. DestroyIcon(ico);
  100. return (HBITMAP)CopyImage(result, IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_CREATEDIBSECTION);
  101. }
  102. bool ShouldSaveZoneInformation() {
  103. // Check if the "Do not preserve zone information in file attachments" policy is enabled.
  104. const auto keyName = L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Attachments";
  105. const auto valueName = L"SaveZoneInformation";
  106. auto key = HKEY();
  107. auto result = RegOpenKeyEx(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &key);
  108. if (result != ERROR_SUCCESS) {
  109. // If the registry key cannot be opened, assume the default behavior:
  110. // Windows preserves zone information for downloaded files.
  111. return true;
  112. }
  113. DWORD value = 0, type = 0, size = sizeof(value);
  114. result = RegQueryValueEx(key, valueName, 0, &type, (LPBYTE)&value, &size);
  115. RegCloseKey(key);
  116. if (result != ERROR_SUCCESS || type != REG_DWORD) {
  117. return true;
  118. }
  119. return (value != 1);
  120. }
  121. } // namespace
  122. void UnsafeOpenEmailLink(const QString &email) {
  123. auto url = QUrl(qstr("mailto:") + email);
  124. if (!QDesktopServices::openUrl(url)) {
  125. auto wstringUrl = url.toString(QUrl::FullyEncoded).toStdWString();
  126. if (Dlls::SHOpenWithDialog) {
  127. OPENASINFO info;
  128. info.oaifInFlags = OAIF_ALLOW_REGISTRATION
  129. | OAIF_REGISTER_EXT
  130. | OAIF_EXEC
  131. #if WINVER >= 0x0602
  132. | OAIF_FILE_IS_URI
  133. #endif // WINVER >= 0x602
  134. | OAIF_URL_PROTOCOL;
  135. info.pcszClass = NULL;
  136. info.pcszFile = wstringUrl.c_str();
  137. Dlls::SHOpenWithDialog(0, &info);
  138. } else if (Dlls::OpenAs_RunDLL) {
  139. Dlls::OpenAs_RunDLL(0, 0, wstringUrl.c_str(), SW_SHOWNORMAL);
  140. } else {
  141. ShellExecute(0, L"open", wstringUrl.c_str(), 0, 0, SW_SHOWNORMAL);
  142. }
  143. }
  144. }
  145. bool UnsafeShowOpenWithDropdown(const QString &filepath) {
  146. if (!Dlls::SHAssocEnumHandlers || !Dlls::SHCreateItemFromParsingName) {
  147. return false;
  148. }
  149. auto window = Core::App().activeWindow();
  150. if (!window) {
  151. return false;
  152. }
  153. auto parentHWND = window->widget()->psHwnd();
  154. auto wstringPath = QDir::toNativeSeparators(filepath).toStdWString();
  155. auto result = false;
  156. std::vector<OpenWithApp> handlers;
  157. IShellItem* pItem = nullptr;
  158. if (SUCCEEDED(Dlls::SHCreateItemFromParsingName(wstringPath.c_str(), nullptr, IID_PPV_ARGS(&pItem)))) {
  159. IEnumAssocHandlers *assocHandlers = nullptr;
  160. if (SUCCEEDED(pItem->BindToHandler(nullptr, BHID_EnumAssocHandlers, IID_PPV_ARGS(&assocHandlers)))) {
  161. HRESULT hr = S_FALSE;
  162. do {
  163. IAssocHandler *handler = nullptr;
  164. ULONG ulFetched = 0;
  165. hr = assocHandlers->Next(1, &handler, &ulFetched);
  166. if (FAILED(hr) || hr == S_FALSE || !ulFetched) break;
  167. LPWSTR name = 0;
  168. if (SUCCEEDED(handler->GetUIName(&name))) {
  169. LPWSTR icon = 0;
  170. int iconindex = 0;
  171. if (SUCCEEDED(handler->GetIconLocation(&icon, &iconindex)) && icon) {
  172. handlers.push_back(OpenWithApp(QString::fromWCharArray(name), handler, IconToBitmap(icon, iconindex)));
  173. CoTaskMemFree(icon);
  174. } else {
  175. handlers.push_back(OpenWithApp(QString::fromWCharArray(name), handler));
  176. }
  177. CoTaskMemFree(name);
  178. } else {
  179. handler->Release();
  180. }
  181. } while (hr != S_FALSE);
  182. assocHandlers->Release();
  183. }
  184. if (!handlers.empty()) {
  185. HMENU menu = CreatePopupMenu();
  186. ranges::sort(handlers, [](const OpenWithApp &a, auto &b) {
  187. return a.name() < b.name();
  188. });
  189. for (int32 i = 0, l = handlers.size(); i < l; ++i) {
  190. MENUITEMINFO menuInfo = { 0 };
  191. menuInfo.cbSize = sizeof(menuInfo);
  192. menuInfo.fMask = MIIM_STRING | MIIM_DATA | MIIM_ID;
  193. menuInfo.fType = MFT_STRING;
  194. menuInfo.wID = i + 1;
  195. if (auto icon = handlers[i].icon()) {
  196. menuInfo.fMask |= MIIM_BITMAP;
  197. menuInfo.hbmpItem = icon;
  198. }
  199. auto name = handlers[i].name();
  200. if (name.size() > 512) name = name.mid(0, 512);
  201. WCHAR nameArr[1024];
  202. name.toWCharArray(nameArr);
  203. nameArr[name.size()] = 0;
  204. menuInfo.dwTypeData = nameArr;
  205. InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &menuInfo);
  206. }
  207. MENUITEMINFO sepInfo = { 0 };
  208. sepInfo.cbSize = sizeof(sepInfo);
  209. sepInfo.fMask = MIIM_STRING | MIIM_DATA;
  210. sepInfo.fType = MFT_SEPARATOR;
  211. InsertMenuItem(menu, GetMenuItemCount(menu), true, &sepInfo);
  212. MENUITEMINFO menuInfo = { 0 };
  213. menuInfo.cbSize = sizeof(menuInfo);
  214. menuInfo.fMask = MIIM_STRING | MIIM_DATA | MIIM_ID;
  215. menuInfo.fType = MFT_STRING;
  216. menuInfo.wID = handlers.size() + 1;
  217. QString name = tr::lng_wnd_choose_program_menu(tr::now);
  218. if (name.size() > 512) name = name.mid(0, 512);
  219. WCHAR nameArr[1024];
  220. name.toWCharArray(nameArr);
  221. nameArr[name.size()] = 0;
  222. menuInfo.dwTypeData = nameArr;
  223. InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &menuInfo);
  224. POINT position;
  225. GetCursorPos(&position);
  226. int sel = TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD, position.x, position.y, 0, parentHWND, 0);
  227. DestroyMenu(menu);
  228. if (sel > 0) {
  229. if (sel <= handlers.size()) {
  230. IDataObject *dataObj = 0;
  231. if (SUCCEEDED(pItem->BindToHandler(nullptr, BHID_DataObject, IID_PPV_ARGS(&dataObj))) && dataObj) {
  232. handlers[sel - 1].handler()->Invoke(dataObj);
  233. dataObj->Release();
  234. result = true;
  235. }
  236. }
  237. } else {
  238. result = true;
  239. }
  240. }
  241. pItem->Release();
  242. }
  243. return result;
  244. }
  245. bool UnsafeShowOpenWith(const QString &filepath) {
  246. auto wstringPath = QDir::toNativeSeparators(filepath).toStdWString();
  247. if (Dlls::SHOpenWithDialog) {
  248. OPENASINFO info;
  249. info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC;
  250. info.pcszClass = NULL;
  251. info.pcszFile = wstringPath.c_str();
  252. Dlls::SHOpenWithDialog(0, &info);
  253. return true;
  254. } else if (Dlls::OpenAs_RunDLL) {
  255. Dlls::OpenAs_RunDLL(0, 0, wstringPath.c_str(), SW_SHOWNORMAL);
  256. return true;
  257. }
  258. return false;
  259. }
  260. void UnsafeLaunch(const QString &filepath) {
  261. auto wstringPath = QDir::toNativeSeparators(filepath).toStdWString();
  262. ShellExecute(0, L"open", wstringPath.c_str(), 0, 0, SW_SHOWNORMAL);
  263. }
  264. void PostprocessDownloaded(const QString &filepath) {
  265. // Mark file saved to the NTFS file system as originating from the Internet security zone
  266. // unless this feature is disabled by Group Policy.
  267. if (!ShouldSaveZoneInformation()) {
  268. return;
  269. }
  270. auto wstringZoneFile = QDir::toNativeSeparators(filepath).toStdWString() + L":Zone.Identifier";
  271. auto f = CreateFile(wstringZoneFile.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
  272. if (f == INVALID_HANDLE_VALUE) { // :(
  273. return;
  274. }
  275. const char data[] = "[ZoneTransfer]\r\nZoneId=3\r\n";
  276. DWORD written = 0;
  277. BOOL result = WriteFile(f, data, sizeof(data), &written, NULL);
  278. CloseHandle(f);
  279. if (!result || written != sizeof(data)) { // :(
  280. return;
  281. }
  282. }
  283. } // namespace File
  284. namespace FileDialog {
  285. namespace {
  286. using Type = ::FileDialog::internal::Type;
  287. } // namespace
  288. void InitLastPath() {
  289. // hack to restore previous dir without hurting performance
  290. QSettings settings(QSettings::UserScope, qstr("QtProject"));
  291. settings.beginGroup(qstr("Qt"));
  292. QByteArray sd = settings.value(qstr("filedialog")).toByteArray();
  293. QDataStream stream(&sd, QIODevice::ReadOnly);
  294. if (!stream.atEnd()) {
  295. int version = 3, _QFileDialogMagic = 190;
  296. QByteArray splitterState;
  297. QByteArray headerData;
  298. QList<QUrl> bookmarks;
  299. QStringList history;
  300. QString currentDirectory;
  301. qint32 marker;
  302. qint32 v;
  303. qint32 viewMode;
  304. stream >> marker;
  305. stream >> v;
  306. if (marker == _QFileDialogMagic && v == version) {
  307. stream >> splitterState
  308. >> bookmarks
  309. >> history
  310. >> currentDirectory
  311. >> headerData
  312. >> viewMode;
  313. cSetDialogLastPath(currentDirectory);
  314. }
  315. }
  316. if (cDialogHelperPath().isEmpty()) {
  317. QDir temppath(cWorkingDir() + "tdata/tdummy/");
  318. if (!temppath.exists()) {
  319. temppath.mkpath(temppath.absolutePath());
  320. }
  321. if (temppath.exists()) {
  322. cSetDialogHelperPath(temppath.absolutePath());
  323. }
  324. }
  325. }
  326. bool Get(
  327. QPointer<QWidget> parent,
  328. QStringList &files,
  329. QByteArray &remoteContent,
  330. const QString &caption,
  331. const QString &filter,
  332. ::FileDialog::internal::Type type,
  333. QString startFile) {
  334. if (cDialogLastPath().isEmpty()) {
  335. Platform::FileDialog::InitLastPath();
  336. }
  337. // A hack for fast dialog create. There was some huge performance problem
  338. // if we open a file dialog in some folder with a large amount of files.
  339. // Some internal Qt watcher iterated over all of them, querying some information
  340. // that forced file icon and maybe other properties being resolved and this was
  341. // a blocking operation.
  342. auto helperPath = cDialogHelperPathFinal();
  343. QFileDialog dialog(parent, caption, helperPath, filter);
  344. dialog.setModal(true);
  345. if (type == Type::ReadFile || type == Type::ReadFiles) {
  346. dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile);
  347. dialog.setAcceptMode(QFileDialog::AcceptOpen);
  348. } else if (type == Type::ReadFolder) { // save dir
  349. dialog.setAcceptMode(QFileDialog::AcceptOpen);
  350. dialog.setFileMode(QFileDialog::Directory);
  351. dialog.setOption(QFileDialog::ShowDirsOnly);
  352. } else { // save file
  353. dialog.setFileMode(QFileDialog::AnyFile);
  354. dialog.setAcceptMode(QFileDialog::AcceptSave);
  355. }
  356. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  357. dialog.show();
  358. #endif // Qt < 6.0.0
  359. auto realLastPath = [=] {
  360. // If we're given some non empty path containing a folder - use it.
  361. if (!startFile.isEmpty() && (startFile.indexOf('/') >= 0 || startFile.indexOf('\\') >= 0)) {
  362. return QFileInfo(startFile).dir().absolutePath();
  363. }
  364. return cDialogLastPath();
  365. }();
  366. if (realLastPath.isEmpty() || realLastPath.endsWith(qstr("/tdummy"))) {
  367. realLastPath = QStandardPaths::writableLocation(
  368. QStandardPaths::DownloadLocation);
  369. }
  370. dialog.setDirectory(realLastPath);
  371. auto toSelect = startFile;
  372. if (type == Type::WriteFile) {
  373. const auto lastSlash = toSelect.lastIndexOf('/');
  374. if (lastSlash >= 0) {
  375. toSelect = toSelect.mid(lastSlash + 1);
  376. }
  377. const auto lastBackSlash = toSelect.lastIndexOf('\\');
  378. if (lastBackSlash >= 0) {
  379. toSelect = toSelect.mid(lastBackSlash + 1);
  380. }
  381. dialog.selectFile(toSelect);
  382. }
  383. CrashReports::SetAnnotation(
  384. "file_dialog",
  385. QString("caption:%1;helper:%2;filter:%3;real:%4;select:%5"
  386. ).arg(caption
  387. ).arg(helperPath
  388. ).arg(filter
  389. ).arg(realLastPath
  390. ).arg(toSelect));
  391. const auto result = dialog.exec();
  392. CrashReports::ClearAnnotation("file_dialog");
  393. if (type != Type::ReadFolder) {
  394. // Save last used directory for all queries except directory choosing.
  395. const auto path = dialog.directory().absolutePath();
  396. if (path != cDialogLastPath()) {
  397. cSetDialogLastPath(path);
  398. Local::writeSettings();
  399. }
  400. }
  401. if (result == QDialog::Accepted) {
  402. if (type == Type::ReadFiles) {
  403. files = dialog.selectedFiles();
  404. } else {
  405. files = dialog.selectedFiles().mid(0, 1);
  406. }
  407. //if (type == Type::ReadFile || type == Type::ReadFiles) {
  408. // remoteContent = dialog.selectedRemoteContent();
  409. //}
  410. return true;
  411. }
  412. files = QStringList();
  413. remoteContent = QByteArray();
  414. return false;
  415. }
  416. } // namespace FileDialog
  417. } // namespace Platform