mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-24 02:46:44 +00:00
chore: merge release/perth_narrows into devel
This commit is contained in:
@ -19,6 +19,7 @@
|
||||
#include "AppController.h"
|
||||
#include "QMLBackend.h"
|
||||
#include "SentryUtils.h"
|
||||
#include "Settings.h"
|
||||
#include <bridgepp/GRPC/GRPCClient.h>
|
||||
#include <bridgepp/Exception/Exception.h>
|
||||
#include <bridgepp/ProcessMonitor.h>
|
||||
@ -48,10 +49,19 @@ AppController &app() {
|
||||
AppController::AppController()
|
||||
: backend_(std::make_unique<QMLBackend>())
|
||||
, grpc_(std::make_unique<GRPCClient>())
|
||||
, log_(std::make_unique<Log>()) {
|
||||
, log_(std::make_unique<Log>())
|
||||
, settings_(new Settings) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
// The following is in the implementation file because of unique pointers with incomplete types in headers.
|
||||
// See https://stackoverflow.com/questions/6012157/is-stdunique-ptrt-required-to-know-the-full-definition-of-t
|
||||
//****************************************************************************************************************************************************
|
||||
AppController::~AppController() = default;
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The bridge worker, which can be null if the application was run in 'attach' mode (-a command-line switch).
|
||||
//****************************************************************************************************************************************************
|
||||
@ -71,6 +81,14 @@ ProcessMonitor *AppController::bridgeMonitor() const {
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return A reference to the application settings.
|
||||
//****************************************************************************************************************************************************
|
||||
Settings &AppController::settings() {
|
||||
return *settings_;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] exception The exception that triggered the fatal error.
|
||||
//****************************************************************************************************************************************************
|
||||
@ -102,4 +120,4 @@ void AppController::restart(bool isCrashing) {
|
||||
void AppController::setLauncherArgs(const QString &launcher, const QStringList &args) {
|
||||
launcher_ = launcher;
|
||||
launcherArgs_ = args;
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,8 +20,9 @@
|
||||
#define BRIDGE_GUI_APP_CONTROLLER_H
|
||||
|
||||
|
||||
// @formatter:off
|
||||
//@formatter:off
|
||||
class QMLBackend;
|
||||
class Settings;
|
||||
namespace bridgepp {
|
||||
class Log;
|
||||
class Overseer;
|
||||
@ -29,7 +30,7 @@ class GRPCClient;
|
||||
class ProcessMonitor;
|
||||
class Exception;
|
||||
}
|
||||
// @formatter:off
|
||||
//@formatter:on
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
@ -42,7 +43,7 @@ Q_OBJECT
|
||||
public: // member functions.
|
||||
AppController(AppController const &) = delete; ///< Disabled copy-constructor.
|
||||
AppController(AppController &&) = delete; ///< Disabled assignment copy-constructor.
|
||||
~AppController() override = default; ///< Destructor.
|
||||
~AppController() override; ///< Destructor.
|
||||
AppController &operator=(AppController const &) = delete; ///< Disabled assignment operator.
|
||||
AppController &operator=(AppController &&) = delete; ///< Disabled move assignment operator.
|
||||
QMLBackend &backend() { return *backend_; } ///< Return a reference to the backend.
|
||||
@ -50,7 +51,8 @@ public: // member functions.
|
||||
bridgepp::Log &log() { return *log_; } ///< Return a reference to the log.
|
||||
std::unique_ptr<bridgepp::Overseer> &bridgeOverseer() { return bridgeOverseer_; }; ///< Returns a reference the bridge overseer
|
||||
bridgepp::ProcessMonitor *bridgeMonitor() const; ///< Return the bridge worker.
|
||||
void setLauncherArgs(const QString& launcher, const QStringList& args);
|
||||
Settings &settings();; ///< Return the application settings.
|
||||
void setLauncherArgs(const QString &launcher, const QStringList &args);
|
||||
|
||||
public slots:
|
||||
void onFatalError(bridgepp::Exception const& e); ///< Handle fatal errors.
|
||||
@ -64,6 +66,7 @@ private: // data members
|
||||
std::unique_ptr<bridgepp::GRPCClient> grpc_; ///< The RPC client.
|
||||
std::unique_ptr<bridgepp::Log> log_; ///< The log.
|
||||
std::unique_ptr<bridgepp::Overseer> bridgeOverseer_; ///< The overseer for the bridge monitor worker.
|
||||
std::unique_ptr<Settings> settings_; ///< The application settings.
|
||||
QString launcher_;
|
||||
QStringList launcherArgs_;
|
||||
};
|
||||
|
||||
@ -19,12 +19,13 @@
|
||||
#ifndef BRIDGE_GUI_VERSION_H
|
||||
#define BRIDGE_GUI_VERSION_H
|
||||
|
||||
#define PROJECT_FULL_NAME "@BRIDGE_APP_FULL_NAME@"
|
||||
#define PROJECT_VENDOR "@BRIDGE_VENDOR@"
|
||||
#define PROJECT_VER "@BRIDGE_APP_VERSION@"
|
||||
#define PROJECT_REVISION "@BRIDGE_REVISION@"
|
||||
#define PROJECT_BUILD_TIME "@BRIDGE_BUILD_TIME@"
|
||||
#define PROJECT_DSN_SENTRY "@BRIDGE_DSN_SENTRY@"
|
||||
#define PROJECT_BUILD_ENV "@BRIDGE_BUILD_ENV@"
|
||||
#define PROJECT_FULL_NAME "@BRIDGE_APP_FULL_NAME@"
|
||||
#define PROJECT_VENDOR "@BRIDGE_VENDOR@"
|
||||
#define PROJECT_VER "@BRIDGE_APP_VERSION@"
|
||||
#define PROJECT_REVISION "@BRIDGE_REVISION@"
|
||||
#define PROJECT_BUILD_TIME "@BRIDGE_BUILD_TIME@"
|
||||
#define PROJECT_DSN_SENTRY "@BRIDGE_DSN_SENTRY@"
|
||||
#define PROJECT_BUILD_ENV "@BRIDGE_BUILD_ENV@"
|
||||
#define PROJECT_CRASHPAD_HANDLER_PATH "@BRIDGE_CRASHPAD_HANDLER_PATH@"
|
||||
|
||||
#endif // BRIDGE_GUI_VERSION_H
|
||||
|
||||
@ -85,7 +85,6 @@ message(STATUS "Using Qt ${Qt6_VERSION}")
|
||||
#*****************************************************************************************************************************************************
|
||||
find_package(sentry CONFIG REQUIRED)
|
||||
|
||||
|
||||
#*****************************************************************************************************************************************************
|
||||
# Source files and output
|
||||
#*****************************************************************************************************************************************************
|
||||
@ -110,24 +109,23 @@ add_executable(bridge-gui
|
||||
Resources.qrc
|
||||
AppController.cpp AppController.h
|
||||
BridgeApp.cpp BridgeApp.h
|
||||
BuildConfig.h
|
||||
CommandLine.cpp CommandLine.h
|
||||
EventStreamWorker.cpp EventStreamWorker.h
|
||||
LogUtils.cpp LogUtils.h
|
||||
main.cpp
|
||||
Pch.h
|
||||
BuildConfig.h
|
||||
QMLBackend.cpp QMLBackend.h
|
||||
UserList.cpp UserList.h
|
||||
SentryUtils.cpp SentryUtils.h
|
||||
Settings.cpp Settings.h
|
||||
${DOCK_ICON_SRC_FILE} MacOS/DockIcon.h
|
||||
)
|
||||
|
||||
|
||||
if (APPLE)
|
||||
target_sources(bridge-gui PRIVATE MacOS/SecondInstance.mm MacOS/SecondInstance.h)
|
||||
endif(APPLE)
|
||||
|
||||
|
||||
if (WIN32) # on Windows, we add a (non-Qt) resource file that contains the application icon and version information.
|
||||
string(TIMESTAMP BRIDGE_BUILD_YEAR "%Y")
|
||||
set(REGEX_NUMBER "[0123456789]") # CMake matches does not support \d.
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
|
||||
#include "LogUtils.h"
|
||||
#include "BuildConfig.h"
|
||||
#include <bridgepp/BridgeUtils.h>
|
||||
|
||||
|
||||
@ -24,9 +25,52 @@ using namespace bridgepp;
|
||||
|
||||
|
||||
namespace {
|
||||
qsizetype const logFileTailMaxLength = 25 * 1024; ///< The maximum length of the portion of log returned by tailOfLatestBridgeLog()
|
||||
qsizetype const logFileTailMaxLength = 25 * 1024; ///< The maximum length of the portion of log returned by tailOfLatestBridgeLog()
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return user logs directory used by bridge.
|
||||
//****************************************************************************************************************************************************
|
||||
QString userLogsDir() {
|
||||
QString const path = QDir(bridgepp::userDataDir()).absoluteFilePath("logs");
|
||||
QDir().mkpath(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return A reference to the log.
|
||||
//****************************************************************************************************************************************************
|
||||
Log &initLog() {
|
||||
Log &log = app().log();
|
||||
log.registerAsQtMessageHandler();
|
||||
log.setEchoInConsole(true);
|
||||
|
||||
// remove old gui log files
|
||||
QDir const logsDir(userLogsDir());
|
||||
for (QFileInfo const fileInfo: logsDir.entryInfoList({ "gui_v*.log" }, QDir::Filter::Files)) { // entryInfolist apparently only support wildcards, not regex.
|
||||
QFile(fileInfo.absoluteFilePath()).remove();
|
||||
}
|
||||
|
||||
// create new GUI log file
|
||||
QString error;
|
||||
if (!log.startWritingToFile(logsDir.absoluteFilePath(QString("gui_v%1_%2.log").arg(PROJECT_VER).arg(QDateTime::currentSecsSinceEpoch())), &error)) {
|
||||
log.error(error);
|
||||
}
|
||||
|
||||
log.info("bridge-gui starting");
|
||||
QString const qtCompileTimeVersion = QT_VERSION_STR;
|
||||
QString const qtRuntimeVersion = qVersion();
|
||||
QString msg = QString("Using Qt %1").arg(qtRuntimeVersion);
|
||||
if (qtRuntimeVersion != qtCompileTimeVersion) {
|
||||
msg += QString(" (compiled against %1)").arg(qtCompileTimeVersion);
|
||||
}
|
||||
log.info(msg);
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Return the path of the latest bridge log.
|
||||
/// \return The path of the latest bridge log file.
|
||||
@ -58,5 +102,3 @@ QByteArray tailOfLatestBridgeLog() {
|
||||
return file.open(QIODevice::Text | QIODevice::ReadOnly) ? file.readAll().right(logFileTailMaxLength) : QByteArray();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -20,6 +20,10 @@
|
||||
#define BRIDGE_GUI_LOG_UTILS_H
|
||||
|
||||
|
||||
#include <bridgepp/Log/Log.h>
|
||||
|
||||
|
||||
bridgepp::Log &initLog(); ///< Initialize the application log.
|
||||
QByteArray tailOfLatestBridgeLog(); ///< Return the last bytes of the last bridge log.
|
||||
|
||||
|
||||
|
||||
@ -17,11 +17,12 @@
|
||||
|
||||
|
||||
#include "QMLBackend.h"
|
||||
#include "EventStreamWorker.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "EventStreamWorker.h"
|
||||
#include "LogUtils.h"
|
||||
#include <bridgepp/GRPC/GRPCClient.h>
|
||||
#include <bridgepp/BridgeUtils.h>
|
||||
#include <bridgepp/Exception/Exception.h>
|
||||
#include <bridgepp/GRPC/GRPCClient.h>
|
||||
#include <bridgepp/Worker/Overseer.h>
|
||||
#include <bridgepp/BridgeUtils.h>
|
||||
|
||||
@ -58,7 +59,7 @@ void QMLBackend::init(GRPCConfig const &serviceConfig) {
|
||||
app().grpc().setLog(&log);
|
||||
this->connectGrpcEvents();
|
||||
|
||||
app().grpc().connectToServer(serviceConfig, app().bridgeMonitor());
|
||||
app().grpc().connectToServer(bridgepp::userConfigDir(), serviceConfig, app().bridgeMonitor());
|
||||
app().log().info("Connected to backend via gRPC service.");
|
||||
|
||||
QString bridgeVer;
|
||||
@ -178,16 +179,6 @@ void QMLBackend::setShowSplashScreen(bool show) {
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The value for the 'showSplashScreen' property.
|
||||
//****************************************************************************************************************************************************
|
||||
bool QMLBackend::showSplashScreen() const {
|
||||
HANDLE_EXCEPTION_RETURN_BOOL(
|
||||
return showSplashScreen_;
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The value for the 'GOOS' property.
|
||||
//****************************************************************************************************************************************************
|
||||
@ -198,6 +189,16 @@ QString QMLBackend::goos() const {
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The value for the 'showSplashScreen' property.
|
||||
//****************************************************************************************************************************************************
|
||||
bool QMLBackend::showSplashScreen() const {
|
||||
HANDLE_EXCEPTION_RETURN_BOOL(
|
||||
return showSplashScreen_;
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The value for the 'logsPath' property.
|
||||
//****************************************************************************************************************************************************
|
||||
@ -465,6 +466,7 @@ bool QMLBackend::isDoHEnabled() const {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The value for the 'isAutomaticUpdateOn' property.
|
||||
//****************************************************************************************************************************************************
|
||||
@ -910,6 +912,24 @@ void QMLBackend::onUserBadEvent(QString const &userID, QString const& ) {
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] username The username (or primary email address)
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::onIMAPLoginFailed(QString const &username) {
|
||||
HANDLE_EXCEPTION(
|
||||
SPUser const user = users_->getUserWithUsernameOrEmail(username);
|
||||
if ((!user) || (user->state() != UserState::SignedOut)) { // We want to pop-up only if a signed-out user has been detected
|
||||
return;
|
||||
}
|
||||
if (user->isInIMAPLoginFailureCooldown())
|
||||
return;
|
||||
user->startImapLoginFailureCooldown(60 * 60 * 1000); // 1 hour cooldown during which we will not display this notification to this user again.
|
||||
emit selectUser(user->id());
|
||||
emit imapLoginWhileSignedOut(username);
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
@ -1018,6 +1038,8 @@ void QMLBackend::connectGrpcEvents() {
|
||||
// user events
|
||||
connect(client, &GRPCClient::userDisconnected, this, &QMLBackend::userDisconnected);
|
||||
connect(client, &GRPCClient::userBadEvent, this, &QMLBackend::onUserBadEvent);
|
||||
connect(client, &GRPCClient::imapLoginFailed, this, &QMLBackend::onIMAPLoginFailed);
|
||||
|
||||
users_->connectGRPCEvents();
|
||||
}
|
||||
|
||||
|
||||
@ -181,6 +181,7 @@ public slots: // slot for signals received from gRPC that need transformation in
|
||||
void onLoginFinished(QString const &userID, bool wasSignedOut); ///< Slot for LoginFinished gRPC event.
|
||||
void onLoginAlreadyLoggedIn(QString const &userID); ///< Slot for the LoginAlreadyLoggedIn gRPC event.
|
||||
void onUserBadEvent(QString const& userID, QString const& errorMessage); ///< Slot for the userBadEvent gRPC event.
|
||||
void onIMAPLoginFailed(QString const& username); ///< Slot the the imapLoginFailed event.
|
||||
|
||||
signals: // Signals received from the Go backend, to be forwarded to QML
|
||||
void toggleAutostartFinished(); ///< Signal for the 'toggleAutostartFinished' gRPC stream event.
|
||||
@ -234,6 +235,7 @@ signals: // Signals received from the Go backend, to be forwarded to QML
|
||||
void hideMainWindow(); ///< Signal for the 'hideMainWindow' gRPC stream event.
|
||||
void genericError(QString const &title, QString const &description); ///< Signal for the 'genericError' gRPC stream event.
|
||||
void selectUser(QString const); ///< Signal that request the given user account to be displayed.
|
||||
void imapLoginWhileSignedOut(QString const& username); ///< Signal for the notification of IMAP login attempt on a signed out account.
|
||||
|
||||
// This signal is emitted when an exception is intercepted is calls triggered by QML. QML engine would intercept the exception otherwise.
|
||||
void fatalError(bridgepp::Exception const& e) const; ///< Signal emitted when an fatal error occurs.
|
||||
|
||||
@ -20,10 +20,8 @@
|
||||
#include <bridgepp/BridgeUtils.h>
|
||||
#include <bridgepp/Exception/Exception.h>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QCryptographicHash>
|
||||
#include <QString>
|
||||
#include <QSysInfo>
|
||||
|
||||
using namespace bridgepp;
|
||||
|
||||
|
||||
static constexpr const char *LoggerName = "bridge-gui";
|
||||
@ -46,25 +44,54 @@ QString sentryAttachmentFilePath() {
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Get a hash of the computer's host name
|
||||
//****************************************************************************************************************************************************
|
||||
QByteArray getProtectedHostname() {
|
||||
QByteArray hostname = QCryptographicHash::hash(QSysInfo::machineHostName().toUtf8(), QCryptographicHash::Sha256);
|
||||
return hostname.toHex();
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The OS String used by sentry
|
||||
//****************************************************************************************************************************************************
|
||||
QString getApiOS() {
|
||||
#if defined(Q_OS_DARWIN)
|
||||
return "macos";
|
||||
#elif defined(Q_OS_WINDOWS)
|
||||
return "windows";
|
||||
#else
|
||||
return "linux";
|
||||
#endif
|
||||
switch (os()) {
|
||||
case OS::MacOS:
|
||||
return "macos";
|
||||
case OS::Windows:
|
||||
return "windows";
|
||||
case OS::Linux:
|
||||
default:
|
||||
return "linux";
|
||||
}
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The application version number.
|
||||
//****************************************************************************************************************************************************
|
||||
QString appVersion(const QString& version) {
|
||||
return QString("%1-bridge@%2").arg(getApiOS()).arg(version);
|
||||
return QString("%1-bridge@%2").arg(getApiOS(), version);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void initSentry() {
|
||||
sentry_options_t *sentryOptions = newSentryOptions(PROJECT_DSN_SENTRY, sentryCacheDir().toStdString().c_str());
|
||||
if (!QString(PROJECT_CRASHPAD_HANDLER_PATH).isEmpty())
|
||||
sentry_options_set_handler_path(sentryOptions, PROJECT_CRASHPAD_HANDLER_PATH);
|
||||
|
||||
if (sentry_init(sentryOptions) != 0) {
|
||||
QTextStream(stderr) << "Failed to initialize sentry\n";
|
||||
}
|
||||
setSentryReportScope();
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void setSentryReportScope() {
|
||||
sentry_set_tag("OS", bridgepp::goos().toUtf8());
|
||||
sentry_set_tag("Client", PROJECT_FULL_NAME);
|
||||
@ -76,6 +103,10 @@ void setSentryReportScope() {
|
||||
sentry_set_user(user);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
sentry_options_t* newSentryOptions(const char *sentryDNS, const char *cacheDir) {
|
||||
sentry_options_t *sentryOptions = sentry_options_new();
|
||||
sentry_options_set_dsn(sentryOptions, sentryDNS);
|
||||
@ -92,12 +123,18 @@ sentry_options_t* newSentryOptions(const char *sentryDNS, const char *cacheDir)
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
sentry_uuid_t reportSentryEvent(sentry_level_t level, const char *message) {
|
||||
auto event = sentry_value_new_message_event(level, LoggerName, message);
|
||||
return sentry_capture_event(event);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
sentry_uuid_t reportSentryException(sentry_level_t level, const char *message, const char *exceptionType, const char *exception) {
|
||||
auto event = sentry_value_new_message_event(level, LoggerName, message);
|
||||
sentry_event_add_exception(event, sentry_value_new_exception(exceptionType, exception));
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
#include <sentry.h>
|
||||
|
||||
|
||||
void initSentry();
|
||||
void setSentryReportScope();
|
||||
sentry_options_t* newSentryOptions(const char * sentryDNS, const char * cacheDir);
|
||||
sentry_uuid_t reportSentryEvent(sentry_level_t level, const char *message);
|
||||
|
||||
56
internal/frontend/bridge-gui/bridge-gui/Settings.cpp
Normal file
56
internal/frontend/bridge-gui/bridge-gui/Settings.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge 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.
|
||||
//
|
||||
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "Settings.h"
|
||||
#include <bridgepp/BridgeUtils.h>
|
||||
|
||||
|
||||
using namespace bridgepp;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
QString const settingsFileName = "bridge-gui.ini"; ///< The name of the settings file.
|
||||
QString const keyUseSoftwareRenderer = "UseSoftwareRenderer"; ///< The key for storing the 'Use software rendering' setting.
|
||||
|
||||
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
Settings::Settings()
|
||||
: settings_(QDir(userConfigDir()).absoluteFilePath("bridge-gui.ini"), QSettings::Format::IniFormat) {
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The value for the 'Use software renderer' setting.
|
||||
//****************************************************************************************************************************************************
|
||||
bool Settings::useSoftwareRenderer() const {
|
||||
return settings_.value(keyUseSoftwareRenderer, false).toBool();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] value The value for the 'Use software renderer' setting.
|
||||
//****************************************************************************************************************************************************
|
||||
void Settings::setUseSoftwareRenderer(bool value) {
|
||||
settings_.setValue(keyUseSoftwareRenderer, value);
|
||||
}
|
||||
47
internal/frontend/bridge-gui/bridge-gui/Settings.h
Normal file
47
internal/frontend/bridge-gui/bridge-gui/Settings.h
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge 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.
|
||||
//
|
||||
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_GUI_SETTINGS_H
|
||||
#define BRIDGE_GUI_SETTINGS_H
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Application settings class
|
||||
//****************************************************************************************************************************************************
|
||||
class Settings {
|
||||
public: // member functions.
|
||||
Settings(Settings const&) = delete; ///< Disabled copy-constructor.
|
||||
Settings(Settings&&) = delete; ///< Disabled assignment copy-constructor.
|
||||
~Settings() = default; ///< Destructor.
|
||||
Settings& operator=(Settings const&) = delete; ///< Disabled assignment operator.
|
||||
Settings& operator=(Settings&&) = delete; ///< Disabled move assignment operator.
|
||||
|
||||
bool useSoftwareRenderer() const; ///< Get the 'Use software renderer' settings value.
|
||||
void setUseSoftwareRenderer(bool value); ///< Set the 'Use software renderer' settings value.
|
||||
|
||||
private: // member functions.
|
||||
Settings(); ///< Default constructor.
|
||||
|
||||
private: // data members.
|
||||
QSettings settings_; ///< The settings.
|
||||
|
||||
friend class AppController;
|
||||
};
|
||||
|
||||
|
||||
#endif //BRIDGE_GUI_SETTINGS_H
|
||||
@ -38,6 +38,10 @@ void UserList::connectGRPCEvents() const {
|
||||
GRPCClient &client = app().grpc();
|
||||
connect(&client, &GRPCClient::userChanged, this, &UserList::onUserChanged);
|
||||
connect(&client, &GRPCClient::toggleSplitModeFinished, this, &UserList::onToggleSplitModeFinished);
|
||||
connect(&client, &GRPCClient::usedBytesChanged, this, &UserList::onUsedBytesChanged);
|
||||
connect(&client, &GRPCClient::syncStarted, this, &UserList::onSyncStarted);
|
||||
connect(&client, &GRPCClient::syncFinished, this, &UserList::onSyncFinished);
|
||||
connect(&client, &GRPCClient::syncProgress, this, &UserList::onSyncProgress);
|
||||
}
|
||||
|
||||
|
||||
@ -148,6 +152,19 @@ bridgepp::SPUser UserList::getUserWithID(QString const &userID) const {
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] username The username or email.
|
||||
/// \return The user with the given ID.
|
||||
/// \return A null pointer if the user could not be found.
|
||||
//****************************************************************************************************************************************************
|
||||
bridgepp::SPUser UserList::getUserWithUsernameOrEmail(QString const &username) const {
|
||||
QList<SPUser>::const_iterator it = std::find_if(users_.begin(), users_.end(), [username](SPUser const &user) -> bool {
|
||||
return user && ((username.compare(user->username(), Qt::CaseInsensitive) == 0) || user->addresses().contains(username, Qt::CaseInsensitive));
|
||||
});
|
||||
return (it == users_.end()) ? nullptr : *it;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] row The row.
|
||||
//****************************************************************************************************************************************************
|
||||
@ -223,3 +240,61 @@ void UserList::onToggleSplitModeFinished(QString const &userID) {
|
||||
int UserList::count() const {
|
||||
return users_.size();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] userID The userID.
|
||||
/// \param[in] usedBytes The used space, in bytes.
|
||||
//****************************************************************************************************************************************************
|
||||
void UserList::onUsedBytesChanged(QString const &userID, qint64 usedBytes) {
|
||||
int const index = this->rowOfUserID(userID);
|
||||
if (index < 0) {
|
||||
app().log().error(QString("Received usedBytesChanged event for unknown userID %1").arg(userID));
|
||||
return;
|
||||
}
|
||||
users_[index]->setUsedBytes(usedBytes);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] userID The userID.
|
||||
//****************************************************************************************************************************************************
|
||||
void UserList::onSyncStarted(QString const &userID) {
|
||||
int const index = this->rowOfUserID(userID);
|
||||
if (index < 0) {
|
||||
app().log().error(QString("Received onSyncStarted event for unknown userID %1").arg(userID));
|
||||
return;
|
||||
}
|
||||
users_[index]->setIsSyncing(true);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] userID The userID.
|
||||
//****************************************************************************************************************************************************
|
||||
void UserList::onSyncFinished(QString const &userID) {
|
||||
int const index = this->rowOfUserID(userID);
|
||||
if (index < 0) {
|
||||
app().log().error(QString("Received onSyncFinished event for unknown userID %1").arg(userID));
|
||||
return;
|
||||
}
|
||||
users_[index]->setIsSyncing(false);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] userID The userID.
|
||||
/// \param[in] progress The sync progress ratio.
|
||||
/// \param[in] elapsedMs The elapsed sync time in milliseconds.
|
||||
/// \param[in] remainingMs The remaining sync time in milliseconds.
|
||||
//****************************************************************************************************************************************************
|
||||
void UserList::onSyncProgress(QString const &userID, double progress, float elapsedMs, float remainingMs) {
|
||||
Q_UNUSED(elapsedMs)
|
||||
Q_UNUSED(remainingMs)
|
||||
int const index = this->rowOfUserID(userID);
|
||||
if (index < 0) {
|
||||
app().log().error(QString("Received onSyncFinished event for unknown userID %1").arg(userID));
|
||||
return;
|
||||
}
|
||||
users_[index]->setSyncProgress(progress);
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ public: // member functions.
|
||||
void appendUser(bridgepp::SPUser const &user); ///< Add a new user.
|
||||
void updateUserAtRow(int row, bridgepp::User const &user); ///< Update the user at given row.
|
||||
bridgepp::SPUser getUserWithID(QString const &userID) const; ///< Retrieve the user with the given ID.
|
||||
bridgepp::SPUser getUserWithUsernameOrEmail(QString const& username) const; ///< Retrieve the user with the given primary email address or username
|
||||
|
||||
// the userCount property.
|
||||
Q_PROPERTY(int count READ count NOTIFY countChanged)
|
||||
@ -59,7 +60,11 @@ public:
|
||||
public slots: ///< handler for signals coming from the gRPC service
|
||||
void onUserChanged(QString const &userID);
|
||||
void onToggleSplitModeFinished(QString const &userID);
|
||||
|
||||
void onUsedBytesChanged(QString const &userID, qint64 usedBytes); ///< Slot for usedBytesChanged events.
|
||||
void onSyncStarted(QString const &userID); ///< Slot for syncStarted events.
|
||||
void onSyncFinished(QString const &userID); ///< Slot for syncFinished events.
|
||||
void onSyncProgress(QString const &userID, double progress, float elapsedMs, float remainingMs); ///< Slot for syncFinished events.
|
||||
|
||||
private: // data members
|
||||
QList<bridgepp::SPUser> users_; ///< The user list.
|
||||
};
|
||||
|
||||
@ -92,7 +92,7 @@ git submodule update --init --recursive $vcpkgRoot
|
||||
. $cmakeExe -G "Visual Studio 17 2022" -DCMAKE_BUILD_TYPE="$buildConfig" `
|
||||
-DBRIDGE_APP_FULL_NAME="$bridgeFullName" `
|
||||
-DBRIDGE_VENDOR="$bridgeVendor" `
|
||||
-DBRIDGE_REVISION=$REVISION_HASH `
|
||||
-DBRIDGE_REVISION="$REVISION_HASH" `
|
||||
-DBRIDGE_APP_VERSION="$bridgeVersion" `
|
||||
-DBRIDGE_BUILD_TIME="$bridgeBuidTime" `
|
||||
-DBRIDGE_DSN_SENTRY="$bridgeDsnSentry" `
|
||||
|
||||
@ -16,20 +16,18 @@
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "Pch.h"
|
||||
#include "BridgeApp.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "CommandLine.h"
|
||||
#include "LogUtils.h"
|
||||
#include "QMLBackend.h"
|
||||
#include "SentryUtils.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "LogUtils.h"
|
||||
#include "Settings.h"
|
||||
#include <bridgepp/BridgeUtils.h>
|
||||
#include <bridgepp/Exception/Exception.h>
|
||||
#include <bridgepp/FocusGRPC/FocusGRPCClient.h>
|
||||
#include <bridgepp/Log/Log.h>
|
||||
#include <bridgepp/ProcessMonitor.h>
|
||||
#include <sentry.h>
|
||||
#include <SentryUtils.h>
|
||||
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
@ -99,38 +97,6 @@ void initQtApplication() {
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return A reference to the log.
|
||||
//****************************************************************************************************************************************************
|
||||
Log &initLog() {
|
||||
Log &log = app().log();
|
||||
log.registerAsQtMessageHandler();
|
||||
log.setEchoInConsole(true);
|
||||
|
||||
// remove old gui log files
|
||||
QDir const logsDir(userLogsDir());
|
||||
for (QFileInfo const fileInfo: logsDir.entryInfoList({ "gui_v*.log" }, QDir::Filter::Files)) { // entryInfolist apparently only support wildcards, not regex.
|
||||
QFile(fileInfo.absoluteFilePath()).remove();
|
||||
}
|
||||
|
||||
// create new GUI log file
|
||||
QString error;
|
||||
if (!log.startWritingToFile(logsDir.absoluteFilePath(QString("gui_v%1_%2.log").arg(PROJECT_VER).arg(QDateTime::currentSecsSinceEpoch())), &error)) {
|
||||
log.error(error);
|
||||
}
|
||||
|
||||
log.info("bridge-gui starting");
|
||||
QString const qtCompileTimeVersion = QT_VERSION_STR;
|
||||
QString const qtRuntimeVersion = qVersion();
|
||||
QString msg = QString("Using Qt %1").arg(qtRuntimeVersion);
|
||||
if (qtRuntimeVersion != qtCompileTimeVersion) {
|
||||
msg += QString(" (compiled against %1)").arg(qtCompileTimeVersion);
|
||||
}
|
||||
log.info(msg);
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] engine The QML component.
|
||||
@ -150,8 +116,9 @@ QQmlComponent *createRootQmlComponent(QQmlApplicationEngine &engine) {
|
||||
|
||||
rootComponent->loadUrl(QUrl(qrcQmlDir + "/Bridge.qml"));
|
||||
if (rootComponent->status() != QQmlComponent::Status::Ready) {
|
||||
app().log().error(rootComponent->errorString());
|
||||
throw Exception("Could not load QML component");
|
||||
QString const &err =rootComponent->errorString();
|
||||
app().log().error(err);
|
||||
throw Exception("Could not load QML component", err);
|
||||
}
|
||||
return rootComponent;
|
||||
}
|
||||
@ -218,7 +185,7 @@ QUrl getApiUrl() {
|
||||
/// \return true if an instance of bridge is already running.
|
||||
//****************************************************************************************************************************************************
|
||||
bool isBridgeRunning() {
|
||||
QLockFile lockFile(QDir(userCacheDir()).absoluteFilePath(bridgeLock));
|
||||
QLockFile lockFile(QDir(bridgepp::userCacheDir()).absoluteFilePath(bridgeLock));
|
||||
return (!lockFile.tryLock()) && (lockFile.error() == QLockFile::LockFailedError);
|
||||
}
|
||||
|
||||
@ -229,8 +196,21 @@ bool isBridgeRunning() {
|
||||
void focusOtherInstance() {
|
||||
try {
|
||||
FocusGRPCClient client;
|
||||
GRPCConfig sc;
|
||||
QString const path = FocusGRPCClient::grpcFocusServerConfigPath(bridgepp::userConfigDir());
|
||||
QFile file(path);
|
||||
if (file.exists()) {
|
||||
if (!sc.load(path)) {
|
||||
throw Exception("The gRPC focus service configuration file is invalid.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw Exception("Server did not provide gRPC Focus service configuration.");
|
||||
}
|
||||
|
||||
|
||||
QString error;
|
||||
if (!client.connectToServer(5000, &error)) {
|
||||
if (!client.connectToServer(5000, sc.port, &error)) {
|
||||
throw Exception(QString("Could not connect to bridge focus service for a raise call: %1").arg(error));
|
||||
}
|
||||
if (!client.raise().ok()) {
|
||||
@ -247,6 +227,7 @@ void focusOtherInstance() {
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param [in] args list of arguments to pass to bridge.
|
||||
/// \return bridge executable path
|
||||
//****************************************************************************************************************************************************
|
||||
const QString launchBridge(QStringList const &args) {
|
||||
UPOverseer &overseer = app().bridgeOverseer();
|
||||
@ -276,12 +257,8 @@ void closeBridgeApp() {
|
||||
app().grpc().quit(); // this will cause the grpc service and the bridge app to close.
|
||||
|
||||
UPOverseer &overseer = app().bridgeOverseer();
|
||||
if (!overseer) { // The app was run in 'attach' mode and attached to an existing instance of Bridge. We're not monitoring it.
|
||||
return;
|
||||
}
|
||||
|
||||
while (!overseer->isFinished()) {
|
||||
QThread::msleep(20);
|
||||
if (overseer) { // A null overseer means the app was run in 'attach' mode. We're not monitoring it.
|
||||
overseer->wait(Overseer::maxTerminationWaitTimeMs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,24 +269,23 @@ void closeBridgeApp() {
|
||||
/// \return The exit code for the application.
|
||||
//****************************************************************************************************************************************************
|
||||
int main(int argc, char *argv[]) {
|
||||
// Init sentry.
|
||||
sentry_options_t *sentryOptions = newSentryOptions(PROJECT_DSN_SENTRY, sentryCacheDir().toStdString().c_str());
|
||||
|
||||
if (sentry_init(sentryOptions) != 0) {
|
||||
std::cerr << "Failed to initialize sentry" << std::endl;
|
||||
}
|
||||
setSentryReportScope();
|
||||
auto sentryClose = qScopeGuard([] { sentry_close(); });
|
||||
|
||||
// The application instance is needed to display system message boxes. As we may have to do it in the exception handler,
|
||||
// application instance is create outside the try/catch clause.
|
||||
if (QSysInfo::productType() != "windows") {
|
||||
QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
|
||||
QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); // must be called before instantiating the BridgeApp
|
||||
}
|
||||
|
||||
BridgeApp guiApp(argc, argv);
|
||||
|
||||
try {
|
||||
QString const& configDir = bridgepp::userConfigDir();
|
||||
|
||||
// Init sentry.
|
||||
initSentry();
|
||||
|
||||
auto sentryClose = qScopeGuard([] { sentry_close(); });
|
||||
|
||||
|
||||
initQtApplication();
|
||||
|
||||
Log &log = initLog();
|
||||
@ -339,14 +315,16 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
// before launching bridge, we remove any trailing service config file, because we need to make sure we get a newly generated one.
|
||||
GRPCClient::removeServiceConfigFile();
|
||||
FocusGRPCClient::removeServiceConfigFile(configDir);
|
||||
GRPCClient::removeServiceConfigFile(configDir);
|
||||
bridgeexec = launchBridge(cliOptions.bridgeArgs);
|
||||
}
|
||||
|
||||
log.info(QString("Retrieving gRPC service configuration from '%1'").arg(QDir::toNativeSeparators(grpcServerConfigPath())));
|
||||
app().backend().init(GRPCClient::waitAndRetrieveServiceConfig(cliOptions.attach ? 0 : grpcServiceConfigWaitDelayMs, app().bridgeMonitor()));
|
||||
log.info(QString("Retrieving gRPC service configuration from '%1'").arg(QDir::toNativeSeparators(grpcServerConfigPath(configDir))));
|
||||
app().backend().init(GRPCClient::waitAndRetrieveServiceConfig(configDir, cliOptions.attach ? 0 : grpcServiceConfigWaitDelayMs,
|
||||
app().bridgeMonitor()));
|
||||
if (!cliOptions.attach) {
|
||||
GRPCClient::removeServiceConfigFile();
|
||||
GRPCClient::removeServiceConfigFile(configDir);
|
||||
}
|
||||
|
||||
// gRPC communication is established. From now on, log events will be sent to bridge via gRPC. bridge will write these to file,
|
||||
@ -359,7 +337,7 @@ int main(int argc, char *argv[]) {
|
||||
// The following allows to render QML content in software with a 'Rendering Hardware Interface' (OpenGL, Vulkan, Metal, Direct3D...)
|
||||
// Note that it is different from the Qt::AA_UseSoftwareOpenGL attribute we use on some platforms that instruct Qt that we would like
|
||||
// to use a software-only implementation of OpenGL.
|
||||
QQuickWindow::setSceneGraphBackend(cliOptions.useSoftwareRenderer ? "software" : "rhi");
|
||||
QQuickWindow::setSceneGraphBackend((app().settings().useSoftwareRenderer() || cliOptions.useSoftwareRenderer) ? "software" : "rhi");
|
||||
log.info(QString("Qt Quick renderer: %1").arg(QQuickWindow::sceneGraphBackend()));
|
||||
|
||||
|
||||
@ -412,7 +390,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
QObject::disconnect(connection);
|
||||
app().grpc().stopEventStreamReader();
|
||||
if (!app().backend().waitForEventStreamReaderToFinish(5000)) {
|
||||
if (!app().backend().waitForEventStreamReaderToFinish(Overseer::maxTerminationWaitTimeMs)) {
|
||||
log.warn("Event stream reader took too long to finish.");
|
||||
}
|
||||
|
||||
|
||||
@ -29,14 +29,19 @@ Item {
|
||||
|
||||
property var _spacing: 12 * ProtonStyle.px
|
||||
|
||||
property color usedSpaceColor : {
|
||||
property color progressColor : {
|
||||
if (!root.enabled) return root.colorScheme.text_weak
|
||||
if (root.type == AccountDelegate.SmallView) return root.colorScheme.text_weak
|
||||
if (root.usedFraction < .50) return root.colorScheme.signal_success
|
||||
if (root.usedFraction < .75) return root.colorScheme.signal_warning
|
||||
if (root.user && root.user.isSyncing) return root.colorScheme.text_weak
|
||||
if (root.progressRatio < .50) return root.colorScheme.signal_success
|
||||
if (root.progressRatio < .75) return root.colorScheme.signal_warning
|
||||
return root.colorScheme.signal_danger
|
||||
}
|
||||
property real usedFraction: root.user ? reasonableFraction(root.user.usedBytes, root.user.totalBytes) : 0
|
||||
property real progressRatio: {
|
||||
if (!root.user)
|
||||
return 0
|
||||
return root.user.isSyncing ? root.user.syncProgress : reasonableFraction(root.user.usedBytes, root.user.totalBytes)
|
||||
}
|
||||
property string totalSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.totalBytes) : 0)
|
||||
property string usedSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.usedBytes) : 0)
|
||||
|
||||
@ -171,18 +176,21 @@ Item {
|
||||
case EUserState.Locked:
|
||||
return qsTr("Connecting") + dotsTimer.dots
|
||||
case EUserState.Connected:
|
||||
return root.usedSpace
|
||||
if (root.user.isSyncing)
|
||||
return qsTr("Synchronizing (%1%)").arg(Math.floor(root.user.syncProgress * 100)) + dotsTimer.dots
|
||||
else
|
||||
return root.usedSpace
|
||||
}
|
||||
}
|
||||
|
||||
Timer { // dots animation while connecting. 1 sec cycle, roughly similar to the webmail loading page.
|
||||
Timer { // dots animation while connecting & syncing.
|
||||
id:dotsTimer
|
||||
property string dots: ""
|
||||
interval: 250;
|
||||
interval: 500;
|
||||
repeat: true;
|
||||
running: (root.user != null) && (root.user.state === EUserState.Locked)
|
||||
running: (root.user != null) && ((root.user.state === EUserState.Locked) || (root.user.isSyncing))
|
||||
onTriggered: {
|
||||
dots = dots + "."
|
||||
dots += "."
|
||||
if (dots.length > 3)
|
||||
dots = ""
|
||||
}
|
||||
@ -191,7 +199,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
color: root.usedSpaceColor
|
||||
color: root.progressColor
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Caption
|
||||
@ -202,7 +210,7 @@ Item {
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.user && root.user.state == EUserState.Connected ? " / " + root.totalSpace : ""
|
||||
text: root.user && root.user.state == EUserState.Connected && !root.user.isSyncing ? " / " + root.totalSpace : ""
|
||||
color: root.colorScheme.text_weak
|
||||
type: {
|
||||
switch (root.type) {
|
||||
@ -213,26 +221,27 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Item { implicitHeight: root.type == AccountDelegate.LargeView ? 3 * ProtonStyle.px : 0 }
|
||||
|
||||
Rectangle {
|
||||
id: storage_bar
|
||||
id: progress_bar
|
||||
visible: root.user ? root.type == AccountDelegate.LargeView : false
|
||||
width: 140 * ProtonStyle.px
|
||||
height: 4 * ProtonStyle.px
|
||||
radius: ProtonStyle.storage_bar_radius
|
||||
radius: ProtonStyle.progress_bar_radius
|
||||
color: root.colorScheme.border_weak
|
||||
|
||||
Rectangle {
|
||||
id: storage_bar_filled
|
||||
radius: ProtonStyle.storage_bar_radius
|
||||
color: root.usedSpaceColor
|
||||
visible: root.user ? parent.visible && (root.user.state == EUserState.Connected) : false
|
||||
id: progress_bar_filled
|
||||
radius: ProtonStyle.progress_bar_radius
|
||||
color: root.progressColor
|
||||
visible: root.user ? parent.visible && (root.user.state == EUserState.Connected): false
|
||||
anchors {
|
||||
top : parent.top
|
||||
bottom : parent.bottom
|
||||
left : parent.left
|
||||
}
|
||||
width: Math.min(1,Math.max(0.02,root.usedFraction)) * parent.width
|
||||
width: Math.min(1,Math.max(0.02,root.progressRatio)) * parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,8 @@ Item {
|
||||
property var notifications
|
||||
|
||||
signal showSetupGuide(var user, string address)
|
||||
signal closeWindow()
|
||||
signal quitBridge()
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
@ -107,7 +109,7 @@ Item {
|
||||
|
||||
Layout.topMargin: 16
|
||||
Layout.bottomMargin: 9
|
||||
Layout.rightMargin: 16
|
||||
Layout.rightMargin: 4
|
||||
|
||||
horizontalPadding: 0
|
||||
|
||||
@ -115,6 +117,55 @@ Item {
|
||||
|
||||
onClicked: rightContent.showGeneralSettings()
|
||||
}
|
||||
|
||||
Button {
|
||||
id: dotMenuButton
|
||||
Layout.bottomMargin: 9
|
||||
Layout.maximumHeight: 36
|
||||
Layout.maximumWidth: 36
|
||||
Layout.minimumHeight: 36
|
||||
Layout.minimumWidth: 36
|
||||
Layout.preferredHeight: 36
|
||||
Layout.preferredWidth: 36
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 16
|
||||
colorScheme: leftBar.colorScheme
|
||||
horizontalPadding: 0
|
||||
icon.source: "/qml/icons/ic-three-dots-vertical.svg"
|
||||
|
||||
onClicked: {
|
||||
dotMenu.open()
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: dotMenu
|
||||
colorScheme: root.colorScheme
|
||||
modal: true
|
||||
y: dotMenuButton.Layout.preferredHeight + dotMenuButton.Layout.bottomMargin
|
||||
|
||||
MenuItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Close window")
|
||||
onClicked: {
|
||||
root.closeWindow()
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Quit Bridge")
|
||||
onClicked: {
|
||||
root.quitBridge()
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
parent.checked = false
|
||||
}
|
||||
onOpened: {
|
||||
parent.checked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {implicitHeight:10}
|
||||
|
||||
@ -142,6 +142,17 @@ ApplicationWindow {
|
||||
onShowSetupGuide: function(user, address) {
|
||||
root.showSetup(user,address)
|
||||
}
|
||||
|
||||
onCloseWindow: {
|
||||
root.close()
|
||||
}
|
||||
|
||||
onQuitBridge: {
|
||||
// If we ever want to add a confirmation dialog before quitting:
|
||||
//root.notifications.askQuestion("Quit Bridge", "Insert warning message here.", "Quit", "Cancel", Backend.quit, null)
|
||||
root.close()
|
||||
Backend.quit()
|
||||
}
|
||||
}
|
||||
|
||||
WelcomeGuide { // 1
|
||||
|
||||
@ -138,4 +138,9 @@ Item {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.genericError
|
||||
}
|
||||
|
||||
NotificationDialog {
|
||||
colorScheme: root.colorScheme
|
||||
notification: root.notifications.genericQuestion
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ QtObject {
|
||||
signal askResetBridge()
|
||||
signal askChangeAllMailVisibility(var isVisibleNow)
|
||||
signal askDeleteAccount(var user)
|
||||
|
||||
signal askQuestion(var title, var description, var option1, var option2, var action1, var action2)
|
||||
enum Group {
|
||||
Connection = 1,
|
||||
Update = 2,
|
||||
@ -81,7 +81,9 @@ QtObject {
|
||||
root.apiCertIssue,
|
||||
root.noActiveKeyForRecipient,
|
||||
root.userBadEvent,
|
||||
root.genericError
|
||||
root.imapLoginWhileSignedOut,
|
||||
root.genericError,
|
||||
root.genericQuestion,
|
||||
]
|
||||
|
||||
// Connection
|
||||
@ -1143,6 +1145,34 @@ QtObject {
|
||||
|
||||
}
|
||||
|
||||
property Notification imapLoginWhileSignedOut: Notification {
|
||||
title: qsTr("IMAP Login failed")
|
||||
brief: title
|
||||
description: "#PlaceHolderText"
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Danger
|
||||
group: Notifications.Group.Connection
|
||||
|
||||
Connections {
|
||||
target: Backend
|
||||
function onImapLoginWhileSignedOut(username) {
|
||||
root.imapLoginWhileSignedOut.description = qsTr("An email client tried to connect to the account %1, but this account is signed " +
|
||||
"out. Please sign-in to continue.").arg(username)
|
||||
root.imapLoginWhileSignedOut.active = true
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: qsTr("OK")
|
||||
|
||||
onTriggered: {
|
||||
root.imapLoginWhileSignedOut.active = false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
property Notification genericError: Notification {
|
||||
title: "#PlaceholderText#"
|
||||
description: "#PlaceholderText#"
|
||||
@ -1168,4 +1198,50 @@ QtObject {
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
property Notification genericQuestion: Notification {
|
||||
title: ""
|
||||
brief: ""
|
||||
description: ""
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Dialogs
|
||||
property var option1: ""
|
||||
property var option2: ""
|
||||
property variant action1: null
|
||||
property variant action2: null
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onAskQuestion(title, description, option1, option2, action1, action2) {
|
||||
root.genericQuestion.title = title
|
||||
root.genericQuestion.description = description
|
||||
root.genericQuestion.option1 = option1
|
||||
root.genericQuestion.option2 = option2
|
||||
root.genericQuestion.action1 = action1
|
||||
root.genericQuestion.action2 = action2
|
||||
root.genericQuestion.active = true
|
||||
}
|
||||
}
|
||||
|
||||
action: [
|
||||
Action {
|
||||
text: root.genericQuestion.option1
|
||||
|
||||
onTriggered: {
|
||||
root.genericQuestion.active = false
|
||||
if (root.genericQuestion.action1)
|
||||
root.genericQuestion.action1()
|
||||
}
|
||||
},
|
||||
Action {
|
||||
text: root.genericQuestion.option2
|
||||
|
||||
onTriggered: {
|
||||
root.genericQuestion.active = false
|
||||
if (root.genericQuestion.action2)
|
||||
root.genericQuestion.action2()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,13 +26,12 @@ T.MenuItem {
|
||||
|
||||
property ColorScheme colorScheme
|
||||
|
||||
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
|
||||
implicitContentWidth + leftPadding + rightPadding)
|
||||
width: parent.width // required. Other item overflows to the right of the menu and get clipped.
|
||||
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
|
||||
implicitContentHeight + topPadding + bottomPadding,
|
||||
implicitIndicatorHeight + topPadding + bottomPadding)
|
||||
|
||||
padding: 6
|
||||
padding: 12
|
||||
spacing: 6
|
||||
|
||||
icon.width: 24
|
||||
|
||||
@ -362,7 +362,7 @@ QtObject {
|
||||
property real banner_radius : 12 * root.px // px
|
||||
property real dialog_radius : 12 * root.px // px
|
||||
property real card_radius : 12 * root.px // px
|
||||
property real storage_bar_radius : 3 * root.px // px
|
||||
property real progress_bar_radius : 3 * root.px // px
|
||||
property real tooltip_radius : 8 * root.px // px
|
||||
|
||||
property int heading_font_size: 28
|
||||
|
||||
Reference in New Issue
Block a user