diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7870ee465..e743d7ed4 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1825,6 +1825,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_language_not_ready_about" = "Unfortunately, this custom language pack ({lang_name}) doesn't contain data for Telegram Desktop. You can contribute to this language pack using the {link}."; "lng_language_not_ready_link" = "translations platform"; +"lng_launch_exe_warning" = "This file has a {extension} extension.\nAre you sure you want to run it?"; +"lng_launch_exe_sure" = "Run"; +"lng_launch_exe_dont_ask" = "Don't ask me again"; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/SourceFiles/auth_session.cpp b/Telegram/SourceFiles/auth_session.cpp index d9042b239..a6636ac8d 100644 --- a/Telegram/SourceFiles/auth_session.cpp +++ b/Telegram/SourceFiles/auth_session.cpp @@ -45,7 +45,7 @@ AuthSessionSettings::Variables::Variables() } QByteArray AuthSessionSettings::serialize() const { - auto size = sizeof(qint32) * 10; + auto size = sizeof(qint32) * 23; for (auto i = _variables.soundOverrides.cbegin(), e = _variables.soundOverrides.cend(); i != e; ++i) { size += Serialize::stringSize(i.key()) + Serialize::stringSize(i.value()); } @@ -87,6 +87,7 @@ QByteArray AuthSessionSettings::serialize() const { stream << qint32(_variables.supportChatsTimeSlice.current()); stream << qint32(_variables.includeMutedCounter ? 1 : 0); stream << qint32(_variables.countUnreadMessages ? 1 : 0); + stream << qint32(_variables.exeLaunchWarning ? 1 : 0); } return result; } @@ -120,6 +121,7 @@ void AuthSessionSettings::constructFromSerialized(const QByteArray &serialized) qint32 supportChatsTimeSlice = _variables.supportChatsTimeSlice.current(); qint32 includeMutedCounter = _variables.includeMutedCounter ? 1 : 0; qint32 countUnreadMessages = _variables.countUnreadMessages ? 1 : 0; + qint32 exeLaunchWarning = _variables.exeLaunchWarning ? 1 : 0; stream >> selectorTab; stream >> lastSeenWarningSeen; @@ -190,6 +192,9 @@ void AuthSessionSettings::constructFromSerialized(const QByteArray &serialized) stream >> includeMutedCounter; stream >> countUnreadMessages; } + if (!stream.atEnd()) { + stream >> exeLaunchWarning; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for AuthSessionSettings::constructFromSerialized()")); @@ -253,6 +258,7 @@ void AuthSessionSettings::constructFromSerialized(const QByteArray &serialized) _variables.hadLegacyCallsPeerToPeerNobody = (legacyCallsPeerToPeer == kLegacyCallsPeerToPeerNobody); _variables.includeMutedCounter = (includeMutedCounter == 1); _variables.countUnreadMessages = (countUnreadMessages == 1); + _variables.exeLaunchWarning = (exeLaunchWarning == 1); } void AuthSessionSettings::setSupportChatsTimeSlice(int slice) { diff --git a/Telegram/SourceFiles/auth_session.h b/Telegram/SourceFiles/auth_session.h index 16e79298c..71d0c90ef 100644 --- a/Telegram/SourceFiles/auth_session.h +++ b/Telegram/SourceFiles/auth_session.h @@ -199,6 +199,12 @@ public: void setCountUnreadMessages(bool value) { _variables.countUnreadMessages = value; } + bool exeLaunchWarning() const { + return _variables.exeLaunchWarning; + } + void setExeLaunchWarning(bool warning) { + _variables.exeLaunchWarning = warning; + } private: struct Variables { @@ -227,6 +233,7 @@ private: bool hadLegacyCallsPeerToPeerNobody = false; bool includeMutedCounter = true; bool countUnreadMessages = true; + bool exeLaunchWarning = true; static constexpr auto kDefaultSupportChatsLimitSlice = 7 * 24 * 60 * 60; diff --git a/Telegram/SourceFiles/boxes/abstract_box.cpp b/Telegram/SourceFiles/boxes/abstract_box.cpp index ea7348809..7e0587403 100644 --- a/Telegram/SourceFiles/boxes/abstract_box.cpp +++ b/Telegram/SourceFiles/boxes/abstract_box.cpp @@ -145,6 +145,16 @@ void BoxContent::onInnerResize() { updateShadowsVisibility(); } +void BoxContent::setDimensionsToContent( + int newWidth, + not_null content) { + content->resizeToWidth(newWidth); + content->heightValue( + ) | rpl::start_with_next([=](int height) { + setDimensions(newWidth, height); + }, content->lifetime()); +} + void BoxContent::setInnerTopSkip(int innerTopSkip, bool scrollBottomFixed) { if (_innerTopSkip != innerTopSkip) { auto delta = innerTopSkip - _innerTopSkip; diff --git a/Telegram/SourceFiles/boxes/abstract_box.h b/Telegram/SourceFiles/boxes/abstract_box.h index e58ef9efb..5da8eeb8a 100644 --- a/Telegram/SourceFiles/boxes/abstract_box.h +++ b/Telegram/SourceFiles/boxes/abstract_box.h @@ -152,6 +152,9 @@ protected: void setDimensions(int newWidth, int maxHeight) { getDelegate()->setDimensions(newWidth, maxHeight); } + void setDimensionsToContent( + int newWidth, + not_null content); void setInnerTopSkip(int topSkip, bool scrollBottomFixed = false); void setInnerBottomSkip(int bottomSkip); diff --git a/Telegram/SourceFiles/boxes/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp index 82897244b..9ae4d0a8a 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/boxes/confirm_box.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" +#include "ui/wrap/vertical_layout.h" #include "ui/toast/toast.h" #include "ui/image/image.h" #include "ui/empty_userpic.h" @@ -803,3 +804,51 @@ void ConfirmInviteBox::paintEvent(QPaintEvent *e) { } ConfirmInviteBox::~ConfirmInviteBox() = default; + +ConfirmDontWarnBox::ConfirmDontWarnBox( + QWidget*, + const QString &text, + const QString &checkbox, + const QString &confirm, + FnMut callback) +: _confirm(confirm) +, _content(setupContent(text, checkbox, std::move(callback))) { +} + +void ConfirmDontWarnBox::prepare() { + setDimensionsToContent(st::boxWidth, _content); + addButton([=] { return _confirm; }, [=] { _callback(); }); + addButton(langFactory(lng_cancel), [=] { closeBox(); }); +} + +not_null ConfirmDontWarnBox::setupContent( + const QString &text, + const QString &checkbox, + FnMut callback) { + const auto result = Ui::CreateChild(this); + result->add( + object_ptr( + result, + text, + Ui::FlatLabel::InitType::Rich, + st::boxLabel), + st::boxPadding); + const auto control = result->add( + object_ptr( + result, + checkbox, + false, + st::defaultBoxCheckbox), + style::margins( + st::boxPadding.left(), + st::boxPadding.bottom(), + st::boxPadding.right(), + st::boxPadding.bottom())); + _callback = [=, callback = std::move(callback)]() mutable { + const auto checked = control->checked(); + auto local = std::move(callback); + closeBox(); + local(checked); + }; + return result; +} diff --git a/Telegram/SourceFiles/boxes/confirm_box.h b/Telegram/SourceFiles/boxes/confirm_box.h index 3f6c9c611..b513d51f3 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.h +++ b/Telegram/SourceFiles/boxes/confirm_box.h @@ -232,3 +232,27 @@ private: int _userWidth = 0; }; + +class ConfirmDontWarnBox : public BoxContent { +public: + ConfirmDontWarnBox( + QWidget*, + const QString &text, + const QString &checkbox, + const QString &confirm, + FnMut callback); + +protected: + void prepare() override; + +private: + not_null setupContent( + const QString &text, + const QString &checkbox, + FnMut callback); + + QString _confirm; + FnMut _callback; + not_null _content; + +}; diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index 8d7110ab7..bbe8892bb 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -718,11 +718,7 @@ void ProxyBox::prepare() { setTitle(langFactory(lng_proxy_edit)); refreshButtons(); - - _content->heightValue( - ) | rpl::start_with_next([=](int height) { - setDimensions(st::boxWideWidth, height); - }, _content->lifetime()); + setDimensionsToContent(st::boxWideWidth, _content); } void ProxyBox::refreshButtons() { @@ -905,8 +901,6 @@ void ProxyBox::setupControls(const ProxyData &data) { setupCredentials(data); setupMtprotoCredentials(data); - _content->resizeToWidth(st::boxWideWidth); - const auto handleType = [=](Type type) { _credentials->toggle( type == Type::Http || type == Type::Socks5, @@ -1032,15 +1026,7 @@ void AutoDownloadBox::setupContent() { }); addButton(langFactory(lng_cancel), [=] { closeBox(); }); - widthValue( - ) | rpl::start_with_next([=](int width) { - content->resizeToWidth(width); - }, content->lifetime()); - - content->heightValue( - ) | rpl::start_with_next([=](int height) { - setDimensions(st::boxWideWidth, height); - }, content->lifetime()); + setDimensionsToContent(st::boxWideWidth, content); } ProxiesBoxController::ProxiesBoxController() diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index d6491c914..5ec4f2c25 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -221,8 +221,6 @@ object_ptr Controller::createContent() { _wrap->add(createUpgradeButton()); _wrap->add(createDeleteButton()); - _wrap->resizeToWidth(st::boxWideWidth); - return result; } @@ -1451,10 +1449,7 @@ void EditPeerInfoBox::prepare() { [=] { controller->setFocus(); }, lifetime()); auto content = controller->createContent(); - content->heightValue( - ) | rpl::start_with_next([this](int height) { - setDimensions(st::boxWideWidth, height); - }, content->lifetime()); + setDimensionsToContent(st::boxWideWidth, content); setInnerWidget(object_ptr( this, std::move(content))); diff --git a/Telegram/SourceFiles/boxes/peers/manage_peer_box.cpp b/Telegram/SourceFiles/boxes/peers/manage_peer_box.cpp index 1cfdd2f9b..54abd18d1 100644 --- a/Telegram/SourceFiles/boxes/peers/manage_peer_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/manage_peer_box.cpp @@ -218,14 +218,7 @@ void ManagePeerBox::prepare() { } void ManagePeerBox::setupContent() { - auto content = Ui::CreateChild(this); + const auto content = Ui::CreateChild(this); FillManageBox(App::wnd()->controller(), _channel, content); - widthValue( - ) | rpl::start_with_next([=](int width) { - content->resizeToWidth(width); - }, content->lifetime()); - content->heightValue( - ) | rpl::start_with_next([=](int height) { - setDimensions(st::boxWidth, height); - }, content->lifetime()); + setDimensionsToContent(st::boxWidth, content); } diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 48ef207cb..5b93e7ee7 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_media_types.h" #include "window/window_controller.h" #include "storage/cache/storage_cache_database.h" +#include "boxes/confirm_box.h" #include "ui/image/image.h" #include "ui/image/image_source.h" #include "auth_session.h" @@ -65,6 +66,29 @@ QString JoinStringList(const QStringList &list, const QString &separator) { return result; } +void LaunchWithWarning(const QString &name) { + if (!Data::IsExecutableName(name) + || !Auth().settings().exeLaunchWarning()) { + File::Launch(name); + return; + } + const auto extension = '.' + Data::FileExtension(name); + const auto callback = [=](bool checked) { + if (checked) { + Auth().settings().setExeLaunchWarning(false); + Auth().saveSettingsDelayed(); + } + File::Launch(name); + }; + Ui::show(Box( + lng_launch_exe_warning( + lt_extension, + textcmdStartSemibold() + extension + textcmdStopSemibold()), + lang(lng_launch_exe_dont_ask), + lang(lng_launch_exe_sure), + callback)); +} + } // namespace bool fileIsImage(const QString &name, const QString &mime) { @@ -308,15 +332,15 @@ void DocumentOpenClickHandler::Open( Messenger::Instance().showDocument(data, context); location.accessDisable(); } else { - auto filepath = location.name(); - if (documentIsValidMediaFile(filepath)) { + const auto filepath = location.name(); + if (Data::IsValidMediaFile(filepath)) { File::Launch(filepath); } } data->session()->data().markMediaRead(data); } else if (data->isVoiceMessage() || data->isAudioFile() || data->isVideoFile()) { - auto filepath = location.name(); - if (documentIsValidMediaFile(filepath)) { + const auto filepath = location.name(); + if (Data::IsValidMediaFile(filepath)) { File::Launch(filepath); } data->session()->data().markMediaRead(data); @@ -335,14 +359,14 @@ void DocumentOpenClickHandler::Open( Messenger::Instance().showDocument(data, context); } } else { - File::Launch(location.name()); + LaunchWithWarning(location.name()); } location.accessDisable(); } else { - File::Launch(location.name()); + LaunchWithWarning(location.name()); } } else { - File::Launch(location.name()); + LaunchWithWarning(location.name()); } return; } @@ -703,7 +727,7 @@ void DocumentData::performActionOnLoad() { File::OpenWith(already, QCursor::pos()); } else if (_actionOnLoad == ActionOnLoadOpen || _actionOnLoad == ActionOnLoadPlayInline) { if (isVoiceMessage() || isAudioFile() || isVideoFile()) { - if (documentIsValidMediaFile(already)) { + if (Data::IsValidMediaFile(already)) { File::Launch(already); } _session->data().markMediaRead(this); @@ -711,11 +735,11 @@ void DocumentData::performActionOnLoad() { if (showImage && QImageReader(loc.name()).canRead()) { Messenger::Instance().showDocument(this, item); } else { - File::Launch(already); + LaunchWithWarning(already); } loc.accessDisable(); } else { - File::Launch(already); + LaunchWithWarning(already); } } } @@ -1405,3 +1429,57 @@ QString DocumentData::ComposeNameString( auto trackTitle = (songTitle.isEmpty() ? qsl("Unknown Track") : songTitle); return songPerformer + QString::fromUtf8(" \xe2\x80\x93 ") + trackTitle; } + +namespace Data { + +QString FileExtension(const QString &filepath) { + const auto reversed = ranges::view::reverse(filepath); + const auto last = ranges::find_first_of(reversed, ".\\/"); + if (last == reversed.end() || *last != '.') { + return QString(); + } + return QString(last.base(), last - reversed.begin()); +} + +bool IsValidMediaFile(const QString &filepath) { + static const auto kExtensions = [] { + const auto list = qsl("\ +webm mkv flv vob ogv ogg drc gif gifv mng avi mov qt wmv yuv rm rmvb asf \ +amv mp4 m4p m4v mpg mp2 mpeg mpe mpv m2v svi 3gp 3g2 mxf roq nsv f4v f4p \ +f4a f4b wma divx evo mk3d mka mks mcf m2p ps ts m2ts ifo aaf avchd cam dat \ +dsh dvr-ms m1v fla flr sol wrap smi swf wtv 8svx 16svx iff aiff aif aifc \ +au bwf cdda raw wav flac la pac m4a ape ofr ofs off rka shn tak tta wv \ +brstm dts dtshd dtsma ast amr mp3 spx gsm aac mpc vqf ra ots swa vox voc \ +dwd smp aup cust mid mus sib sid ly gym vgm psf nsf mod ptb s3m xm it mt2 \ +minipsf psflib 2sf dsf gsf psf2 qsf ssf usf rmj spc niff mxl xml txm ym \ +jam mp1 mscz").split(' '); + return base::flat_set(list.begin(), list.end()); + }(); + + return ranges::binary_search( + kExtensions, + FileExtension(filepath).toLower()); +} + +bool IsExecutableName(const QString &filepath) { + static const auto kExtensions = [] { + const auto joined = +#ifdef Q_OS_MAC + qsl("action app bin command csh osx workflow"); +#elif defined Q_OS_LINUX // Q_OS_MAC + qsl("bin csh ksh out run"); +#else // Q_OS_MAC || Q_OS_LINUX + qsl("\ +bat bin cmd com cpl exe gadget inf ins inx isu job jse lnk msc msi msp mst \ +paf pif ps1 reg rgs scr sct shb shs u3p vb vbe vbs vbscript ws wsf"); +#endif // !Q_OS_MAC && !Q_OS_LINUX + const auto list = joined.split(' '); + return base::flat_set(list.begin(), list.end()); + }(); + + return ranges::binary_search( + kExtensions, + FileExtension(filepath).toLower()); +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index 0bfe80821..9db7fd849 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -331,3 +331,11 @@ QString FileNameForSave( QString name, bool savingAs, const QDir &dir = QDir()); + +namespace Data { + +QString FileExtension(const QString &filepath); +bool IsValidMediaFile(const QString &filepath); +bool IsExecutableName(const QString &filepath); + +} // namespace Data diff --git a/Telegram/SourceFiles/history/history_media_types.cpp b/Telegram/SourceFiles/history/history_media_types.cpp index 041d6255e..6ba11476c 100644 --- a/Telegram/SourceFiles/history/history_media_types.cpp +++ b/Telegram/SourceFiles/history/history_media_types.cpp @@ -1349,10 +1349,10 @@ void HistoryDocument::createComponents(bool caption) { } else { mask |= HistoryDocumentNamed::Bit(); if (!_data->isSong() - && !documentIsExecutableName(_data->filename()) && !_data->thumb->isNull() && _data->thumb->width() - && _data->thumb->height()) { + && _data->thumb->height() + && !Data::IsExecutableName(_data->filename())) { mask |= HistoryDocumentThumbed::Bit(); } } diff --git a/Telegram/SourceFiles/layout.cpp b/Telegram/SourceFiles/layout.cpp index 5685356e7..f5e8bf424 100644 --- a/Telegram/SourceFiles/layout.cpp +++ b/Telegram/SourceFiles/layout.cpp @@ -179,50 +179,6 @@ RoundCorners documentCorners(int32 colorIndex) { return RoundCorners(Doc1Corners + (colorIndex & 3)); } -bool documentIsValidMediaFile(const QString &filepath) { - static StaticNeverFreedPointer> validMediaTypes(([] { - std::unique_ptr> result = std::make_unique>(); - *result = qsl("\ -webm mkv flv vob ogv ogg drc gif gifv mng avi mov qt wmv yuv rm rmvb asf amv mp4 m4p \ -m4v mpg mp2 mpeg mpe mpv m2v svi 3gp 3g2 mxf roq nsv f4v f4p f4a f4b wma divx evo mk3d \ -mka mks mcf m2p ps ts m2ts ifo aaf avchd cam dat dsh dvr-ms m1v fla flr sol wrap smi swf \ -wtv 8svx 16svx iff aiff aif aifc au bwf cdda raw wav flac la pac m4a ape ofr ofs off rka \ -shn tak tta wv brstm dts dtshd dtsma ast amr mp3 spx gsm aac mpc vqf ra ots swa vox voc \ -dwd smp aup cust mid mus sib sid ly gym vgm psf nsf mod ptb s3m xm it mt2 minipsf psflib \ -2sf dsf gsf psf2 qsf ssf usf rmj spc niff mxl xml txm ym jam mp1 mscz\ -").split(' '); - return result.release(); - })()); - - QFileInfo info(filepath); - auto parts = info.fileName().split('.', QString::SkipEmptyParts); - return !parts.isEmpty() && (validMediaTypes->indexOf(parts.back().toLower()) >= 0); -} - -bool documentIsExecutableName(const QString &filename) { - static StaticNeverFreedPointer> executableTypes(([] { - std::unique_ptr> result = std::make_unique>(); -#ifdef Q_OS_MAC - *result = qsl("\ -action app bin command csh osx workflow\ -").split(' '); -#elif defined Q_OS_LINUX // Q_OS_MAC - *result = qsl("\ -bin csh ksh out run\ -").split(' '); -#else // Q_OS_MAC || Q_OS_LINUX - *result = qsl("\ -bat bin cmd com cpl exe gadget inf ins inx isu job jse lnk msc msi \ -msp mst paf pif ps1 reg rgs sct shb shs u3p vb vbe vbs vbscript ws wsf\ -").split(' '); -#endif // !Q_OS_MAC && !Q_OS_LINUX - return result.release(); - })()); - - auto lastDotIndex = filename.lastIndexOf('.'); - return (lastDotIndex >= 0) && (executableTypes->indexOf(filename.mid(lastDotIndex + 1).toLower()) >= 0); -} - [[nodiscard]] HistoryView::TextState LayoutItemBase::getState( QPoint point, StateRequest request) const { diff --git a/Telegram/SourceFiles/layout.h b/Telegram/SourceFiles/layout.h index 738f9b8a5..0b2018a6e 100644 --- a/Telegram/SourceFiles/layout.h +++ b/Telegram/SourceFiles/layout.h @@ -68,8 +68,6 @@ style::color documentDarkColor(int colorIndex); style::color documentOverColor(int colorIndex); style::color documentSelectedColor(int colorIndex); RoundCorners documentCorners(int colorIndex); -bool documentIsValidMediaFile(const QString &filepath); -bool documentIsExecutableName(const QString &filename); class PaintContextBase { public: diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 2926468bb..e732fd3e6 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1141,7 +1141,7 @@ void MainWidget::handleAudioUpdate(const AudioMsgId &audioId) { auto filepath = document->filepath(DocumentData::FilePathResolveSaveFromData); if (!filepath.isEmpty()) { - if (documentIsValidMediaFile(filepath)) { + if (Data::IsValidMediaFile(filepath)) { File::Launch(filepath); } } diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 4328e033e..902648542 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -1183,7 +1183,7 @@ bool Document::withThumb() const { && !_data->thumb->isNull() && _data->thumb->width() && _data->thumb->height() - && !documentIsExecutableName(_data->filename()); + && !Data::IsExecutableName(_data->filename()); } bool Document::updateStatusText() {