Skip to content
Snippets Groups Projects
Commit d3ebc436 authored by Ming Rui Zhang's avatar Ming Rui Zhang Committed by Ming Rui Zhang
Browse files

dbus: handle dbus error with UI interaction

Gitlab: #160
Change-Id: Ica0aab9ba7f043c4ab56314bbd5312f75239ad51
parent a4787e2a
No related branches found
No related tags found
No related merge requests found
......@@ -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
......
......@@ -137,5 +137,7 @@
<file>src/commoncomponents/PresenceIndicator.qml</file>
<file>src/commoncomponents/AvatarImage.qml</file>
<file>src/mainview/components/ParticipantOverlayMenu.qml</file>
<file>src/commoncomponents/DaemonReconnectPopup.qml</file>
<file>src/DaemonReconnectWindow.qml</file>
</qresource>
</RCC>
/*
* Copyright (C) 2020 by Savoir-faire Linux
* Author: Mingrui Zhang <mingrui.zhang@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 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
}
}
......@@ -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)
}
}
/*
* Copyright (C) 2020 by Savoir-faire Linux
* Author: Mingrui Zhang <mingrui.zhang@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 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()
}
}
}
}
......@@ -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.")
}
/*!
* Copyright (C) 2020 by Savoir-faire Linux
* Author: Mingrui Zhang <mingrui.zhang@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 <http://www.gnu.org/licenses/>.
*/
#include "dbuserrorhandler.h"
#include "api/lrc.h"
#include "globalinstances.h"
#include <QTimer>
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<DBusErrorHandler&>(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
/*!
* Copyright (C) 2020 by Savoir-faire Linux
* Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
* Author: Mingrui Zhang <mingrui.zhang@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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <interfaces/dbuserrorhandleri.h>
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*)
......@@ -80,7 +80,10 @@ main(int argc, char* argv[])
return 0;
}
app.init();
if (!app.init()) {
guard.release();
return 0;
}
/*
* Exec the application.
......
......@@ -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 <windows.h>
#endif
#ifdef Q_OS_UNIX
#include "globalinstances.h"
#include "dbuserrorhandler.h"
#endif
#if defined _MSC_VER && !COMPILE_ONLY
#include <gnutls/gnutls.h>
#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<Interfaces::DBusErrorHandler>());
auto dBusErrorHandlerQObject = dynamic_cast<QObject*>(&GlobalInstances::dBusErrorHandler());
qmlRegisterSingletonType<Interfaces::DBusErrorHandler>("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")));
}
......
......@@ -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<QFile> debugFile_;
QQmlApplicationEngine* engine_;
QScopedPointer<QQmlApplicationEngine> engine_;
ConnectivityMonitor* connectivityMonitor_;
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment