Fix pasting images from Firefox on Windows.

Fixes #10564.

Together with the image data Firefox sets to the clipboard an URLs list
which has a path to local temp file, created from that image.

Reading images from disk is slower + sometimes the content of the file
is wrong so for this case we prefer to read the image data directly.
This commit is contained in:
John Preston 2023-03-06 11:58:25 +04:00
parent c687882760
commit ff4af1b9bc
10 changed files with 70 additions and 34 deletions

View file

@ -69,10 +69,10 @@ namespace {
auto ListFromMimeData(not_null<const QMimeData*> data, bool premium) {
using Error = Ui::PreparedList::Error;
auto result = data->hasUrls()
const auto list = Core::ReadMimeUrls(data);
auto result = !list.isEmpty()
? Storage::PrepareMediaList(
// When we edit media, we need only 1 file.
base::GetMimeUrls(data).mid(0, 1),
list.mid(0, 1), // When we edit media, we need only 1 file.
st::sendMediaPreviewSize,
premium)
: Ui::PreparedList(Error::EmptyFile, QString());

View file

@ -1091,13 +1091,13 @@ void SendFilesBox::captionResized() {
}
bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const {
return CanAddUrls(base::GetMimeUrls(data)) || data->hasImage();
return data->hasImage() || CanAddUrls(Core::ReadMimeUrls(data));
}
bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
const auto premium = _controller->session().premium();
auto list = [&] {
const auto urls = base::GetMimeUrls(data);
const auto urls = Core::ReadMimeUrls(data);
auto result = CanAddUrls(urls)
? Storage::PrepareMediaList(
urls,

View file

@ -14,6 +14,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QMimeData>
namespace Core {
namespace {
[[nodiscard]] bool IsImageFromFirefox(not_null<const QMimeData*> data) {
// See https://bugs.telegram.org/c/6765/public
// See https://github.com/telegramdesktop/tdesktop/issues/10564
//
// Usually we prefer pasting from URLs list instead of pasting from
// image data, because sometimes a file is copied together with an
// image data of its File Explorer thumbnail or smth like that. In
// that case you end up sending this thumbnail instead of the file.
//
// But in case of "Copy Image" from Firefox on Windows we get both
// URLs list with a file path to some Temp folder in the list and
// the image data that was copied. The file is read slower + it may
// have incorrect content in case the URL can't be accessed without
// authorization. So in that case we want only image data and we
// check for a special Firefox mime type to check for that case.
return data->hasFormat(u"application/x-moz-nativeimage"_q)
&& data->hasImage();
}
} // namespace
MimeType::MimeType(const QMimeType &type) : _typeStruct(type) {
}
@ -170,9 +192,10 @@ std::shared_ptr<QMimeData> ShareMimeMediaData(
result->setData(u"application/x-td-use-jpeg"_q, "1");
result->setData(u"image/jpeg"_q, original->data(u"image/jpeg"_q));
}
if (auto list = base::GetMimeUrls(original); !list.isEmpty()) {
if (auto list = Core::ReadMimeUrls(original); !list.isEmpty()) {
result->setUrls(std::move(list));
}
result->setText(Core::ReadMimeText(original));
return result;
}
@ -192,4 +215,16 @@ MimeImageData ReadMimeImage(not_null<const QMimeData*> data) {
return {};
}
QString ReadMimeText(not_null<const QMimeData*> data) {
return IsImageFromFirefox(data) ? QString() : data->text();
}
QList<QUrl> ReadMimeUrls(not_null<const QMimeData*> data) {
return (data->hasUrls() && !IsImageFromFirefox(data))
? KUrlMimeData::urlsFromMimeData(
data,
KUrlMimeData::PreferLocalUrls)
: QList<QUrl>();
}
} // namespace Core

View file

@ -65,5 +65,7 @@ struct MimeImageData {
}
};
[[nodiscard]] MimeImageData ReadMimeImage(not_null<const QMimeData*> data);
[[nodiscard]] QString ReadMimeText(not_null<const QMimeData*> data);
[[nodiscard]] QList<QUrl> ReadMimeUrls(not_null<const QMimeData*> data);
} // namespace Core

View file

@ -38,18 +38,6 @@ inline bool in_range(Value &&value, From &&from, Till &&till) {
return (value >= from) && (value < till);
}
#if __has_include(<kurlmimedata.h>)
inline QList<QUrl> GetMimeUrls(const QMimeData *data) {
if (!data->hasUrls()) {
return {};
}
return KUrlMimeData::urlsFromMimeData(
data,
KUrlMimeData::PreferLocalUrls);
}
#endif
#if __has_include(<ksandbox.h>)
inline QString IconName() {
static const auto Result = KSandbox::isFlatpak()

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "editor/editor_paint.h"
#include "core/mime_type.h"
#include "ui/boxes/confirm_box.h"
#include "editor/controllers/controllers.h"
#include "editor/scene/scene.h"
@ -204,16 +205,17 @@ void Paint::handleMimeData(const QMimeData *data) {
using Error = Ui::PreparedList::Error;
const auto premium = false; // Don't support > 2GB files here.
auto result = data->hasUrls()
const auto list = Core::ReadMimeUrls(data);
auto result = !list.isEmpty()
? Storage::PrepareMediaList(
base::GetMimeUrls(data).mid(0, 1),
list.mid(0, 1),
_imageSize.width() / 2,
premium)
: Ui::PreparedList(Error::EmptyFile, QString());
if (result.error == Error::None) {
add(base::take(result.files.front().preview));
} else if (data->hasImage()) {
add(qvariant_cast<QImage>(data->imageData()));
} else if (auto read = Core::ReadMimeImage(data)) {
add(std::move(read.image));
}
}

View file

@ -459,7 +459,10 @@ HistoryWidget::HistoryWidget(
if (action == Ui::InputField::MimeAction::Check) {
return canSendFiles(data);
} else if (action == Ui::InputField::MimeAction::Insert) {
return confirmSendingFiles(data, std::nullopt, data->text());
return confirmSendingFiles(
data,
std::nullopt,
Core::ReadMimeText(data));
}
Unexpected("action in MimeData hook.");
});
@ -5230,7 +5233,7 @@ bool HistoryWidget::canSendFiles(not_null<const QMimeData*> data) const {
return false;
} else if (data->hasImage()) {
return true;
} else if (const auto urls = base::GetMimeUrls(data); !urls.empty()) {
} else if (const auto urls = Core::ReadMimeUrls(data); !urls.empty()) {
if (ranges::all_of(urls, &QUrl::isLocalFile)) {
return true;
}
@ -5249,7 +5252,7 @@ bool HistoryWidget::confirmSendingFiles(
const auto hasImage = data->hasImage();
const auto premium = controller()->session().user()->isPremium();
if (const auto urls = base::GetMimeUrls(data); !urls.empty()) {
if (const auto urls = Core::ReadMimeUrls(data); !urls.empty()) {
auto list = Storage::PrepareMediaList(
urls,
st::sendMediaPreviewSize,

View file

@ -100,7 +100,7 @@ constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200);
bool CanSendFiles(not_null<const QMimeData*> data) {
if (data->hasImage()) {
return true;
} else if (const auto urls = base::GetMimeUrls(data); !urls.empty()) {
} else if (const auto urls = Core::ReadMimeUrls(data); !urls.empty()) {
if (ranges::all_of(urls, &QUrl::isLocalFile)) {
return true;
}
@ -825,7 +825,10 @@ void RepliesWidget::setupComposeControls() {
if (action == Ui::InputField::MimeAction::Check) {
return CanSendFiles(data);
} else if (action == Ui::InputField::MimeAction::Insert) {
return confirmSendingFiles(data, std::nullopt, data->text());
return confirmSendingFiles(
data,
std::nullopt,
Core::ReadMimeText(data));
}
Unexpected("action in MimeData hook.");
});
@ -908,7 +911,7 @@ bool RepliesWidget::confirmSendingFiles(
const auto hasImage = data->hasImage();
const auto premium = controller()->session().user()->isPremium();
if (const auto urls = base::GetMimeUrls(data); !urls.empty()) {
if (const auto urls = Core::ReadMimeUrls(data); !urls.empty()) {
auto list = Storage::PrepareMediaList(
urls,
st::sendMediaPreviewSize,

View file

@ -70,7 +70,7 @@ namespace {
bool CanSendFiles(not_null<const QMimeData*> data) {
if (data->hasImage()) {
return true;
} else if (const auto urls = base::GetMimeUrls(data); !urls.empty()) {
} else if (const auto urls = Core::ReadMimeUrls(data); !urls.empty()) {
if (ranges::all_of(urls, &QUrl::isLocalFile)) {
return true;
}
@ -314,7 +314,10 @@ void ScheduledWidget::setupComposeControls() {
if (action == Ui::InputField::MimeAction::Check) {
return CanSendFiles(data);
} else if (action == Ui::InputField::MimeAction::Insert) {
return confirmSendingFiles(data, std::nullopt, data->text());
return confirmSendingFiles(
data,
std::nullopt,
Core::ReadMimeText(data));
}
Unexpected("action in MimeData hook.");
});
@ -373,7 +376,7 @@ bool ScheduledWidget::confirmSendingFiles(
const auto hasImage = data->hasImage();
const auto premium = controller()->session().user()->isPremium();
if (const auto urls = base::GetMimeUrls(data); !urls.empty()) {
if (const auto urls = Core::ReadMimeUrls(data); !urls.empty()) {
auto list = Storage::PrepareMediaList(
urls,
st::sendMediaPreviewSize,

View file

@ -74,7 +74,7 @@ void PrepareDetailsInParallel(PreparedList &result, int previewWidth) {
} // namespace
bool ValidatePhotoEditorMediaDragData(not_null<const QMimeData*> data) {
const auto urls = base::GetMimeUrls(data);
const auto urls = Core::ReadMimeUrls(data);
if (urls.size() > 1) {
return false;
} else if (data->hasImage()) {
@ -98,7 +98,7 @@ bool ValidatePhotoEditorMediaDragData(not_null<const QMimeData*> data) {
bool ValidateEditMediaDragData(
not_null<const QMimeData*> data,
Ui::AlbumType albumType) {
const auto urls = base::GetMimeUrls(data);
const auto urls = Core::ReadMimeUrls(data);
if (urls.size() > 1) {
return false;
} else if (data->hasImage()) {
@ -126,7 +126,7 @@ MimeDataState ComputeMimeDataState(const QMimeData *data) {
return MimeDataState::Image;
}
const auto urls = base::GetMimeUrls(data);
const auto urls = Core::ReadMimeUrls(data);
if (urls.isEmpty()) {
return MimeDataState::None;
}