mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 12:46:46 +00:00
729 lines
22 KiB
Go
729 lines
22 KiB
Go
// Copyright (c) 2021 Proton Technologies AG
|
|
//
|
|
// This file is part of ProtonMail Bridge.
|
|
//
|
|
// ProtonMail 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.
|
|
//
|
|
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
// +build build_qt
|
|
|
|
// Package qt is the Qt User interface for Desktop bridge.
|
|
//
|
|
// The FrontendQt implements Frontend interface: `frontend.go`.
|
|
// The helper functions are in `helpers.go`.
|
|
// Notification specific is written in `notification.go`.
|
|
// The AccountsModel is container providing account info to QML ListView.
|
|
//
|
|
// Since we are using QML there is only one Qt loop in `ui.go`.
|
|
package qt
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ProtonMail/go-autostart"
|
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
|
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
|
"github.com/ProtonMail/proton-bridge/internal/frontend/autoconfig"
|
|
qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common"
|
|
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
|
"github.com/ProtonMail/proton-bridge/internal/locations"
|
|
"github.com/ProtonMail/proton-bridge/internal/updater"
|
|
"github.com/ProtonMail/proton-bridge/pkg/keychain"
|
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
|
"github.com/ProtonMail/proton-bridge/pkg/ports"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/skratchdot/open-golang/open"
|
|
"github.com/therecipe/qt/core"
|
|
"github.com/therecipe/qt/gui"
|
|
"github.com/therecipe/qt/qml"
|
|
"github.com/therecipe/qt/widgets"
|
|
)
|
|
|
|
var log = logrus.WithField("pkg", "frontend-qt")
|
|
var accountMutex = &sync.Mutex{}
|
|
|
|
// API between Bridge and Qt.
|
|
//
|
|
// With this interface it is possible to control Qt-Gui interface using pointers to
|
|
// Qt and QML objects. QML signals and slots are connected via methods of GoQMLInterface.
|
|
type FrontendQt struct {
|
|
version string
|
|
buildVersion string
|
|
programName string
|
|
showWindowOnStart bool
|
|
panicHandler types.PanicHandler
|
|
locations *locations.Locations
|
|
settings *settings.Settings
|
|
eventListener listener.Listener
|
|
updater types.Updater
|
|
userAgent *useragent.UserAgent
|
|
bridge types.Bridger
|
|
noEncConfirmator types.NoEncConfirmator
|
|
|
|
App *widgets.QApplication // Main Application pointer.
|
|
View *qml.QQmlApplicationEngine // QML engine pointer.
|
|
MainWin *core.QObject // Pointer to main window inside QML.
|
|
Qml *GoQMLInterface // Object accessible from both Go and QML for methods and signals.
|
|
Accounts *AccountsModel // Providing data for accounts ListView.
|
|
programVer string // Program version (shown in help).
|
|
|
|
authClient pmapi.Client
|
|
|
|
auth *pmapi.Auth
|
|
|
|
autostart *autostart.App
|
|
|
|
// expand userID when added
|
|
userIDAdded string
|
|
|
|
restarter types.Restarter
|
|
|
|
// saving most up-to-date update info to install it manually
|
|
updateInfo updater.VersionInfo
|
|
|
|
initializing sync.WaitGroup
|
|
initializationDone sync.Once
|
|
}
|
|
|
|
// New returns a new Qt frontend for the bridge.
|
|
func New(
|
|
version,
|
|
buildVersion,
|
|
programName string,
|
|
showWindowOnStart bool,
|
|
panicHandler types.PanicHandler,
|
|
locations *locations.Locations,
|
|
settings *settings.Settings,
|
|
eventListener listener.Listener,
|
|
updater types.Updater,
|
|
userAgent *useragent.UserAgent,
|
|
bridge types.Bridger,
|
|
noEncConfirmator types.NoEncConfirmator,
|
|
autostart *autostart.App,
|
|
restarter types.Restarter,
|
|
) *FrontendQt {
|
|
userAgent.SetPlatform(core.QSysInfo_PrettyProductName())
|
|
|
|
f := &FrontendQt{
|
|
version: version,
|
|
buildVersion: buildVersion,
|
|
programName: programName,
|
|
showWindowOnStart: showWindowOnStart,
|
|
panicHandler: panicHandler,
|
|
locations: locations,
|
|
settings: settings,
|
|
eventListener: eventListener,
|
|
updater: updater,
|
|
userAgent: userAgent,
|
|
bridge: bridge,
|
|
noEncConfirmator: noEncConfirmator,
|
|
programVer: "v" + version,
|
|
autostart: autostart,
|
|
restarter: restarter,
|
|
}
|
|
|
|
// Initializing.Done is only called sync.Once. Please keep the increment
|
|
// set to 1
|
|
f.initializing.Add(1)
|
|
|
|
return f
|
|
}
|
|
|
|
// InstanceExistAlert is a global warning window indicating an instance already exists.
|
|
func (s *FrontendQt) InstanceExistAlert() {
|
|
log.Warn("Instance already exists")
|
|
qtcommon.QtSetupCoreAndControls(s.programName, s.programVer)
|
|
s.App = widgets.NewQApplication(len(os.Args), os.Args)
|
|
s.View = qml.NewQQmlApplicationEngine(s.App)
|
|
s.View.AddImportPath("qrc:///")
|
|
s.View.Load(core.NewQUrl3("qrc:/BridgeUI/InstanceExistsWindow.qml", 0))
|
|
_ = gui.QGuiApplication_Exec()
|
|
}
|
|
|
|
// Loop function for Bridge interface.
|
|
//
|
|
// It runs QtExecute in main thread with no additional function.
|
|
func (s *FrontendQt) Loop() (err error) {
|
|
err = s.qtExecute(func(s *FrontendQt) error { return nil })
|
|
return err
|
|
}
|
|
|
|
func (s *FrontendQt) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {
|
|
s.SetVersion(update)
|
|
s.Qml.SetUpdateCanInstall(canInstall)
|
|
s.Qml.NotifyManualUpdate()
|
|
}
|
|
|
|
func (s *FrontendQt) SetVersion(version updater.VersionInfo) {
|
|
s.Qml.SetUpdateVersion(version.Version.String())
|
|
s.Qml.SetUpdateLandingPage(version.LandingPage)
|
|
s.Qml.SetUpdateReleaseNotesLink(version.ReleaseNotesPage)
|
|
s.updateInfo = version
|
|
}
|
|
|
|
func (s *FrontendQt) NotifySilentUpdateInstalled() {
|
|
s.Qml.NotifySilentUpdateRestartNeeded()
|
|
}
|
|
|
|
func (s *FrontendQt) NotifySilentUpdateError(err error) {
|
|
s.Qml.NotifySilentUpdateError()
|
|
}
|
|
|
|
func (s *FrontendQt) watchEvents() {
|
|
s.WaitUntilFrontendIsReady()
|
|
|
|
errorCh := s.eventListener.ProvideChannel(events.ErrorEvent)
|
|
credentialsErrorCh := s.eventListener.ProvideChannel(events.CredentialsErrorEvent)
|
|
outgoingNoEncCh := s.eventListener.ProvideChannel(events.OutgoingNoEncEvent)
|
|
noActiveKeyForRecipientCh := s.eventListener.ProvideChannel(events.NoActiveKeyForRecipientEvent)
|
|
internetOffCh := s.eventListener.ProvideChannel(events.InternetOffEvent)
|
|
internetOnCh := s.eventListener.ProvideChannel(events.InternetOnEvent)
|
|
secondInstanceCh := s.eventListener.ProvideChannel(events.SecondInstanceEvent)
|
|
restartBridgeCh := s.eventListener.ProvideChannel(events.RestartBridgeEvent)
|
|
addressChangedCh := s.eventListener.ProvideChannel(events.AddressChangedEvent)
|
|
addressChangedLogoutCh := s.eventListener.ProvideChannel(events.AddressChangedLogoutEvent)
|
|
logoutCh := s.eventListener.ProvideChannel(events.LogoutEvent)
|
|
updateApplicationCh := s.eventListener.ProvideChannel(events.UpgradeApplicationEvent)
|
|
newUserCh := s.eventListener.ProvideChannel(events.UserRefreshEvent)
|
|
certIssue := s.eventListener.ProvideChannel(events.TLSCertIssue)
|
|
for {
|
|
select {
|
|
case errorDetails := <-errorCh:
|
|
imapIssue := strings.Contains(errorDetails, "IMAP failed")
|
|
smtpIssue := strings.Contains(errorDetails, "SMTP failed")
|
|
s.Qml.NotifyPortIssue(imapIssue, smtpIssue)
|
|
case <-credentialsErrorCh:
|
|
s.Qml.NotifyHasNoKeychain()
|
|
case idAndSubject := <-outgoingNoEncCh:
|
|
idAndSubjectSlice := strings.SplitN(idAndSubject, ":", 2)
|
|
messageID := idAndSubjectSlice[0]
|
|
subject := idAndSubjectSlice[1]
|
|
s.Qml.ShowOutgoingNoEncPopup(messageID, subject)
|
|
case email := <-noActiveKeyForRecipientCh:
|
|
s.Qml.ShowNoActiveKeyForRecipient(email)
|
|
case <-internetOffCh:
|
|
s.Qml.SetConnectionStatus(false)
|
|
case <-internetOnCh:
|
|
s.Qml.SetConnectionStatus(true)
|
|
case <-secondInstanceCh:
|
|
s.Qml.ShowWindow()
|
|
case <-restartBridgeCh:
|
|
s.restarter.SetToRestart()
|
|
// watchEvents is started in parallel with the Qt app.
|
|
// If the event comes too early, app might not be ready yet.
|
|
if s.App != nil {
|
|
s.App.Quit()
|
|
}
|
|
case address := <-addressChangedCh:
|
|
s.Qml.NotifyAddressChanged(address)
|
|
case address := <-addressChangedLogoutCh:
|
|
s.Qml.NotifyAddressChangedLogout(address)
|
|
case userID := <-logoutCh:
|
|
user, err := s.bridge.GetUser(userID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
s.Qml.NotifyLogout(user.Username())
|
|
case <-updateApplicationCh:
|
|
s.Qml.ProcessFinished()
|
|
s.Qml.NotifyForceUpdate()
|
|
case <-newUserCh:
|
|
s.Qml.LoadAccounts()
|
|
case <-certIssue:
|
|
s.Qml.ShowCertIssue()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Loop function for tests.
|
|
//
|
|
// It runs QtExecute in new thread with function returning itself after setup.
|
|
// Therefore it is possible to run tests on background.
|
|
func (s *FrontendQt) Start() (err error) {
|
|
uiready := make(chan *FrontendQt)
|
|
go func() {
|
|
err := s.qtExecute(func(self *FrontendQt) error {
|
|
// NOTE: Trick to send back UI by channel to access functionality
|
|
// inside application thread. Other only uninitialized `ui` is visible.
|
|
uiready <- self
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
uiready <- nil
|
|
}()
|
|
|
|
// Receive UI pointer and set all pointers.
|
|
running := <-uiready
|
|
s.App = running.App
|
|
s.View = running.View
|
|
s.MainWin = running.MainWin
|
|
return nil
|
|
}
|
|
|
|
// InvMethod runs the function with name `method` defined in RootObject of the QML.
|
|
// Used for tests.
|
|
func (s *FrontendQt) InvMethod(method string) error {
|
|
arg := core.NewQGenericArgument("", nil)
|
|
PauseLong()
|
|
isGoodMethod := core.QMetaObject_InvokeMethod4(s.MainWin, method, arg, arg, arg, arg, arg, arg, arg, arg, arg, arg)
|
|
if isGoodMethod == false {
|
|
return errors.New("Wrong method " + method)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// qtExecute is the main function for starting the Qt application.
|
|
//
|
|
// It is better to have just one Qt application per program (at least per same
|
|
// thread). This functions reads the main user interface defined in QML files.
|
|
// The files are appended to library by Qt-QRC.
|
|
func (s *FrontendQt) qtExecute(Procedure func(*FrontendQt) error) error {
|
|
qtcommon.QtSetupCoreAndControls(s.programName, s.programVer)
|
|
s.App = widgets.NewQApplication(len(os.Args), os.Args)
|
|
if runtime.GOOS == "linux" { // Fix default font.
|
|
s.App.SetFont(gui.NewQFont2(FcMatchSans(), 12, int(gui.QFont__Normal), false), "")
|
|
}
|
|
s.App.SetQuitOnLastWindowClosed(false) // Just to make sure it's not closed.
|
|
|
|
s.View = qml.NewQQmlApplicationEngine(s.App)
|
|
// Add Go-QML bridge.
|
|
s.Qml = NewGoQMLInterface(nil)
|
|
s.Qml.SetIsShownOnStart(s.showWindowOnStart)
|
|
s.Qml.SetFrontend(s) // provides access
|
|
s.View.RootContext().SetContextProperty("go", s.Qml)
|
|
|
|
// Set first start flag.
|
|
s.Qml.SetIsFirstStart(s.settings.GetBool(settings.FirstStartGUIKey))
|
|
s.settings.SetBool(settings.FirstStartGUIKey, false)
|
|
|
|
// Check if it is first start after update (fresh version).
|
|
lastVersion := s.settings.Get(settings.LastVersionKey)
|
|
s.Qml.SetIsFreshVersion(lastVersion != "" && s.version != lastVersion)
|
|
s.settings.Set(settings.LastVersionKey, s.version)
|
|
|
|
// Add AccountsModel.
|
|
s.Accounts = NewAccountsModel(nil)
|
|
s.View.RootContext().SetContextProperty("accountsModel", s.Accounts)
|
|
// Import path and load QML files.
|
|
s.View.AddImportPath("qrc:///")
|
|
s.View.Load(core.NewQUrl3("qrc:/ui.qml", 0))
|
|
|
|
// List of used packages.
|
|
s.Qml.SetCredits(bridge.Credits)
|
|
s.Qml.SetFullversion(s.buildVersion)
|
|
|
|
// Autostart: rewrite the current definition of autostart
|
|
// - when it is the first time
|
|
// - when starting after clear cache
|
|
// - when there is already autostart file from past
|
|
//
|
|
// This will make sure that autostart will use the latest path to
|
|
// launcher or bridge.
|
|
isAutoStartEnabled := s.autostart.IsEnabled()
|
|
if s.Qml.IsFirstStart() || isAutoStartEnabled {
|
|
if isAutoStartEnabled {
|
|
if err := s.autostart.Disable(); err != nil {
|
|
log.
|
|
WithField("first", s.Qml.IsFirstStart()).
|
|
WithField("wasEnabled", isAutoStartEnabled).
|
|
WithError(err).
|
|
Error("Disable on start failed.")
|
|
s.autostartError(err)
|
|
}
|
|
}
|
|
if err := s.autostart.Enable(); err != nil {
|
|
log.
|
|
WithField("first", s.Qml.IsFirstStart()).
|
|
WithField("wasEnabled", isAutoStartEnabled).
|
|
WithError(err).
|
|
Error("Enable on start failed.")
|
|
s.autostartError(err)
|
|
}
|
|
}
|
|
s.Qml.SetIsAutoStart(s.autostart.IsEnabled())
|
|
|
|
s.Qml.SetIsAutoUpdate(s.settings.GetBool(settings.AutoUpdateKey))
|
|
s.Qml.SetIsProxyAllowed(s.settings.GetBool(settings.AllowProxyKey))
|
|
s.Qml.SetIsEarlyAccess(updater.UpdateChannel(s.settings.Get(settings.UpdateChannelKey)) == updater.EarlyChannel)
|
|
|
|
availableKeychain := []string{}
|
|
for chain := range keychain.Helpers {
|
|
availableKeychain = append(availableKeychain, chain)
|
|
}
|
|
s.Qml.SetAvailableKeychain(availableKeychain)
|
|
s.Qml.SetSelectedKeychain(s.settings.Get(settings.PreferredKeychainKey))
|
|
|
|
// Set reporting of outgoing email without encryption.
|
|
s.Qml.SetIsReportingOutgoingNoEnc(s.settings.GetBool(settings.ReportOutgoingNoEncKey))
|
|
|
|
defaultIMAPPort, _ := strconv.Atoi(settings.DefaultIMAPPort)
|
|
defaultSMTPPort, _ := strconv.Atoi(settings.DefaultSMTPPort)
|
|
|
|
// IMAP/SMTP ports.
|
|
s.Qml.SetIsDefaultPort(
|
|
defaultIMAPPort == s.settings.GetInt(settings.IMAPPortKey) &&
|
|
defaultSMTPPort == s.settings.GetInt(settings.SMTPPortKey),
|
|
)
|
|
|
|
// Check QML is loaded properly.
|
|
if len(s.View.RootObjects()) == 0 {
|
|
return errors.New("QML not loaded properly")
|
|
}
|
|
|
|
// Obtain main window (need for invoke method).
|
|
s.MainWin = s.View.RootObjects()[0]
|
|
SetupSystray(s)
|
|
|
|
// Injected procedure for out-of-main-thread applications.
|
|
if err := Procedure(s); err != nil {
|
|
return err
|
|
}
|
|
|
|
go func() {
|
|
defer s.panicHandler.HandlePanic()
|
|
s.watchEvents()
|
|
}()
|
|
|
|
// Loop
|
|
if ret := gui.QGuiApplication_Exec(); ret != 0 {
|
|
err := errors.New("Event loop ended with return value:" + string(ret))
|
|
log.Warn("QGuiApplication_Exec: ", err)
|
|
return err
|
|
}
|
|
HideSystray()
|
|
return nil
|
|
}
|
|
|
|
func (s *FrontendQt) openLogs() {
|
|
logsPath, err := s.locations.ProvideLogsPath()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
go open.Run(logsPath)
|
|
}
|
|
|
|
func (s *FrontendQt) checkIsLatestVersionAndUpdate() bool {
|
|
version, err := s.updater.Check()
|
|
|
|
if err != nil {
|
|
logrus.WithError(err).Error("An error occurred while checking updates manually")
|
|
s.Qml.NotifyManualUpdateError()
|
|
return false
|
|
}
|
|
|
|
s.SetVersion(version)
|
|
|
|
if !s.updater.IsUpdateApplicable(version) {
|
|
logrus.Debug("No need to update")
|
|
return true
|
|
}
|
|
|
|
logrus.WithField("version", version.Version).Info("An update is available")
|
|
|
|
if !s.updater.CanInstall(version) {
|
|
logrus.Debug("A manual update is required")
|
|
s.NotifyManualUpdate(version, false)
|
|
return false
|
|
}
|
|
|
|
s.NotifyManualUpdate(version, true)
|
|
return false
|
|
}
|
|
|
|
func (s *FrontendQt) checkAndOpenReleaseNotes() {
|
|
go func() {
|
|
_ = s.checkIsLatestVersionAndUpdate()
|
|
s.Qml.OpenReleaseNotesExternally()
|
|
}()
|
|
}
|
|
|
|
func (s *FrontendQt) checkForUpdates() {
|
|
go func() {
|
|
if s.checkIsLatestVersionAndUpdate() {
|
|
s.Qml.NotifyVersionIsTheLatest()
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (s *FrontendQt) openLicenseFile() {
|
|
go open.Run(s.locations.GetLicenseFilePath())
|
|
}
|
|
|
|
func (s *FrontendQt) getLocalVersionInfo() {
|
|
// NOTE: Fix this.
|
|
}
|
|
|
|
func (s *FrontendQt) sendBug(description, client, address string) (isOK bool) {
|
|
isOK = true
|
|
var accname = "No account logged in"
|
|
if s.Accounts.Count() > 0 {
|
|
accname = s.Accounts.get(0).Account()
|
|
}
|
|
if accname == "" {
|
|
accname = "Unknown account"
|
|
}
|
|
if err := s.bridge.ReportBug(
|
|
core.QSysInfo_ProductType(),
|
|
core.QSysInfo_PrettyProductName(),
|
|
description,
|
|
accname,
|
|
address,
|
|
client,
|
|
); err != nil {
|
|
log.Error("while sendBug: ", err)
|
|
isOK = false
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *FrontendQt) getLastMailClient() string {
|
|
return s.userAgent.String()
|
|
}
|
|
|
|
func (s *FrontendQt) configureAppleMail(iAccount, iAddress int) {
|
|
acc := s.Accounts.get(iAccount)
|
|
|
|
user, err := s.bridge.GetUser(acc.UserID())
|
|
if err != nil {
|
|
log.Warn("UserConfigFromKeychain failed: ", acc.Account(), err)
|
|
s.SendNotification(TabAccount, s.Qml.GenericErrSeeLogs())
|
|
return
|
|
}
|
|
|
|
imapPort := s.settings.GetInt(settings.IMAPPortKey)
|
|
imapSSL := false
|
|
smtpPort := s.settings.GetInt(settings.SMTPPortKey)
|
|
smtpSSL := s.settings.GetBool(settings.SMTPSSLKey)
|
|
|
|
// If configuring apple mail for Catalina or newer, users should use SSL.
|
|
doRestart := false
|
|
if !smtpSSL && useragent.IsCatalinaOrNewer() {
|
|
smtpSSL = true
|
|
s.settings.SetBool(settings.SMTPSSLKey, true)
|
|
log.Warn("Detected Catalina or newer with bad SMTP SSL settings, now using SSL, bridge needs to restart")
|
|
doRestart = true
|
|
} else if smtpSSL {
|
|
log.Debug("Bridge is already using SMTP SSL, no need to restart")
|
|
} else {
|
|
log.Debug("OS is pre-catalina (or not darwin at all), no need to change to SMTP SSL")
|
|
}
|
|
|
|
for _, autoConf := range autoconfig.Available() {
|
|
if err := autoConf.Configure(imapPort, smtpPort, imapSSL, smtpSSL, user, iAddress); err != nil {
|
|
log.Warn("Autoconfig failed: ", autoConf.Name(), err)
|
|
s.SendNotification(TabAccount, s.Qml.GenericErrSeeLogs())
|
|
return
|
|
}
|
|
}
|
|
|
|
if doRestart {
|
|
time.Sleep(2 * time.Second)
|
|
s.restarter.SetToRestart()
|
|
s.App.Quit()
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *FrontendQt) toggleAutoStart() {
|
|
defer s.Qml.ProcessFinished()
|
|
var err error
|
|
wasEnabled := s.autostart.IsEnabled()
|
|
if wasEnabled {
|
|
err = s.autostart.Disable()
|
|
} else {
|
|
err = s.autostart.Enable()
|
|
}
|
|
isEnabled := s.autostart.IsEnabled()
|
|
if err != nil {
|
|
log.
|
|
WithField("wasEnabled", wasEnabled).
|
|
WithField("isEnabled", isEnabled).
|
|
WithError(err).
|
|
Error("Autostart change failed.")
|
|
s.autostartError(err)
|
|
}
|
|
s.Qml.SetIsAutoStart(isEnabled)
|
|
}
|
|
|
|
func (s *FrontendQt) toggleAutoUpdate() {
|
|
defer s.Qml.ProcessFinished()
|
|
|
|
if s.settings.GetBool(settings.AutoUpdateKey) {
|
|
s.settings.SetBool(settings.AutoUpdateKey, false)
|
|
s.Qml.SetIsAutoUpdate(false)
|
|
} else {
|
|
s.settings.SetBool(settings.AutoUpdateKey, true)
|
|
s.Qml.SetIsAutoUpdate(true)
|
|
}
|
|
}
|
|
|
|
func (s *FrontendQt) toggleEarlyAccess() {
|
|
defer s.Qml.ProcessFinished()
|
|
|
|
channel := s.bridge.GetUpdateChannel()
|
|
if channel == updater.EarlyChannel {
|
|
channel = updater.StableChannel
|
|
} else {
|
|
channel = updater.EarlyChannel
|
|
}
|
|
|
|
needRestart, err := s.bridge.SetUpdateChannel(channel)
|
|
s.Qml.SetIsEarlyAccess(channel == updater.EarlyChannel)
|
|
if err != nil {
|
|
s.Qml.NotifyManualUpdateError()
|
|
return
|
|
}
|
|
if needRestart {
|
|
s.restarter.SetToRestart()
|
|
s.App.Quit()
|
|
}
|
|
}
|
|
|
|
func (s *FrontendQt) toggleAllowProxy() {
|
|
defer s.Qml.ProcessFinished()
|
|
|
|
if s.settings.GetBool(settings.AllowProxyKey) {
|
|
s.settings.SetBool(settings.AllowProxyKey, false)
|
|
s.bridge.DisallowProxy()
|
|
s.Qml.SetIsProxyAllowed(false)
|
|
} else {
|
|
s.settings.SetBool(settings.AllowProxyKey, true)
|
|
s.bridge.AllowProxy()
|
|
s.Qml.SetIsProxyAllowed(true)
|
|
}
|
|
}
|
|
|
|
func (s *FrontendQt) getIMAPPort() string {
|
|
return s.settings.Get(settings.IMAPPortKey)
|
|
}
|
|
|
|
func (s *FrontendQt) getSMTPPort() string {
|
|
return s.settings.Get(settings.SMTPPortKey)
|
|
}
|
|
|
|
// Return 0 -- port is free to use for server.
|
|
// Return 1 -- port is occupied.
|
|
func (s *FrontendQt) isPortOpen(portStr string) int {
|
|
portInt, err := strconv.Atoi(portStr)
|
|
if err != nil {
|
|
return 1
|
|
}
|
|
if !ports.IsPortFree(portInt) {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (s *FrontendQt) setPortsAndSecurity(imapPort, smtpPort string, useSTARTTLSforSMTP bool) {
|
|
s.settings.Set(settings.IMAPPortKey, imapPort)
|
|
s.settings.Set(settings.SMTPPortKey, smtpPort)
|
|
s.settings.SetBool(settings.SMTPSSLKey, !useSTARTTLSforSMTP)
|
|
}
|
|
|
|
func (s *FrontendQt) isSMTPSTARTTLS() bool {
|
|
return !s.settings.GetBool(settings.SMTPSSLKey)
|
|
}
|
|
|
|
func (s *FrontendQt) switchAddressModeUser(iAccount int) {
|
|
defer s.Qml.ProcessFinished()
|
|
userID := s.Accounts.get(iAccount).UserID()
|
|
user, err := s.bridge.GetUser(userID)
|
|
if err != nil {
|
|
log.Error("Get user for switch address mode failed: ", err)
|
|
s.SendNotification(TabAccount, s.Qml.GenericErrSeeLogs())
|
|
return
|
|
}
|
|
if err := user.SwitchAddressMode(); err != nil {
|
|
log.Error("Switch address mode failed: ", err)
|
|
s.SendNotification(TabAccount, s.Qml.GenericErrSeeLogs())
|
|
return
|
|
}
|
|
s.userIDAdded = userID
|
|
}
|
|
|
|
func (s *FrontendQt) autostartError(err error) {
|
|
if strings.Contains(err.Error(), "permission denied") {
|
|
s.Qml.FailedAutostartCode("permission")
|
|
} else if strings.Contains(err.Error(), "error code: 0x") {
|
|
errorCode := err.Error()
|
|
errorCode = errorCode[len(errorCode)-8:]
|
|
s.Qml.FailedAutostartCode(errorCode)
|
|
} else {
|
|
s.Qml.FailedAutostartCode("")
|
|
}
|
|
}
|
|
|
|
func (s *FrontendQt) toggleIsReportingOutgoingNoEnc() {
|
|
shouldReport := !s.Qml.IsReportingOutgoingNoEnc()
|
|
s.settings.SetBool(settings.ReportOutgoingNoEncKey, shouldReport)
|
|
s.Qml.SetIsReportingOutgoingNoEnc(shouldReport)
|
|
}
|
|
|
|
func (s *FrontendQt) shouldSendAnswer(messageID string, shouldSend bool) {
|
|
s.noEncConfirmator.ConfirmNoEncryption(messageID, shouldSend)
|
|
}
|
|
|
|
func (s *FrontendQt) saveOutgoingNoEncPopupCoord(x, y float32) {
|
|
//prefs.SetFloat(prefs.OutgoingNoEncPopupCoordX, x)
|
|
//prefs.SetFloat(prefs.OutgoingNoEncPopupCoordY, y)
|
|
}
|
|
|
|
func (s *FrontendQt) startManualUpdate() {
|
|
go func() {
|
|
err := s.updater.InstallUpdate(s.updateInfo)
|
|
|
|
if err != nil {
|
|
logrus.WithError(err).Error("An error occurred while installing updates manually")
|
|
s.Qml.NotifyManualUpdateError()
|
|
} else {
|
|
s.Qml.NotifyManualUpdateRestartNeeded()
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (s *FrontendQt) WaitUntilFrontendIsReady() {
|
|
s.initializing.Wait()
|
|
}
|
|
|
|
// setGUIIsReady unlocks the WaitFrontendIsReady.
|
|
func (s *FrontendQt) setGUIIsReady() {
|
|
s.initializationDone.Do(func() {
|
|
s.initializing.Done()
|
|
})
|
|
}
|
|
|
|
func (s *FrontendQt) getKeychain() string {
|
|
return s.bridge.GetKeychainApp()
|
|
}
|
|
|
|
func (s *FrontendQt) setKeychain(keychain string) {
|
|
if keychain != s.bridge.GetKeychainApp() {
|
|
s.bridge.SetKeychainApp(keychain)
|
|
|
|
s.restarter.SetToRestart()
|
|
s.App.Quit()
|
|
}
|
|
}
|