Commit 3f88ceda authored by Kateryna Kostiuk's avatar Kateryna Kostiuk Committed by Amin Bandali
Browse files

build: add option to build without Qt WebEngine



This patch allows building the project without Qt WebEngine, by
disabling (for the time being) the features that depend on it:
the emoji picker, link previews, and media file previews in the
chatview.
Co-authored-by: Andreas Traczyk's avatarAndreas Traczyk <andreas.traczyk@savoirfairelinux.com>
Co-authored-by: Amin Bandali's avatarAmin Bandali <amin.bandali@savoirfairelinux.com>
Change-Id: I74751b0cc6f22c61a6fd7281ca3207e0fdbb2212
parent d6d1f903
......@@ -23,7 +23,7 @@ build-local/
*.vcxproj
*.vcxproj.filters
*qmlcache.qrc
qml_without_webengine.qrc
.deploy.stamp
# auto-gen files
......
......@@ -42,12 +42,22 @@ set(QT_MODULES
Concurrent
Core
Core5Compat
WebEngineCore
WebEngineQuick
WebChannel
WebEngineWidgets
Multimedia
)
if(NOT DEFINED WITH_WEBENGINE)
set(WITH_WEBENGINE true)
endif()
if(WITH_WEBENGINE)
list(APPEND QT_MODULES
WebEngineCore
WebEngineQuick
WebChannel
WebEngineWidgets
)
endif()
find_package(Qt6 COMPONENTS ${QT_MODULES} REQUIRED)
foreach(MODULE ${QT_MODULES})
list(APPEND QT_LIBS "Qt::${MODULE}")
......@@ -57,8 +67,23 @@ set(SRC_DIR ${PROJECT_SOURCE_DIR}/src)
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS})
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH true)
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.12.0")
include(FindPython3)
find_package (Python3 COMPONENTS Interpreter)
set(PYTHON_EXEC ${Python3_EXECUTABLE})
else()
include(FindPythonInterp)
set(PYTHON_EXEC ${PYTHON_EXECUTABLE})
endif()
set(QML_RESOURCES ${PROJECT_SOURCE_DIR}/resources.qrc)
set(QML_RESOURCES_QML ${PROJECT_SOURCE_DIR}/qml.qrc)
if(WITH_WEBENGINE)
set(QML_RESOURCES_QML ${PROJECT_SOURCE_DIR}/qml.qrc)
else()
execute_process(COMMAND ${PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/gen-qrc-without-webengine.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set(QML_RESOURCES_QML ${PROJECT_SOURCE_DIR}/qml_without_webengine.qrc)
endif()
if (APPLE)
include(FetchContent)
......@@ -78,14 +103,6 @@ file(GLOB_RECURSE
RES_FILES CONFIGURE_DEPENDS
${PROJECT_SOURCE_DIR}/resources/*
)
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.12.0")
include(FindPython3)
find_package (Python3 COMPONENTS Interpreter)
set(PYTHON_EXEC ${Python3_EXECUTABLE})
else()
include(FindPythonInterp)
set(PYTHON_EXEC ${PYTHON_EXECUTABLE})
endif()
execute_process(
COMMAND ${PYTHON_EXEC} ${PROJECT_SOURCE_DIR}/gen-resources.py
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
......@@ -137,7 +154,6 @@ set(COMMON_SOURCES
${SRC_DIR}/currentconversation.cpp
${SRC_DIR}/currentaccount.cpp
${SRC_DIR}/videodevices.cpp
${SRC_DIR}/previewengine.cpp
${SRC_DIR}/videoprovider.cpp
${SRC_DIR}/callparticipantsmodel.cpp
)
......@@ -194,11 +210,19 @@ set(COMMON_HEADERS
${SRC_DIR}/currentconversation.h
${SRC_DIR}/currentaccount.h
${SRC_DIR}/videodevices.h
${SRC_DIR}/previewengine.h
${SRC_DIR}/videoprovider.h
${SRC_DIR}/callparticipantsmodel.h
)
if(WITH_WEBENGINE)
list(APPEND COMMON_SOURCES
${SRC_DIR}/previewengine.cpp)
add_definitions(-DWITH_WEBENGINE)
else()
list(APPEND COMMON_SOURCES
${SRC_DIR}/nowebengine/previewengine.cpp)
endif()
# For libavutil/avframe.
set(LIBJAMI_CONTRIB_DIR "${PROJECT_SOURCE_DIR}/../daemon/contrib")
find_path(AVUTIL_INCLUDE_DIR libavutil/avutil.h
......
......@@ -30,6 +30,7 @@
<file>src/commoncomponents/PresenceIndicator.qml</file>
<file>src/commoncomponents/DaemonReconnectPopup.qml</file>
<file>src/commoncomponents/SpinningAnimation.qml</file>
<file>src/commoncomponents/MediaPreviewBase.qml</file>
<file>src/settingsview/SettingsView.qml</file>
<file>src/settingsview/components/ChatviewSettings.qml</file>
<file>src/settingsview/components/FileTransferSettings.qml</file>
......
#!/usr/bin/env python3
# Copyright (C) 2022 Savoir-faire Linux Inc.
#
# Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
with open('qml_without_webengine.qrc', 'w') as outfile:
with open('qml.qrc', 'r') as infile:
line = infile.readline()
while line:
if 'EmojiPicker.qml' in line:
outfile.write('\t<file>src/nowebengine/EmojiPicker.qml</file>\n')
elif 'MediaPreviewBase.qml' in line:
outfile.write('\t<file>src/nowebengine/MediaPreviewBase.qml</file>\n')
else:
outfile.write(line)
line = infile.readline()
......@@ -22,7 +22,6 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import QtWebEngine
import net.jami.Models 1.1
import net.jami.Constants 1.1
......@@ -41,7 +40,7 @@ Loader {
sourceComponent: {
if (Status === Interaction.Status.TRANSFER_FINISHED) {
mediaInfo = MessagesAdapter.getMediaInfo(Body)
if (Object.keys(mediaInfo).length !== 0)
if (Object.keys(mediaInfo).length !== 0 && WITH_WEBENGINE)
return localMediaMsgComp
}
return dataTransferMsgComp
......@@ -264,48 +263,10 @@ Loader {
}
Component {
id: avComp
WebEngineView {
id: wev
anchors.right: isOutgoing ? parent.right : undefined
readonly property real minSize: 192
readonly property real maxSize: 256
readonly property real aspectRatio: 1 / .75
readonly property real adjustedWidth: Math.min(maxSize,
Math.max(minSize,
innerContent.width - senderMargin))
width: isFullScreen ? parent.width : adjustedWidth
height: mediaInfo.isVideo ?
isFullScreen ?
parent.height :
Math.ceil(adjustedWidth / aspectRatio) :
54
onContextMenuRequested: function(request) {
request.accepted = true
}
settings.fullScreenSupportEnabled: mediaInfo.isVideo
settings.javascriptCanOpenWindows: false
Component.onCompleted: loadHtml(mediaInfo.html, 'file://')
layer.enabled: !isFullScreen
layer.effect: OpacityMask {
maskSource: MessageBubble {
out: isOutgoing
type: seq
width: wev.width
height: wev.height
radius: msgRadius
}
}
onFullScreenRequested: function(request) {
if (request.toggleOn) {
layoutManager.pushFullScreenItem(
this,
localMediaCompLoader,
null,
function() { wev.fullScreenCancelled() })
} else if (!request.toggleOn) {
layoutManager.removeFullScreenItem(this)
}
request.accept()
Loader {
Component.onCompleted: {
var qml = WITH_WEBENGINE ? "qrc:/src/commoncomponents/MediaPreviewBase.qml" : "qrc:/src/nowebengine/MediaPreviewBase.qml"
setSource( qml, { isVideo: mediaInfo.isVideo, html:mediaInfo.html } )
}
}
}
......
/*
* Copyright (C) 2021-2022 Savoir-faire Linux Inc.
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import QtWebEngine
import net.jami.Models 1.1
import net.jami.Constants 1.1
import net.jami.Adapters 1.1
WebEngineView {
id: wev
property bool isVideo
property string html
readonly property real minSize: 192
readonly property real maxSize: 256
readonly property real aspectRatio: 1 / .75
readonly property real adjustedWidth: Math.min(maxSize,
Math.max(minSize,
innerContent.width - senderMargin))
anchors.right: isOutgoing ? parent.right : undefined
width: isFullScreen ? parent.width : adjustedWidth
height: isVideo ?
isFullScreen ?
parent.height :
Math.ceil(adjustedWidth / aspectRatio) :
54
onContextMenuRequested: function(request) {
request.accepted = true
}
settings.fullScreenSupportEnabled: isVideo
settings.javascriptCanOpenWindows: false
Component.onCompleted: loadHtml(html, 'file://')
layer.enabled: !isFullScreen
layer.effect: OpacityMask {
maskSource: MessageBubble {
out: isOutgoing
type: seq
width: wev.width
height: wev.height
radius: msgRadius
}
}
onFullScreenRequested: function(request) {
if (request.toggleOn) {
layoutManager.pushFullScreenItem(
this,
localMediaCompLoader,
null,
function() { wev.fullScreenCancelled() })
} else if (!request.toggleOn) {
layoutManager.removeFullScreenItem(this)
}
request.accept()
}
}
......@@ -24,8 +24,11 @@
#include <QCryptographicHash>
#include <QApplication>
#include <QtQuick>
#ifdef WITH_WEBENGINE
#include <QtWebEngineCore>
#include <QtWebEngineQuick>
#endif
#if defined(HAS_VULKAN) && !defined(Q_OS_LINUX)
#include <QVulkanInstance>
#endif
......@@ -56,9 +59,11 @@ parseInputArgument(int& argc, char* argv[], QList<char*> argsToParse)
return newArgv;
}
#ifdef WITH_WEBENGINE
// Qt WebEngine Chromium Flags
static char disableWebSecurity[] {"--disable-web-security"};
static char singleProcess[] {"--single-process"};
#endif
int
main(int argc, char* argv[])
......@@ -88,8 +93,10 @@ main(int argc, char* argv[])
*/
unsetenv("QT_STYLE_OVERRIDE");
#endif
#ifdef WITH_WEBENGINE
qtWebEngineChromiumFlags << disableWebSecurity;
qtWebEngineChromiumFlags << singleProcess;
#endif
QApplication::setApplicationName("Jami");
QApplication::setOrganizationDomain("jami.net");
......
......@@ -193,6 +193,11 @@ MainApplication::init()
auto startMinimizedSetting = settingsManager_->getValue(Settings::Key::StartMinimized).toBool();
// The presence of start URI should override the startMinimized setting for this instance.
set_startMinimized(startMinimizedSetting && runOptions_[Option::StartUri].isNull());
#ifdef WITH_WEBENGINE
engine_.get()->rootContext()->setContextProperty("WITH_WEBENGINE", QVariant(true));
#else
engine_.get()->rootContext()->setContextProperty("WITH_WEBENGINE", QVariant(false));
#endif
initQmlLayer();
......
......@@ -24,7 +24,6 @@ import net.jami.Constants 1.1
import net.jami.Adapters 1.1
import "../../commoncomponents"
import "../../commoncomponents/emojipicker"
Rectangle {
id: root
......@@ -90,10 +89,19 @@ Rectangle {
visible: false
}
EmojiPicker {
id: emojiPicker
Loader {
id: empjiLoader
source: WITH_WEBENGINE ? "qrc:/src/commoncomponents/emojipicker/EmojiPicker.qml" : "qrc:/src/nowebengine/EmojiPicker.qml"
onEmojiIsPicked: messageBar.textAreaObj.insertText(content)
function openEmojiPicker() {
item.openEmojiPicker()
}
Connections {
target: empjiLoader.item
function onEmojiIsPicked(content) {
messageBar.textAreaObj.insertText(content)
}
}
}
JamiFileDialog {
......@@ -126,20 +134,20 @@ Rectangle {
onEmojiButtonClicked: {
JamiQmlUtils.updateMessageBarButtonsPoints()
emojiPicker.parent = JamiQmlUtils.mainViewRectObj
empjiLoader.parent = JamiQmlUtils.mainViewRectObj
emojiPicker.x = Qt.binding(function() {
empjiLoader.x = Qt.binding(function() {
var buttonX = JamiQmlUtils.emojiPickerButtonInMainViewPoint.x +
JamiQmlUtils.emojiPickerButtonObj.width
return buttonX - emojiPicker.width
return buttonX - empjiLoader.width
})
emojiPicker.y = Qt.binding(function() {
empjiLoader.y = Qt.binding(function() {
var buttonY = JamiQmlUtils.audioRecordMessageButtonInMainViewPoint.y
return buttonY - emojiPicker.height - messageBar.marginSize
return buttonY - empjiLoader.height - messageBar.marginSize
- JamiTheme.chatViewHairLineSize
})
emojiPicker.openEmojiPicker()
empjiLoader.openEmojiPicker()
}
onSendFileButtonClicked: jamiFileDialog.open()
onSendMessageButtonClicked: {
......
......@@ -157,6 +157,7 @@ ColumnLayout {
PushButton {
id: emojiButton
visible: WITH_WEBENGINE
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: sendMessageButton.visible ? 0 : marginSize
......
......@@ -61,10 +61,7 @@ MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
});
connect(previewEngine_, &PreviewEngine::infoReady, this, &MessagesAdapter::onPreviewInfoReady);
connect(previewEngine_,
&PreviewEngine::linkifyReady,
this,
&MessagesAdapter::onMessageLinkified);
connect(previewEngine_, &PreviewEngine::linkified, this, &MessagesAdapter::onMessageLinkified);
}
void
......
/*
* Copyright (C) 2022 Savoir-faire Linux Inc.
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
Rectangle {
id: root
signal emojiIsPicked(string content)
function openEmojiPicker() {}
function closeEmojiPicker() {}
}
/*
* Copyright (C) 2022 Savoir-faire Linux Inc.
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Rectangle {
property bool isVideo
property string html
}
/*
* Copyright (C) 2022 Savoir-faire Linux Inc.
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "previewengine.h"
struct PreviewEngine::Impl : public QObject
{
Impl(PreviewEngine&)
: QObject(nullptr)
{}
};
PreviewEngine::PreviewEngine(QObject* parent)
: QObject(parent)
, pimpl_(std::make_unique<Impl>(*this))
{}
PreviewEngine::~PreviewEngine() {}
void
PreviewEngine::parseMessage(const QString&, const QString&, bool)
{}
void
PreviewEngine::log(const QString&)
{}
#include "moc_previewengine.cpp"
#include "previewengine.moc"
/*
/*
* Copyright (C) 2021-2022 Savoir-faire Linux Inc.
* Author: Trevor Tabah <trevor.tabah@savoirfairelinux.com>
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
......@@ -23,65 +23,78 @@
#include <QWebEngineProfile>
#include <QWebEngineSettings>
PreviewEngine::PreviewEngine(QObject* parent)
: QWebEnginePage(parent)
, pimpl_(new PreviewEnginePrivate(this))
#include <QtWebChannel>
#include <QWebEnginePage>
struct PreviewEngine::Impl : public QWebEnginePage
{
QWebEngineProfile* profile = QWebEngineProfile::defaultProfile();
public:
PreviewEngine& parent_;
QWebChannel* channel_;
QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
dataDir.cdUp();
auto cachePath = dataDir.absolutePath() + "/jami";
profile->setCachePath(cachePath);
profile->setPersistentStoragePath(cachePath);
profile->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
profile->setHttpCacheType(QWebEngineProfile::NoCache);
Impl(PreviewEngine& parent)
: QWebEnginePage((QObject*) nullptr)
, parent_(parent)
{
QWebEngineProfile* profile = QWebEngineProfile::defaultProfile();
settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
settings()->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, false);
settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
settings()->setAttribute(QWebEngineSettings::PluginsEnabled, false);
settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, false);
settings()->setAttribute(QWebEngineSettings::LinksIncludedInFocusChain, false);
settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
settings()->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true);
settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
settings()->setAttribute(QWebEngineSettings::XSSAuditingEnabled, false);
settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, true);
QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
dataDir.cdUp();
auto cachePath = dataDir.absolutePath() + "/jami";
profile->setCachePath(cachePath);
profile->setPersistentStoragePath(cachePath);
profile->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
profile->setHttpCacheType(QWebEngineProfile::NoCache);
channel_ = new QWebChannel(this);
channel_->registerObject(QStringLiteral("jsbridge"), pimpl_);
settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
settings()->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, false);
settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
settings()->setAttribute(QWebEngineSettings::PluginsEnabled, false);
settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, false);
settings()->setAttribute(QWebEngineSettings::LinksIncludedInFocusChain, false);
settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
settings()->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true);
settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
settings()->setAttribute(QWebEngineSettings::XSSAuditingEnabled, false);
settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, true);
setWebChannel(channel_);
runJavaScript(Utils::QByteArrayFromFile(":/linkify.js"), QWebEngineScript::MainWorld);
runJavaScript(Utils::QByteArrayFromFile(":/linkify-string.js"), QWebEngineScript::MainWorld);
runJavaScript(Utils::QByteArrayFromFile(":/qwebchannel.js"), QWebEngineScript::MainWorld);
runJavaScript(Utils::QByteArrayFromFile(":/previewInfo.js"), QWebEngineScript::MainWorld);
runJavaScript(Utils::QByteArrayFromFile(":/misc/previewInterop.js"),
QWebEngineScript::MainWorld);
}
channel_ = new QWebChannel(this);
channel_->registerObject(QStringLiteral("jsbridge"), &parent_);
setWebChannel(channel_);
runJavaScript(Utils::QByteArrayFromFile(":/linkify.js"), QWebEngineScript::MainWorld);
runJavaScript(Utils::QByteArrayFromFile(":/linkify-string.js"), QWebEngineScript::MainWorld);
runJavaScript(Utils::QByteArrayFromFile(":/qwebchannel.js"), QWebEngineScript::MainWorld);
runJavaScript(Utils::QByteArrayFromFile(":/previewInfo.js"), QWebEngineScript::MainWorld);
runJavaScript(Utils::QByteArrayFromFile(":/misc/previewInterop.js"),
QWebEngineScript::MainWorld);
}