Platform-dependent file methods called async.

Some major platform-dependent file operations refactoring.
All methods like "open file", "open file with", "show in folder"
were moved to core/file_utilities module with platform-dependent
backends. All methods interacting with DesktopServices made async.
This commit is contained in:
John Preston 2017-02-28 17:05:30 +03:00
parent 6f0cf30b12
commit f8318177b9
56 changed files with 1254 additions and 1088 deletions

View File

@ -28,7 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwindow.h"
#include "lang.h"
#include "boxes/confirmbox.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "ui/widgets/tooltip.h"
#include "langloaderplain.h"
#include "localstorage.h"

View File

@ -28,7 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "boxes/contactsbox.h"
#include "boxes/confirmbox.h"
#include "boxes/photocropbox.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"

View File

@ -21,7 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once
#include "boxes/abstractbox.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
class ConfirmBox;

View File

@ -33,7 +33,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "messenger.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "ui/widgets/multi_select.h"
#include "ui/widgets/scroll_area.h"
#include "ui/effects/widget_slide_wrap.h"

View File

@ -23,7 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "lang.h"
#include "localstorage.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
#include "pspecific.h"
@ -100,19 +100,19 @@ void DownloadPathBox::onChange() {
}
void DownloadPathBox::onEditPath() {
filedialogInit();
QString path, lastPath = cDialogLastPath();
if (!Global::DownloadPath().isEmpty() && Global::DownloadPath() != qstr("tmp")) {
cSetDialogLastPath(Global::DownloadPath().left(Global::DownloadPath().size() - (Global::DownloadPath().endsWith('/') ? 1 : 0)));
}
if (filedialogGetDir(path, lang(lng_download_path_choose))) {
if (!path.isEmpty()) {
_path = path + '/';
auto initialPath = [] {
if (!Global::DownloadPath().isEmpty() && Global::DownloadPath() != qstr("tmp")) {
return Global::DownloadPath().left(Global::DownloadPath().size() - (Global::DownloadPath().endsWith('/') ? 1 : 0));
}
return QString();
};
FileDialog::GetFolder(lang(lng_download_path_choose), initialPath(), base::lambda_guarded(this, [this](const QString &result) {
if (!result.isEmpty()) {
_path = result + '/';
_pathBookmark = psDownloadPathBookmark(_path);
setPathText(QDir::toNativeSeparators(_path));
}
}
cSetDialogLastPath(lastPath);
}));
}
void DownloadPathBox::save() {

View File

@ -25,7 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "localstorage.h"
#include "mainwidget.h"
#include "history/history_media_types.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"

View File

@ -28,6 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "core/qthelp_url.h"
#include "localstorage.h"
#include "ui/widgets/tooltip.h"
#include "core/file_utilities.h"
QString UrlClickHandler::copyToClipboardContextItemText() const {
return lang(isEmail() ? lng_context_copy_email : lng_context_copy_link);
@ -68,10 +69,7 @@ void UrlClickHandler::doOpen(QString url) {
Ui::Tooltip::Hide();
if (isEmail(url)) {
QUrl u(qstr("mailto:") + url);
if (!QDesktopServices::openUrl(u)) {
psOpenFile(u.toString(QUrl::FullyEncoded), true);
}
File::OpenEmailLink(url);
return;
}

View File

@ -19,70 +19,25 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "mainwindow.h"
#include "localstorage.h"
#include "platform/platform_file_utilities.h"
#include "core/task_queue.h"
void filedialogInit() {
if (cDialogLastPath().isEmpty()) {
#ifdef Q_OS_WIN
// 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());
}
}
#else // Q_OS_WIN
cSetDialogLastPath(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
#endif // Q_OS_WIN
}
}
namespace FileDialog {
namespace internal {
bool getFiles(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, FileDialog::internal::Type type, QString startFile = QString()) {
filedialogInit();
void InitLastPathDefault() {
cSetDialogLastPath(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
}
if (Platform::FileDialog::Supported()) {
return Platform::FileDialog::Get(files, remoteContent, caption, filter, type, startFile);
bool GetDefault(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, FileDialog::internal::Type type, QString startFile = QString()) {
if (cDialogLastPath().isEmpty()) {
Platform::FileDialog::InitLastPath();
}
#if defined Q_OS_LINUX || defined Q_OS_MAC // use native
remoteContent = QByteArray();
if (startFile.isEmpty() || startFile.at(0) != '/') {
startFile = cDialogLastPath() + '/' + startFile;
@ -107,122 +62,56 @@ bool getFiles(QStringList &files, QByteArray &remoteContent, const QString &capt
files = QStringList();
return false;
}
QString path = QFileInfo(file).absoluteDir().absolutePath();
if (!path.isEmpty() && path != cDialogLastPath()) {
cSetDialogLastPath(path);
Local::writeUserSettings();
if (type != Type::ReadFolder) {
// Save last used directory for all queries except directory choosing.
auto path = QFileInfo(file).absoluteDir().absolutePath();
if (!path.isEmpty() && path != cDialogLastPath()) {
cSetDialogLastPath(path);
Local::writeUserSettings();
}
}
files = QStringList(file);
return true;
#else // Q_OS_LINUX || Q_OS_MAC
// 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 = cDialogLastPath();
if (realLastPath.isEmpty() || realLastPath.endsWith(qstr("/tdummy"))) {
realLastPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
}
dialog.setDirectory(realLastPath);
if (type == Type::WriteFile) {
QString toSelect(startFile);
#ifdef Q_OS_WIN
int32 lastSlash = toSelect.lastIndexOf('/');
if (lastSlash >= 0) {
toSelect = toSelect.mid(lastSlash + 1);
}
int32 lastBackSlash = toSelect.lastIndexOf('\\');
if (lastBackSlash >= 0) {
toSelect = toSelect.mid(lastBackSlash + 1);
}
#endif
dialog.selectFile(toSelect);
}
int res = dialog.exec();
QString 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) {
#ifdef Q_OS_WIN
remoteContent = dialog.selectedRemoteContent();
#endif // Q_OS_WIN
}
return true;
}
files = QStringList();
remoteContent = QByteArray();
return false;
#endif // Q_OS_WIN
}
} // namespace internal
} // namespace FileDialog
bool filedialogGetOpenFiles(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter) {
return FileDialog::internal::getFiles(files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFiles);
return Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFiles);
}
bool filedialogGetOpenFile(QString &file, QByteArray &remoteContent, const QString &caption, const QString &filter) {
QStringList files;
bool result = FileDialog::internal::getFiles(files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFile);
bool result = Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFile);
file = files.isEmpty() ? QString() : files.at(0);
return result;
}
bool filedialogGetSaveFile(QString &file, const QString &caption, const QString &filter, const QString &startName) {
bool filedialogGetSaveFile(QString &file, const QString &caption, const QString &filter, const QString &initialPath) {
QStringList files;
QByteArray remoteContent;
bool result = FileDialog::internal::getFiles(files, remoteContent, caption, filter, FileDialog::internal::Type::WriteFile, startName);
bool result = Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::WriteFile, initialPath);
file = files.isEmpty() ? QString() : files.at(0);
return result;
}
bool filedialogGetDir(QString &dir, const QString &caption) {
bool filedialogGetDir(QString &dir, const QString &caption, const QString &initialPath) {
QStringList files;
QByteArray remoteContent;
bool result = FileDialog::internal::getFiles(files, remoteContent, caption, QString(), FileDialog::internal::Type::ReadFolder);
bool result = Platform::FileDialog::Get(files, remoteContent, caption, QString(), FileDialog::internal::Type::ReadFolder, initialPath);
dir = files.isEmpty() ? QString() : files.at(0);
return result;
}
QString filedialogDefaultName(const QString &prefix, const QString &extension, const QString &path, bool skipExistance) {
filedialogInit();
auto directoryPath = path;
if (directoryPath.isEmpty()) {
if (cDialogLastPath().isEmpty()) {
Platform::FileDialog::InitLastPath();
}
directoryPath = cDialogLastPath();
}
time_t t = time(NULL);
struct tm tm;
@ -235,7 +124,7 @@ QString filedialogDefaultName(const QString &prefix, const QString &extension, c
if (skipExistance) {
name = base + extension;
} else {
QDir dir(path.isEmpty() ? cDialogLastPath() : path);
QDir dir(directoryPath);
QString nameBase = dir.absolutePath() + '/' + base;
name = nameBase + extension;
for (int i = 0; QFileInfo(name).exists(); ++i) {
@ -268,6 +157,50 @@ QString filedialogAllFilesFilter() {
#endif // Q_OS_WIN
}
namespace File {
void OpenEmailLink(const QString &email) {
base::TaskQueue::Main().Put([email] {
Platform::File::UnsafeOpenEmailLink(email);
});
}
void OpenWith(const QString &filepath, QPoint menuPosition) {
base::TaskQueue::Main().Put([filepath, menuPosition] {
if (!Platform::File::UnsafeShowOpenWithDropdown(filepath, menuPosition)) {
if (!Platform::File::UnsafeShowOpenWith(filepath)) {
Platform::File::UnsafeLaunch(filepath);
}
}
});
}
void Launch(const QString &filepath) {
base::TaskQueue::Main().Put([filepath] {
Platform::File::UnsafeLaunch(filepath);
});
}
void ShowInFolder(const QString &filepath) {
base::TaskQueue::Main().Put([filepath] {
Platform::File::UnsafeShowInFolder(filepath);
});
}
namespace internal {
void UnsafeOpenEmailLinkDefault(const QString &email) {
auto url = QUrl(qstr("mailto:") + email);
QDesktopServices::openUrl(url);
}
void UnsafeLaunchDefault(const QString &filepath) {
QDesktopServices::openUrl(QUrl::fromLocalFile(filepath));
}
} // namespace internal
} // namespace File
namespace FileDialog {
namespace {
@ -375,7 +308,7 @@ bool processQuery() {
case Query::Type::ReadFolder: {
QString folder;
if (filedialogGetDir(folder, query.caption)) {
if (filedialogGetDir(folder, query.caption, query.filePath)) {
if (!folder.isEmpty()) {
update.filePaths.push_back(folder);
}
@ -394,7 +327,7 @@ base::Observable<QueryUpdate> &QueryDone() {
return QueryDoneObservable;
}
void askOpenPath(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed) {
void GetOpenPath(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed) {
base::TaskQueue::Main().Put([caption, filter, callback = std::move(callback), failed = std::move(failed)] {
auto file = QString();
auto remoteContent = QByteArray();
@ -413,7 +346,7 @@ void askOpenPath(const QString &caption, const QString &filter, base::lambda<voi
});
}
void askOpenPaths(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed) {
void GetOpenPaths(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed) {
base::TaskQueue::Main().Put([caption, filter, callback = std::move(callback), failed = std::move(failed)] {
auto files = QStringList();
auto remoteContent = QByteArray();
@ -431,7 +364,7 @@ void askOpenPaths(const QString &caption, const QString &filter, base::lambda<vo
}
void askWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed) {
void GetWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed) {
base::TaskQueue::Main().Put([caption, filter, initialPath, callback = std::move(callback), failed = std::move(failed)] {
auto file = QString();
if (filedialogGetSaveFile(file, caption, filter, initialPath)) {
@ -444,10 +377,10 @@ void askWritePath(const QString &caption, const QString &filter, const QString &
});
}
void askFolder(const QString &caption, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed) {
base::TaskQueue::Main().Put([caption, callback = std::move(callback), failed = std::move(failed)] {
void GetFolder(const QString &caption, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed) {
base::TaskQueue::Main().Put([caption, initialPath, callback = std::move(callback), failed = std::move(failed)] {
auto folder = QString();
if (filedialogGetDir(folder, caption) && !folder.isEmpty()) {
if (filedialogGetDir(folder, caption, initialPath) && !folder.isEmpty()) {
if (callback) {
callback(folder);
}

View File

@ -22,17 +22,37 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "core/observer.h"
void filedialogInit();
// legacy
bool filedialogGetOpenFiles(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter);
bool filedialogGetOpenFile(QString &file, QByteArray &remoteContent, const QString &caption, const QString &filter);
bool filedialogGetSaveFile(QString &file, const QString &caption, const QString &filter, const QString &startName);
bool filedialogGetDir(QString &dir, const QString &caption);
bool filedialogGetSaveFile(QString &file, const QString &caption, const QString &filter, const QString &initialPath);
bool filedialogGetDir(QString &dir, const QString &caption, const QString &initialPath);
QString filedialogDefaultName(const QString &prefix, const QString &extension, const QString &path = QString(), bool skipExistance = false);
QString filedialogNextFilename(const QString &name, const QString &cur, const QString &path = QString());
QString filedialogAllFilesFilter();
namespace File {
// Those functions are async wrappers to Platform::File::Unsafe* calls.
void OpenEmailLink(const QString &email);
void OpenWith(const QString &filepath, QPoint menuPosition);
void Launch(const QString &filepath);
void ShowInFolder(const QString &filepath);
namespace internal {
inline QString UrlToLocalDefault(const QUrl &url) {
return url.toLocalFile();
}
void UnsafeOpenEmailLinkDefault(const QString &email);
void UnsafeLaunchDefault(const QString &filepath);
} // namespace internal
} // namespace File
namespace FileDialog {
namespace internal {
@ -43,6 +63,10 @@ enum class Type {
WriteFile,
};
void InitLastPathDefault();
bool GetDefault(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile);
} // namespace internal
using QueryId = uint64;
@ -69,9 +93,9 @@ struct OpenResult {
QStringList paths;
QByteArray remoteContent;
};
void askOpenPath(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
void askOpenPaths(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
void askWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
void askFolder(const QString &caption, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
void GetOpenPath(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
void GetOpenPaths(const QString &caption, const QString &filter, base::lambda<void(const OpenResult &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
void GetWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
void GetFolder(const QString &caption, const QString &initialPath, base::lambda<void(const QString &result)> callback, base::lambda<void()> failed = base::lambda<void()>());
} // namespace FileDialog

View File

@ -489,10 +489,11 @@ enum DBIPeerReportSpamStatus {
dbiprsRequesting = 5, // requesting the cloud setting right now
};
inline QString strMakeFromLetters(const uint32 *letters, int32 len) {
template <int Size>
inline QString strMakeFromLetters(const uint32 (&letters)[Size]) {
QString result;
result.reserve(len);
for (int32 i = 0; i < len; ++i) {
result.reserve(Size);
for (int32 i = 0; i < Size; ++i) {
result.push_back(QChar((((letters[i] >> 16) & 0xFF) << 8) | (letters[i] & 0xFF)));
}
return result;

View File

@ -29,7 +29,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "boxes/confirmbox.h"
#include "boxes/send_files_box.h"
#include "boxes/sharebox.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "ui/toast/toast.h"
#include "ui/buttons/history_down_button.h"
#include "ui/widgets/buttons.h"
@ -1532,7 +1532,7 @@ void HistoryInner::showContextInFolder() {
}
}
if (!filepath.isEmpty()) {
psShowInFolder(filepath);
File::ShowInFolder(filepath);
}
}

View File

@ -21,7 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once
#include "localimageloader.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "ui/widgets/tooltip.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/scroll_area.h"

View File

@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "inline_bots/inline_bot_layout_item.h"
#include "inline_bots/inline_bot_send_data.h"
#include "mtproto/file_download.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "mainwidget.h"
namespace InlineBots {

View File

@ -23,7 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_intro.h"
#include "styles/style_boxes.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "boxes/confirmbox.h"
#include "lang.h"
#include "application.h"

View File

@ -23,7 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_intro.h"
#include "styles/style_boxes.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "boxes/photocropbox.h"
#include "boxes/confirmbox.h"
#include "lang.h"

View File

@ -21,7 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once
#include "intro/introwidget.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
namespace Ui {
class RoundButton;

View File

@ -27,7 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "application.h"
#include "mainwindow.h"
#include "mainwidget.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "styles/style_boxes.h"
#include "styles/style_widgets.h"
#include "styles/style_stickers.h"

View File

@ -26,7 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "application.h"
#include "fileuploader.h"
#include "mainwindow.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "boxes/addcontactbox.h"
#include "boxes/confirmbox.h"
#include "media/media_audio.h"

View File

@ -20,9 +20,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "localimageloader.h"
#include "ui/filedialog.h"
#include "media/media_audio.h"
#include "core/file_utilities.h"
#include "media/media_audio.h"
#include "boxes/send_files_box.h"
#include "media/media_clip_reader.h"
#include "mainwidget.h"

View File

@ -1578,7 +1578,7 @@ void MainWidget::handleAudioUpdate(const AudioMsgId &audioId) {
auto filepath = document->filepath(DocumentData::FilePathResolveSaveFromData);
if (!filepath.isEmpty()) {
if (documentIsValidMediaFile(filepath)) {
psOpenFile(filepath);
File::Launch(filepath);
}
}
}

View File

@ -25,7 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwidget.h"
#include "mainwindow.h"
#include "application.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/buttons.h"
#include "media/media_clip_reader.h"
@ -631,7 +631,7 @@ void MediaView::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool presse
}
void MediaView::showSaveMsgFile() {
psShowInFolder(_saveMsgFilename);
File::ShowInFolder(_saveMsgFilename);
}
void MediaView::close() {
@ -908,9 +908,9 @@ void MediaView::onSaveCancel() {
void MediaView::onShowInFolder() {
if (!_doc) return;
QString filepath = _doc->filepath(DocumentData::FilePathResolveChecked);
auto filepath = _doc->filepath(DocumentData::FilePathResolveChecked);
if (!filepath.isEmpty()) {
psShowInFolder(filepath);
File::ShowInFolder(filepath);
}
}

View File

@ -38,7 +38,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "window/themes/window_theme.h"
#include "history/history_location_manager.h"
#include "ui/widgets/tooltip.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "serialize/serialize_common.h"
namespace {

View File

@ -25,6 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwindow.h"
#include "messenger.h"
#include "localstorage.h"
#include "platform/platform_file_utilities.h"
namespace {
@ -203,7 +204,7 @@ void FileLoader::localLoaded(const StorageImageSaved &result, const QByteArray &
if (_fileIsOpen) {
_file.close();
_fileIsOpen = false;
psPostprocessFile(QFileInfo(_file).absoluteFilePath());
Platform::File::PostprocessDownloaded(QFileInfo(_file).absoluteFilePath());
}
FileDownload::ImageLoaded().notify();
@ -531,7 +532,7 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe
if (_fileIsOpen) {
_file.close();
_fileIsOpen = false;
psPostprocessFile(QFileInfo(_file).absoluteFilePath());
Platform::File::PostprocessDownloaded(QFileInfo(_file).absoluteFilePath());
}
removeFromQueue();
@ -693,7 +694,7 @@ void webFileLoader::onFinished(const QByteArray &data) {
if (_fileIsOpen) {
_file.close();
_fileIsOpen = false;
psPostprocessFile(QFileInfo(_file).absoluteFilePath());
Platform::File::PostprocessDownloaded(QFileInfo(_file).absoluteFilePath());
}
removeFromQueue();

View File

@ -23,7 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_overview.h"
#include "styles/style_history.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "boxes/addcontactbox.h"
#include "boxes/confirmbox.h"
#include "lang.h"

View File

@ -27,7 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "boxes/addcontactbox.h"
#include "boxes/confirmbox.h"
#include "boxes/photocropbox.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/tooltip.h"
#include "ui/widgets/buttons.h"
@ -1440,7 +1440,7 @@ void OverviewInner::showContextInFolder() {
if (auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLnk.data())) {
auto filepath = lnkDocument->document()->filepath(DocumentData::FilePathResolveChecked);
if (!filepath.isEmpty()) {
psShowInFolder(filepath);
File::ShowInFolder(filepath);
}
}
}

View File

@ -30,6 +30,44 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
QStringList qt_make_filter_list(const QString &filter);
namespace Platform {
namespace File {
namespace internal {
QByteArray EscapeShell(const QByteArray &content) {
auto result = QByteArray();
auto b = content.constData(), e = content.constEnd();
for (auto ch = b; ch != e; ++ch) {
if (*ch == ' ' || *ch == '"' || *ch == '\'' || *ch == '\\') {
if (result.isEmpty()) {
result.reserve(content.size() * 2);
}
if (ch > b) {
result.append(b, ch - b);
}
result.append('\\');
b = ch;
}
}
if (result.isEmpty()) {
return content;
}
if (e > b) {
result.append(b, e - b);
}
return result;
}
} // namespace internal
void UnsafeShowInFolder(const QString &filepath) {
Ui::hideLayer(true);
system(("xdg-open " + internal::EscapeShell(QFile::encodeName(QFileInfo(filepath).absoluteDir().absolutePath()))).constData());
}
} // namespace File
namespace FileDialog {
namespace {
@ -43,11 +81,9 @@ namespace {
constexpr auto kPreviewWidth = 256;
constexpr auto kPreviewHeight = 512;
} // namespace
using Type = ::FileDialog::internal::Type;
bool Supported() {
bool NativeSupported() {
return Platform::internal::GdkHelperLoaded()
&& (Libs::gtk_widget_hide_on_delete != nullptr)
&& (Libs::gtk_clipboard_store != nullptr)
@ -84,11 +120,11 @@ bool Supported() {
}
bool PreviewSupported() {
return Supported()
return NativeSupported()
&& (Libs::gdk_pixbuf_new_from_file_at_size != nullptr);
}
bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, Type type, QString startFile) {
bool GetNative(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, Type type, QString startFile) {
auto parent = App::wnd() ? App::wnd()->filedialogParent() : nullptr;
internal::GtkFileDialog dialog(parent, caption, QString(), filter);
@ -131,6 +167,15 @@ bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption,
return false;
}
} // namespace
bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, Type type, QString startFile) {
if (NativeSupported()) {
return GetNative(files, remoteContent, caption, filter, type, startFile);
}
return ::FileDialog::internal::GetDefault(files, remoteContent, caption, filter, type, startFile);
}
namespace internal {
QGtkDialog::QGtkDialog(GtkWidget *gtkWidget) : gtkWidget(gtkWidget) {

View File

@ -31,18 +31,42 @@ extern "C" {
namespace Platform {
namespace File {
namespace internal {
QByteArray EscapeShell(const QByteArray &content);
} // namespace internal
inline QString UrlToLocal(const QUrl &url) {
return url.toLocalFile();
return ::File::internal::UrlToLocalDefault(url);
}
inline void UnsafeOpenEmailLink(const QString &email) {
return ::File::internal::UnsafeOpenEmailLinkDefault(email);
}
inline bool UnsafeShowOpenWithDropdown(const QString &filepath, QPoint menuPosition) {
return false;
}
inline bool UnsafeShowOpenWith(const QString &filepath) {
return false;
}
inline void UnsafeLaunch(const QString &filepath) {
return ::File::internal::UnsafeLaunchDefault(filepath);
}
inline void PostprocessDownloaded(const QString &filepath) {
}
} // namespace File
namespace FileDialog {
bool Supported();
bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile);
inline void InitLastPath() {
::FileDialog::internal::InitLastPathDefault();
}
namespace internal {

View File

@ -374,7 +374,7 @@ void MainWindow::updateIconCounters() {
}
bool MainWindow::psHasNativeNotifications() {
return Notifications::supported();
return Notifications::Supported();
}
void MainWindow::LibsLoaded() {

View File

@ -23,14 +23,25 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "platform/platform_file_utilities.h"
namespace Platform {
namespace File {
inline void UnsafeOpenEmailLink(const QString &email) {
return ::File::internal::UnsafeOpenEmailLinkDefault(email);
}
inline void PostprocessDownloaded(const QString &filepath) {
}
} // namespace File
namespace FileDialog {
inline bool Supported() {
return false;
inline void InitLastPath() {
::FileDialog::internal::InitLastPathDefault();
}
inline bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile) {
return false;
return ::FileDialog::internal::GetDefault(files, remoteContent, caption, filter, type, startFile);
}
} // namespace FileDialog

View File

@ -22,10 +22,366 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "platform/mac/file_utilities_mac.h"
#include "platform/mac/mac_utilities.h"
#include "styles/style_window.h"
#include <Cocoa/Cocoa.h>
#include <CoreFoundation/CFURL.h>
namespace {
using namespace Platform;
QString strNeedToReload() {
const uint32 letters[] = { 0x82007746, 0xBB00C649, 0x7E00235F, 0x9A00FE54, 0x4C004542, 0x91001772, 0x8A00D76F, 0xC700B977, 0x7F005F73, 0x34003665, 0x2300D572, 0x72002E54, 0x18001461, 0x14004A62, 0x5100CC6C, 0x83002365, 0x5A002C56, 0xA5004369, 0x26004265, 0x0D006577 };
return strMakeFromLetters(letters);
}
QString strNeedToRefresh1() {
const uint32 letters[] = { 0xEF006746, 0xF500CE49, 0x1500715F, 0x95001254, 0x3A00CB4C, 0x17009469, 0xB400DA73, 0xDE00C574, 0x9200EC56, 0x3C00A669, 0xFD00D865, 0x59000977 };
return strMakeFromLetters(letters);
}
QString strNeedToRefresh2() {
const uint32 letters[] = { 0x8F001546, 0xAF007A49, 0xB8002B5F, 0x1A000B54, 0x0D003E49, 0xE0003663, 0x4900796F, 0x0500836E, 0x9A00D156, 0x5E00FF69, 0x5900C765, 0x3D00D177 };
return strMakeFromLetters(letters);
}
} // namespace
@interface OpenWithApp : NSObject {
NSString *fullname;
NSURL *app;
NSImage *icon;
}
@property (nonatomic, retain) NSString *fullname;
@property (nonatomic, retain) NSURL *app;
@property (nonatomic, retain) NSImage *icon;
@end
@implementation OpenWithApp
@synthesize fullname, app, icon;
- (void) dealloc {
[fullname release];
[app release];
[icon release];
[super dealloc];
}
@end
@interface OpenFileWithInterface : NSObject {
}
- (id) init:(NSString *)file;
- (BOOL) popupAtX:(int)x andY:(int)y;
- (void) itemChosen:(id)sender;
- (void) dealloc;
@end
@implementation OpenFileWithInterface {
NSString *toOpen;
NSURL *defUrl;
NSString *defBundle, *defName, *defVersion;
NSImage *defIcon;
NSMutableArray *apps;
NSMenu *menu;
}
- (void) fillAppByUrl:(NSURL*)url bundle:(NSString**)bundle name:(NSString**)name version:(NSString**)version icon:(NSImage**)icon {
NSBundle *b = [NSBundle bundleWithURL:url];
if (b) {
NSString *path = [url path];
*name = [[NSFileManager defaultManager] displayNameAtPath: path];
if (!*name) *name = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleDisplayName"];
if (!*name) *name = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleName"];
if (*name) {
*bundle = [b bundleIdentifier];
if (bundle) {
*version = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
*icon = [[NSWorkspace sharedWorkspace] iconForFile: path];
if (*icon && [*icon isValid]) [*icon setSize: CGSizeMake(16., 16.)];
return;
}
}
}
*bundle = *name = *version = nil;
*icon = nil;
}
- (id) init:(NSString*)file {
toOpen = [file retain];
if (self = [super init]) {
NSURL *url = [NSURL fileURLWithPath:file];
defUrl = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:url];
if (defUrl) {
[self fillAppByUrl:defUrl bundle:&defBundle name:&defName version:&defVersion icon:&defIcon];
if (!defBundle || !defName) {
defUrl = nil;
}
}
NSArray *appsList = (NSArray*)LSCopyApplicationURLsForURL(CFURLRef(url), kLSRolesAll);
NSMutableDictionary *data = [NSMutableDictionary dictionaryWithCapacity:16];
int fullcount = 0;
for (id app in appsList) {
if (fullcount > 15) break;
NSString *bundle = nil, *name = nil, *version = nil;
NSImage *icon = nil;
[self fillAppByUrl:(NSURL*)app bundle:&bundle name:&name version:&version icon:&icon];
if (bundle && name) {
if ([bundle isEqualToString:defBundle] && [version isEqualToString:defVersion]) continue;
NSString *key = [[NSArray arrayWithObjects:bundle, name, nil] componentsJoinedByString:@"|"];
if (!version) version = @"";
NSMutableDictionary *versions = (NSMutableDictionary*)[data objectForKey:key];
if (!versions) {
versions = [NSMutableDictionary dictionaryWithCapacity:2];
[data setValue:versions forKey:key];
}
if (![versions objectForKey:version]) {
[versions setValue:[NSArray arrayWithObjects:name, icon, app, nil] forKey:version];
++fullcount;
}
}
}
if (fullcount || defUrl) {
apps = [NSMutableArray arrayWithCapacity:fullcount];
for (id key in data) {
NSMutableDictionary *val = (NSMutableDictionary*)[data objectForKey:key];
for (id ver in val) {
NSArray *app = (NSArray*)[val objectForKey:ver];
OpenWithApp *a = [[OpenWithApp alloc] init];
NSString *fullname = (NSString*)[app objectAtIndex:0], *version = (NSString*)ver;
BOOL showVersion = ([val count] > 1);
if (!showVersion) {
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^\\d+\\.\\d+\\.\\d+(\\.\\d+)?$" options:NSRegularExpressionCaseInsensitive error:&error];
showVersion = ![regex numberOfMatchesInString:version options:NSMatchingWithoutAnchoringBounds range:{0,[version length]}];
}
if (showVersion) fullname = [[NSArray arrayWithObjects:fullname, @" (", version, @")", nil] componentsJoinedByString:@""];
[a setFullname:fullname];
[a setIcon:(NSImage*)[app objectAtIndex:1]];
[a setApp:(NSURL*)[app objectAtIndex:2]];
[apps addObject:a];
[a release];
}
}
}
[apps sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"fullname" ascending:YES]]];
[appsList release];
menu = nil;
}
return self;
}
- (BOOL) popupAtX:(int)x andY:(int)y {
if (![apps count] && !defName) return NO;
menu = [[NSMenu alloc] initWithTitle:@"Open With"];
int index = 0;
if (defName) {
NSMenuItem *item = [menu insertItemWithTitle:[[NSArray arrayWithObjects:defName, @" (default)", nil] componentsJoinedByString:@""] action:@selector(itemChosen:) keyEquivalent:@"" atIndex:index++];
if (defIcon) [item setImage:defIcon];
[item setTarget:self];
[menu insertItem:[NSMenuItem separatorItem] atIndex:index++];
}
if ([apps count]) {
for (id a in apps) {
OpenWithApp *app = (OpenWithApp*)a;
NSMenuItem *item = [menu insertItemWithTitle:[a fullname] action:@selector(itemChosen:) keyEquivalent:@"" atIndex:index++];
if ([app icon]) [item setImage:[app icon]];
[item setTarget:self];
}
[menu insertItem:[NSMenuItem separatorItem] atIndex:index++];
}
NSMenuItem *item = [menu insertItemWithTitle:NSlang(lng_mac_choose_program_menu) action:@selector(itemChosen:) keyEquivalent:@"" atIndex:index++];
[item setTarget:self];
[menu popUpMenuPositioningItem:nil atLocation:CGPointMake(x, y) inView:nil];
return YES;
}
- (void) itemChosen:(id)sender {
NSArray *items = [menu itemArray];
NSURL *url = nil;
for (int i = 0, l = [items count]; i < l; ++i) {
if ([items objectAtIndex:i] == sender) {
if (defName) i -= 2;
if (i < 0) {
url = defUrl;
} else if (i < int([apps count])) {
url = [(OpenWithApp*)[apps objectAtIndex:i] app];
}
break;
}
}
if (url) {
[[NSWorkspace sharedWorkspace] openFile:toOpen withApplication:[url path]];
} else if (!Platform::File::UnsafeShowOpenWith(NS2QString(toOpen))) {
Platform::File::UnsafeLaunch(NS2QString(toOpen));
}
}
- (void) dealloc {
[toOpen release];
if (menu) [menu release];
[super dealloc];
}
@end
@interface NSURL(CompareUrls)
- (BOOL) isEquivalent:(NSURL *)aURL;
@end
@implementation NSURL(CompareUrls)
- (BOOL) isEquivalent:(NSURL *)aURL {
if ([self isEqual:aURL]) return YES;
if ([[self scheme] caseInsensitiveCompare:[aURL scheme]] != NSOrderedSame) return NO;
if ([[self host] caseInsensitiveCompare:[aURL host]] != NSOrderedSame) return NO;
if ([[self path] compare:[aURL path]] != NSOrderedSame) return NO;
if ([[self port] compare:[aURL port]] != NSOrderedSame) return NO;
if ([[self query] compare:[aURL query]] != NSOrderedSame) return NO;
return YES;
}
@end
@interface ChooseApplicationDelegate : NSObject<NSOpenSavePanelDelegate> {
}
- (id) init:(NSArray *)recommendedApps withPanel:(NSOpenPanel *)creator withSelector:(NSPopUpButton *)menu withGood:(NSTextField *)goodLabel withBad:(NSTextField *)badLabel withIcon:(NSImageView *)badIcon withAccessory:(NSView *)acc;
- (BOOL) panel:(id)sender shouldEnableURL:(NSURL *)url;
- (void) panelSelectionDidChange:(id)sender;
- (void) menuDidClose;
- (void) dealloc;
@end
@implementation ChooseApplicationDelegate {
BOOL onlyRecommended;
NSArray *apps;
NSOpenPanel *panel;
NSPopUpButton *selector;
NSTextField *good, *bad;
NSImageView *icon;
NSString *recom;
NSView *accessory;
}
- (id) init:(NSArray *)recommendedApps withPanel:(NSOpenPanel *)creator withSelector:(NSPopUpButton *)menu withGood:(NSTextField *)goodLabel withBad:(NSTextField *)badLabel withIcon:(NSImageView *)badIcon withAccessory:(NSView *)acc {
if (self = [super init]) {
onlyRecommended = YES;
recom = [NSlang(lng_mac_recommended_apps) copy];
apps = recommendedApps;
panel = creator;
selector = menu;
good = goodLabel;
bad = badLabel;
icon = badIcon;
accessory = acc;
[selector setAction:@selector(menuDidClose)];
}
return self;
}
- (BOOL) isRecommended:(NSURL *)url {
if (apps) {
for (id app in apps) {
if ([(NSURL*)app isEquivalent:url]) {
return YES;
}
}
}
return NO;
}
- (BOOL) panel:(id)sender shouldEnableURL:(NSURL *)url {
NSNumber *isDirectory;
if ([url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil] && isDirectory != nil && [isDirectory boolValue]) {
if (onlyRecommended) {
CFStringRef ext = CFURLCopyPathExtension((CFURLRef)url);
NSNumber *isPackage;
if ([url getResourceValue:&isPackage forKey:NSURLIsPackageKey error:nil] && isPackage != nil && [isPackage boolValue]) {
return [self isRecommended:url];
}
}
return YES;
}
return NO;
}
- (void) panelSelectionDidChange:(id)sender {
NSArray *urls = [panel URLs];
if ([urls count]) {
if ([self isRecommended:[urls firstObject]]) {
[bad removeFromSuperview];
[icon removeFromSuperview];
[accessory addSubview:good];
} else {
[good removeFromSuperview];
[accessory addSubview:bad];
[accessory addSubview:icon];
}
} else {
[good removeFromSuperview];
[bad removeFromSuperview];
[icon removeFromSuperview];
}
}
- (void) menuDidClose {
onlyRecommended = [[[selector selectedItem] title] isEqualToString:recom];
[self refreshPanelTable];
}
- (BOOL) refreshDataInViews: (NSArray*)subviews {
for (id view in subviews) {
NSString *cls = [view className];
if ([cls isEqualToString:Q2NSString(strNeedToReload())]) {
[view reloadData];
} else if ([cls isEqualToString:Q2NSString(strNeedToRefresh1())] || [cls isEqualToString:Q2NSString(strNeedToRefresh2())]) {
[view reloadData];
return YES;
} else {
NSArray *next = [view subviews];
if ([next count] && [self refreshDataInViews:next]) {
return YES;
}
}
}
return NO;
}
- (void) refreshPanelTable {
@autoreleasepool {
[self refreshDataInViews:[[panel contentView] subviews]];
[panel validateVisibleColumns];
}
}
- (void) dealloc {
if (apps) {
[apps release];
[recom release];
}
[super dealloc];
}
@end
namespace Platform {
namespace File {
@ -40,5 +396,195 @@ QString UrlToLocal(const QUrl &url) {
return result;
}
bool UnsafeShowOpenWithDropdown(const QString &filepath, QPoint menuPosition) {
@autoreleasepool {
NSString *file = Q2NSString(filepath);
@try {
OpenFileWithInterface *menu = [[[OpenFileWithInterface alloc] init:file] autorelease];
auto r = QApplication::desktop()->screenGeometry(menuPosition);
auto x = menuPosition.x();
auto y = r.y() + r.height() - menuPosition.y();
return !![menu popupAtX:x andY:y];
}
@catch (NSException *exception) {
}
@finally {
}
}
return false;
}
bool UnsafeShowOpenWith(const QString &filepath) {
@autoreleasepool {
NSString *file = Q2NSString(filepath);
@try {
NSURL *url = [NSURL fileURLWithPath:file];
NSString *ext = [url pathExtension];
NSArray *names = [url pathComponents];
NSString *name = [names count] ? [names lastObject] : @"";
NSArray *apps = (NSArray*)LSCopyApplicationURLsForURL(CFURLRef(url), kLSRolesAll);
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
NSRect fullRect = { { 0., 0. }, { st::macAccessoryWidth, st::macAccessoryHeight } };
NSView *accessory = [[NSView alloc] initWithFrame:fullRect];
[accessory setAutoresizesSubviews:YES];
NSPopUpButton *selector = [[NSPopUpButton alloc] init];
[accessory addSubview:selector];
[selector addItemWithTitle:NSlang(lng_mac_recommended_apps)];
[selector addItemWithTitle:NSlang(lng_mac_all_apps)];
[selector sizeToFit];
NSTextField *enableLabel = [[NSTextField alloc] init];
[accessory addSubview:enableLabel];
[enableLabel setStringValue:NSlang(lng_mac_enable_filter)];
[enableLabel setFont:[selector font]];
[enableLabel setBezeled:NO];
[enableLabel setDrawsBackground:NO];
[enableLabel setEditable:NO];
[enableLabel setSelectable:NO];
[enableLabel sizeToFit];
NSRect selectorFrame = [selector frame], enableFrame = [enableLabel frame];
enableFrame.size.width += st::macEnableFilterAdd;
enableFrame.origin.x = (fullRect.size.width - selectorFrame.size.width - enableFrame.size.width) / 2.;
selectorFrame.origin.x = (fullRect.size.width - selectorFrame.size.width + enableFrame.size.width) / 2.;
enableFrame.origin.y = fullRect.size.height - selectorFrame.size.height - st::macEnableFilterTop + (selectorFrame.size.height - enableFrame.size.height) / 2.;
selectorFrame.origin.y = fullRect.size.height - selectorFrame.size.height - st::macSelectorTop;
[enableLabel setFrame:enableFrame];
[enableLabel setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
[selector setFrame:selectorFrame];
[selector setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
NSButton *button = [[NSButton alloc] init];
[accessory addSubview:button];
[button setButtonType:NSSwitchButton];
[button setFont:[selector font]];
[button setTitle:NSlang(lng_mac_always_open_with)];
[button sizeToFit];
NSRect alwaysRect = [button frame];
alwaysRect.origin.x = (fullRect.size.width - alwaysRect.size.width) / 2;
alwaysRect.origin.y = selectorFrame.origin.y - alwaysRect.size.height - st::macAlwaysThisAppTop;
[button setFrame:alwaysRect];
[button setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
#ifdef OS_MAC_STORE
[button setHidden:YES];
#endif // OS_MAC_STORE
NSTextField *goodLabel = [[NSTextField alloc] init];
[goodLabel setStringValue:Q2NSString(lng_mac_this_app_can_open(lt_file, NS2QString(name)))];
[goodLabel setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
[goodLabel setBezeled:NO];
[goodLabel setDrawsBackground:NO];
[goodLabel setEditable:NO];
[goodLabel setSelectable:NO];
[goodLabel sizeToFit];
NSRect goodFrame = [goodLabel frame];
goodFrame.origin.x = (fullRect.size.width - goodFrame.size.width) / 2.;
goodFrame.origin.y = alwaysRect.origin.y - goodFrame.size.height - st::macAppHintTop;
[goodLabel setFrame:goodFrame];
NSTextField *badLabel = [[NSTextField alloc] init];
[badLabel setStringValue:Q2NSString(lng_mac_not_known_app(lt_file, NS2QString(name)))];
[badLabel setFont:[goodLabel font]];
[badLabel setBezeled:NO];
[badLabel setDrawsBackground:NO];
[badLabel setEditable:NO];
[badLabel setSelectable:NO];
[badLabel sizeToFit];
NSImageView *badIcon = [[NSImageView alloc] init];
NSImage *badImage = [NSImage imageNamed:NSImageNameCaution];
[badIcon setImage:badImage];
[badIcon setFrame:NSMakeRect(0, 0, st::macCautionIconSize, st::macCautionIconSize)];
NSRect badFrame = [badLabel frame], badIconFrame = [badIcon frame];
badFrame.origin.x = (fullRect.size.width - badFrame.size.width + badIconFrame.size.width) / 2.;
badIconFrame.origin.x = (fullRect.size.width - badFrame.size.width - badIconFrame.size.width) / 2.;
badFrame.origin.y = alwaysRect.origin.y - badFrame.size.height - st::macAppHintTop;
badIconFrame.origin.y = badFrame.origin.y;
[badLabel setFrame:badFrame];
[badIcon setFrame:badIconFrame];
[openPanel setAccessoryView:accessory];
ChooseApplicationDelegate *delegate = [[ChooseApplicationDelegate alloc] init:apps withPanel:openPanel withSelector:selector withGood:goodLabel withBad:badLabel withIcon:badIcon withAccessory:accessory];
[openPanel setDelegate:delegate];
[openPanel setCanChooseDirectories:NO];
[openPanel setCanChooseFiles:YES];
[openPanel setAllowsMultipleSelection:NO];
[openPanel setResolvesAliases:YES];
[openPanel setTitle:NSlang(lng_mac_choose_app)];
[openPanel setMessage:Q2NSString(lng_mac_choose_text(lt_file, NS2QString(name)))];
NSArray *appsPaths = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationDirectory inDomains:NSLocalDomainMask];
if ([appsPaths count]) [openPanel setDirectoryURL:[appsPaths firstObject]];
[openPanel beginWithCompletionHandler:^(NSInteger result){
if (result == NSFileHandlingPanelOKButton) {
if ([[openPanel URLs] count] > 0) {
NSURL *app = [[openPanel URLs] objectAtIndex:0];
NSString *path = [app path];
if ([button state] == NSOnState) {
NSArray *UTIs = (NSArray *)UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension,
(CFStringRef)ext,
nil);
for (NSString *UTI in UTIs) {
OSStatus result = LSSetDefaultRoleHandlerForContentType((CFStringRef)UTI,
kLSRolesAll,
(CFStringRef)[[NSBundle bundleWithPath:path] bundleIdentifier]);
DEBUG_LOG(("App Info: set default handler for '%1' UTI result: %2").arg(NS2QString(UTI)).arg(result));
}
[UTIs release];
}
[[NSWorkspace sharedWorkspace] openFile:file withApplication:[app path]];
}
}
[selector release];
[button release];
[enableLabel release];
[goodLabel release];
[badLabel release];
[badIcon release];
[accessory release];
[delegate release];
}];
}
@catch (NSException *exception) {
[[NSWorkspace sharedWorkspace] openFile:file];
}
@finally {
}
}
return YES;
}
void UnsafeLaunch(const QString &filepath) {
@autoreleasepool {
NSString *file = Q2NSString(filepath);
if ([[NSWorkspace sharedWorkspace] openFile:file] == NO) {
UnsafeShowOpenWith(filepath);
}
}
}
void UnsafeShowInFolder(const QString &filepath) {
auto folder = QFileInfo(filepath).absolutePath();
@autoreleasepool {
[[NSWorkspace sharedWorkspace] selectFile:Q2NSString(filepath) inFileViewerRootedAtPath:Q2NSString(folder)];
}
}
} // namespace File
} // namespace Platform

View File

@ -293,7 +293,7 @@ void MainWindow::psUpdateWorkmode() {
trayIcon = nullptr;
}
}
if (auto manager = Platform::Notifications::ManagerNative()) {
if (auto manager = Platform::Notifications::GetNativeManager()) {
manager->updateDelegate();
}
}

View File

@ -36,10 +36,6 @@ inline bool SkipToast() {
return false;
}
inline Window::Notifications::Manager *GetManager() {
return GetNativeManager();
}
class Manager : public Window::Notifications::NativeManager {
public:
Manager();
@ -59,5 +55,9 @@ private:
};
inline Window::Notifications::Manager *GetManager() {
return GetNativeManager();
}
} // namespace Notifications
} // namespace Platform

View File

@ -20,20 +20,29 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "ui/filedialog.h"
#include "core/file_utilities.h"
namespace Platform {
namespace File {
QString UrlToLocal(const QUrl &url);
// All these functions may enter a nested event loop. Use with caution.
void UnsafeOpenEmailLink(const QString &email);
bool UnsafeShowOpenWithDropdown(const QString &filepath, QPoint menuPosition);
bool UnsafeShowOpenWith(const QString &filepath);
void UnsafeLaunch(const QString &filepath);
void UnsafeShowInFolder(const QString &filepath);
void PostprocessDownloaded(const QString &filepath);
} // namespace File
namespace FileDialog {
bool Supported();
void InitLastPath();
bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile);
bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile = QString());
} // namespace FileDialog
} // namespace Platform

View File

@ -180,7 +180,6 @@ void DeInit() {
if (WasCoInitialized) {
CoUninitialize();
}
AUDCLNT_E_NOT_INITIALIZED;
}
} // namespace Audio

View File

@ -21,3 +21,401 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "stdafx.h"
#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

View File

@ -30,16 +30,4 @@ inline QString UrlToLocal(const QUrl &url) {
}
} // namespace File
namespace FileDialog {
inline bool Supported() {
return false;
}
inline bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile) {
return false;
}
} // namespace FileDialog
} // namespace Platform

View File

@ -26,7 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "profile/profile_cover_drop_area.h"
#include "profile/profile_userpic_button.h"
#include "ui/widgets/buttons.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "ui/widgets/labels.h"
#include "observer_peer.h"
#include "boxes/confirmbox.h"

View File

@ -21,7 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once
#include "core/observer.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
namespace style {
struct RoundButton;

View File

@ -23,7 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "application.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "platform/linux/file_utilities_linux.h"
#include "localstorage.h"
#include <sys/stat.h>
@ -38,32 +38,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include <qpa/qplatformnativeinterface.h>
using namespace Platform;
using Platform::File::internal::EscapeShell;
namespace {
QByteArray escapeShell(const QByteArray &str) {
QByteArray result;
const char *b = str.constData(), *e = str.constEnd();
for (const char *ch = b; ch != e; ++ch) {
if (*ch == ' ' || *ch == '"' || *ch == '\'' || *ch == '\\') {
if (result.isEmpty()) {
result.reserve(str.size() * 2);
}
if (ch > b) {
result.append(b, ch - b);
}
result.append('\\');
b = ch;
}
}
if (result.isEmpty()) return str;
if (e > b) {
result.append(b, e - b);
}
return result;
}
class _PsEventFilter : public QAbstractNativeEventFilter {
public:
bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) {
@ -129,7 +107,7 @@ QStringList addr2linestr(uint64 *addresses, int count) {
if (!count) return result;
result.reserve(count);
QByteArray cmd = "addr2line -e " + escapeShell(QFile::encodeName(cExeDir() + cExeName()));
QByteArray cmd = "addr2line -e " + EscapeShell(QFile::encodeName(cExeDir() + cExeName()));
for (int i = 0; i < count; ++i) {
if (addresses[i]) {
cmd += qsl(" 0x%1").arg(addresses[i], 0, 16).toUtf8();
@ -387,18 +365,6 @@ int psFixPrevious() {
return 0;
}
void psPostprocessFile(const QString &name) {
}
void psOpenFile(const QString &name, bool openWith) {
QDesktopServices::openUrl(QUrl::fromLocalFile(name));
}
void psShowInFolder(const QString &name) {
Ui::hideLayer(true);
system(("xdg-open " + escapeShell(QFile::encodeName(QFileInfo(name).absoluteDir().absolutePath()))).constData());
}
namespace Platform {
void start() {
@ -503,7 +469,7 @@ void psRegisterCustomScheme() {
s << "Version=1.0\n";
s << "Name=Telegram Desktop\n";
s << "Comment=Official desktop version of Telegram messaging app\n";
s << "Exec=" << escapeShell(QFile::encodeName(cExeDir() + cExeName())) << " -- %u\n";
s << "Exec=" << EscapeShell(QFile::encodeName(cExeDir() + cExeName())) << " -- %u\n";
s << "Icon=telegram\n";
s << "Terminal=false\n";
s << "StartupWMClass=Telegram\n";
@ -512,11 +478,11 @@ void psRegisterCustomScheme() {
s << "MimeType=x-scheme-handler/tg;\n";
f.close();
if (_psRunCommand("desktop-file-install --dir=" + escapeShell(QFile::encodeName(home + qsl(".local/share/applications"))) + " --delete-original " + escapeShell(QFile::encodeName(file)))) {
if (_psRunCommand("desktop-file-install --dir=" + EscapeShell(QFile::encodeName(home + qsl(".local/share/applications"))) + " --delete-original " + EscapeShell(QFile::encodeName(file)))) {
DEBUG_LOG(("App Info: removing old .desktop file"));
QFile(qsl("%1.local/share/applications/telegram.desktop").arg(home)).remove();
_psRunCommand("update-desktop-database " + escapeShell(QFile::encodeName(home + qsl(".local/share/applications"))));
_psRunCommand("update-desktop-database " + EscapeShell(QFile::encodeName(home + qsl(".local/share/applications"))));
_psRunCommand("xdg-mime default telegramdesktop.desktop x-scheme-handler/tg");
}
} else {
@ -526,7 +492,7 @@ void psRegisterCustomScheme() {
#endif // !TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION
DEBUG_LOG(("App Info: registerting for Gnome"));
if (_psRunCommand("gconftool-2 -t string -s /desktop/gnome/url-handlers/tg/command " + escapeShell(escapeShell(QFile::encodeName(cExeDir() + cExeName())) + " -- %s"))) {
if (_psRunCommand("gconftool-2 -t string -s /desktop/gnome/url-handlers/tg/command " + EscapeShell(EscapeShell(QFile::encodeName(cExeDir() + cExeName())) + " -- %s"))) {
_psRunCommand("gconftool-2 -t bool -s /desktop/gnome/url-handlers/tg/needs_terminal false");
_psRunCommand("gconftool-2 -t bool -s /desktop/gnome/url-handlers/tg/enabled true");
}
@ -547,7 +513,7 @@ void psRegisterCustomScheme() {
QTextStream s(&f);
s.setCodec("UTF-8");
s << "[Protocol]\n";
s << "exec=" << QFile::decodeName(escapeShell(QFile::encodeName(cExeDir() + cExeName()))) << " -- %u\n";
s << "exec=" << QFile::decodeName(EscapeShell(QFile::encodeName(cExeDir() + cExeName()))) << " -- %u\n";
s << "protocol=tg\n";
s << "input=none\n";
s << "output=none\n";

View File

@ -63,12 +63,6 @@ int psFixPrevious();
void psExecUpdater();
void psExecTelegram(const QString &arg = QString());
bool psShowOpenWithMenu(int x, int y, const QString &file);
void psPostprocessFile(const QString &name);
void psOpenFile(const QString &name, bool openWith = false);
void psShowInFolder(const QString &name);
QAbstractNativeEventFilter *psNativeEventFilter();
void psNewVersion();

View File

@ -384,21 +384,6 @@ int psFixPrevious() {
return 0;
}
bool psShowOpenWithMenu(int x, int y, const QString &file) {
return objc_showOpenWithMenu(x, y, file);
}
void psPostprocessFile(const QString &name) {
}
void psOpenFile(const QString &name, bool openWith) {
objc_openFile(name, openWith);
}
void psShowInFolder(const QString &name) {
objc_showInFinder(name, QFileInfo(name).absolutePath());
}
namespace Platform {
void start() {
@ -413,7 +398,7 @@ void finish() {
}
bool TransparentWindowsSupported(QPoint globalPosition) {
return true;
return true;
}
namespace ThirdParty {
@ -468,35 +453,20 @@ bool psLaunchMaps(const LocationCoords &coords) {
QString strNotificationAboutThemeChange() {
const uint32 letters[] = { 0xE9005541, 0x5600DC70, 0x88001570, 0xF500D86C, 0x8100E165, 0xEE005949, 0x2900526E, 0xAE00FB74, 0x96000865, 0x7000CD72, 0x3B001566, 0x5F007361, 0xAE00B663, 0x74009A65, 0x29003054, 0xC6002668, 0x98003865, 0xFA00336D, 0xA3007A65, 0x93001443, 0xBB007868, 0xE100E561, 0x3500366E, 0xC0007A67, 0x0200CA65, 0xBE00DF64, 0xE300BB4E, 0x2900D26F, 0xD500D374, 0xE900E269, 0x86008F66, 0xC4006669, 0x1C00A863, 0xE600A761, 0x8E00EE74, 0xB300B169, 0xCF00B36F, 0xE600D36E };
return strMakeFromLetters(letters, sizeof(letters) / sizeof(letters[0]));
return strMakeFromLetters(letters);
}
QString strNotificationAboutScreenLocked() {
const uint32 letters[] = { 0x22008263, 0x0800DB6F, 0x45004F6D, 0xCC00972E, 0x0E00A861, 0x9700D970, 0xA100D570, 0x8900686C, 0xB300B365, 0xFE00DE2E, 0x76009B73, 0xFA00BF63, 0xE000A772, 0x9C009F65, 0x4E006065, 0xD900426E, 0xB7007849, 0x64006473, 0x6700824C, 0xE300706F, 0x7C00A063, 0x8F00D76B, 0x04001C65, 0x1C00A664 };
return strMakeFromLetters(letters, base::array_size(letters));
return strMakeFromLetters(letters);
}
QString strNotificationAboutScreenUnlocked() {
const uint32 letters[] = { 0x9200D763, 0xC8003C6F, 0xD2003F6D, 0x6000012E, 0x36004061, 0x4400E570, 0xA500BF70, 0x2E00796C, 0x4A009E65, 0x2E00612E, 0xC8001D73, 0x57002263, 0xF0005872, 0x49000765, 0xE5008D65, 0xE600D76E, 0xE8007049, 0x19005C73, 0x34009455, 0xB800B36E, 0xF300CA6C, 0x4C00806F, 0x5300A763, 0xD1003B6B, 0x63003565, 0xF800F264 };
return strMakeFromLetters(letters, base::array_size(letters));
return strMakeFromLetters(letters);
}
QString strStyleOfInterface() {
const uint32 letters[] = { 0xEF004041, 0x4C007F70, 0x1F007A70, 0x9E00A76C, 0x8500D165, 0x2E003749, 0x7B00526E, 0x3400E774, 0x3C00FA65, 0x6200B172, 0xF7001D66, 0x0B002961, 0x71008C63, 0x86005465, 0xA3006F53, 0x11006174, 0xCD001779, 0x8200556C, 0x6C009B65 };
return strMakeFromLetters(letters, sizeof(letters) / sizeof(letters[0]));
}
QString strNeedToReload() {
const uint32 letters[] = { 0x82007746, 0xBB00C649, 0x7E00235F, 0x9A00FE54, 0x4C004542, 0x91001772, 0x8A00D76F, 0xC700B977, 0x7F005F73, 0x34003665, 0x2300D572, 0x72002E54, 0x18001461, 0x14004A62, 0x5100CC6C, 0x83002365, 0x5A002C56, 0xA5004369, 0x26004265, 0x0D006577 };
return strMakeFromLetters(letters, sizeof(letters) / sizeof(letters[0]));
}
QString strNeedToRefresh1() {
const uint32 letters[] = { 0xEF006746, 0xF500CE49, 0x1500715F, 0x95001254, 0x3A00CB4C, 0x17009469, 0xB400DA73, 0xDE00C574, 0x9200EC56, 0x3C00A669, 0xFD00D865, 0x59000977 };
return strMakeFromLetters(letters, sizeof(letters) / sizeof(letters[0]));
}
QString strNeedToRefresh2() {
const uint32 letters[] = { 0x8F001546, 0xAF007A49, 0xB8002B5F, 0x1A000B54, 0x0D003E49, 0xE0003663, 0x4900796F, 0x0500836E, 0x9A00D156, 0x5E00FF69, 0x5900C765, 0x3D00D177 };
return strMakeFromLetters(letters, sizeof(letters) / sizeof(letters[0]));
return strMakeFromLetters(letters);
}

View File

@ -68,10 +68,6 @@ void psExecTelegram(const QString &crashreport = QString());
bool psShowOpenWithMenu(int x, int y, const QString &file);
void psPostprocessFile(const QString &name);
void psOpenFile(const QString &name, bool openWith = false);
void psShowInFolder(const QString &name);
QAbstractNativeEventFilter *psNativeEventFilter();
void psNewVersion();
@ -111,8 +107,5 @@ QString strNotificationAboutThemeChange();
QString strNotificationAboutScreenLocked();
QString strNotificationAboutScreenUnlocked();
QString strStyleOfInterface();
QString strNeedToReload();
QString strNeedToRefresh1();
QString strNeedToRefresh2();
bool psLaunchMaps(const LocationCoords &coords);

View File

@ -31,10 +31,6 @@ void objc_outputDebugString(const QString &str);
bool objc_idleSupported();
bool objc_idleTime(TimeMs &idleTime);
bool objc_showOpenWithMenu(int x, int y, const QString &file);
void objc_showInFinder(const QString &file, const QString &path);
void objc_openFile(const QString &file, bool openwith);
void objc_start();
void objc_finish();
bool objc_execUpdater();

View File

@ -325,516 +325,6 @@ bool objc_idleTime(TimeMs &idleTime) { // taken from https://github.com/trueinte
return true;
}
@interface OpenWithApp : NSObject {
NSString *fullname;
NSURL *app;
NSImage *icon;
}
@property (nonatomic, retain) NSString *fullname;
@property (nonatomic, retain) NSURL *app;
@property (nonatomic, retain) NSImage *icon;
@end
@implementation OpenWithApp
@synthesize fullname, app, icon;
- (void) dealloc {
[fullname release];
[app release];
[icon release];
[super dealloc];
}
@end
@interface OpenFileWithInterface : NSObject {
}
- (id) init:(NSString *)file;
- (BOOL) popupAtX:(int)x andY:(int)y;
- (void) itemChosen:(id)sender;
- (void) dealloc;
@end
@implementation OpenFileWithInterface {
NSString *toOpen;
NSURL *defUrl;
NSString *defBundle, *defName, *defVersion;
NSImage *defIcon;
NSMutableArray *apps;
NSMenu *menu;
}
- (void) fillAppByUrl:(NSURL*)url bundle:(NSString**)bundle name:(NSString**)name version:(NSString**)version icon:(NSImage**)icon {
NSBundle *b = [NSBundle bundleWithURL:url];
if (b) {
NSString *path = [url path];
*name = [[NSFileManager defaultManager] displayNameAtPath: path];
if (!*name) *name = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleDisplayName"];
if (!*name) *name = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleName"];
if (*name) {
*bundle = [b bundleIdentifier];
if (bundle) {
*version = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
*icon = [[NSWorkspace sharedWorkspace] iconForFile: path];
if (*icon && [*icon isValid]) [*icon setSize: CGSizeMake(16., 16.)];
return;
}
}
}
*bundle = *name = *version = nil;
*icon = nil;
}
- (id) init:(NSString*)file {
toOpen = [file retain];
if (self = [super init]) {
NSURL *url = [NSURL fileURLWithPath:file];
defUrl = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:url];
if (defUrl) {
[self fillAppByUrl:defUrl bundle:&defBundle name:&defName version:&defVersion icon:&defIcon];
if (!defBundle || !defName) {
defUrl = nil;
}
}
NSArray *appsList = (NSArray*)LSCopyApplicationURLsForURL(CFURLRef(url), kLSRolesAll);
NSMutableDictionary *data = [NSMutableDictionary dictionaryWithCapacity:16];
int fullcount = 0;
for (id app in appsList) {
if (fullcount > 15) break;
NSString *bundle = nil, *name = nil, *version = nil;
NSImage *icon = nil;
[self fillAppByUrl:(NSURL*)app bundle:&bundle name:&name version:&version icon:&icon];
if (bundle && name) {
if ([bundle isEqualToString:defBundle] && [version isEqualToString:defVersion]) continue;
NSString *key = [[NSArray arrayWithObjects:bundle, name, nil] componentsJoinedByString:@"|"];
if (!version) version = @"";
NSMutableDictionary *versions = (NSMutableDictionary*)[data objectForKey:key];
if (!versions) {
versions = [NSMutableDictionary dictionaryWithCapacity:2];
[data setValue:versions forKey:key];
}
if (![versions objectForKey:version]) {
[versions setValue:[NSArray arrayWithObjects:name, icon, app, nil] forKey:version];
++fullcount;
}
}
}
if (fullcount || defUrl) {
apps = [NSMutableArray arrayWithCapacity:fullcount];
for (id key in data) {
NSMutableDictionary *val = (NSMutableDictionary*)[data objectForKey:key];
for (id ver in val) {
NSArray *app = (NSArray*)[val objectForKey:ver];
OpenWithApp *a = [[OpenWithApp alloc] init];
NSString *fullname = (NSString*)[app objectAtIndex:0], *version = (NSString*)ver;
BOOL showVersion = ([val count] > 1);
if (!showVersion) {
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^\\d+\\.\\d+\\.\\d+(\\.\\d+)?$" options:NSRegularExpressionCaseInsensitive error:&error];
showVersion = ![regex numberOfMatchesInString:version options:NSMatchingWithoutAnchoringBounds range:{0,[version length]}];
}
if (showVersion) fullname = [[NSArray arrayWithObjects:fullname, @" (", version, @")", nil] componentsJoinedByString:@""];
[a setFullname:fullname];
[a setIcon:(NSImage*)[app objectAtIndex:1]];
[a setApp:(NSURL*)[app objectAtIndex:2]];
[apps addObject:a];
[a release];
}
}
}
[apps sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"fullname" ascending:YES]]];
[appsList release];
menu = nil;
}
return self;
}
- (BOOL) popupAtX:(int)x andY:(int)y {
if (![apps count] && !defName) return NO;
menu = [[NSMenu alloc] initWithTitle:@"Open With"];
int index = 0;
if (defName) {
NSMenuItem *item = [menu insertItemWithTitle:[[NSArray arrayWithObjects:defName, @" (default)", nil] componentsJoinedByString:@""] action:@selector(itemChosen:) keyEquivalent:@"" atIndex:index++];
if (defIcon) [item setImage:defIcon];
[item setTarget:self];
[menu insertItem:[NSMenuItem separatorItem] atIndex:index++];
}
if ([apps count]) {
for (id a in apps) {
OpenWithApp *app = (OpenWithApp*)a;
NSMenuItem *item = [menu insertItemWithTitle:[a fullname] action:@selector(itemChosen:) keyEquivalent:@"" atIndex:index++];
if ([app icon]) [item setImage:[app icon]];
[item setTarget:self];
}
[menu insertItem:[NSMenuItem separatorItem] atIndex:index++];
}
NSMenuItem *item = [menu insertItemWithTitle:NSlang(lng_mac_choose_program_menu) action:@selector(itemChosen:) keyEquivalent:@"" atIndex:index++];
[item setTarget:self];
[menu popUpMenuPositioningItem:nil atLocation:CGPointMake(x, y) inView:nil];
return YES;
}
- (void) itemChosen:(id)sender {
NSArray *items = [menu itemArray];
NSURL *url = nil;
for (int i = 0, l = [items count]; i < l; ++i) {
if ([items objectAtIndex:i] == sender) {
if (defName) i -= 2;
if (i < 0) {
url = defUrl;
} else if (i < int([apps count])) {
url = [(OpenWithApp*)[apps objectAtIndex:i] app];
}
break;
}
}
if (url) {
[[NSWorkspace sharedWorkspace] openFile:toOpen withApplication:[url path]];
} else {
objc_openFile(NS2QString(toOpen), true);
}
}
- (void) dealloc {
[toOpen release];
if (menu) [menu release];
[super dealloc];
}
@end
bool objc_showOpenWithMenu(int x, int y, const QString &f) {
@autoreleasepool {
NSString *file = Q2NSString(f);
@try {
OpenFileWithInterface *menu = [[[OpenFileWithInterface alloc] init:file] autorelease];
QRect r = QApplication::desktop()->screenGeometry(QPoint(x, y));
y = r.y() + r.height() - y;
return !![menu popupAtX:x andY:y];
}
@catch (NSException *exception) {
}
@finally {
}
}
return false;
}
void objc_showInFinder(const QString &file, const QString &path) {
@autoreleasepool {
[[NSWorkspace sharedWorkspace] selectFile:Q2NSString(file) inFileViewerRootedAtPath:Q2NSString(path)];
}
}
@interface NSURL(CompareUrls)
- (BOOL) isEquivalent:(NSURL *)aURL;
@end
@implementation NSURL(CompareUrls)
- (BOOL) isEquivalent:(NSURL *)aURL {
if ([self isEqual:aURL]) return YES;
if ([[self scheme] caseInsensitiveCompare:[aURL scheme]] != NSOrderedSame) return NO;
if ([[self host] caseInsensitiveCompare:[aURL host]] != NSOrderedSame) return NO;
if ([[self path] compare:[aURL path]] != NSOrderedSame) return NO;
if ([[self port] compare:[aURL port]] != NSOrderedSame) return NO;
if ([[self query] compare:[aURL query]] != NSOrderedSame) return NO;
return YES;
}
@end
@interface ChooseApplicationDelegate : NSObject<NSOpenSavePanelDelegate> {
}
- (id) init:(NSArray *)recommendedApps withPanel:(NSOpenPanel *)creator withSelector:(NSPopUpButton *)menu withGood:(NSTextField *)goodLabel withBad:(NSTextField *)badLabel withIcon:(NSImageView *)badIcon withAccessory:(NSView *)acc;
- (BOOL) panel:(id)sender shouldEnableURL:(NSURL *)url;
- (void) panelSelectionDidChange:(id)sender;
- (void) menuDidClose;
- (void) dealloc;
@end
@implementation ChooseApplicationDelegate {
BOOL onlyRecommended;
NSArray *apps;
NSOpenPanel *panel;
NSPopUpButton *selector;
NSTextField *good, *bad;
NSImageView *icon;
NSString *recom;
NSView *accessory;
}
- (id) init:(NSArray *)recommendedApps withPanel:(NSOpenPanel *)creator withSelector:(NSPopUpButton *)menu withGood:(NSTextField *)goodLabel withBad:(NSTextField *)badLabel withIcon:(NSImageView *)badIcon withAccessory:(NSView *)acc {
if (self = [super init]) {
onlyRecommended = YES;
recom = [NSlang(lng_mac_recommended_apps) copy];
apps = recommendedApps;
panel = creator;
selector = menu;
good = goodLabel;
bad = badLabel;
icon = badIcon;
accessory = acc;
[selector setAction:@selector(menuDidClose)];
}
return self;
}
- (BOOL) isRecommended:(NSURL *)url {
if (apps) {
for (id app in apps) {
if ([(NSURL*)app isEquivalent:url]) {
return YES;
}
}
}
return NO;
}
- (BOOL) panel:(id)sender shouldEnableURL:(NSURL *)url {
NSNumber *isDirectory;
if ([url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil] && isDirectory != nil && [isDirectory boolValue]) {
if (onlyRecommended) {
CFStringRef ext = CFURLCopyPathExtension((CFURLRef)url);
NSNumber *isPackage;
if ([url getResourceValue:&isPackage forKey:NSURLIsPackageKey error:nil] && isPackage != nil && [isPackage boolValue]) {
return [self isRecommended:url];
}
}
return YES;
}
return NO;
}
- (void) panelSelectionDidChange:(id)sender {
NSArray *urls = [panel URLs];
if ([urls count]) {
if ([self isRecommended:[urls firstObject]]) {
[bad removeFromSuperview];
[icon removeFromSuperview];
[accessory addSubview:good];
} else {
[good removeFromSuperview];
[accessory addSubview:bad];
[accessory addSubview:icon];
}
} else {
[good removeFromSuperview];
[bad removeFromSuperview];
[icon removeFromSuperview];
}
}
- (void) menuDidClose {
onlyRecommended = [[[selector selectedItem] title] isEqualToString:recom];
[self refreshPanelTable];
}
- (BOOL) refreshDataInViews: (NSArray*)subviews {
for (id view in subviews) {
NSString *cls = [view className];
if ([cls isEqualToString:Q2NSString(strNeedToReload())]) {
[view reloadData];
} else if ([cls isEqualToString:Q2NSString(strNeedToRefresh1())] || [cls isEqualToString:Q2NSString(strNeedToRefresh2())]) {
[view reloadData];
return YES;
} else {
NSArray *next = [view subviews];
if ([next count] && [self refreshDataInViews:next]) {
return YES;
}
}
}
return NO;
}
- (void) refreshPanelTable {
@autoreleasepool {
[self refreshDataInViews:[[panel contentView] subviews]];
[panel validateVisibleColumns];
}
}
- (void) dealloc {
if (apps) {
[apps release];
[recom release];
}
[super dealloc];
}
@end
void objc_openFile(const QString &f, bool openwith) {
@autoreleasepool {
NSString *file = Q2NSString(f);
if (openwith || [[NSWorkspace sharedWorkspace] openFile:file] == NO) {
@try {
NSURL *url = [NSURL fileURLWithPath:file];
NSString *ext = [url pathExtension];
NSArray *names = [url pathComponents];
NSString *name = [names count] ? [names lastObject] : @"";
NSArray *apps = (NSArray*)LSCopyApplicationURLsForURL(CFURLRef(url), kLSRolesAll);
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
NSRect fullRect = { { 0., 0. }, { st::macAccessoryWidth, st::macAccessoryHeight } };
NSView *accessory = [[NSView alloc] initWithFrame:fullRect];
[accessory setAutoresizesSubviews:YES];
NSPopUpButton *selector = [[NSPopUpButton alloc] init];
[accessory addSubview:selector];
[selector addItemWithTitle:NSlang(lng_mac_recommended_apps)];
[selector addItemWithTitle:NSlang(lng_mac_all_apps)];
[selector sizeToFit];
NSTextField *enableLabel = [[NSTextField alloc] init];
[accessory addSubview:enableLabel];
[enableLabel setStringValue:NSlang(lng_mac_enable_filter)];
[enableLabel setFont:[selector font]];
[enableLabel setBezeled:NO];
[enableLabel setDrawsBackground:NO];
[enableLabel setEditable:NO];
[enableLabel setSelectable:NO];
[enableLabel sizeToFit];
NSRect selectorFrame = [selector frame], enableFrame = [enableLabel frame];
enableFrame.size.width += st::macEnableFilterAdd;
enableFrame.origin.x = (fullRect.size.width - selectorFrame.size.width - enableFrame.size.width) / 2.;
selectorFrame.origin.x = (fullRect.size.width - selectorFrame.size.width + enableFrame.size.width) / 2.;
enableFrame.origin.y = fullRect.size.height - selectorFrame.size.height - st::macEnableFilterTop + (selectorFrame.size.height - enableFrame.size.height) / 2.;
selectorFrame.origin.y = fullRect.size.height - selectorFrame.size.height - st::macSelectorTop;
[enableLabel setFrame:enableFrame];
[enableLabel setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
[selector setFrame:selectorFrame];
[selector setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
NSButton *button = [[NSButton alloc] init];
[accessory addSubview:button];
[button setButtonType:NSSwitchButton];
[button setFont:[selector font]];
[button setTitle:NSlang(lng_mac_always_open_with)];
[button sizeToFit];
NSRect alwaysRect = [button frame];
alwaysRect.origin.x = (fullRect.size.width - alwaysRect.size.width) / 2;
alwaysRect.origin.y = selectorFrame.origin.y - alwaysRect.size.height - st::macAlwaysThisAppTop;
[button setFrame:alwaysRect];
[button setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin];
#ifdef OS_MAC_STORE
[button setHidden:YES];
#endif // OS_MAC_STORE
NSTextField *goodLabel = [[NSTextField alloc] init];
[goodLabel setStringValue:Q2NSString(lng_mac_this_app_can_open(lt_file, NS2QString(name)))];
[goodLabel setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
[goodLabel setBezeled:NO];
[goodLabel setDrawsBackground:NO];
[goodLabel setEditable:NO];
[goodLabel setSelectable:NO];
[goodLabel sizeToFit];
NSRect goodFrame = [goodLabel frame];
goodFrame.origin.x = (fullRect.size.width - goodFrame.size.width) / 2.;
goodFrame.origin.y = alwaysRect.origin.y - goodFrame.size.height - st::macAppHintTop;
[goodLabel setFrame:goodFrame];
NSTextField *badLabel = [[NSTextField alloc] init];
[badLabel setStringValue:Q2NSString(lng_mac_not_known_app(lt_file, NS2QString(name)))];
[badLabel setFont:[goodLabel font]];
[badLabel setBezeled:NO];
[badLabel setDrawsBackground:NO];
[badLabel setEditable:NO];
[badLabel setSelectable:NO];
[badLabel sizeToFit];
NSImageView *badIcon = [[NSImageView alloc] init];
NSImage *badImage = [NSImage imageNamed:NSImageNameCaution];
[badIcon setImage:badImage];
[badIcon setFrame:NSMakeRect(0, 0, st::macCautionIconSize, st::macCautionIconSize)];
NSRect badFrame = [badLabel frame], badIconFrame = [badIcon frame];
badFrame.origin.x = (fullRect.size.width - badFrame.size.width + badIconFrame.size.width) / 2.;
badIconFrame.origin.x = (fullRect.size.width - badFrame.size.width - badIconFrame.size.width) / 2.;
badFrame.origin.y = alwaysRect.origin.y - badFrame.size.height - st::macAppHintTop;
badIconFrame.origin.y = badFrame.origin.y;
[badLabel setFrame:badFrame];
[badIcon setFrame:badIconFrame];
[openPanel setAccessoryView:accessory];
ChooseApplicationDelegate *delegate = [[ChooseApplicationDelegate alloc] init:apps withPanel:openPanel withSelector:selector withGood:goodLabel withBad:badLabel withIcon:badIcon withAccessory:accessory];
[openPanel setDelegate:delegate];
[openPanel setCanChooseDirectories:NO];
[openPanel setCanChooseFiles:YES];
[openPanel setAllowsMultipleSelection:NO];
[openPanel setResolvesAliases:YES];
[openPanel setTitle:NSlang(lng_mac_choose_app)];
[openPanel setMessage:Q2NSString(lng_mac_choose_text(lt_file, NS2QString(name)))];
NSArray *appsPaths = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationDirectory inDomains:NSLocalDomainMask];
if ([appsPaths count]) [openPanel setDirectoryURL:[appsPaths firstObject]];
[openPanel beginWithCompletionHandler:^(NSInteger result){
if (result == NSFileHandlingPanelOKButton) {
if ([[openPanel URLs] count] > 0) {
NSURL *app = [[openPanel URLs] objectAtIndex:0];
NSString *path = [app path];
if ([button state] == NSOnState) {
NSArray *UTIs = (NSArray *)UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension,
(CFStringRef)ext,
nil);
for (NSString *UTI in UTIs) {
OSStatus result = LSSetDefaultRoleHandlerForContentType((CFStringRef)UTI,
kLSRolesAll,
(CFStringRef)[[NSBundle bundleWithPath:path] bundleIdentifier]);
DEBUG_LOG(("App Info: set default handler for '%1' UTI result: %2").arg(NS2QString(UTI)).arg(result));
}
[UTIs release];
}
[[NSWorkspace sharedWorkspace] openFile:file withApplication:[app path]];
}
}
[selector release];
[button release];
[enableLabel release];
[goodLabel release];
[badLabel release];
[badIcon release];
[accessory release];
[delegate release];
}];
}
@catch (NSException *exception) {
[[NSWorkspace sharedWorkspace] openFile:file];
}
@finally {
}
}
}
}
void objc_start() {
_sharedDelegate = [[ApplicationDelegate alloc] init];
[[NSApplication sharedApplication] setDelegate:_sharedDelegate];

View File

@ -525,211 +525,6 @@ int psFixPrevious() {
return 0;
}
void psPostprocessFile(const QString &name) {
std::wstring zoneFile = QDir::toNativeSeparators(name).toStdWString() + L":Zone.Identifier";
HANDLE f = CreateFile(zoneFile.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;
}
}
HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &, int hbitmapFormat);
namespace {
struct OpenWithApp {
OpenWithApp(const QString &name, HBITMAP icon, IAssocHandler *handler) : name(name), icon(icon), handler(handler) {
}
OpenWithApp(const QString &name, IAssocHandler *handler) : name(name), icon(0), handler(handler) {
}
void destroy() {
if (icon) DeleteBitmap(icon);
if (handler) handler->Release();
}
QString name;
HBITMAP icon;
IAssocHandler *handler;
};
bool OpenWithAppLess(const OpenWithApp &a, const OpenWithApp &b) {
return a.name < b.name;
}
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);
// return result;
}
}
bool psShowOpenWithMenu(int x, int y, const QString &file) {
if (!useOpenWith || !App::wnd()) return false;
bool result = false;
QList<OpenWithApp> handlers;
IShellItem* pItem = nullptr;
if (SUCCEEDED(Dlls::SHCreateItemFromParsingName(QDir::toNativeSeparators(file).toStdWString().c_str(), nullptr, IID_PPV_ARGS(&pItem)))) {
IEnumAssocHandlers *assocHandlers = 0;
if (SUCCEEDED(pItem->BindToHandler(nullptr, BHID_EnumAssocHandlers, IID_PPV_ARGS(&assocHandlers)))) {
HRESULT hr = S_FALSE;
do
{
IAssocHandler *handler = 0;
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), _iconToBitmap(icon, iconindex), handler));
CoTaskMemFree(icon);
} else {
handlers.push_back(OpenWithApp(QString::fromWCharArray(name), handler));
}
CoTaskMemFree(name);
} else {
handler->Release();
}
} while (hr != S_FALSE);
assocHandlers->Release();
}
if (!handlers.isEmpty()) {
HMENU menu = CreatePopupMenu();
std::sort(handlers.begin(), handlers.end(), OpenWithAppLess);
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 (handlers.at(i).icon) {
menuInfo.fMask |= MIIM_BITMAP;
menuInfo.hbmpItem = handlers.at(i).icon;
}
QString name = handlers.at(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, x, y, 0, App::wnd()->psHwnd(), 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.at(sel - 1).handler->Invoke(dataObj);
dataObj->Release();
result = true;
}
}
} else {
result = true;
}
for (int i = 0, l = handlers.size(); i < l; ++i) {
handlers[i].destroy();
}
}
pItem->Release();
}
return result;
}
void psOpenFile(const QString &name, bool openWith) {
base::TaskQueue::Main().Put([name, openWith] {
bool mailtoScheme = name.startsWith(qstr("mailto:"));
std::wstring wname = mailtoScheme ? name.toStdWString() : QDir::toNativeSeparators(name).toStdWString();
if (openWith && useOpenAs) {
if (Dlls::SHOpenWithDialog) {
OPENASINFO info;
info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC;
if (mailtoScheme) info.oaifInFlags |= OAIF_FILE_IS_URI | OAIF_URL_PROTOCOL;
info.pcszClass = NULL;
info.pcszFile = wname.c_str();
Dlls::SHOpenWithDialog(0, &info);
} else {
Dlls::OpenAs_RunDLL(0, 0, wname.c_str(), SW_SHOWNORMAL);
}
} else {
ShellExecute(0, L"open", wname.c_str(), 0, 0, SW_SHOWNORMAL);
}
});
}
void psShowInFolder(const QString &name) {
base::TaskQueue::Main().Put([name] {
auto nameEscaped = QDir::toNativeSeparators(name).replace('"', qsl("\"\""));
ShellExecute(0, 0, qsl("explorer").toStdWString().c_str(), (qsl("/select,") + nameEscaped).toStdWString().c_str(), 0, SW_SHOWNORMAL);
});
}
namespace Platform {
void start() {

View File

@ -63,12 +63,6 @@ int psFixPrevious();
void psExecUpdater();
void psExecTelegram(const QString &arg = QString());
bool psShowOpenWithMenu(int x, int y, const QString &file);
void psPostprocessFile(const QString &name);
void psOpenFile(const QString &name, bool openWith = false);
void psShowInFolder(const QString &name);
QAbstractNativeEventFilter *psNativeEventFilter();
void psNewVersion();

View File

@ -22,7 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "settings/settings_block_widget.h"
#include "ui/effects/radial_animation.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
namespace Settings {

View File

@ -21,7 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once
#include "core/observer.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "settings/settings_block_widget.h"

View File

@ -33,7 +33,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "boxes/languagebox.h"
#include "boxes/confirmbox.h"
#include "boxes/aboutbox.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "langloaderplain.h"
#include "autoupdater.h"

View File

@ -21,7 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once
#include "settings/settings_block_widget.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
namespace Settings {

View File

@ -34,7 +34,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "localstorage.h"
#include "boxes/confirmbox.h"
#include "application.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "window/themes/window_theme.h"
namespace Settings {

View File

@ -30,6 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwindow.h"
#include "window/notifications_manager.h"
#include "boxes/notifications_box.h"
#include "platform/platform_notifications_manager.h"
namespace Settings {
@ -72,16 +73,14 @@ void NotificationsWidget::createNotificationsControls() {
style::margins margin(0, 0, 0, st::settingsSkip);
style::margins slidedPadding(0, margin.bottom() / 2, 0, margin.bottom() - (margin.bottom() / 2));
QString nativeNotificationsLabel;
auto nativeNotificationsLabel = QString();
if (Platform::Notifications::Supported()) {
#ifdef Q_OS_WIN
if (App::wnd()->psHasNativeNotifications()) {
nativeNotificationsLabel = lang(lng_settings_use_windows);
}
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32
if (App::wnd()->psHasNativeNotifications()) {
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_WIN
nativeNotificationsLabel = lang(lng_settings_use_native_notifications);
#endif // Q_OS_WIN || Q_OS_LINUX64 || Q_OS_LINUX32
}
#endif // Q_OS_WIN
if (!nativeNotificationsLabel.isEmpty()) {
addChildRow(_nativeNotifications, margin, nativeNotificationsLabel, SLOT(onNativeNotifications()), Global::NativeNotifications());
}

View File

@ -35,7 +35,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "boxes/confirmbox.h"
#include "lang.h"
#include "messenger.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "window/themes/window_theme.h"
#include "window/themes/window_theme_editor.h"
@ -93,7 +93,7 @@ void fillCodes() {
}
});
Codes.insert(qsl("loadcolors"), []() {
FileDialog::askOpenPath("Open palette file", "Palette (*.tdesktop-palette)", [](const FileDialog::OpenResult &result) {
FileDialog::GetOpenPath("Open palette file", "Palette (*.tdesktop-palette)", [](const FileDialog::OpenResult &result) {
if (!result.paths.isEmpty()) {
Window::Theme::Apply(result.paths.front());
}

View File

@ -28,7 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "application.h"
#include "fileuploader.h"
#include "mainwindow.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "apiwrap.h"
#include "boxes/confirmbox.h"
#include "media/media_audio.h"
@ -1191,16 +1191,14 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context,
} else {
auto filepath = location.name();
if (documentIsValidMediaFile(filepath)) {
psOpenFile(filepath);
} else {
psShowInFolder(filepath);
File::Launch(filepath);
}
}
if (App::main()) App::main()->mediaMarkRead(data);
} else if (data->voice() || data->song() || data->isVideo()) {
auto filepath = location.name();
if (documentIsValidMediaFile(filepath)) {
psOpenFile(filepath);
File::Launch(filepath);
}
if (App::main()) App::main()->mediaMarkRead(data);
} else if (data->size < App::kImageSizeLimit) {
@ -1218,14 +1216,14 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context,
App::wnd()->showDocument(data, context);
}
} else {
psOpenFile(location.name());
File::Launch(location.name());
}
location.accessDisable();
} else {
psOpenFile(location.name());
File::Launch(location.name());
}
} else {
psOpenFile(location.name());
File::Launch(location.name());
}
return;
}
@ -1256,10 +1254,7 @@ void DocumentSaveClickHandler::doSave(DocumentData *data, bool forceSavingAs) {
auto filepath = data->filepath(DocumentData::FilePathResolveSaveFromDataSilent, forceSavingAs);
if (!filepath.isEmpty() && !forceSavingAs) {
auto pos = QCursor::pos();
if (!psShowOpenWithMenu(pos.x(), pos.y(), filepath)) {
psOpenFile(filepath, true);
}
File::OpenWith(filepath, QCursor::pos());
} else {
auto fileinfo = QFileInfo(filepath);
auto filedir = filepath.isEmpty() ? QDir() : fileinfo.dir();
@ -1499,14 +1494,11 @@ void DocumentData::performActionOnLoad() {
if (already.isEmpty()) return;
if (_actionOnLoad == ActionOnLoadOpenWith) {
QPoint pos(QCursor::pos());
if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) {
psOpenFile(already, true);
}
File::OpenWith(already, QCursor::pos());
} else if (_actionOnLoad == ActionOnLoadOpen || _actionOnLoad == ActionOnLoadPlayInline) {
if (voice() || song() || isVideo()) {
if (documentIsValidMediaFile(already)) {
psOpenFile(already);
File::Launch(already);
}
if (App::main()) App::main()->mediaMarkRead(this);
} else if (loc.accessEnable()) {
@ -1517,11 +1509,11 @@ void DocumentData::performActionOnLoad() {
App::wnd()->showDocument(this, item);
}
} else {
psOpenFile(already);
File::Launch(already);
}
loc.accessDisable();
} else {
psOpenFile(already);
File::Launch(already);
}
}
}

View File

@ -39,7 +39,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "core/task_queue.h"
#include "core/zlib_help.h"
#include "ui/toast/toast.h"
#include "ui/filedialog.h"
#include "core/file_utilities.h"
#include "boxes/editcolorbox.h"
#include "lang.h"
@ -635,7 +635,7 @@ void ThemeExportBox::updateThumbnail() {
}
void ThemeExportBox::chooseBackgroundFromFile() {
FileDialog::askOpenPath(lang(lng_theme_editor_choose_image), "Image files (*.jpeg *.jpg *.png)", base::lambda_guarded(this, [this](const FileDialog::OpenResult &result) {
FileDialog::GetOpenPath(lang(lng_theme_editor_choose_image), "Image files (*.jpeg *.jpg *.png)", base::lambda_guarded(this, [this](const FileDialog::OpenResult &result) {
auto content = result.remoteContent;
if (!result.paths.isEmpty()) {
QFile f(result.paths.front());
@ -664,7 +664,7 @@ void ThemeExportBox::exportTheme() {
auto caption = lang(lng_theme_editor_choose_name);
auto filter = "Themes (*.tdesktop-theme)";
auto name = "awesome.tdesktop-theme";
FileDialog::askWritePath(caption, filter, name, base::lambda_guarded(this, [this](const QString &path) {
FileDialog::GetWritePath(caption, filter, name, base::lambda_guarded(this, [this](const QString &path) {
zlib::FileToWrite zip;
zip_fileinfo zfi = { { 0, 0, 0, 0, 0, 0 }, 0, 0, 0 };
@ -798,7 +798,7 @@ void Editor::paintEvent(QPaintEvent *e) {
void Editor::Start() {
auto palettePath = Local::themePaletteAbsolutePath();
if (palettePath.isEmpty()) {
FileDialog::askWritePath(lang(lng_theme_editor_save_palette), "Palette (*.tdesktop-palette)", "colors.tdesktop-palette", [](const QString &path) {
FileDialog::GetWritePath(lang(lng_theme_editor_save_palette), "Palette (*.tdesktop-palette)", "colors.tdesktop-palette", [](const QString &path) {
if (!Local::copyThemeColorsToPalette(path)) {
writeDefaultPalette(path);
}

View File

@ -213,6 +213,8 @@
'<(src_loc)/core/click_handler.h',
'<(src_loc)/core/click_handler_types.cpp',
'<(src_loc)/core/click_handler_types.h',
'<(src_loc)/core/file_utilities.cpp',
'<(src_loc)/core/file_utilities.h',
'<(src_loc)/core/lambda.h',
'<(src_loc)/core/observer.cpp',
'<(src_loc)/core/observer.h',
@ -563,8 +565,6 @@
'<(src_loc)/ui/countryinput.h',
'<(src_loc)/ui/emoji_config.cpp',
'<(src_loc)/ui/emoji_config.h',
'<(src_loc)/ui/filedialog.cpp',
'<(src_loc)/ui/filedialog.h',
'<(src_loc)/ui/images.cpp',
'<(src_loc)/ui/images.h',
'<(src_loc)/ui/twidget.cpp',