2023-02-22 01:48:49 +03:00
|
|
|
// SPDX-FileCopyrightText: Nheko Contributors
|
2021-03-05 02:35:15 +03:00
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
2020-11-01 01:24:07 +03:00
|
|
|
#include "InputBar.h"
|
|
|
|
|
2022-03-21 00:49:33 +03:00
|
|
|
#include <QBuffer>
|
2020-11-01 01:24:07 +03:00
|
|
|
#include <QClipboard>
|
2020-11-25 19:02:23 +03:00
|
|
|
#include <QDropEvent>
|
2020-11-15 06:52:49 +03:00
|
|
|
#include <QFileDialog>
|
2020-11-01 01:24:07 +03:00
|
|
|
#include <QGuiApplication>
|
2022-03-20 00:30:35 +03:00
|
|
|
#include <QInputMethod>
|
2022-03-21 02:48:27 +03:00
|
|
|
#include <QMediaMetaData>
|
|
|
|
#include <QMediaPlayer>
|
2020-11-01 01:24:07 +03:00
|
|
|
#include <QMimeData>
|
2020-11-15 06:52:49 +03:00
|
|
|
#include <QMimeDatabase>
|
|
|
|
#include <QStandardPaths>
|
2021-09-02 04:15:07 +03:00
|
|
|
#include <QTextBoundaryFinder>
|
2020-11-01 01:24:07 +03:00
|
|
|
|
2021-03-28 18:37:36 +03:00
|
|
|
#include <QRegularExpression>
|
2020-11-09 05:12:37 +03:00
|
|
|
#include <mtx/responses/common.hpp>
|
2020-11-15 06:52:49 +03:00
|
|
|
#include <mtx/responses/media.hpp>
|
2020-11-09 05:12:37 +03:00
|
|
|
|
|
|
|
#include "Cache.h"
|
2022-09-30 04:27:05 +03:00
|
|
|
#include "Cache_p.h"
|
2020-11-09 05:12:37 +03:00
|
|
|
#include "ChatPage.h"
|
2021-07-21 02:03:38 +03:00
|
|
|
#include "CombinedImagePackModel.h"
|
2021-05-29 00:25:57 +03:00
|
|
|
#include "Config.h"
|
2022-09-30 04:27:05 +03:00
|
|
|
#include "EventAccessors.h"
|
2020-11-01 01:24:07 +03:00
|
|
|
#include "Logging.h"
|
2020-11-09 05:12:37 +03:00
|
|
|
#include "MatrixClient.h"
|
|
|
|
#include "TimelineModel.h"
|
2020-12-13 18:23:04 +03:00
|
|
|
#include "TimelineViewManager.h"
|
2020-11-09 05:12:37 +03:00
|
|
|
#include "UserSettingsPage.h"
|
|
|
|
#include "Utils.h"
|
2020-11-15 06:52:49 +03:00
|
|
|
|
|
|
|
#include "blurhash.hpp"
|
2020-11-09 05:12:37 +03:00
|
|
|
|
|
|
|
static constexpr size_t INPUT_HISTORY_SIZE = 10;
|
2020-11-01 01:24:07 +03:00
|
|
|
|
2022-09-30 04:27:05 +03:00
|
|
|
std::string
|
|
|
|
threadFallbackEventId(const std::string &room_id, const std::string &thread_id)
|
|
|
|
{
|
|
|
|
auto event_ids = cache::client()->relatedEvents(room_id, thread_id);
|
|
|
|
|
|
|
|
std::map<uint64_t, std::string_view, std::greater<>> orderedEvents;
|
|
|
|
|
|
|
|
for (const auto &e : event_ids) {
|
|
|
|
if (auto index = cache::client()->getTimelineIndex(room_id, e))
|
|
|
|
orderedEvents.emplace(*index, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto &[index, event_id] : orderedEvents) {
|
|
|
|
(void)index;
|
|
|
|
if (auto event = cache::client()->getEvent(room_id, event_id)) {
|
|
|
|
if (mtx::accessors::relations(event->data).thread() == thread_id)
|
|
|
|
return std::string(event_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return thread_id;
|
|
|
|
}
|
|
|
|
|
2022-03-21 07:49:12 +03:00
|
|
|
QUrl
|
|
|
|
MediaUpload::thumbnailDataUrl() const
|
|
|
|
{
|
|
|
|
if (thumbnail_.isNull())
|
|
|
|
return {};
|
|
|
|
|
|
|
|
QByteArray byteArray;
|
|
|
|
QBuffer buffer(&byteArray);
|
|
|
|
buffer.open(QIODevice::WriteOnly);
|
|
|
|
thumbnail_.save(&buffer, "PNG");
|
|
|
|
QString base64 = QString::fromUtf8(byteArray.toBase64());
|
|
|
|
return QString("data:image/png;base64,") + base64;
|
|
|
|
}
|
|
|
|
|
2022-03-21 03:24:53 +03:00
|
|
|
bool
|
|
|
|
InputVideoSurface::present(const QVideoFrame &frame)
|
|
|
|
{
|
2022-03-22 03:19:48 +03:00
|
|
|
QImage::Format format = QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat());
|
2022-03-21 03:24:53 +03:00
|
|
|
|
|
|
|
if (format == QImage::Format_Invalid) {
|
|
|
|
emit newImage({});
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
QVideoFrame frametodraw(frame);
|
|
|
|
|
|
|
|
if (!frametodraw.map(QAbstractVideoBuffer::ReadOnly)) {
|
|
|
|
emit newImage({});
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// this is a shallow operation. it just refer the frame buffer
|
2022-03-22 03:19:48 +03:00
|
|
|
QImage image(qAsConst(frametodraw).bits(),
|
2022-03-21 03:24:53 +03:00
|
|
|
frametodraw.width(),
|
|
|
|
frametodraw.height(),
|
|
|
|
frametodraw.bytesPerLine(),
|
|
|
|
format);
|
2022-03-22 03:19:48 +03:00
|
|
|
image.detach();
|
|
|
|
|
|
|
|
frametodraw.unmap();
|
2022-03-21 03:24:53 +03:00
|
|
|
|
|
|
|
emit newImage(std::move(image));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<QVideoFrame::PixelFormat>
|
|
|
|
InputVideoSurface::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const
|
|
|
|
{
|
|
|
|
if (type == QAbstractVideoBuffer::NoHandle) {
|
|
|
|
return {
|
|
|
|
QVideoFrame::Format_ARGB32,
|
|
|
|
QVideoFrame::Format_ARGB32_Premultiplied,
|
|
|
|
QVideoFrame::Format_RGB24,
|
|
|
|
QVideoFrame::Format_BGR24,
|
|
|
|
QVideoFrame::Format_RGB32,
|
|
|
|
QVideoFrame::Format_RGB565,
|
|
|
|
QVideoFrame::Format_RGB555,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-13 14:16:49 +03:00
|
|
|
bool
|
|
|
|
InputBar::tryPasteAttachment(bool fromMouse)
|
2020-11-01 01:24:07 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
const QMimeData *md = nullptr;
|
2020-11-01 01:24:07 +03:00
|
|
|
|
2021-09-28 00:19:43 +03:00
|
|
|
if (fromMouse && QGuiApplication::clipboard()->supportsSelection()) {
|
|
|
|
md = QGuiApplication::clipboard()->mimeData(QClipboard::Selection);
|
2021-09-18 01:22:33 +03:00
|
|
|
} else {
|
|
|
|
md = QGuiApplication::clipboard()->mimeData(QClipboard::Clipboard);
|
|
|
|
}
|
2020-11-01 01:24:07 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
if (md)
|
2022-06-13 14:16:49 +03:00
|
|
|
return insertMimeData(md);
|
|
|
|
|
|
|
|
return false;
|
2020-11-25 19:02:23 +03:00
|
|
|
}
|
|
|
|
|
2022-06-13 14:16:49 +03:00
|
|
|
bool
|
2020-11-25 19:02:23 +03:00
|
|
|
InputBar::insertMimeData(const QMimeData *md)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (!md)
|
2022-06-13 14:16:49 +03:00
|
|
|
return false;
|
2021-09-18 01:22:33 +03:00
|
|
|
|
2021-12-29 08:01:38 +03:00
|
|
|
nhlog::ui()->debug("Got mime formats: {}",
|
|
|
|
md->formats().join(QStringLiteral(", ")).toStdString());
|
2021-12-29 06:28:08 +03:00
|
|
|
const auto formats = md->formats().filter(QStringLiteral("/"));
|
|
|
|
const auto image = formats.filter(QStringLiteral("image/"), Qt::CaseInsensitive);
|
|
|
|
const auto audio = formats.filter(QStringLiteral("audio/"), Qt::CaseInsensitive);
|
|
|
|
const auto video = formats.filter(QStringLiteral("video/"), Qt::CaseInsensitive);
|
2021-09-18 01:22:33 +03:00
|
|
|
|
2021-09-28 00:19:43 +03:00
|
|
|
if (md->hasImage()) {
|
2021-12-29 06:28:08 +03:00
|
|
|
if (formats.contains(QStringLiteral("image/svg+xml"), Qt::CaseInsensitive)) {
|
2022-03-21 00:49:33 +03:00
|
|
|
startUploadFromMimeData(*md, QStringLiteral("image/svg+xml"));
|
2022-03-22 01:15:09 +03:00
|
|
|
} else if (formats.contains(QStringLiteral("image/png"), Qt::CaseInsensitive)) {
|
2022-03-21 00:49:33 +03:00
|
|
|
startUploadFromMimeData(*md, QStringLiteral("image/png"));
|
2022-03-22 01:15:09 +03:00
|
|
|
} else if (image.empty()) {
|
|
|
|
QByteArray ba;
|
|
|
|
QBuffer buffer(&ba);
|
|
|
|
buffer.open(QIODevice::WriteOnly);
|
|
|
|
qvariant_cast<QImage>(md->imageData()).save(&buffer, "PNG");
|
|
|
|
QMimeData d;
|
|
|
|
d.setData(QStringLiteral("image/png"), ba);
|
2022-03-22 01:19:18 +03:00
|
|
|
startUploadFromMimeData(d, QStringLiteral("image/png"));
|
2021-09-28 02:42:35 +03:00
|
|
|
} else {
|
2022-03-21 00:49:33 +03:00
|
|
|
startUploadFromMimeData(*md, image.first());
|
2021-09-28 02:42:35 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
} else if (!audio.empty()) {
|
2022-03-21 00:49:33 +03:00
|
|
|
startUploadFromMimeData(*md, audio.first());
|
2021-09-18 01:22:33 +03:00
|
|
|
} else if (!video.empty()) {
|
2022-03-21 00:49:33 +03:00
|
|
|
startUploadFromMimeData(*md, video.first());
|
2023-01-09 06:52:49 +03:00
|
|
|
} else if (md->hasUrls() &&
|
|
|
|
// NOTE(Nico): Safari, when copying the url, sends a url list. Since we only paste
|
|
|
|
// local files, skip remote ones.
|
|
|
|
[&md] {
|
2023-01-09 07:00:22 +03:00
|
|
|
for (auto &&u : md->urls()) {
|
2023-01-09 06:49:53 +03:00
|
|
|
if (u.isLocalFile())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}()) {
|
2021-09-18 01:22:33 +03:00
|
|
|
// Generic file path for any platform.
|
|
|
|
for (auto &&u : md->urls()) {
|
|
|
|
if (u.isLocalFile()) {
|
2022-03-21 00:49:33 +03:00
|
|
|
startUploadFromPath(u.toLocalFile());
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
|
|
|
}
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (md->hasFormat(QStringLiteral("x-special/gnome-copied-files"))) {
|
2021-09-18 01:22:33 +03:00
|
|
|
// Special case for X11 users. See "Notes for X11 Users" in md.
|
|
|
|
// Source: http://doc.qt.io/qt-5/qclipboard.html
|
|
|
|
|
|
|
|
// This MIME type returns a string with multiple lines separated by '\n'. The first
|
|
|
|
// line is the command to perform with the clipboard (not useful to us). The
|
|
|
|
// following lines are the file URIs.
|
|
|
|
//
|
|
|
|
// Source: the nautilus source code in file 'src/nautilus-clipboard.c' in function
|
|
|
|
// nautilus_clipboard_get_uri_list_from_selection_data()
|
|
|
|
// https://github.com/GNOME/nautilus/blob/master/src/nautilus-clipboard.c
|
|
|
|
|
2021-12-29 06:28:08 +03:00
|
|
|
auto data = md->data(QStringLiteral("x-special/gnome-copied-files")).split('\n');
|
2021-09-18 01:22:33 +03:00
|
|
|
if (data.size() < 2) {
|
|
|
|
nhlog::ui()->warn("MIME format is malformed, cannot perform paste.");
|
2022-06-13 14:16:49 +03:00
|
|
|
return false;
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
2020-11-15 06:52:49 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
for (int i = 1; i < data.size(); ++i) {
|
|
|
|
QUrl url{data[i]};
|
|
|
|
if (url.isLocalFile()) {
|
2022-03-21 00:49:33 +03:00
|
|
|
startUploadFromPath(url.toLocalFile());
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (md->hasText()) {
|
2022-06-13 14:16:49 +03:00
|
|
|
return false;
|
2021-09-18 01:22:33 +03:00
|
|
|
} else {
|
2021-12-29 06:28:08 +03:00
|
|
|
nhlog::ui()->debug("formats: {}", md->formats().join(QStringLiteral(", ")).toStdString());
|
2022-06-13 14:16:49 +03:00
|
|
|
return false;
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
2022-06-13 14:16:49 +03:00
|
|
|
|
|
|
|
return true;
|
2020-11-01 01:24:07 +03:00
|
|
|
}
|
|
|
|
|
2021-09-02 04:15:07 +03:00
|
|
|
void
|
|
|
|
InputBar::updateAtRoom(const QString &t)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
bool roomMention = false;
|
|
|
|
|
|
|
|
if (t.size() > 4) {
|
|
|
|
QTextBoundaryFinder finder(QTextBoundaryFinder::BoundaryType::Word, t);
|
|
|
|
|
|
|
|
finder.toStart();
|
|
|
|
do {
|
|
|
|
auto start = finder.position();
|
|
|
|
finder.toNextBoundary();
|
|
|
|
auto end = finder.position();
|
2021-12-29 08:01:38 +03:00
|
|
|
if (start > 0 && end - start >= 4 &&
|
|
|
|
t.mid(start, end - start) == QLatin1String("room") &&
|
2021-09-18 01:22:33 +03:00
|
|
|
t.at(start - 1) == QChar('@')) {
|
|
|
|
roomMention = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} while (finder.position() < t.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (roomMention != this->containsAtRoom_) {
|
|
|
|
this->containsAtRoom_ = roomMention;
|
|
|
|
emit containsAtRoomChanged();
|
|
|
|
}
|
2021-09-02 04:15:07 +03:00
|
|
|
}
|
|
|
|
|
2021-02-25 02:50:17 +03:00
|
|
|
void
|
2021-12-03 03:54:43 +03:00
|
|
|
InputBar::setText(const QString &newText)
|
2021-02-25 02:50:17 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (history_.empty())
|
|
|
|
history_.push_front(newText);
|
|
|
|
else
|
|
|
|
history_.front() = newText;
|
|
|
|
history_index_ = 0;
|
2021-02-25 02:50:17 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
if (history_.size() == INPUT_HISTORY_SIZE)
|
|
|
|
history_.pop_back();
|
2021-02-25 02:50:17 +03:00
|
|
|
|
2021-12-29 06:28:08 +03:00
|
|
|
updateAtRoom(QLatin1String(""));
|
2021-09-18 01:22:33 +03:00
|
|
|
emit textChanged(newText);
|
2021-02-25 02:50:17 +03:00
|
|
|
}
|
2020-11-01 01:24:07 +03:00
|
|
|
void
|
2021-12-03 03:54:43 +03:00
|
|
|
InputBar::updateState(int selectionStart_,
|
|
|
|
int selectionEnd_,
|
|
|
|
int cursorPosition_,
|
|
|
|
const QString &text_)
|
2020-11-01 01:24:07 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (text_.isEmpty())
|
|
|
|
stopTyping();
|
|
|
|
else
|
|
|
|
startTyping();
|
2020-11-17 04:37:43 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
if (text_ != text()) {
|
|
|
|
if (history_.empty())
|
|
|
|
history_.push_front(text_);
|
|
|
|
else
|
|
|
|
history_.front() = text_;
|
|
|
|
history_index_ = 0;
|
2021-09-02 04:15:07 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
updateAtRoom(text_);
|
2022-03-30 08:02:52 +03:00
|
|
|
// disabled, as it moves the cursor to the end
|
|
|
|
// emit textChanged(text_);
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
2020-11-17 15:25:16 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
selectionStart = selectionStart_;
|
|
|
|
selectionEnd = selectionEnd_;
|
|
|
|
cursorPosition = cursorPosition_;
|
2020-11-17 15:25:16 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
QString
|
|
|
|
InputBar::text() const
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (history_index_ < history_.size())
|
|
|
|
return history_.at(history_index_);
|
2020-11-17 15:25:16 +03:00
|
|
|
|
2021-12-29 06:28:08 +03:00
|
|
|
return QString();
|
2020-11-17 15:25:16 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
QString
|
|
|
|
InputBar::previousText()
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
history_index_++;
|
|
|
|
if (history_index_ >= INPUT_HISTORY_SIZE)
|
|
|
|
history_index_ = INPUT_HISTORY_SIZE;
|
|
|
|
else if (text().isEmpty())
|
|
|
|
history_index_--;
|
|
|
|
|
|
|
|
updateAtRoom(text());
|
|
|
|
return text();
|
2020-11-17 15:25:16 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
QString
|
|
|
|
InputBar::nextText()
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
history_index_--;
|
|
|
|
if (history_index_ >= INPUT_HISTORY_SIZE)
|
|
|
|
history_index_ = 0;
|
2020-11-17 15:25:16 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
updateAtRoom(text());
|
|
|
|
return text();
|
2020-11-01 01:24:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
InputBar::send()
|
|
|
|
{
|
2022-03-20 00:30:35 +03:00
|
|
|
QInputMethod *im = QGuiApplication::inputMethod();
|
|
|
|
im->commit();
|
2022-05-23 13:32:04 +03:00
|
|
|
if (text().trimmed().isEmpty()) {
|
|
|
|
acceptUploads();
|
2021-09-18 01:22:33 +03:00
|
|
|
return;
|
2022-05-23 13:32:04 +03:00
|
|
|
}
|
2020-11-09 05:12:37 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
nhlog::ui()->debug("Send: {}", text().toStdString());
|
|
|
|
|
|
|
|
auto wasEdit = !room->edit().isEmpty();
|
|
|
|
|
|
|
|
if (text().startsWith('/')) {
|
2021-12-29 06:28:08 +03:00
|
|
|
int command_end = text().indexOf(QRegularExpression(QStringLiteral("\\s")));
|
2021-09-18 01:22:33 +03:00
|
|
|
if (command_end == -1)
|
|
|
|
command_end = text().size();
|
|
|
|
auto name = text().mid(1, command_end - 1);
|
|
|
|
auto args = text().mid(command_end + 1);
|
2021-12-29 06:28:08 +03:00
|
|
|
if (name.isEmpty() || name == QLatin1String("/")) {
|
2021-09-18 01:22:33 +03:00
|
|
|
message(args);
|
|
|
|
} else {
|
|
|
|
command(name, args);
|
2021-02-25 02:50:17 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
} else {
|
|
|
|
message(text());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!wasEdit) {
|
2021-12-29 06:28:08 +03:00
|
|
|
history_.push_front(QLatin1String(""));
|
|
|
|
setText(QLatin1String(""));
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
2020-11-01 01:24:07 +03:00
|
|
|
}
|
2020-11-09 05:12:37 +03:00
|
|
|
|
2020-11-15 06:52:49 +03:00
|
|
|
void
|
|
|
|
InputBar::openFileSelection()
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
|
2022-01-12 21:09:46 +03:00
|
|
|
const auto fileName =
|
|
|
|
QFileDialog::getOpenFileName(nullptr, tr("Select a file"), homeFolder, tr("All Files (*)"));
|
2020-11-15 06:52:49 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
if (fileName.isEmpty())
|
|
|
|
return;
|
2020-11-15 06:52:49 +03:00
|
|
|
|
2022-03-21 00:49:33 +03:00
|
|
|
startUploadFromPath(fileName);
|
2020-11-15 06:52:49 +03:00
|
|
|
}
|
|
|
|
|
2022-07-20 14:52:13 +03:00
|
|
|
QString
|
|
|
|
replaceMatrixToMarkdownLink(QString input)
|
|
|
|
{
|
|
|
|
bool replaced = false;
|
|
|
|
do {
|
|
|
|
replaced = false;
|
|
|
|
|
|
|
|
int endOfName = input.indexOf("](https://matrix.to/#/");
|
|
|
|
int startOfName;
|
|
|
|
int nestingCount = 0;
|
|
|
|
for (startOfName = endOfName - 1; startOfName > 0; startOfName--) {
|
|
|
|
// skip escaped chars
|
|
|
|
if (startOfName > 0 && input[startOfName - 1] == '\\')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (input[startOfName] == '[') {
|
|
|
|
if (nestingCount <= 0)
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
nestingCount--;
|
|
|
|
}
|
|
|
|
if (input[startOfName] == ']')
|
|
|
|
nestingCount++;
|
|
|
|
}
|
|
|
|
if (startOfName < 0 || nestingCount > 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
int endOfLink = input.indexOf(')', endOfName);
|
|
|
|
int newline = input.indexOf('\n', endOfName);
|
|
|
|
if (endOfLink > endOfName && (newline == -1 || endOfLink < newline)) {
|
|
|
|
auto name = input.mid(startOfName + 1, endOfName - startOfName - 1);
|
|
|
|
name.replace("\\[", "[");
|
|
|
|
name.replace("\\]", "]");
|
|
|
|
input.replace(startOfName, endOfLink - startOfName + 1, name);
|
|
|
|
replaced = true;
|
|
|
|
}
|
|
|
|
} while (replaced);
|
|
|
|
|
|
|
|
return input;
|
|
|
|
}
|
|
|
|
|
2022-09-30 04:27:05 +03:00
|
|
|
mtx::common::Relations
|
|
|
|
InputBar::generateRelations() const
|
|
|
|
{
|
|
|
|
mtx::common::Relations relations;
|
|
|
|
if (!room->thread().isEmpty()) {
|
|
|
|
relations.relations.push_back(
|
|
|
|
{mtx::common::RelationType::Thread, room->thread().toStdString()});
|
|
|
|
if (room->reply().isEmpty())
|
|
|
|
relations.relations.push_back(
|
|
|
|
{mtx::common::RelationType::InReplyTo,
|
|
|
|
threadFallbackEventId(room->roomId().toStdString(), room->thread().toStdString()),
|
|
|
|
std::nullopt,
|
|
|
|
true});
|
|
|
|
}
|
|
|
|
if (!room->reply().isEmpty()) {
|
|
|
|
relations.relations.push_back(
|
|
|
|
{mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
|
|
|
|
}
|
|
|
|
if (!room->edit().isEmpty()) {
|
|
|
|
relations.relations.push_back(
|
|
|
|
{mtx::common::RelationType::Replace, room->edit().toStdString()});
|
|
|
|
}
|
|
|
|
return relations;
|
|
|
|
}
|
|
|
|
|
2020-11-09 05:12:37 +03:00
|
|
|
void
|
2021-12-03 03:54:43 +03:00
|
|
|
InputBar::message(const QString &msg, MarkdownOverride useMarkdown, bool rainbowify)
|
2020-11-09 05:12:37 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
mtx::events::msg::Text text = {};
|
|
|
|
text.body = msg.trimmed().toStdString();
|
|
|
|
|
|
|
|
if ((ChatPage::instance()->userSettings()->markdown() &&
|
|
|
|
useMarkdown == MarkdownOverride::NOT_SPECIFIED) ||
|
|
|
|
useMarkdown == MarkdownOverride::ON) {
|
|
|
|
text.formatted_body = utils::markdownToHtml(msg, rainbowify).toStdString();
|
|
|
|
// Remove markdown links by completer
|
2022-07-20 14:52:13 +03:00
|
|
|
text.body = replaceMatrixToMarkdownLink(msg.trimmed()).toStdString();
|
2021-09-18 01:22:33 +03:00
|
|
|
|
|
|
|
// Don't send formatted_body, when we don't need to
|
2023-01-28 12:50:39 +03:00
|
|
|
// Specifically, if it includes no html tag and no newlines (which behave differently in
|
|
|
|
// formatted bodies). Probably we forgot something, so this might need to expand at some
|
|
|
|
// point.
|
|
|
|
if (text.formatted_body.find('<') == std::string::npos &&
|
|
|
|
text.body.find('\n') == std::string::npos)
|
2021-09-18 01:22:33 +03:00
|
|
|
text.formatted_body = "";
|
|
|
|
else
|
|
|
|
text.format = "org.matrix.custom.html";
|
2023-01-31 20:22:12 +03:00
|
|
|
} else if (useMarkdown == MarkdownOverride::CMARK) {
|
|
|
|
// disable all markdown extensions
|
|
|
|
text.formatted_body = utils::markdownToHtml(msg, rainbowify, true).toStdString();
|
|
|
|
// keep everything as it was
|
|
|
|
text.body = msg.trimmed().toStdString();
|
|
|
|
|
|
|
|
// always send formatted
|
|
|
|
text.format = "org.matrix.custom.html";
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
2020-11-09 05:12:37 +03:00
|
|
|
|
2022-09-30 04:27:05 +03:00
|
|
|
text.relations = generateRelations();
|
|
|
|
if (!room->reply().isEmpty() && room->thread().isEmpty() && room->edit().isEmpty()) {
|
2021-09-18 01:22:33 +03:00
|
|
|
auto related = room->relatedInfo(room->reply());
|
|
|
|
|
2022-07-05 13:57:17 +03:00
|
|
|
// Skip reply fallbacks to users who would cause a room ping with the fallback.
|
|
|
|
// This should be fine, since in some cases the reply fallback can be omitted now and the
|
|
|
|
// alternative is worse! On Element Android this applies to any substring, but that is their
|
|
|
|
// bug to fix.
|
|
|
|
if (!related.quoted_user.startsWith("@room:")) {
|
|
|
|
QString body;
|
|
|
|
bool firstLine = true;
|
|
|
|
auto lines = related.quoted_body.splitRef(u'\n');
|
|
|
|
for (auto line : qAsConst(lines)) {
|
|
|
|
if (firstLine) {
|
|
|
|
firstLine = false;
|
|
|
|
body = QStringLiteral("> <%1> %2\n").arg(related.quoted_user, line);
|
|
|
|
} else {
|
|
|
|
body += QStringLiteral("> %1\n").arg(line);
|
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
2020-11-09 05:12:37 +03:00
|
|
|
|
2022-07-20 14:52:13 +03:00
|
|
|
text.body =
|
|
|
|
QStringLiteral("%1\n%2").arg(body, QString::fromStdString(text.body)).toStdString();
|
2020-11-09 05:12:37 +03:00
|
|
|
|
2022-07-05 13:57:17 +03:00
|
|
|
// NOTE(Nico): rich replies always need a formatted_body!
|
|
|
|
text.format = "org.matrix.custom.html";
|
|
|
|
if ((ChatPage::instance()->userSettings()->markdown() &&
|
|
|
|
useMarkdown == MarkdownOverride::NOT_SPECIFIED) ||
|
|
|
|
useMarkdown == MarkdownOverride::ON)
|
|
|
|
text.formatted_body =
|
|
|
|
utils::getFormattedQuoteBody(related, utils::markdownToHtml(msg, rainbowify))
|
|
|
|
.toStdString();
|
|
|
|
else
|
|
|
|
text.formatted_body =
|
|
|
|
utils::getFormattedQuoteBody(related, msg.toHtmlEscaped()).toStdString();
|
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
2020-11-09 05:12:37 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
room->sendMessageEvent(text, mtx::events::EventType::RoomMessage);
|
2020-11-09 05:12:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2021-12-03 03:54:43 +03:00
|
|
|
InputBar::emote(const QString &msg, bool rainbowify)
|
2020-11-09 05:12:37 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
auto html = utils::markdownToHtml(msg, rainbowify);
|
|
|
|
|
|
|
|
mtx::events::msg::Emote emote;
|
|
|
|
emote.body = msg.trimmed().toStdString();
|
|
|
|
|
|
|
|
if (html != msg.trimmed().toHtmlEscaped() && ChatPage::instance()->userSettings()->markdown()) {
|
|
|
|
emote.formatted_body = html.toStdString();
|
|
|
|
emote.format = "org.matrix.custom.html";
|
|
|
|
// Remove markdown links by completer
|
2022-07-20 14:52:13 +03:00
|
|
|
emote.body = replaceMatrixToMarkdownLink(msg.trimmed()).toStdString();
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
|
|
|
|
2022-09-30 04:27:05 +03:00
|
|
|
emote.relations = generateRelations();
|
2021-09-18 01:22:33 +03:00
|
|
|
|
|
|
|
room->sendMessageEvent(emote, mtx::events::EventType::RoomMessage);
|
2020-11-09 05:12:37 +03:00
|
|
|
}
|
|
|
|
|
2021-04-11 22:47:20 +03:00
|
|
|
void
|
2021-12-03 03:54:43 +03:00
|
|
|
InputBar::notice(const QString &msg, bool rainbowify)
|
2021-04-11 22:47:20 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
auto html = utils::markdownToHtml(msg, rainbowify);
|
|
|
|
|
|
|
|
mtx::events::msg::Notice notice;
|
|
|
|
notice.body = msg.trimmed().toStdString();
|
|
|
|
|
|
|
|
if (html != msg.trimmed().toHtmlEscaped() && ChatPage::instance()->userSettings()->markdown()) {
|
|
|
|
notice.formatted_body = html.toStdString();
|
|
|
|
notice.format = "org.matrix.custom.html";
|
|
|
|
// Remove markdown links by completer
|
2022-07-20 14:52:13 +03:00
|
|
|
notice.body = replaceMatrixToMarkdownLink(msg.trimmed()).toStdString();
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
|
|
|
|
2022-09-30 04:27:05 +03:00
|
|
|
notice.relations = generateRelations();
|
2021-09-18 01:22:33 +03:00
|
|
|
|
|
|
|
room->sendMessageEvent(notice, mtx::events::EventType::RoomMessage);
|
2021-04-11 22:47:20 +03:00
|
|
|
}
|
|
|
|
|
2022-12-10 18:17:15 +03:00
|
|
|
void
|
|
|
|
InputBar::confetti(const QString &body, bool rainbowify)
|
|
|
|
{
|
|
|
|
auto html = utils::markdownToHtml(body, rainbowify);
|
|
|
|
|
|
|
|
mtx::events::msg::Confetti confetti;
|
|
|
|
confetti.body = body.trimmed().toStdString();
|
|
|
|
|
|
|
|
if (html != body.trimmed().toHtmlEscaped() &&
|
|
|
|
ChatPage::instance()->userSettings()->markdown()) {
|
|
|
|
confetti.formatted_body = html.toStdString();
|
|
|
|
confetti.format = "org.matrix.custom.html";
|
|
|
|
// Remove markdown links by completer
|
|
|
|
confetti.body = replaceMatrixToMarkdownLink(body.trimmed()).toStdString();
|
|
|
|
}
|
|
|
|
|
|
|
|
confetti.relations = generateRelations();
|
|
|
|
|
|
|
|
room->sendMessageEvent(confetti, mtx::events::EventType::RoomMessage);
|
|
|
|
}
|
|
|
|
|
2020-11-15 06:52:49 +03:00
|
|
|
void
|
|
|
|
InputBar::image(const QString &filename,
|
|
|
|
const std::optional<mtx::crypto::EncryptedFile> &file,
|
|
|
|
const QString &url,
|
|
|
|
const QString &mime,
|
|
|
|
uint64_t dsize,
|
|
|
|
const QSize &dimensions,
|
2022-03-21 07:49:12 +03:00
|
|
|
const std::optional<mtx::crypto::EncryptedFile> &thumbnailEncryptedFile,
|
|
|
|
const QString &thumbnailUrl,
|
|
|
|
uint64_t thumbnailSize,
|
|
|
|
const QSize &thumbnailDimensions,
|
2020-11-15 06:52:49 +03:00
|
|
|
const QString &blurhash)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
mtx::events::msg::Image image;
|
|
|
|
image.info.mimetype = mime.toStdString();
|
|
|
|
image.info.size = dsize;
|
|
|
|
image.info.blurhash = blurhash.toStdString();
|
|
|
|
image.body = filename.toStdString();
|
|
|
|
image.info.h = dimensions.height();
|
|
|
|
image.info.w = dimensions.width();
|
|
|
|
|
|
|
|
if (file)
|
|
|
|
image.file = file;
|
|
|
|
else
|
|
|
|
image.url = url.toStdString();
|
|
|
|
|
2022-03-21 07:49:12 +03:00
|
|
|
if (!thumbnailUrl.isEmpty()) {
|
|
|
|
if (thumbnailEncryptedFile)
|
|
|
|
image.info.thumbnail_file = thumbnailEncryptedFile;
|
|
|
|
else
|
|
|
|
image.info.thumbnail_url = thumbnailUrl.toStdString();
|
|
|
|
|
|
|
|
image.info.thumbnail_info.h = thumbnailDimensions.height();
|
|
|
|
image.info.thumbnail_info.w = thumbnailDimensions.width();
|
|
|
|
image.info.thumbnail_info.size = thumbnailSize;
|
|
|
|
image.info.thumbnail_info.mimetype = "image/png";
|
|
|
|
}
|
|
|
|
|
2022-09-30 04:27:05 +03:00
|
|
|
image.relations = generateRelations();
|
2021-09-18 01:22:33 +03:00
|
|
|
|
|
|
|
room->sendMessageEvent(image, mtx::events::EventType::RoomMessage);
|
2020-11-15 06:52:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
InputBar::file(const QString &filename,
|
|
|
|
const std::optional<mtx::crypto::EncryptedFile> &encryptedFile,
|
|
|
|
const QString &url,
|
|
|
|
const QString &mime,
|
|
|
|
uint64_t dsize)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
mtx::events::msg::File file;
|
|
|
|
file.info.mimetype = mime.toStdString();
|
|
|
|
file.info.size = dsize;
|
|
|
|
file.body = filename.toStdString();
|
|
|
|
|
|
|
|
if (encryptedFile)
|
|
|
|
file.file = encryptedFile;
|
|
|
|
else
|
|
|
|
file.url = url.toStdString();
|
|
|
|
|
2022-09-30 04:27:05 +03:00
|
|
|
file.relations = generateRelations();
|
2021-09-18 01:22:33 +03:00
|
|
|
|
|
|
|
room->sendMessageEvent(file, mtx::events::EventType::RoomMessage);
|
2020-11-15 06:52:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
InputBar::audio(const QString &filename,
|
|
|
|
const std::optional<mtx::crypto::EncryptedFile> &file,
|
|
|
|
const QString &url,
|
|
|
|
const QString &mime,
|
2022-03-21 02:48:27 +03:00
|
|
|
uint64_t dsize,
|
|
|
|
uint64_t duration)
|
2020-11-15 06:52:49 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
mtx::events::msg::Audio audio;
|
|
|
|
audio.info.mimetype = mime.toStdString();
|
|
|
|
audio.info.size = dsize;
|
|
|
|
audio.body = filename.toStdString();
|
|
|
|
audio.url = url.toStdString();
|
|
|
|
|
2022-03-21 02:48:27 +03:00
|
|
|
if (duration > 0)
|
|
|
|
audio.info.duration = duration;
|
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
if (file)
|
|
|
|
audio.file = file;
|
|
|
|
else
|
|
|
|
audio.url = url.toStdString();
|
|
|
|
|
2022-09-30 04:27:05 +03:00
|
|
|
audio.relations = generateRelations();
|
2021-09-18 01:22:33 +03:00
|
|
|
|
|
|
|
room->sendMessageEvent(audio, mtx::events::EventType::RoomMessage);
|
2020-11-15 06:52:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
InputBar::video(const QString &filename,
|
|
|
|
const std::optional<mtx::crypto::EncryptedFile> &file,
|
|
|
|
const QString &url,
|
|
|
|
const QString &mime,
|
2022-03-21 02:48:27 +03:00
|
|
|
uint64_t dsize,
|
|
|
|
uint64_t duration,
|
2022-03-21 03:24:53 +03:00
|
|
|
const QSize &dimensions,
|
|
|
|
const std::optional<mtx::crypto::EncryptedFile> &thumbnailEncryptedFile,
|
|
|
|
const QString &thumbnailUrl,
|
|
|
|
uint64_t thumbnailSize,
|
2022-03-21 07:49:12 +03:00
|
|
|
const QSize &thumbnailDimensions,
|
|
|
|
const QString &blurhash)
|
2020-11-15 06:52:49 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
mtx::events::msg::Video video;
|
|
|
|
video.info.mimetype = mime.toStdString();
|
|
|
|
video.info.size = dsize;
|
2022-03-21 07:49:12 +03:00
|
|
|
video.info.blurhash = blurhash.toStdString();
|
2021-09-18 01:22:33 +03:00
|
|
|
video.body = filename.toStdString();
|
|
|
|
|
2022-03-21 02:48:27 +03:00
|
|
|
if (duration > 0)
|
|
|
|
video.info.duration = duration;
|
|
|
|
if (dimensions.isValid()) {
|
|
|
|
video.info.h = dimensions.height();
|
|
|
|
video.info.w = dimensions.width();
|
|
|
|
}
|
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
if (file)
|
|
|
|
video.file = file;
|
|
|
|
else
|
|
|
|
video.url = url.toStdString();
|
|
|
|
|
2022-03-21 03:24:53 +03:00
|
|
|
if (!thumbnailUrl.isEmpty()) {
|
|
|
|
if (thumbnailEncryptedFile)
|
|
|
|
video.info.thumbnail_file = thumbnailEncryptedFile;
|
|
|
|
else
|
|
|
|
video.info.thumbnail_url = thumbnailUrl.toStdString();
|
|
|
|
|
|
|
|
video.info.thumbnail_info.h = thumbnailDimensions.height();
|
|
|
|
video.info.thumbnail_info.w = thumbnailDimensions.width();
|
|
|
|
video.info.thumbnail_info.size = thumbnailSize;
|
|
|
|
video.info.thumbnail_info.mimetype = "image/png";
|
|
|
|
}
|
|
|
|
|
2022-09-30 04:27:05 +03:00
|
|
|
video.relations = generateRelations();
|
2021-09-18 01:22:33 +03:00
|
|
|
|
|
|
|
room->sendMessageEvent(video, mtx::events::EventType::RoomMessage);
|
2020-11-15 06:52:49 +03:00
|
|
|
}
|
|
|
|
|
2021-07-15 21:37:52 +03:00
|
|
|
void
|
2021-07-21 02:03:38 +03:00
|
|
|
InputBar::sticker(CombinedImagePackModel *model, int row)
|
2021-07-15 21:37:52 +03:00
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (!model || row < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto img = model->imageAt(row);
|
|
|
|
|
|
|
|
mtx::events::msg::StickerImage sticker{};
|
|
|
|
sticker.info = img.info.value_or(mtx::common::ImageInfo{});
|
|
|
|
sticker.url = img.url;
|
2022-03-22 21:45:25 +03:00
|
|
|
sticker.body = img.body.empty() ? model->shortcodeAt(row).toStdString() : img.body;
|
2021-09-18 01:22:33 +03:00
|
|
|
|
|
|
|
// workaround for https://github.com/vector-im/element-ios/issues/2353
|
|
|
|
sticker.info.thumbnail_url = sticker.url;
|
|
|
|
sticker.info.thumbnail_info.mimetype = sticker.info.mimetype;
|
|
|
|
sticker.info.thumbnail_info.size = sticker.info.size;
|
|
|
|
sticker.info.thumbnail_info.h = sticker.info.h;
|
|
|
|
sticker.info.thumbnail_info.w = sticker.info.w;
|
|
|
|
|
2022-09-30 04:27:05 +03:00
|
|
|
sticker.relations = generateRelations();
|
2021-09-18 01:22:33 +03:00
|
|
|
|
|
|
|
room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
|
2021-07-15 21:37:52 +03:00
|
|
|
}
|
|
|
|
|
2020-11-09 05:12:37 +03:00
|
|
|
void
|
2021-12-03 03:54:43 +03:00
|
|
|
InputBar::command(const QString &command, QString args)
|
2020-11-09 05:12:37 +03:00
|
|
|
{
|
2021-12-29 06:28:08 +03:00
|
|
|
if (command == QLatin1String("me")) {
|
2021-09-18 01:22:33 +03:00
|
|
|
emote(args, false);
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("react")) {
|
2021-09-18 01:22:33 +03:00
|
|
|
auto eventId = room->reply();
|
|
|
|
if (!eventId.isEmpty())
|
|
|
|
reaction(eventId, args.trimmed());
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("join")) {
|
2022-03-31 00:38:38 +03:00
|
|
|
ChatPage::instance()->joinRoom(args.section(' ', 0, 0), args.section(' ', 1, -1));
|
2022-03-30 22:15:22 +03:00
|
|
|
} else if (command == QLatin1String("knock")) {
|
2022-03-31 00:38:38 +03:00
|
|
|
ChatPage::instance()->knockRoom(args.section(' ', 0, 0), args.section(' ', 1, -1));
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("part") || command == QLatin1String("leave")) {
|
2022-03-31 00:38:38 +03:00
|
|
|
ChatPage::instance()->timelineManager()->openLeaveRoomDialog(room->roomId(), args);
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("invite")) {
|
2022-05-07 19:53:16 +03:00
|
|
|
ChatPage::instance()->inviteUser(
|
|
|
|
room->roomId(), args.section(' ', 0, 0), args.section(' ', 1, -1));
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("kick")) {
|
2022-05-07 19:53:16 +03:00
|
|
|
ChatPage::instance()->kickUser(
|
|
|
|
room->roomId(), args.section(' ', 0, 0), args.section(' ', 1, -1));
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("ban")) {
|
2022-05-07 19:53:16 +03:00
|
|
|
ChatPage::instance()->banUser(
|
|
|
|
room->roomId(), args.section(' ', 0, 0), args.section(' ', 1, -1));
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("unban")) {
|
2022-05-07 19:53:16 +03:00
|
|
|
ChatPage::instance()->unbanUser(
|
|
|
|
room->roomId(), args.section(' ', 0, 0), args.section(' ', 1, -1));
|
2022-07-01 11:24:12 +03:00
|
|
|
} else if (command == QLatin1String("redact")) {
|
|
|
|
if (args.startsWith('@')) {
|
|
|
|
room->redactAllFromUser(args.section(' ', 0, 0), args.section(' ', 1, -1));
|
|
|
|
} else if (args.startsWith('$')) {
|
|
|
|
room->redactEvent(args.section(' ', 0, 0), args.section(' ', 1, -1));
|
|
|
|
}
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("roomnick")) {
|
2021-09-18 01:22:33 +03:00
|
|
|
mtx::events::state::Member member;
|
|
|
|
member.display_name = args.toStdString();
|
|
|
|
member.avatar_url =
|
|
|
|
cache::avatarUrl(room->roomId(),
|
|
|
|
QString::fromStdString(http::client()->user_id().to_string()))
|
|
|
|
.toStdString();
|
|
|
|
member.membership = mtx::events::state::Membership::Join;
|
|
|
|
|
2021-12-03 03:54:43 +03:00
|
|
|
http::client()->send_state_event(
|
|
|
|
room->roomId().toStdString(),
|
|
|
|
http::client()->user_id().to_string(),
|
|
|
|
member,
|
|
|
|
[](const mtx::responses::EventId &, mtx::http::RequestErr err) {
|
|
|
|
if (err)
|
|
|
|
nhlog::net()->error("Failed to set room displayname: {}",
|
|
|
|
err->matrix_error.error);
|
|
|
|
});
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("shrug")) {
|
|
|
|
message("¯\\_(ツ)_/¯" + (args.isEmpty() ? QLatin1String("") : " " + args));
|
|
|
|
} else if (command == QLatin1String("fliptable")) {
|
|
|
|
message(QStringLiteral("(╯°□°)╯︵ ┻━┻"));
|
|
|
|
} else if (command == QLatin1String("unfliptable")) {
|
|
|
|
message(QStringLiteral(" ┯━┯╭( º _ º╭)"));
|
|
|
|
} else if (command == QLatin1String("sovietflip")) {
|
|
|
|
message(QStringLiteral("ノ┬─┬ノ ︵ ( \\o°o)\\"));
|
|
|
|
} else if (command == QLatin1String("clear-timeline")) {
|
2021-09-18 01:22:33 +03:00
|
|
|
room->clearTimeline();
|
2022-02-05 10:40:56 +03:00
|
|
|
} else if (command == QLatin1String("reset-state")) {
|
|
|
|
room->resetState();
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("rotate-megolm-session")) {
|
2021-09-18 01:22:33 +03:00
|
|
|
cache::dropOutboundMegolmSession(room->roomId().toStdString());
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("md")) {
|
2021-09-18 01:22:33 +03:00
|
|
|
message(args, MarkdownOverride::ON);
|
2023-01-31 20:22:12 +03:00
|
|
|
} else if (command == QLatin1String("cmark")) {
|
|
|
|
message(args, MarkdownOverride::CMARK);
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("plain")) {
|
2021-09-18 01:22:33 +03:00
|
|
|
message(args, MarkdownOverride::OFF);
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("rainbow")) {
|
2021-09-18 01:22:33 +03:00
|
|
|
message(args, MarkdownOverride::ON, true);
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("rainbowme")) {
|
2021-09-18 01:22:33 +03:00
|
|
|
emote(args, true);
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("notice")) {
|
2021-09-18 01:22:33 +03:00
|
|
|
notice(args, false);
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("rainbownotice")) {
|
2021-09-18 01:22:33 +03:00
|
|
|
notice(args, true);
|
2022-12-10 18:17:15 +03:00
|
|
|
} else if (command == QLatin1String("confetti")) {
|
|
|
|
confetti(args, false);
|
|
|
|
} else if (command == QLatin1String("rainbowconfetti")) {
|
|
|
|
confetti(args, true);
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("goto")) {
|
2021-09-18 01:22:33 +03:00
|
|
|
// Goto has three different modes:
|
|
|
|
// 1 - Going directly to a given event ID
|
|
|
|
if (args[0] == '$') {
|
|
|
|
room->showEvent(args);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// 2 - Going directly to a given message index
|
|
|
|
if (args[0] >= '0' && args[0] <= '9') {
|
|
|
|
room->showEvent(args);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// 3 - Matrix URI handler, as if you clicked the URI
|
|
|
|
if (ChatPage::instance()->handleMatrixUri(args)) {
|
|
|
|
return;
|
2020-11-09 05:12:37 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
nhlog::net()->error("Could not resolve goto: {}", args.toStdString());
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("converttodm")) {
|
2021-11-21 07:23:38 +03:00
|
|
|
utils::markRoomAsDirect(this->room->roomId(),
|
|
|
|
cache::getMembers(this->room->roomId().toStdString(), 0, -1));
|
2021-12-29 06:28:08 +03:00
|
|
|
} else if (command == QLatin1String("converttoroom")) {
|
2021-11-21 07:23:38 +03:00
|
|
|
utils::removeDirectFromRoom(this->room->roomId());
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
2020-11-09 05:12:37 +03:00
|
|
|
}
|
2020-11-15 06:52:49 +03:00
|
|
|
|
2022-03-21 00:49:33 +03:00
|
|
|
MediaUpload::MediaUpload(std::unique_ptr<QIODevice> source_,
|
2022-10-04 00:57:30 +03:00
|
|
|
const QString &mimetype,
|
|
|
|
const QString &originalFilename,
|
2022-03-21 00:49:33 +03:00
|
|
|
bool encrypt,
|
|
|
|
QObject *parent)
|
|
|
|
: QObject(parent)
|
|
|
|
, source(std::move(source_))
|
|
|
|
, mimetype_(std::move(mimetype))
|
|
|
|
, originalFilename_(QFileInfo(originalFilename).fileName())
|
|
|
|
, encrypt_(encrypt)
|
2020-11-15 06:52:49 +03:00
|
|
|
{
|
2022-03-21 00:49:33 +03:00
|
|
|
mimeClass_ = mimetype_.left(mimetype_.indexOf(u'/'));
|
|
|
|
|
|
|
|
if (!source->isOpen())
|
|
|
|
source->open(QIODevice::ReadOnly);
|
|
|
|
|
|
|
|
data = source->readAll();
|
2022-03-21 02:48:27 +03:00
|
|
|
source->reset();
|
2022-03-21 00:49:33 +03:00
|
|
|
|
|
|
|
if (!data.size()) {
|
|
|
|
nhlog::ui()->warn("Attempted to upload zero-byte file?! Mimetype {}, filename {}",
|
|
|
|
mimetype_.toStdString(),
|
|
|
|
originalFilename_.toStdString());
|
|
|
|
emit uploadFailed(this);
|
2021-09-18 01:22:33 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-21 00:49:33 +03:00
|
|
|
nhlog::ui()->debug("Mime: {}", mimetype_.toStdString());
|
|
|
|
if (mimeClass_ == u"image") {
|
|
|
|
QImage img = utils::readImage(data);
|
2022-03-21 02:48:27 +03:00
|
|
|
setThumbnail(img.scaled(
|
|
|
|
std::min(800, img.width()), std::min(800, img.height()), Qt::KeepAspectRatioByExpanding));
|
2022-03-21 00:49:33 +03:00
|
|
|
|
|
|
|
dimensions_ = img.size();
|
|
|
|
if (img.height() > 200 && img.width() > 360)
|
|
|
|
img = img.scaled(360, 200, Qt::KeepAspectRatioByExpanding);
|
|
|
|
std::vector<unsigned char> data_;
|
|
|
|
for (int y = 0; y < img.height(); y++) {
|
|
|
|
for (int x = 0; x < img.width(); x++) {
|
|
|
|
auto p = img.pixel(x, y);
|
|
|
|
data_.push_back(static_cast<unsigned char>(qRed(p)));
|
|
|
|
data_.push_back(static_cast<unsigned char>(qGreen(p)));
|
|
|
|
data_.push_back(static_cast<unsigned char>(qBlue(p)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
blurhash_ =
|
|
|
|
QString::fromStdString(blurhash::encode(data_.data(), img.width(), img.height(), 4, 3));
|
2022-03-21 02:48:27 +03:00
|
|
|
} else if (mimeClass_ == u"video" || mimeClass_ == u"audio") {
|
|
|
|
auto mediaPlayer = new QMediaPlayer(
|
|
|
|
this,
|
2022-03-22 03:19:48 +03:00
|
|
|
mimeClass_ == u"video" ? QFlags{QMediaPlayer::VideoSurface} : QMediaPlayer::Flags{});
|
2022-03-21 02:48:27 +03:00
|
|
|
mediaPlayer->setMuted(true);
|
|
|
|
|
|
|
|
if (mimeClass_ == u"video") {
|
|
|
|
auto newSurface = new InputVideoSurface(this);
|
|
|
|
connect(
|
|
|
|
newSurface, &InputVideoSurface::newImage, this, [this, mediaPlayer](QImage img) {
|
2022-03-22 03:19:48 +03:00
|
|
|
if (img.size().isEmpty())
|
|
|
|
return;
|
|
|
|
|
2022-03-21 02:48:27 +03:00
|
|
|
mediaPlayer->stop();
|
|
|
|
|
2022-03-21 03:24:53 +03:00
|
|
|
auto orientation = mediaPlayer->metaData(QMediaMetaData::Orientation).toInt();
|
|
|
|
if (orientation == 90 || orientation == 270 || orientation == 180) {
|
|
|
|
img =
|
|
|
|
img.transformed(QTransform().rotate(orientation), Qt::SmoothTransformation);
|
|
|
|
}
|
|
|
|
|
2022-03-21 02:48:27 +03:00
|
|
|
nhlog::ui()->debug("Got image {}x{}", img.width(), img.height());
|
|
|
|
|
|
|
|
this->setThumbnail(img);
|
|
|
|
|
|
|
|
if (!dimensions_.isValid())
|
|
|
|
this->dimensions_ = img.size();
|
|
|
|
|
|
|
|
if (img.height() > 200 && img.width() > 360)
|
|
|
|
img = img.scaled(360, 200, Qt::KeepAspectRatioByExpanding);
|
|
|
|
std::vector<unsigned char> data_;
|
|
|
|
for (int y = 0; y < img.height(); y++) {
|
|
|
|
for (int x = 0; x < img.width(); x++) {
|
|
|
|
auto p = img.pixel(x, y);
|
|
|
|
data_.push_back(static_cast<unsigned char>(qRed(p)));
|
|
|
|
data_.push_back(static_cast<unsigned char>(qGreen(p)));
|
|
|
|
data_.push_back(static_cast<unsigned char>(qBlue(p)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
blurhash_ = QString::fromStdString(
|
|
|
|
blurhash::encode(data_.data(), img.width(), img.height(), 4, 3));
|
|
|
|
});
|
|
|
|
mediaPlayer->setVideoOutput(newSurface);
|
|
|
|
}
|
|
|
|
|
|
|
|
connect(mediaPlayer,
|
|
|
|
qOverload<QMediaPlayer::Error>(&QMediaPlayer::error),
|
|
|
|
this,
|
2022-03-21 03:35:47 +03:00
|
|
|
[mediaPlayer](QMediaPlayer::Error error) {
|
2022-03-21 03:24:53 +03:00
|
|
|
nhlog::ui()->debug("Media player error {} and errorStr {}",
|
|
|
|
error,
|
|
|
|
mediaPlayer->errorString().toStdString());
|
2022-03-21 02:48:27 +03:00
|
|
|
});
|
|
|
|
connect(mediaPlayer,
|
|
|
|
&QMediaPlayer::mediaStatusChanged,
|
2022-03-21 03:35:47 +03:00
|
|
|
[mediaPlayer](QMediaPlayer::MediaStatus status) {
|
2022-03-21 03:24:53 +03:00
|
|
|
nhlog::ui()->debug(
|
2022-03-21 02:48:27 +03:00
|
|
|
"Media player status {} and error {}", status, mediaPlayer->error());
|
|
|
|
});
|
|
|
|
connect(mediaPlayer,
|
|
|
|
qOverload<const QString &, const QVariant &>(&QMediaPlayer::metaDataChanged),
|
2022-07-23 14:19:24 +03:00
|
|
|
this,
|
2022-10-04 00:57:30 +03:00
|
|
|
[this, mediaPlayer](const QString &t, const QVariant &) {
|
2022-03-21 03:24:53 +03:00
|
|
|
nhlog::ui()->debug("Got metadata {}", t.toStdString());
|
2022-03-21 02:48:27 +03:00
|
|
|
|
|
|
|
if (mediaPlayer->duration() > 0)
|
|
|
|
this->duration_ = mediaPlayer->duration();
|
|
|
|
|
2022-03-22 03:19:48 +03:00
|
|
|
auto dimensions = mediaPlayer->metaData(QMediaMetaData::Resolution).toSize();
|
|
|
|
if (!dimensions.isEmpty()) {
|
|
|
|
dimensions_ = dimensions;
|
|
|
|
auto orientation =
|
|
|
|
mediaPlayer->metaData(QMediaMetaData::Orientation).toInt();
|
|
|
|
if (orientation == 90 || orientation == 270) {
|
|
|
|
dimensions_.transpose();
|
|
|
|
}
|
2022-03-21 02:48:27 +03:00
|
|
|
}
|
|
|
|
});
|
2022-07-23 14:19:24 +03:00
|
|
|
connect(
|
|
|
|
mediaPlayer, &QMediaPlayer::durationChanged, this, [this, mediaPlayer](qint64 duration) {
|
|
|
|
if (duration > 0) {
|
|
|
|
this->duration_ = mediaPlayer->duration();
|
|
|
|
if (mimeClass_ == u"audio")
|
|
|
|
mediaPlayer->stop();
|
|
|
|
}
|
|
|
|
nhlog::ui()->debug("Duration changed {}", duration);
|
|
|
|
});
|
2022-03-22 20:12:39 +03:00
|
|
|
|
|
|
|
auto originalFile = qobject_cast<QFile *>(source.get());
|
|
|
|
|
|
|
|
mediaPlayer->setMedia(
|
|
|
|
QMediaContent(originalFile ? originalFile->fileName() : originalFilename_), source.get());
|
2022-03-21 03:32:31 +03:00
|
|
|
|
2022-03-21 02:48:27 +03:00
|
|
|
mediaPlayer->play();
|
2022-03-21 00:49:33 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaUpload::startUpload()
|
|
|
|
{
|
2022-03-21 03:24:53 +03:00
|
|
|
if (!thumbnail_.isNull() && thumbnailUrl_.isEmpty()) {
|
|
|
|
QByteArray ba;
|
|
|
|
QBuffer buffer(&ba);
|
|
|
|
buffer.open(QIODevice::WriteOnly);
|
2022-03-24 21:34:20 +03:00
|
|
|
thumbnail_.save(&buffer, "PNG", 0);
|
|
|
|
if (ba.size() >= (data.size() - data.size() / 10)) {
|
|
|
|
nhlog::ui()->info(
|
|
|
|
"Thumbnail is not a lot smaller than original image, not uploading it");
|
|
|
|
nhlog::ui()->debug(
|
|
|
|
"\n Image size: {:9d}\nThumbnail size: {:9d}", data.size(), ba.size());
|
|
|
|
} else {
|
|
|
|
auto payload = std::string(ba.data(), ba.size());
|
|
|
|
if (encrypt_) {
|
|
|
|
mtx::crypto::BinaryBuf buf;
|
|
|
|
std::tie(buf, thumbnailEncryptedFile) =
|
|
|
|
mtx::crypto::encrypt_file(std::move(payload));
|
|
|
|
payload = mtx::crypto::to_string(buf);
|
|
|
|
}
|
|
|
|
thumbnailSize_ = payload.size();
|
|
|
|
|
|
|
|
http::client()->upload(
|
|
|
|
payload,
|
|
|
|
encryptedFile ? "application/octet-stream" : "image/png",
|
|
|
|
"",
|
|
|
|
[this](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) mutable {
|
|
|
|
if (err) {
|
|
|
|
emit ChatPage::instance()->showNotification(
|
|
|
|
tr("Failed to upload media. Please try again."));
|
|
|
|
nhlog::net()->warn("failed to upload media: {} {} ({})",
|
|
|
|
err->matrix_error.error,
|
|
|
|
to_string(err->matrix_error.errcode),
|
|
|
|
static_cast<int>(err->status_code));
|
|
|
|
thumbnail_ = QImage();
|
|
|
|
startUpload();
|
|
|
|
return;
|
|
|
|
}
|
2022-03-21 03:24:53 +03:00
|
|
|
|
2022-03-24 21:34:20 +03:00
|
|
|
thumbnailUrl_ = QString::fromStdString(res.content_uri);
|
|
|
|
if (thumbnailEncryptedFile)
|
|
|
|
thumbnailEncryptedFile->url = res.content_uri;
|
2022-03-21 03:24:53 +03:00
|
|
|
|
2022-03-24 21:34:20 +03:00
|
|
|
startUpload();
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
2022-03-21 03:24:53 +03:00
|
|
|
}
|
|
|
|
|
2022-03-21 00:49:33 +03:00
|
|
|
auto payload = std::string(data.data(), data.size());
|
|
|
|
if (encrypt_) {
|
|
|
|
mtx::crypto::BinaryBuf buf;
|
|
|
|
std::tie(buf, encryptedFile) = mtx::crypto::encrypt_file(std::move(payload));
|
|
|
|
payload = mtx::crypto::to_string(buf);
|
|
|
|
}
|
|
|
|
size_ = payload.size();
|
|
|
|
|
|
|
|
http::client()->upload(
|
|
|
|
payload,
|
|
|
|
encryptedFile ? "application/octet-stream" : mimetype_.toStdString(),
|
2022-03-21 03:24:53 +03:00
|
|
|
encrypt_ ? "" : originalFilename_.toStdString(),
|
2022-03-21 00:49:33 +03:00
|
|
|
[this](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) mutable {
|
|
|
|
if (err) {
|
|
|
|
emit ChatPage::instance()->showNotification(
|
|
|
|
tr("Failed to upload media. Please try again."));
|
|
|
|
nhlog::net()->warn("failed to upload media: {} {} ({})",
|
|
|
|
err->matrix_error.error,
|
|
|
|
to_string(err->matrix_error.errcode),
|
|
|
|
static_cast<int>(err->status_code));
|
|
|
|
emit uploadFailed(this);
|
2021-09-28 02:42:35 +03:00
|
|
|
return;
|
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
|
2022-03-21 00:49:33 +03:00
|
|
|
auto url = QString::fromStdString(res.content_uri);
|
|
|
|
if (encryptedFile)
|
|
|
|
encryptedFile->url = res.content_uri;
|
2021-09-18 01:22:33 +03:00
|
|
|
|
2022-03-21 00:49:33 +03:00
|
|
|
emit uploadComplete(this, std::move(url));
|
|
|
|
});
|
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
|
2022-03-21 00:49:33 +03:00
|
|
|
void
|
2022-10-04 00:57:30 +03:00
|
|
|
InputBar::finalizeUpload(MediaUpload *upload, const QString &url)
|
2022-03-21 00:49:33 +03:00
|
|
|
{
|
|
|
|
auto mime = upload->mimetype();
|
|
|
|
auto filename = upload->filename();
|
|
|
|
auto mimeClass = upload->mimeClass();
|
|
|
|
auto size = upload->size();
|
|
|
|
auto encryptedFile = upload->encryptedFile_();
|
|
|
|
if (mimeClass == u"image")
|
2022-03-21 07:49:12 +03:00
|
|
|
image(filename,
|
|
|
|
encryptedFile,
|
|
|
|
url,
|
|
|
|
mime,
|
|
|
|
size,
|
|
|
|
upload->dimensions(),
|
|
|
|
upload->thumbnailEncryptedFile_(),
|
|
|
|
upload->thumbnailUrl(),
|
|
|
|
upload->thumbnailSize(),
|
|
|
|
upload->thumbnailImg().size(),
|
|
|
|
upload->blurhash());
|
2022-03-21 00:49:33 +03:00
|
|
|
else if (mimeClass == u"audio")
|
2022-03-21 02:48:27 +03:00
|
|
|
audio(filename, encryptedFile, url, mime, size, upload->duration());
|
2022-03-21 00:49:33 +03:00
|
|
|
else if (mimeClass == u"video")
|
2022-03-21 03:24:53 +03:00
|
|
|
video(filename,
|
|
|
|
encryptedFile,
|
|
|
|
url,
|
|
|
|
mime,
|
|
|
|
size,
|
|
|
|
upload->duration(),
|
|
|
|
upload->dimensions(),
|
|
|
|
upload->thumbnailEncryptedFile_(),
|
|
|
|
upload->thumbnailUrl(),
|
|
|
|
upload->thumbnailSize(),
|
2022-03-21 07:49:12 +03:00
|
|
|
upload->thumbnailImg().size(),
|
|
|
|
upload->blurhash());
|
2022-03-21 00:49:33 +03:00
|
|
|
else
|
|
|
|
file(filename, encryptedFile, url, mime, size);
|
2021-09-18 01:22:33 +03:00
|
|
|
|
2022-03-21 00:49:33 +03:00
|
|
|
removeRunUpload(upload);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
InputBar::removeRunUpload(MediaUpload *upload)
|
|
|
|
{
|
|
|
|
auto it = std::find_if(runningUploads.begin(),
|
|
|
|
runningUploads.end(),
|
|
|
|
[upload](const UploadHandle &h) { return h.get() == upload; });
|
|
|
|
if (it != runningUploads.end())
|
|
|
|
runningUploads.erase(it);
|
|
|
|
|
|
|
|
if (runningUploads.empty())
|
|
|
|
setUploading(false);
|
|
|
|
else
|
|
|
|
runningUploads.front()->startUpload();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
InputBar::startUploadFromPath(const QString &path)
|
|
|
|
{
|
|
|
|
if (path.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto file = std::make_unique<QFile>(path);
|
|
|
|
|
|
|
|
if (!file->open(QIODevice::ReadOnly)) {
|
|
|
|
nhlog::ui()->warn(
|
|
|
|
"Failed to open file ({}): {}", path.toStdString(), file->errorString().toStdString());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QMimeDatabase db;
|
|
|
|
auto mime = db.mimeTypeForFileNameAndData(path, file.get());
|
|
|
|
|
|
|
|
startUpload(std::move(file), path, mime.name());
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
InputBar::startUploadFromMimeData(const QMimeData &source, const QString &format)
|
|
|
|
{
|
|
|
|
auto file = std::make_unique<QBuffer>();
|
|
|
|
file->setData(source.data(format));
|
|
|
|
|
|
|
|
if (!file->open(QIODevice::ReadOnly)) {
|
|
|
|
nhlog::ui()->warn("Failed to open buffer: {}", file->errorString().toStdString());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-22 20:28:35 +03:00
|
|
|
QMimeDatabase db;
|
|
|
|
auto mime = db.mimeTypeForName(format);
|
|
|
|
auto suffix = mime.preferredSuffix();
|
|
|
|
QString filename = QStringLiteral("clipboard");
|
|
|
|
|
|
|
|
startUpload(std::move(file), suffix.isEmpty() ? filename : (filename + "." + suffix), format);
|
2022-03-21 00:49:33 +03:00
|
|
|
}
|
|
|
|
void
|
|
|
|
InputBar::startUpload(std::unique_ptr<QIODevice> dev, const QString &orgPath, const QString &format)
|
|
|
|
{
|
|
|
|
auto upload =
|
|
|
|
UploadHandle(new MediaUpload(std::move(dev), format, orgPath, room->isEncrypted(), this));
|
|
|
|
connect(upload.get(), &MediaUpload::uploadComplete, this, &InputBar::finalizeUpload);
|
2022-06-15 16:55:03 +03:00
|
|
|
// TODO(Nico): Show a retry option
|
|
|
|
connect(upload.get(), &MediaUpload::uploadFailed, this, [this](MediaUpload *up) {
|
|
|
|
ChatPage::instance()->showNotification(tr("Upload of '%1' failed").arg(up->filename()));
|
|
|
|
removeRunUpload(up);
|
|
|
|
});
|
2022-03-21 00:49:33 +03:00
|
|
|
|
|
|
|
unconfirmedUploads.push_back(std::move(upload));
|
|
|
|
|
2022-03-21 03:24:53 +03:00
|
|
|
nhlog::ui()->debug("Uploads {}", unconfirmedUploads.size());
|
2022-03-21 00:49:33 +03:00
|
|
|
emit uploadsChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
InputBar::acceptUploads()
|
|
|
|
{
|
|
|
|
if (unconfirmedUploads.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
bool wasntRunning = runningUploads.empty();
|
|
|
|
runningUploads.insert(runningUploads.end(),
|
|
|
|
std::make_move_iterator(unconfirmedUploads.begin()),
|
|
|
|
std::make_move_iterator(unconfirmedUploads.end()));
|
|
|
|
unconfirmedUploads.clear();
|
|
|
|
emit uploadsChanged();
|
|
|
|
|
|
|
|
if (wasntRunning) {
|
|
|
|
setUploading(true);
|
|
|
|
runningUploads.front()->startUpload();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
InputBar::declineUploads()
|
|
|
|
{
|
|
|
|
unconfirmedUploads.clear();
|
|
|
|
emit uploadsChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariantList
|
|
|
|
InputBar::uploads() const
|
|
|
|
{
|
|
|
|
QVariantList l;
|
|
|
|
l.reserve((int)unconfirmedUploads.size());
|
|
|
|
|
|
|
|
for (auto &e : unconfirmedUploads)
|
|
|
|
l.push_back(QVariant::fromValue(e.get()));
|
|
|
|
return l;
|
2020-11-15 06:52:49 +03:00
|
|
|
}
|
2020-11-16 01:14:47 +03:00
|
|
|
|
2020-11-17 04:37:43 +03:00
|
|
|
void
|
|
|
|
InputBar::startTyping()
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (!typingRefresh_.isActive()) {
|
|
|
|
typingRefresh_.start();
|
|
|
|
|
|
|
|
if (ChatPage::instance()->userSettings()->typingNotifications()) {
|
|
|
|
http::client()->start_typing(
|
|
|
|
room->roomId().toStdString(), 10'000, [](mtx::http::RequestErr err) {
|
|
|
|
if (err) {
|
|
|
|
nhlog::net()->warn("failed to send typing notification: {}",
|
|
|
|
err->matrix_error.error);
|
|
|
|
}
|
|
|
|
});
|
2020-11-17 04:37:43 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
|
|
|
typingTimeout_.start();
|
2020-11-17 04:37:43 +03:00
|
|
|
}
|
|
|
|
void
|
|
|
|
InputBar::stopTyping()
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
typingRefresh_.stop();
|
|
|
|
typingTimeout_.stop();
|
2020-11-17 04:37:43 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
if (!ChatPage::instance()->userSettings()->typingNotifications())
|
|
|
|
return;
|
2020-11-17 04:37:43 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
http::client()->stop_typing(room->roomId().toStdString(), [](mtx::http::RequestErr err) {
|
|
|
|
if (err) {
|
|
|
|
nhlog::net()->warn("failed to stop typing notifications: {}", err->matrix_error.error);
|
|
|
|
}
|
|
|
|
});
|
2020-11-17 04:37:43 +03:00
|
|
|
}
|
2021-05-28 23:14:59 +03:00
|
|
|
|
|
|
|
void
|
|
|
|
InputBar::reaction(const QString &reactedEvent, const QString &reactionKey)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
auto reactions = room->reactions(reactedEvent.toStdString());
|
2021-05-28 23:14:59 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
QString selfReactedEvent;
|
|
|
|
for (const auto &reaction : reactions) {
|
|
|
|
if (reactionKey == reaction.key_) {
|
|
|
|
selfReactedEvent = reaction.selfReactedEvent_;
|
|
|
|
break;
|
2021-05-28 23:14:59 +03:00
|
|
|
}
|
2021-09-18 01:22:33 +03:00
|
|
|
}
|
|
|
|
|
2021-12-29 06:28:08 +03:00
|
|
|
if (selfReactedEvent.startsWith(QLatin1String("m")))
|
2021-09-18 01:22:33 +03:00
|
|
|
return;
|
|
|
|
|
|
|
|
// If selfReactedEvent is empty, that means we haven't previously reacted
|
|
|
|
if (selfReactedEvent.isEmpty()) {
|
|
|
|
mtx::events::msg::Reaction reaction;
|
|
|
|
mtx::common::Relation rel;
|
|
|
|
rel.rel_type = mtx::common::RelationType::Annotation;
|
|
|
|
rel.event_id = reactedEvent.toStdString();
|
|
|
|
rel.key = reactionKey.toStdString();
|
|
|
|
reaction.relations.relations.push_back(rel);
|
|
|
|
|
|
|
|
room->sendMessageEvent(reaction, mtx::events::EventType::Reaction);
|
2021-12-13 02:43:05 +03:00
|
|
|
|
|
|
|
auto recents = UserSettings::instance()->recentReactions();
|
|
|
|
if (recents.contains(reactionKey))
|
|
|
|
recents.removeOne(reactionKey);
|
|
|
|
else if (recents.size() >= 6)
|
|
|
|
recents.removeLast();
|
|
|
|
recents.push_front(reactionKey);
|
|
|
|
UserSettings::instance()->setRecentReactions(recents);
|
2021-09-18 01:22:33 +03:00
|
|
|
// Otherwise, we have previously reacted and the reaction should be redacted
|
|
|
|
} else {
|
|
|
|
room->redactEvent(selfReactedEvent);
|
|
|
|
}
|
2021-05-28 23:14:59 +03:00
|
|
|
}
|