diff --git a/jami-qt.pro b/jami-qt.pro
index 15cbafc36..69bffcb45 100644
--- a/jami-qt.pro
+++ b/jami-qt.pro
@@ -118,9 +118,11 @@ unix {
# unix specific
HEADERS += \
- src/xrectsel.h
+ src/dbuserrorhandler.h \
+ src/xrectsel.h
SOURCES += \
- src/xrectsel.c
+ src/dbuserrorhandler.cpp \
+ src/xrectsel.c
}
# Input
diff --git a/qml.qrc b/qml.qrc
index b06ef1cb9..b8e16a65c 100644
--- a/qml.qrc
+++ b/qml.qrc
@@ -137,5 +137,7 @@
src/commoncomponents/PresenceIndicator.qml
src/commoncomponents/AvatarImage.qml
src/mainview/components/ParticipantOverlayMenu.qml
+ src/commoncomponents/DaemonReconnectPopup.qml
+ src/DaemonReconnectWindow.qml
diff --git a/src/DaemonReconnectWindow.qml b/src/DaemonReconnectWindow.qml
new file mode 100644
index 000000000..ad6fd0564
--- /dev/null
+++ b/src/DaemonReconnectWindow.qml
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2020 by Savoir-faire Linux
+ * Author: Mingrui Zhang
+ *
+ * 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 .
+ */
+
+import QtQuick 2.14
+import QtQuick.Window 2.14
+import QtQuick.Controls 2.14
+import QtQuick.Layouts 1.14
+import QtQuick.Controls.Universal 2.14
+import QtGraphicalEffects 1.14
+
+// Should not import anything other than this
+// to make sure that it is self-dependent
+import net.jami.Models 1.0
+
+import "commoncomponents"
+
+ApplicationWindow {
+ id: root
+
+ property bool connectionFailed: false
+ property int preferredMargin: 15
+
+ Universal.theme: Universal.Light
+
+ title: "Jami"
+
+ width: 600
+ height: 500
+ minimumWidth: 600
+ minimumHeight: 500
+
+ visible: true
+
+ TextMetrics {
+ id: textMetrics
+ }
+
+ function getTextBoundingRect(font, text) {
+ textMetrics.font = font
+ textMetrics.text = text
+
+ return textMetrics.boundingRect
+ }
+
+ ResponsiveImage {
+ id: jamiLogoImage
+
+ anchors.fill: parent
+
+ smooth: true
+ antialiasing: true
+ source: "qrc:/images/logo-jami-standard-coul.svg"
+ }
+
+ Popup {
+ id: popup
+
+ // center in parent
+ x: Math.round((root.width - width) / 2)
+ y: Math.round((root.height - height) / 2)
+
+ modal: true
+ visible: false
+ closePolicy: Popup.NoAutoClose
+
+ contentItem: Rectangle {
+ id: contentRect
+
+ implicitHeight: daemonReconnectPopupColumnLayout.implicitHeight + 50
+
+ ColumnLayout {
+ id: daemonReconnectPopupColumnLayout
+
+ anchors.fill: parent
+
+ spacing: 0
+
+ Text {
+ id: daemonReconnectPopupTextLabel
+
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
+ Layout.topMargin: preferredMargin
+
+ text: connectionFailed ?
+ qsTr("Could not re-connect to the Jami daemon (dring).\nJami will now quit.") :
+ qsTr("Trying to reconnect to the Jami daemon (dring)…")
+ font.pointSize: 11
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+
+ Component.onCompleted: {
+ contentRect.implicitWidth = getTextBoundingRect(
+ font, text).width + 100
+ }
+ }
+
+ AnimatedImage {
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
+ Layout.preferredHeight: 30
+ Layout.preferredWidth: 30
+ Layout.bottomMargin: preferredMargin
+
+ visible: !connectionFailed
+
+ source: "qrc:/images/jami_rolling_spinner.gif"
+
+ playing: true
+ paused: false
+ mipmap: true
+ smooth: true
+ fillMode: Image.PreserveAspectFit
+ }
+
+ Button {
+ id: btnOk
+
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
+ Layout.preferredWidth: 128
+ Layout.preferredHeight: 32
+ Layout.bottomMargin: preferredMargin
+ visible: connectionFailed
+
+ property color hoveredColor: "#0e81c5"
+ property color pressedColor: "#273261"
+ property color normalColor: "#00aaff"
+
+ contentItem: Item {
+ Rectangle {
+ anchors.fill: parent
+ color: "transparent"
+
+ Text {
+ id: buttonText
+
+ anchors.centerIn: parent
+
+ width: {
+ return (parent.width / 2 - 18) * 2
+ }
+
+ text: qsTr("Ok")
+
+ color: {
+ if (btnOk.hovered)
+ return btnOk.hoveredColor
+ if (btnOk.checked)
+ return btnOk.pressedColor
+ return btnOk.normalColor
+ }
+ font: root.font
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
+
+ onClicked: Qt.quit()
+
+ background: Rectangle {
+ id: backgroundRect
+ anchors.fill: parent
+ color: "transparent"
+ border.color: {
+ if (btnOk.hovered)
+ return btnOk.hoveredColor
+ if (btnOk.checked)
+ return btnOk.pressedColor
+ return btnOk.normalColor
+ }
+ radius: 4
+ }
+ }
+ }
+ }
+ }
+
+ Connections {
+ target: DBusErrorHandler
+
+ function onShowDaemonReconnectPopup(visible) {
+ if (visible)
+ popup.open()
+ else {
+ popup.close()
+ Qt.quit()
+ }
+ }
+
+ function onDaemonReconnectFailed() {
+ root.connectionFailed = true
+ }
+ }
+
+ overlay.modal: ColorOverlay {
+ source: root.contentItem
+ color: "transparent"
+
+ // Color animation for overlay when pop up is shown.
+ ColorAnimation on color {
+ to: Qt.rgba(0, 0, 0, 0.33)
+ duration: 500
+ }
+ }
+
+ Component.onCompleted: {
+ DBusErrorHandler.setActive(true)
+
+ x = Screen.width / 2 - width / 2
+ y = Screen.height / 2 - height / 2
+ }
+}
diff --git a/src/MainApplicationWindow.qml b/src/MainApplicationWindow.qml
index 32319ce29..d92f9972b 100644
--- a/src/MainApplicationWindow.qml
+++ b/src/MainApplicationWindow.qml
@@ -116,6 +116,9 @@ ApplicationWindow {
onAccountMigrationFinished: startClient()
}
+ DaemonReconnectPopup {
+ id: daemonReconnectPopup
+ }
Loader {
id: mainApplicationLoader
@@ -167,6 +170,26 @@ ApplicationWindow {
}
}
+ Connections {
+ target: {
+ if (Qt.platform.os !== "windows")
+ return DBusErrorHandler
+ return null
+ }
+ ignoreUnknownSignals: true
+
+ function onShowDaemonReconnectPopup(visible) {
+ if (visible)
+ daemonReconnectPopup.open()
+ else
+ daemonReconnectPopup.close()
+ }
+
+ function onDaemonReconnectFailed() {
+ daemonReconnectPopup.connectionFailed = true
+ }
+ }
+
onClosing: root.close()
onScreenChanged: JamiQmlUtils.mainApplicationScreen = root.screen
@@ -176,5 +199,8 @@ ApplicationWindow {
startClient()
}
JamiQmlUtils.mainApplicationScreen = root.screen
+
+ if (Qt.platform.os !== "windows")
+ DBusErrorHandler.setActive(true)
}
}
diff --git a/src/commoncomponents/DaemonReconnectPopup.qml b/src/commoncomponents/DaemonReconnectPopup.qml
new file mode 100644
index 000000000..631f9318a
--- /dev/null
+++ b/src/commoncomponents/DaemonReconnectPopup.qml
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 by Savoir-faire Linux
+ * Author: Mingrui Zhang
+ *
+ * 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 .
+ */
+
+import QtQuick 2.14
+import QtQuick.Controls 2.14
+import QtQuick.Layouts 1.14
+import net.jami.Models 1.0
+import net.jami.Constants 1.0
+
+ModalPopup {
+ id: root
+
+ property bool connectionFailed: false
+ property int preferredMargin: 15
+
+ autoClose: false
+
+ contentItem: Rectangle {
+ id: contentRect
+
+ implicitHeight: daemonReconnectPopupColumnLayout.implicitHeight + 50
+
+ color: JamiTheme.secondaryBackgroundColor
+
+ ColumnLayout {
+ id: daemonReconnectPopupColumnLayout
+
+ anchors.fill: parent
+
+ spacing: 0
+
+ Text {
+ id: daemonReconnectPopupTextLabel
+
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
+ Layout.topMargin: preferredMargin
+
+ text: connectionFailed ? JamiStrings.reconnectionFailed :
+ JamiStrings.reconnectDaemon
+ font.pointSize: JamiTheme.textFontSize + 2
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ color: JamiTheme.textColor
+
+ Component.onCompleted: {
+ contentRect.implicitWidth = JamiQmlUtils.getTextBoundingRect(
+ font, text).width + 100
+ }
+ }
+
+ AnimatedImage {
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
+ Layout.preferredHeight: 30
+ Layout.preferredWidth: 30
+ Layout.bottomMargin: preferredMargin
+
+ visible: !connectionFailed
+
+ source: "qrc:/images/jami_rolling_spinner.gif";
+
+ playing: true
+ paused: false
+ mipmap: true
+ smooth: true
+ fillMode: Image.PreserveAspectFit
+ }
+
+ MaterialButton {
+ id: btnOk
+
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
+ Layout.preferredWidth: JamiTheme.preferredFieldWidth / 2
+ Layout.preferredHeight: JamiTheme.preferredFieldHeight
+ Layout.bottomMargin: preferredMargin
+ visible: connectionFailed
+
+ text: qsTr("Ok")
+ color: JamiTheme.buttonTintedBlue
+ hoveredColor: JamiTheme.buttonTintedBlueHovered
+ pressedColor: JamiTheme.buttonTintedBluePressed
+ outlined: true
+
+ onClicked: Qt.quit()
+ }
+ }
+ }
+}
diff --git a/src/constant/JamiStrings.qml b/src/constant/JamiStrings.qml
index 517fa28b9..30b13236b 100644
--- a/src/constant/JamiStrings.qml
+++ b/src/constant/JamiStrings.qml
@@ -406,5 +406,9 @@ Item {
property string maximizeParticipant: qsTr("Maximize")
property string minimizeParticipant: qsTr("Minimize")
property string hangupParticipant: qsTr("Hangup")
+
+ // Daemon reconnection
+ property string reconnectDaemon: qsTr("Trying to reconnect to the Jami daemon (dring)…")
+ property string reconnectionFailed: qsTr("Could not re-connect to the Jami daemon (dring).\nJami will now quit.")
}
diff --git a/src/dbuserrorhandler.cpp b/src/dbuserrorhandler.cpp
new file mode 100644
index 000000000..33d6bf331
--- /dev/null
+++ b/src/dbuserrorhandler.cpp
@@ -0,0 +1,93 @@
+/*!
+ * Copyright (C) 2020 by Savoir-faire Linux
+ * Author: Mingrui Zhang
+ *
+ * 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 .
+ */
+
+#include "dbuserrorhandler.h"
+
+#include "api/lrc.h"
+#include "globalinstances.h"
+
+#include
+
+namespace Interfaces {
+
+void
+DBusErrorHandler::errorCallback()
+{
+ qDebug() << "Dring has possibly crashed, "
+ "or has been killed... will wait 2.5 seconds and try to reconnect";
+
+ emit showDaemonReconnectPopup(true);
+
+ QTimer::singleShot(2500, [this]() {
+ if ((!lrc::api::Lrc::isConnected()) || (!lrc::api::Lrc::dbusIsValid())) {
+ qDebug() << "Could not reconnect to the daemon";
+ emit daemonReconnectFailed();
+ } else {
+ static_cast(GlobalInstances::dBusErrorHandler())
+ .finishedHandlingError();
+ }
+ });
+}
+
+void
+DBusErrorHandler::setActive(bool active)
+{
+ handlerActive_ = active;
+
+ if (active) {
+ if ((!lrc::api::Lrc::isConnected()) || (!lrc::api::Lrc::dbusIsValid()))
+ connectionError(QString());
+ }
+}
+
+void
+DBusErrorHandler::connectionError(const QString& error)
+{
+ qDebug() << error;
+
+ if (!handlerActive_)
+ return;
+
+ if (!handlingError) {
+ handlingError = true;
+ errorCallback();
+ }
+}
+
+void
+DBusErrorHandler::invalidInterfaceError(const QString& error)
+{
+ qDebug() << error;
+
+ if (!handlerActive_)
+ return;
+
+ if (!handlingError) {
+ handlingError = true;
+ errorCallback();
+ }
+}
+
+void
+DBusErrorHandler::finishedHandlingError()
+{
+ handlingError = false;
+ emit showDaemonReconnectPopup(false);
+}
+
+} // namespace Interfaces
diff --git a/src/dbuserrorhandler.h b/src/dbuserrorhandler.h
new file mode 100644
index 000000000..15231bc97
--- /dev/null
+++ b/src/dbuserrorhandler.h
@@ -0,0 +1,56 @@
+/*!
+ * Copyright (C) 2020 by Savoir-faire Linux
+ * Author: Stepan Salenikovich
+ * Author: Mingrui Zhang
+ *
+ * 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 .
+ */
+
+#pragma once
+
+#include
+
+namespace Interfaces {
+
+class DBusErrorHandler : public QObject, public DBusErrorHandlerI
+{
+ Q_OBJECT
+public:
+ DBusErrorHandler() {};
+ ~DBusErrorHandler() {};
+
+ Q_INVOKABLE void setActive(bool active);
+
+ void connectionError(const QString& error) override;
+ void invalidInterfaceError(const QString& error) override;
+
+ void finishedHandlingError();
+
+signals:
+ void showDaemonReconnectPopup(bool visible);
+ void daemonReconnectFailed();
+
+private:
+ void errorCallback();
+
+ // Keeps track if we're in the process of handling an error already,
+ // so that we don't keep displaying error dialogs;
+ // we use an atomic in case the errors come from multiple threads
+ std::atomic_bool handlingError {false};
+
+ bool handlerActive_ {false};
+};
+
+} // namespace Interfaces
+Q_DECLARE_METATYPE(Interfaces::DBusErrorHandler*)
diff --git a/src/main.cpp b/src/main.cpp
index fe63dbf10..51bd99c18 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -80,7 +80,10 @@ main(int argc, char* argv[])
return 0;
}
- app.init();
+ if (!app.init()) {
+ guard.release();
+ return 0;
+ }
/*
* Exec the application.
diff --git a/src/mainapplication.cpp b/src/mainapplication.cpp
index 6b7b365a2..744913ea5 100644
--- a/src/mainapplication.cpp
+++ b/src/mainapplication.cpp
@@ -24,6 +24,7 @@
#include "appsettingsmanager.h"
#include "connectivitymonitor.h"
#include "globalsystemtray.h"
+#include "namedirectory.h"
#include "qmlregister.h"
#include "qrimageprovider.h"
#include "tintedbuttonimageprovider.h"
@@ -43,6 +44,11 @@
#include
#endif
+#ifdef Q_OS_UNIX
+#include "globalinstances.h"
+#include "dbuserrorhandler.h"
+#endif
+
#if defined _MSC_VER && !COMPILE_ONLY
#include
#endif
@@ -122,7 +128,7 @@ MainApplication::MainApplication(int& argc, char** argv)
QObject::connect(this, &QApplication::aboutToQuit, [this] { cleanup(); });
}
-void
+bool
MainApplication::init()
{
setWindowIcon(QIcon(":images/jami.ico"));
@@ -146,6 +152,33 @@ MainApplication::init()
gnutls_global_init();
#endif
+#ifdef Q_OS_UNIX
+ GlobalInstances::setDBusErrorHandler(std::make_unique());
+ auto dBusErrorHandlerQObject = dynamic_cast(&GlobalInstances::dBusErrorHandler());
+ qmlRegisterSingletonType("net.jami.Models",
+ 1,
+ 0,
+ "DBusErrorHandler",
+ [dBusErrorHandlerQObject](QQmlEngine* e,
+ QJSEngine* se)
+ -> QObject* {
+ Q_UNUSED(e)
+ Q_UNUSED(se)
+ return dBusErrorHandlerQObject;
+ });
+ engine_->setObjectOwnership(dBusErrorHandlerQObject, QQmlEngine::CppOwnership);
+
+ if ((!lrc::api::Lrc::isConnected()) || (!lrc::api::Lrc::dbusIsValid())) {
+ engine_->load(QUrl(QStringLiteral("qrc:/src/DaemonReconnectWindow.qml")));
+ exec();
+
+ if ((!lrc::api::Lrc::isConnected()) || (!lrc::api::Lrc::dbusIsValid()))
+ return false;
+ else
+ engine_.reset(new QQmlApplicationEngine());
+ }
+#endif
+
initLrc(results[opts::UPDATEURL].toString(), connectivityMonitor_);
connect(connectivityMonitor_, &ConnectivityMonitor::connectivityChanged, [] {
@@ -173,6 +206,8 @@ MainApplication::init()
initSettings();
initSystray();
initQmlEngine();
+
+ return true;
}
void
@@ -318,6 +353,12 @@ MainApplication::initQmlEngine()
engine_->addImageProvider(QLatin1String("tintedPixmap"), new TintedButtonImageProvider());
engine_->addImageProvider(QLatin1String("avatarImage"), new AvatarImageProvider());
+ engine_->setObjectOwnership(&LRCInstance::avModel(), QQmlEngine::CppOwnership);
+ engine_->setObjectOwnership(&LRCInstance::pluginModel(), QQmlEngine::CppOwnership);
+ engine_->setObjectOwnership(LRCInstance::getUpdateManager(), QQmlEngine::CppOwnership);
+ engine_->setObjectOwnership(&LRCInstance::instance(), QQmlEngine::CppOwnership);
+ engine_->setObjectOwnership(&NameDirectory::instance(), QQmlEngine::CppOwnership);
+
engine_->load(QUrl(QStringLiteral("qrc:/src/MainApplicationWindow.qml")));
}
diff --git a/src/mainapplication.h b/src/mainapplication.h
index 4343ec6b3..41269c9b3 100644
--- a/src/mainapplication.h
+++ b/src/mainapplication.h
@@ -37,7 +37,7 @@ public:
explicit MainApplication(int& argc, char** argv);
~MainApplication() = default;
- void init();
+ bool init();
private:
void loadTranslations();
@@ -51,6 +51,6 @@ private:
private:
QScopedPointer debugFile_;
- QQmlApplicationEngine* engine_;
+ QScopedPointer engine_;
ConnectivityMonitor* connectivityMonitor_;
};