blob: a662e2d587d383a470a0c6cc1791c28a70095dcc [file] [log] [blame]
Andreas Traczyk43c08232018-10-31 13:42:09 -04001/***************************************************************************
2 * Copyright (C) 2017-2018 by Savoir-faire Linux *
3 * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> *
4 * Author: Alexandre Viau <alexandre.viau@savoirfairelinux.com> *
Sébastien Blin942d1022018-12-13 09:49:03 -05005 * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> *
Andreas Traczyk43c08232018-10-31 13:42:09 -04006 * Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com> *
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 3 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
20 **************************************************************************/
21
22#include "messagewebview.h"
23
24#include <QScrollBar>
25#include <QMouseEvent>
26#include <QDebug>
27#include <QMenu>
28#include <QDesktopServices>
29#include <QFileDialog>
30#include <QWebEnginePage>
31#include <QWebEngineScript>
32#include <QWebEngineScriptCollection>
33#include <QWebEngineSettings>
34#include <QWebChannel>
35#include <QTimer>
36
37#include <ciso646>
38#include <fstream>
39
40#include "utils.h"
41#include "webchathelpers.h"
42#include "messagewebpage.h"
43#include "lrcinstance.h"
44
45MessageWebView::MessageWebView(QWidget *parent)
46 : QWebEngineView(parent)
47{
48 setPage(new MessageWebPage());
49
50 settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
51 settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true);
52 settings()->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, false);
53 settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
54 settings()->setAttribute(QWebEngineSettings::PluginsEnabled, false);
55 settings()->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, false);
56 settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, false);
57 settings()->setAttribute(QWebEngineSettings::LinksIncludedInFocusChain, false);
58 settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
59
60 setContextMenuPolicy(Qt::ContextMenuPolicy::NoContextMenu);
61
62 jsBridge_ = new PrivateBridging(this);
63 webChannel_ = new QWebChannel(page());
64 webChannel_->registerObject(QStringLiteral("jsbridge"), jsBridge_);
65 page()->setWebChannel(webChannel_);
66
67 connect(this, &QWebEngineView::renderProcessTerminated,
68 [this](QWebEnginePage::RenderProcessTerminationStatus termStatus, int statusCode) {
69 Q_UNUSED(statusCode);
70 QString status;
71 switch (termStatus) {
72 case QWebEnginePage::NormalTerminationStatus:
73 qDebug() << "MessageWebView - Render process normal exit";
74 break;
75 case QWebEnginePage::AbnormalTerminationStatus:
76 qDebug() << "MessageWebView - Render process abnormal exit";
77 break;
78 case QWebEnginePage::CrashedTerminationStatus:
79 qDebug() << "MessageWebView - Render process crashed";
80 break;
81 case QWebEnginePage::KilledTerminationStatus:
82 qDebug() << "MessageWebView - Render process killed";
83 break;
84 }
85 });
86}
87
88MessageWebView::~MessageWebView()
89{
90}
91
92void MessageWebView::buildView()
93{
94 auto html = Utils::QByteArrayFromFile(":/web/chatview.html");
95 page()->setHtml(html, QUrl(":/web/chatview.html"));
96 connect(this, &QWebEngineView::loadFinished, this, &MessageWebView::slotLoadFinished);
97}
98
99void
100MessageWebView::slotLoadFinished()
101{
102 insertStyleSheet("chatcss", Utils::QByteArrayFromFile(":/web/chatview.css"));
103 page()->runJavaScript(Utils::QByteArrayFromFile(":/web/linkify.js"), QWebEngineScript::MainWorld);
104 page()->runJavaScript(Utils::QByteArrayFromFile(":/web/linkify-html.js"), QWebEngineScript::MainWorld);
105 page()->runJavaScript(Utils::QByteArrayFromFile(":/web/linkify-string.js"), QWebEngineScript::MainWorld);
106 page()->runJavaScript(Utils::QByteArrayFromFile(":/web/qwebchannel.js"), QWebEngineScript::MainWorld);
107 page()->runJavaScript(Utils::QByteArrayFromFile(":/web/chatview.js"), QWebEngineScript::MainWorld);
108}
109
110void
111MessageWebView::insertStyleSheet(const QString &name, const QString &source)
112{
113 QWebEngineScript script;
114 auto simplifiedCSS = source.simplified().replace("'", "\"");
115 QString s = QString::fromLatin1("(function() {"\
116 " var node = document.createElement('style');"\
117 " node.id = '%1';"\
118 " node.innerHTML = '%2';"\
119 " document.head.appendChild(node);"\
120 "})()").arg(name).arg(simplifiedCSS);
121 page()->runJavaScript(s);
122
123 script.setName(name);
124 script.setSourceCode(s);
125 script.setInjectionPoint(QWebEngineScript::DocumentReady);
126 script.setRunsOnSubFrames(true);
127 script.setWorldId(QWebEngineScript::MainWorld);
128 page()->scripts().insert(script);
129}
130
131void
132MessageWebView::removeStyleSheet(const QString &name)
133{
134 QString s = QString::fromLatin1("(function() {"\
135 " var element = document.getElementById('%1');"\
136 " element.outerHTML = '';"\
137 " delete element;"\
138 "})()").arg(name);
139
140 page()->runJavaScript(s);
141
142 QWebEngineScript script = page()->scripts().findScript(name);
143 page()->scripts().remove(script);
144}
145
146void MessageWebView::clear()
147{
148 QString s = QString::fromLatin1("clearMessages();");
149 page()->runJavaScript(s, QWebEngineScript::MainWorld);
150}
151
152void
153MessageWebView::setDisplayLinks(bool display)
154{
155 QString s = QString::fromLatin1("setDisplayLinks('%1');")
156 .arg(display ? "true" : "false");
157 page()->runJavaScript(s, QWebEngineScript::MainWorld);
158}
159
160void
161MessageWebView::printNewInteraction(lrc::api::ConversationModel& conversationModel,
162 uint64_t msgId,
163 const lrc::api::interaction::Info& interaction)
164{
165 auto interactionObject = interactionToJsonInteractionObject(conversationModel, msgId, interaction).toUtf8();
166 QString s = QString::fromLatin1("addMessage(%1);")
167 .arg(interactionObject.constData());
168 page()->runJavaScript(s, QWebEngineScript::MainWorld);
169}
170
171void
172MessageWebView::updateInteraction(lrc::api::ConversationModel& conversationModel,
173 uint64_t msgId,
174 const lrc::api::interaction::Info& interaction)
175{
176 auto interactionObject = interactionToJsonInteractionObject(conversationModel, msgId, interaction).toUtf8();
177 QString s = QString::fromLatin1("updateMessage(%1);")
178 .arg(interactionObject.constData());
179 page()->runJavaScript(s, QWebEngineScript::MainWorld);
180}
181
182void
183MessageWebView::removeInteraction(uint64_t interactionId)
184{
185 QString s = QString::fromLatin1("removeInteraction(%1);")
186 .arg(QString::number(interactionId));
187 page()->runJavaScript(s, QWebEngineScript::MainWorld);
188}
189
190void
191MessageWebView::printHistory(lrc::api::ConversationModel& conversationModel,
192 const std::map<uint64_t,
193 lrc::api::interaction::Info> interactions)
194{
195 auto interactionsStr = interactionsToJsonArrayObject(conversationModel, interactions).toUtf8();
196 QString s = QString::fromLatin1("printHistory(%1);")
197 .arg(interactionsStr.constData());
198 page()->runJavaScript(s, QWebEngineScript::MainWorld);
199}
200
201void
202MessageWebView::setSenderImage(const std::string& sender,
203 const std::string& senderImage)
204{
205 QJsonObject setSenderImageObject = QJsonObject();
206 setSenderImageObject.insert("sender_contact_method", QJsonValue(QString(sender.c_str())));
207 setSenderImageObject.insert("sender_image", QJsonValue(QString(senderImage.c_str())));
208
209 auto setSenderImageObjectString = QString(QJsonDocument(setSenderImageObject).toJson(QJsonDocument::Compact));
210 QString s = QString::fromLatin1("setSenderImage(%1);")
211 .arg(setSenderImageObjectString.toUtf8().constData());
212 page()->runJavaScript(s, QWebEngineScript::MainWorld);
213}
214
215void
Sébastien Blin942d1022018-12-13 09:49:03 -0500216MessageWebView::setInvitation(bool show, const std::string& contactUri, const std::string& contactId)
Andreas Traczyk43c08232018-10-31 13:42:09 -0400217{
Sébastien Blin942d1022018-12-13 09:49:03 -0500218 QString s = QString::fromLatin1(show ? "showInvitation(\"%1\", \"%2\")" : "showInvitation()")
219 .arg(QString(contactUri.c_str())).arg(QString(contactId.c_str()));
Andreas Traczyk43c08232018-10-31 13:42:09 -0400220 page()->runJavaScript(s, QWebEngineScript::MainWorld);
221}
222
223void
224MessageWebView::hideMessages()
225{
226 QString s = QString::fromLatin1("hideBody();");
227 page()->runJavaScript(s, QWebEngineScript::MainWorld);
228}
229
230// JS bridging incoming
231Q_INVOKABLE int
Sébastien Blin942d1022018-12-13 09:49:03 -0500232PrivateBridging::log(const QString& arg)
233{
234 qDebug() << "JS log: " << arg;
235 return 0;
236}
237
238Q_INVOKABLE int
Andreas Traczyk43c08232018-10-31 13:42:09 -0400239PrivateBridging::deleteInteraction(const QString& arg)
240{
241 bool ok;
242 uint64_t interactionUid = arg.toULongLong(&ok);
243 if (ok) {
244 LRCInstance::getCurrentConversationModel()->clearInteractionFromConversation(
245 LRCInstance::getSelectedConvUid(),
246 interactionUid
247 );
248 } else {
249 qDebug() << "deleteInteraction - invalid arg" << arg;
250 }
251 return 0;
252}
253
254Q_INVOKABLE int
255PrivateBridging::retryInteraction(const QString& arg)
256{
257 bool ok;
258 uint64_t interactionUid = arg.toULongLong(&ok);
259 if (ok) {
260 LRCInstance::getCurrentConversationModel()->retryInteraction(
261 LRCInstance::getSelectedConvUid(),
262 interactionUid
263 );
264 } else {
265 qDebug() << "retryInteraction - invalid arg" << arg;
266 }
267 return 0;
268}
269
270Q_INVOKABLE int
271PrivateBridging::openFile(const QString& arg)
272{
273 QDesktopServices::openUrl(arg);
274 return 0;
275}
276
277Q_INVOKABLE int
278PrivateBridging::acceptFile(const QString& arg)
279{
280 try {
281 auto interactionUid = std::stoull(arg.toStdString());
282
283 lrc::api::datatransfer::Info info = {};
284 auto convUid = LRCInstance::getSelectedConvUid();
285 LRCInstance::getCurrentConversationModel()->getTransferInfo(interactionUid, info);
286
Isa Nanic7a3dfa42018-12-12 12:34:42 -0500287 auto downloadsFolder = QSettings().value(SettingsKey::downloadPath, "Downloads").toString().toStdString() + "/";
288
Andreas Traczyk43c08232018-10-31 13:42:09 -0400289 // get full path
290 std::string filename = downloadsFolder.c_str();
291 if (!filename.empty() && filename.back() != '/')
292 filename += "/";
293 auto wantedFilename = filename + info.displayName;
294 auto duplicate = 0;
295 while (std::ifstream(wantedFilename).good()) {
296 ++duplicate;
297 auto extensionIdx = info.displayName.find_last_of(".");
298 if (extensionIdx == std::string::npos)
299 wantedFilename = filename + info.displayName + " (" + std::to_string(duplicate) + ")";
300 else
301 wantedFilename = filename + info.displayName.substr(0, extensionIdx) + " (" + std::to_string(duplicate) + ")" + info.displayName.substr(extensionIdx);
302 }
303 LRCInstance::getCurrentConversationModel()->acceptTransfer(convUid, interactionUid, wantedFilename);
304 } catch (...) {
305 qDebug() << "JS bridging - exception during acceptFile: " << arg;
306 }
307 return 0;
308}
309
310Q_INVOKABLE int
311PrivateBridging::refuseFile(const QString& arg)
312{
313 try {
314 auto interactionUid = std::stoull(arg.toStdString());
315 auto convUid = LRCInstance::getSelectedConvUid();
316 LRCInstance::getCurrentConversationModel()->cancelTransfer(convUid, interactionUid);
317 } catch (...) {
318 qDebug() << "JS bridging - exception during refuseFile:" << arg;
319 }
320 return 0;
321}
322
323Q_INVOKABLE int
324PrivateBridging::sendMessage(const QString& arg)
325{
326 try {
327 auto convUid = LRCInstance::getSelectedConvUid();
328 LRCInstance::getCurrentConversationModel()->sendMessage(convUid, arg.toStdString());
329 } catch (...) {
330 qDebug() << "JS bridging - exception during sendMessage:" << arg;
331 }
332 return 0;
333}
334
335Q_INVOKABLE int
336PrivateBridging::sendFile()
337{
338 qDebug() << "JS bridging - MessageWebView::sendFile";
339 QString filePath = QFileDialog::getOpenFileName((QWidget*)this->parent(), tr("Choose File"), "", tr("Files (*)"));
340 QFileInfo fi(filePath);
341 QString fileName = fi.fileName();
342 try {
343 auto convUid = LRCInstance::getSelectedConvUid();
344 LRCInstance::getCurrentConversationModel()->sendFile(convUid, filePath.toStdString(), fileName.toStdString());
345 } catch (...) {
346 qDebug() << "JS bridging - exception during sendFile";
347 }
348 return 0;
349}
350
351Q_INVOKABLE int
Sébastien Blin942d1022018-12-13 09:49:03 -0500352PrivateBridging::acceptInvitation()
Andreas Traczyk43c08232018-10-31 13:42:09 -0400353{
Sébastien Blin942d1022018-12-13 09:49:03 -0500354 try {
355 auto convUid = LRCInstance::getSelectedConvUid();
356 LRCInstance::getCurrentConversationModel()->makePermanent(convUid);
357 } catch (...) {
358 qDebug() << "JS bridging - exception during acceptInvitation";
359 }
Andreas Traczyk43c08232018-10-31 13:42:09 -0400360 return 0;
Sébastien Blin942d1022018-12-13 09:49:03 -0500361}
362
363Q_INVOKABLE int
364PrivateBridging::refuseInvitation()
365{
366 try {
367 auto convUid = LRCInstance::getSelectedConvUid();
368 LRCInstance::getCurrentConversationModel()->removeConversation(convUid, false);
369 } catch (...) {
370 qDebug() << "JS bridging - exception during refuseInvitation";
371 }
372 return 0;
373}
374
375Q_INVOKABLE int
376PrivateBridging::blockConversation()
377{
378 try {
379 auto convUid = LRCInstance::getSelectedConvUid();
380 LRCInstance::getCurrentConversationModel()->removeConversation(convUid, true);
381 } catch (...) {
382 qDebug() << "JS bridging - exception during blockConversation";
383 }
384 return 0;
385}