blob: 5262edd9eb45336f372e0014d7f64c015b1175f8 [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> *
5 * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> *
6 * 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
216MessageWebView::setInvitation(bool show, const std::string& contactUri)
217{
218 QString s = QString::fromLatin1(show ? "showInvitation(\"%1\")" : "showInvitation()")
219 .arg(QString(contactUri.c_str()));
220 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
232PrivateBridging::deleteInteraction(const QString& arg)
233{
234 bool ok;
235 uint64_t interactionUid = arg.toULongLong(&ok);
236 if (ok) {
237 LRCInstance::getCurrentConversationModel()->clearInteractionFromConversation(
238 LRCInstance::getSelectedConvUid(),
239 interactionUid
240 );
241 } else {
242 qDebug() << "deleteInteraction - invalid arg" << arg;
243 }
244 return 0;
245}
246
247Q_INVOKABLE int
248PrivateBridging::retryInteraction(const QString& arg)
249{
250 bool ok;
251 uint64_t interactionUid = arg.toULongLong(&ok);
252 if (ok) {
253 LRCInstance::getCurrentConversationModel()->retryInteraction(
254 LRCInstance::getSelectedConvUid(),
255 interactionUid
256 );
257 } else {
258 qDebug() << "retryInteraction - invalid arg" << arg;
259 }
260 return 0;
261}
262
263Q_INVOKABLE int
264PrivateBridging::openFile(const QString& arg)
265{
266 QDesktopServices::openUrl(arg);
267 return 0;
268}
269
270Q_INVOKABLE int
271PrivateBridging::acceptFile(const QString& arg)
272{
273 try {
274 auto interactionUid = std::stoull(arg.toStdString());
275
276 lrc::api::datatransfer::Info info = {};
277 auto convUid = LRCInstance::getSelectedConvUid();
278 LRCInstance::getCurrentConversationModel()->getTransferInfo(interactionUid, info);
279
280 auto downloadsFolder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation).toStdString();
281 // get full path
282 std::string filename = downloadsFolder.c_str();
283 if (!filename.empty() && filename.back() != '/')
284 filename += "/";
285 auto wantedFilename = filename + info.displayName;
286 auto duplicate = 0;
287 while (std::ifstream(wantedFilename).good()) {
288 ++duplicate;
289 auto extensionIdx = info.displayName.find_last_of(".");
290 if (extensionIdx == std::string::npos)
291 wantedFilename = filename + info.displayName + " (" + std::to_string(duplicate) + ")";
292 else
293 wantedFilename = filename + info.displayName.substr(0, extensionIdx) + " (" + std::to_string(duplicate) + ")" + info.displayName.substr(extensionIdx);
294 }
295 LRCInstance::getCurrentConversationModel()->acceptTransfer(convUid, interactionUid, wantedFilename);
296 } catch (...) {
297 qDebug() << "JS bridging - exception during acceptFile: " << arg;
298 }
299 return 0;
300}
301
302Q_INVOKABLE int
303PrivateBridging::refuseFile(const QString& arg)
304{
305 try {
306 auto interactionUid = std::stoull(arg.toStdString());
307 auto convUid = LRCInstance::getSelectedConvUid();
308 LRCInstance::getCurrentConversationModel()->cancelTransfer(convUid, interactionUid);
309 } catch (...) {
310 qDebug() << "JS bridging - exception during refuseFile:" << arg;
311 }
312 return 0;
313}
314
315Q_INVOKABLE int
316PrivateBridging::sendMessage(const QString& arg)
317{
318 try {
319 auto convUid = LRCInstance::getSelectedConvUid();
320 LRCInstance::getCurrentConversationModel()->sendMessage(convUid, arg.toStdString());
321 } catch (...) {
322 qDebug() << "JS bridging - exception during sendMessage:" << arg;
323 }
324 return 0;
325}
326
327Q_INVOKABLE int
328PrivateBridging::sendFile()
329{
330 qDebug() << "JS bridging - MessageWebView::sendFile";
331 QString filePath = QFileDialog::getOpenFileName((QWidget*)this->parent(), tr("Choose File"), "", tr("Files (*)"));
332 QFileInfo fi(filePath);
333 QString fileName = fi.fileName();
334 try {
335 auto convUid = LRCInstance::getSelectedConvUid();
336 LRCInstance::getCurrentConversationModel()->sendFile(convUid, filePath.toStdString(), fileName.toStdString());
337 } catch (...) {
338 qDebug() << "JS bridging - exception during sendFile";
339 }
340 return 0;
341}
342
343Q_INVOKABLE int
344PrivateBridging::log(const QString& arg)
345{
346 qDebug() << "JS log: " << arg;
347 return 0;
348}