mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-15 22:56:48 +00:00
GODT-1168 GODT-1169 Facelift: it has begun, qml artifacts for preview
This commit is contained in:
@ -1,64 +0,0 @@
|
||||
QMLfiles=$(shell find ../qml/ -name "*.qml") $(shell find ../qml/ -name "qmldir")
|
||||
FontAwesome=${CURDIR}/../share/fontawesome-webfont.ttf
|
||||
ImageDir=${CURDIR}/../share/icons
|
||||
Icons=$(shell find ${ImageDir} -name "*.png")
|
||||
|
||||
all: qmlcheck moc.go rcc.cpp logo.ico
|
||||
|
||||
deploy:
|
||||
qtdeploy build desktop
|
||||
|
||||
../qml/ProtonUI/fontawesome.ttf:
|
||||
ln -sf ${FontAwesome} $@
|
||||
../qml/ProtonUI/images:
|
||||
ln -sf ${ImageDir} $@
|
||||
|
||||
translate.ts: ${QMLfiles}
|
||||
lupdate -recursive qml/ -ts $@
|
||||
|
||||
rcc.cpp: ${QMLfiles} ${Icons} resources.qrc
|
||||
rm -f rcc.cpp rcc.qrc && qtrcc -o .
|
||||
|
||||
|
||||
qmltest:
|
||||
qmltestrunner -eventdelay 500 -import ../qml/
|
||||
qmlcheck: ../qml/ProtonUI/fontawesome.ttf ../qml/ProtonUI/images
|
||||
qmlscene -I ../qml/ -f ../qml/tst_Gui.qml --quit
|
||||
qmlpreview: ../qml/ProtonUI/fontawesome.ttf ../qml/ProtonUI/images
|
||||
rm -f ../qml/*.qmlc ../qml/BridgeUI/*.qmlc
|
||||
qmlscene -verbose -I ../qml/ -f ../qml/tst_Gui.qml
|
||||
#qmlscene -qmljsdebugger=port:3768,block -verbose -I ../qml/ -f ../qml/tst_Gui.qml
|
||||
|
||||
logo.ico: ../share/icons/logo.ico
|
||||
cp $^ $@
|
||||
|
||||
|
||||
test: qmlcheck moc.go rcc.cpp
|
||||
go test -v -tags=cli
|
||||
|
||||
moc.go: ui.go accountModel.go
|
||||
qtmoc
|
||||
|
||||
distclean: clean
|
||||
rm -rf rcc_cgo*.go
|
||||
|
||||
clean:
|
||||
rm -rf linux/
|
||||
rm -rf darwin/
|
||||
rm -rf windows/
|
||||
rm -rf deploy/
|
||||
rm -f logo.ico
|
||||
rm -f moc.cpp
|
||||
rm -f moc.go
|
||||
rm -f moc.h
|
||||
rm -f moc_cgo*.go
|
||||
rm -f moc_moc.h
|
||||
rm -f rcc.cpp
|
||||
rm -f rcc.qrc
|
||||
rm -f rcc_cgo*.go
|
||||
rm -f ../rcc.cpp
|
||||
rm -f ../rcc.qrc
|
||||
rm -f ../rcc_cgo*.go
|
||||
rm -rf ../qml/ProtonUI/images
|
||||
rm -f ../qml/ProtonUI/fontawesome.ttf
|
||||
find ../qml -name *.qmlc -exec rm {} \;
|
||||
@ -1,240 +0,0 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/therecipe/qt/core"
|
||||
)
|
||||
|
||||
// The element of model.
|
||||
// It contains all data for one account and its aliases.
|
||||
type AccountInfo struct {
|
||||
core.QObject
|
||||
|
||||
_ string `property:"account"`
|
||||
_ string `property:"userID"`
|
||||
_ string `property:"status"`
|
||||
_ string `property:"hostname"`
|
||||
_ string `property:"password"`
|
||||
_ string `property:"security"` // Deprecated, not used.
|
||||
_ int `property:"portSMTP"`
|
||||
_ int `property:"portIMAP"`
|
||||
_ string `property:"aliases"`
|
||||
_ bool `property:"isExpanded"`
|
||||
_ bool `property:"isCombinedAddressMode"`
|
||||
}
|
||||
|
||||
// Constants for data map.
|
||||
// enum-like constants in Go.
|
||||
const (
|
||||
Account = int(core.Qt__UserRole) + 1<<iota
|
||||
UserID
|
||||
Status
|
||||
Hostname
|
||||
Password
|
||||
Security
|
||||
PortIMAP
|
||||
PortSMTP
|
||||
Aliases
|
||||
IsExpanded
|
||||
IsCombinedAddressMode
|
||||
)
|
||||
|
||||
// Registration of new metatype before creating instance.
|
||||
// NOTE: check it is run once per program. Write a log.
|
||||
func init() {
|
||||
AccountInfo_QRegisterMetaType()
|
||||
}
|
||||
|
||||
// Model for providing container for items (account information) to QML.
|
||||
//
|
||||
// QML ListView connects the model from Go and it shows item (accounts) information.
|
||||
//
|
||||
// Copied and edited from `github.com/therecipe/qt/internal/examples/sailfish/listview`.
|
||||
type AccountsModel struct {
|
||||
core.QAbstractListModel
|
||||
|
||||
// QtObject Constructor
|
||||
_ func() `constructor:"init"`
|
||||
|
||||
// List of item properties.
|
||||
// All available item properties are inside the map.
|
||||
_ map[int]*core.QByteArray `property:"roles"`
|
||||
|
||||
// The data storage.
|
||||
// The slice with all accounts. It is not accessed directly but using `data(index,role)`.
|
||||
_ []*AccountInfo `property:"accounts"`
|
||||
|
||||
// Method for adding account.
|
||||
_ func(*AccountInfo) `slot:"addAccount"`
|
||||
|
||||
// Method for retrieving account.
|
||||
_ func(row int) *AccountInfo `slot:"get"`
|
||||
|
||||
// Method for login/logout the account.
|
||||
_ func(row int) `slot:"toggleIsAvailable"`
|
||||
|
||||
// Method for removing account from list.
|
||||
_ func(row int) `slot:"removeAccount"`
|
||||
|
||||
_ int `property:"count"`
|
||||
}
|
||||
|
||||
// init is basically the constructor.
|
||||
// Creates the map for item properties and connects the methods.
|
||||
func (s *AccountsModel) init() {
|
||||
s.SetRoles(map[int]*core.QByteArray{
|
||||
Account: NewQByteArrayFromString("account"),
|
||||
UserID: NewQByteArrayFromString("userID"),
|
||||
Status: NewQByteArrayFromString("status"),
|
||||
Hostname: NewQByteArrayFromString("hostname"),
|
||||
Password: NewQByteArrayFromString("password"),
|
||||
Security: NewQByteArrayFromString("security"),
|
||||
PortIMAP: NewQByteArrayFromString("portIMAP"),
|
||||
PortSMTP: NewQByteArrayFromString("portSMTP"),
|
||||
Aliases: NewQByteArrayFromString("aliases"),
|
||||
IsExpanded: NewQByteArrayFromString("isExpanded"),
|
||||
IsCombinedAddressMode: NewQByteArrayFromString("isCombinedAddressMode"),
|
||||
})
|
||||
// Basic QAbstractListModel methods.
|
||||
s.ConnectData(s.data)
|
||||
s.ConnectRowCount(s.rowCount)
|
||||
s.ConnectColumnCount(s.columnCount)
|
||||
s.ConnectRoleNames(s.roleNames)
|
||||
// Custom AccountModel methods.
|
||||
s.ConnectGet(s.get)
|
||||
s.ConnectAddAccount(s.addAccount)
|
||||
s.ConnectToggleIsAvailable(s.toggleIsAvailable)
|
||||
s.ConnectRemoveAccount(s.removeAccount)
|
||||
}
|
||||
|
||||
// get is a getter for account info pointer.
|
||||
func (s *AccountsModel) get(index int) *AccountInfo {
|
||||
if index < 0 || index >= len(s.Accounts()) {
|
||||
return NewAccountInfo(nil)
|
||||
} else {
|
||||
return s.Accounts()[index]
|
||||
}
|
||||
}
|
||||
|
||||
// data is a getter for account info data.
|
||||
func (s *AccountsModel) data(index *core.QModelIndex, role int) *core.QVariant {
|
||||
if !index.IsValid() {
|
||||
return core.NewQVariant()
|
||||
}
|
||||
|
||||
if index.Row() >= len(s.Accounts()) {
|
||||
return core.NewQVariant()
|
||||
}
|
||||
|
||||
var p = s.Accounts()[index.Row()]
|
||||
|
||||
switch role {
|
||||
case Account:
|
||||
return NewQVariantString(p.Account())
|
||||
case UserID:
|
||||
return NewQVariantString(p.UserID())
|
||||
case Status:
|
||||
return NewQVariantString(p.Status())
|
||||
case Hostname:
|
||||
return NewQVariantString(p.Hostname())
|
||||
case Password:
|
||||
return NewQVariantString(p.Password())
|
||||
case Security:
|
||||
return NewQVariantString(p.Security())
|
||||
case PortIMAP:
|
||||
return NewQVariantInt(p.PortIMAP())
|
||||
case PortSMTP:
|
||||
return NewQVariantInt(p.PortSMTP())
|
||||
case Aliases:
|
||||
return NewQVariantString(p.Aliases())
|
||||
case IsExpanded:
|
||||
return NewQVariantBool(p.IsExpanded())
|
||||
case IsCombinedAddressMode:
|
||||
return NewQVariantBool(p.IsCombinedAddressMode())
|
||||
default:
|
||||
return core.NewQVariant()
|
||||
}
|
||||
}
|
||||
|
||||
// rowCount returns the dimension of model: number of rows is equivalent to number of items in list.
|
||||
func (s *AccountsModel) rowCount(parent *core.QModelIndex) int {
|
||||
return len(s.Accounts())
|
||||
}
|
||||
|
||||
// columnCount returns the dimension of model: AccountsModel has only one column.
|
||||
func (s *AccountsModel) columnCount(parent *core.QModelIndex) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// roleNames returns the names of available item properties.
|
||||
func (s *AccountsModel) roleNames() map[int]*core.QByteArray {
|
||||
return s.Roles()
|
||||
}
|
||||
|
||||
// addAccount is connected to the addAccount slot.
|
||||
func (s *AccountsModel) addAccount(p *AccountInfo) {
|
||||
s.BeginInsertRows(core.NewQModelIndex(), len(s.Accounts()), len(s.Accounts()))
|
||||
s.SetAccounts(append(s.Accounts(), p))
|
||||
s.SetCount(len(s.Accounts()))
|
||||
s.EndInsertRows()
|
||||
}
|
||||
|
||||
// Method connected to toggleIsAvailable slot.
|
||||
func (s *AccountsModel) toggleIsAvailable(row int) {
|
||||
var p = s.Accounts()[row]
|
||||
currentStatus := p.Status()
|
||||
if currentStatus == "active" {
|
||||
p.SetStatus("disabled")
|
||||
} else if currentStatus == "disabled" {
|
||||
p.SetStatus("active")
|
||||
} else {
|
||||
p.SetStatus("error")
|
||||
}
|
||||
var pIndex = s.Index(row, 0, core.NewQModelIndex())
|
||||
s.DataChanged(pIndex, pIndex, []int{Status})
|
||||
}
|
||||
|
||||
// Method connected to removeAccount slot.
|
||||
func (s *AccountsModel) removeAccount(row int) {
|
||||
s.BeginRemoveRows(core.NewQModelIndex(), row, row)
|
||||
s.SetAccounts(append(s.Accounts()[:row], s.Accounts()[row+1:]...))
|
||||
s.SetCount(len(s.Accounts()))
|
||||
s.EndRemoveRows()
|
||||
}
|
||||
|
||||
// Remove all items in model.
|
||||
func (s *AccountsModel) Clear() {
|
||||
s.BeginRemoveRows(core.NewQModelIndex(), 0, len(s.Accounts()))
|
||||
s.SetAccounts(s.Accounts()[0:0])
|
||||
s.SetCount(len(s.Accounts()))
|
||||
s.EndRemoveRows()
|
||||
}
|
||||
|
||||
// Print the content of account models to console.
|
||||
func (s *AccountsModel) Dump() {
|
||||
fmt.Printf("Dimensions rows %d cols %d\n", s.rowCount(nil), s.columnCount(nil))
|
||||
for iAcc := 0; iAcc < s.rowCount(nil); iAcc++ {
|
||||
var p = s.Accounts()[iAcc]
|
||||
fmt.Printf(" %d. %s\n", iAcc, p.Account())
|
||||
}
|
||||
}
|
||||
@ -1,223 +0,0 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/keychain"
|
||||
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func (s *FrontendQt) loadAccounts() {
|
||||
accountMutex.Lock()
|
||||
defer accountMutex.Unlock()
|
||||
|
||||
// Update users.
|
||||
s.Accounts.Clear()
|
||||
|
||||
users := s.bridge.GetUsers()
|
||||
|
||||
// If there are no active accounts.
|
||||
if len(users) == 0 {
|
||||
log.Info("No active accounts")
|
||||
return
|
||||
}
|
||||
for _, user := range users {
|
||||
acc_info := NewAccountInfo(nil)
|
||||
username := user.Username()
|
||||
if username == "" {
|
||||
username = user.ID()
|
||||
}
|
||||
acc_info.SetAccount(username)
|
||||
|
||||
// Set status.
|
||||
if user.IsConnected() {
|
||||
acc_info.SetStatus("connected")
|
||||
} else {
|
||||
acc_info.SetStatus("disconnected")
|
||||
}
|
||||
|
||||
// Set login info.
|
||||
acc_info.SetUserID(user.ID())
|
||||
acc_info.SetHostname(bridge.Host)
|
||||
acc_info.SetPassword(user.GetBridgePassword())
|
||||
acc_info.SetPortIMAP(s.settings.GetInt(settings.IMAPPortKey))
|
||||
acc_info.SetPortSMTP(s.settings.GetInt(settings.SMTPPortKey))
|
||||
|
||||
// Set aliases.
|
||||
acc_info.SetAliases(strings.Join(user.GetAddresses(), ";"))
|
||||
acc_info.SetIsExpanded(user.ID() == s.userIDAdded)
|
||||
acc_info.SetIsCombinedAddressMode(user.IsCombinedAddressMode())
|
||||
|
||||
s.Accounts.addAccount(acc_info)
|
||||
}
|
||||
|
||||
// Updated can clear.
|
||||
s.userIDAdded = ""
|
||||
}
|
||||
|
||||
func (s *FrontendQt) clearCache() {
|
||||
defer s.Qml.ProcessFinished()
|
||||
|
||||
channel := s.bridge.GetUpdateChannel()
|
||||
if channel == updater.EarlyChannel {
|
||||
if _, err := s.bridge.SetUpdateChannel(updater.StableChannel); err != nil {
|
||||
s.Qml.NotifyManualUpdateError()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.bridge.ClearData(); err != nil {
|
||||
log.Error("While clearing cache: ", err)
|
||||
}
|
||||
// Clearing data removes everything (db, preferences, ...)
|
||||
// so everything has to be stopped and started again.
|
||||
s.restarter.SetToRestart()
|
||||
s.App.Quit()
|
||||
}
|
||||
|
||||
func (s *FrontendQt) clearKeychain() {
|
||||
defer s.Qml.ProcessFinished()
|
||||
for _, user := range s.bridge.GetUsers() {
|
||||
if err := s.bridge.DeleteUser(user.ID(), false); err != nil {
|
||||
log.Error("While deleting user: ", err)
|
||||
if err == keychain.ErrNoKeychain { // Probably not needed anymore.
|
||||
s.Qml.NotifyHasNoKeychain()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FrontendQt) logoutAccount(iAccount int) {
|
||||
defer s.Qml.ProcessFinished()
|
||||
userID := s.Accounts.get(iAccount).UserID()
|
||||
user, err := s.bridge.GetUser(userID)
|
||||
if err != nil {
|
||||
log.Error("While logging out ", userID, ": ", err)
|
||||
return
|
||||
}
|
||||
if err := user.Logout(); err != nil {
|
||||
log.Error("While logging out ", userID, ": ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FrontendQt) showLoginError(err error, scope string) bool {
|
||||
if err == nil {
|
||||
s.Qml.SetConnectionStatus(true) // If we are here connection is ok.
|
||||
return false
|
||||
}
|
||||
log.Warnf("%s: %v", scope, err)
|
||||
if err == pmapi.ErrNoConnection {
|
||||
s.Qml.SetConnectionStatus(false)
|
||||
s.SendNotification(TabAccount, s.Qml.CanNotReachAPI())
|
||||
s.Qml.ProcessFinished()
|
||||
return true
|
||||
}
|
||||
s.Qml.SetConnectionStatus(true) // If we are here connection is ok.
|
||||
if err == pmapi.ErrUpgradeApplication {
|
||||
return true
|
||||
}
|
||||
s.Qml.SetAddAccountWarning(err.Error(), -1)
|
||||
return true
|
||||
}
|
||||
|
||||
// login returns:
|
||||
// -1: when error occurred
|
||||
// 0: when no 2FA and no MBOX
|
||||
// 1: when has 2FA
|
||||
// 2: when has no 2FA but have MBOX
|
||||
func (s *FrontendQt) login(login, password string) int {
|
||||
var err error
|
||||
s.authClient, s.auth, err = s.bridge.Login(login, []byte(password))
|
||||
if s.showLoginError(err, "login") {
|
||||
return -1
|
||||
}
|
||||
if s.auth.HasTwoFactor() {
|
||||
return 1
|
||||
}
|
||||
if s.auth.HasMailboxPassword() {
|
||||
return 2
|
||||
}
|
||||
return 0 // No 2FA, no mailbox password.
|
||||
}
|
||||
|
||||
// auth2FA returns:
|
||||
// -1 : error (use SetAddAccountWarning to show message)
|
||||
// 0 : single password mode
|
||||
// 1 : two password mode
|
||||
func (s *FrontendQt) auth2FA(twoFacAuth string) int {
|
||||
var err error
|
||||
if s.auth == nil || s.authClient == nil {
|
||||
err = fmt.Errorf("missing authentication in auth2FA %p %p", s.auth, s.authClient)
|
||||
} else {
|
||||
err = s.authClient.Auth2FA(context.Background(), twoFacAuth)
|
||||
}
|
||||
|
||||
if s.showLoginError(err, "auth2FA") {
|
||||
return -1
|
||||
}
|
||||
|
||||
if s.auth.HasMailboxPassword() {
|
||||
return 1 // Ask for mailbox password.
|
||||
}
|
||||
return 0 // One password.
|
||||
}
|
||||
|
||||
// addAccount adds an account. It should close login modal ProcessFinished if ok.
|
||||
func (s *FrontendQt) addAccount(mailboxPassword string) int {
|
||||
if s.auth == nil || s.authClient == nil {
|
||||
log.Errorf("Missing authentication in addAccount %p %p", s.auth, s.authClient)
|
||||
s.Qml.SetAddAccountWarning(s.Qml.WrongMailboxPassword(), -2)
|
||||
return -1
|
||||
}
|
||||
|
||||
user, err := s.bridge.FinishLogin(s.authClient, s.auth, []byte(mailboxPassword))
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Login was unsuccessful")
|
||||
s.Qml.SetAddAccountWarning("Failure: "+err.Error(), -2)
|
||||
return -1
|
||||
}
|
||||
|
||||
s.userIDAdded = user.ID()
|
||||
s.eventListener.Emit(events.UserRefreshEvent, user.ID())
|
||||
s.Qml.ProcessFinished()
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *FrontendQt) deleteAccount(iAccount int, removePreferences bool) {
|
||||
defer s.Qml.ProcessFinished()
|
||||
userID := s.Accounts.get(iAccount).UserID()
|
||||
if err := s.bridge.DeleteUser(userID, removePreferences); err != nil {
|
||||
log.Warn("deleteUser: cannot remove user: ", err)
|
||||
if err == keychain.ErrNoKeychain {
|
||||
s.Qml.NotifyHasNoKeychain()
|
||||
return
|
||||
}
|
||||
s.SendNotification(TabSettings, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -1,728 +0,0 @@
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/ProtonMail/go-autostart"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
||||
"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/listener"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var log = logrus.WithField("pkg", "frontend-nogui") //nolint[gochecknoglobals]
|
||||
|
||||
type FrontendHeadless struct{}
|
||||
|
||||
func (s *FrontendHeadless) Loop() error {
|
||||
log.Info("Check status on localhost:8081")
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Bridge is running")
|
||||
})
|
||||
return http.ListenAndServe(":8081", nil)
|
||||
}
|
||||
|
||||
func (s *FrontendHeadless) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {
|
||||
// NOTE: Save the update somewhere so that it can be installed when user chooses "install now".
|
||||
}
|
||||
|
||||
func (s *FrontendHeadless) WaitUntilFrontendIsReady() {
|
||||
}
|
||||
|
||||
func (s *FrontendHeadless) SetVersion(update updater.VersionInfo) {
|
||||
}
|
||||
|
||||
func (s *FrontendHeadless) NotifySilentUpdateInstalled() {
|
||||
}
|
||||
|
||||
func (s *FrontendHeadless) NotifySilentUpdateError(err error) {
|
||||
}
|
||||
|
||||
func (s *FrontendHeadless) InstanceExistAlert() {}
|
||||
|
||||
func New(
|
||||
version,
|
||||
buildVersion, appName 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,
|
||||
) *FrontendHeadless {
|
||||
return &FrontendHeadless{}
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/therecipe/qt/core"
|
||||
)
|
||||
|
||||
// NewQByteArrayFromString is a wrapper for new QByteArray from string.
|
||||
func NewQByteArrayFromString(name string) *core.QByteArray {
|
||||
return core.NewQByteArray2(name, len(name))
|
||||
}
|
||||
|
||||
// NewQVariantString is a wrapper for QVariant alocator String.
|
||||
func NewQVariantString(data string) *core.QVariant {
|
||||
return core.NewQVariant1(data)
|
||||
}
|
||||
|
||||
// NewQVariantStringArray is a wrapper for QVariant alocator String Array.
|
||||
func NewQVariantStringArray(data []string) *core.QVariant {
|
||||
return core.NewQVariant1(data)
|
||||
}
|
||||
|
||||
// NewQVariantBool is a wrapper for QVariant alocator Bool.
|
||||
func NewQVariantBool(data bool) *core.QVariant {
|
||||
return core.NewQVariant1(data)
|
||||
}
|
||||
|
||||
// NewQVariantInt is a wrapper for QVariant alocator Int.
|
||||
func NewQVariantInt(data int) *core.QVariant {
|
||||
return core.NewQVariant1(data)
|
||||
}
|
||||
|
||||
// Pause is used to show GUI tests.
|
||||
func Pause() {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
// PauseLong is used to diplay GUI tests.
|
||||
func PauseLong() {
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
|
||||
// FIXME: Not working in test...
|
||||
func WaitForEnter() {
|
||||
log.Print("Press 'Enter' to continue...")
|
||||
bufio.NewReader(os.Stdin).ReadBytes('\n')
|
||||
}
|
||||
|
||||
func FcMatchSans() (family string) {
|
||||
family = "DejaVu Sans"
|
||||
fcMatch, err := exec.Command("fc-match", "-f", "'%{family}'", "sans-serif").Output()
|
||||
if err == nil {
|
||||
return string(fcMatch)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
// 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
|
||||
|
||||
const (
|
||||
TabAccount = 0
|
||||
TabSettings = 1
|
||||
TabHelp = 2
|
||||
TabQuit = 4
|
||||
TabUpdates = 100
|
||||
TabAddAccount = -1
|
||||
)
|
||||
|
||||
func (s *FrontendQt) SendNotification(tabIndex int, msg string) {
|
||||
s.Qml.NotifyBubble(tabIndex, msg)
|
||||
}
|
||||
@ -1,117 +0,0 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/therecipe/qt/core"
|
||||
"github.com/therecipe/qt/gui"
|
||||
"github.com/therecipe/qt/widgets"
|
||||
)
|
||||
|
||||
const (
|
||||
systrayNormal = ""
|
||||
systrayWarning = "-warn"
|
||||
systrayError = "-error"
|
||||
)
|
||||
|
||||
func min(a, b int) int {
|
||||
if b < a {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if b > a {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
var systray *widgets.QSystemTrayIcon
|
||||
|
||||
func SetupSystray(frontend *FrontendQt) {
|
||||
systray = widgets.NewQSystemTrayIcon(nil)
|
||||
NormalSystray()
|
||||
systray.SetToolTip(frontend.programName)
|
||||
systray.SetContextMenu(createMenu(frontend, systray))
|
||||
|
||||
if runtime.GOOS != "darwin" {
|
||||
systray.ConnectActivated(func(reason widgets.QSystemTrayIcon__ActivationReason) {
|
||||
switch reason {
|
||||
case widgets.QSystemTrayIcon__Trigger, widgets.QSystemTrayIcon__DoubleClick:
|
||||
frontend.Qml.ShowWindow()
|
||||
default:
|
||||
systray.ContextMenu().Exec2(menuPosition(systray), nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
systray.Show()
|
||||
}
|
||||
|
||||
func qsTr(msg string) string {
|
||||
return systray.Tr(msg, "Systray menu", -1)
|
||||
}
|
||||
|
||||
func createMenu(frontend *FrontendQt, systray *widgets.QSystemTrayIcon) *widgets.QMenu {
|
||||
menu := widgets.NewQMenu(nil)
|
||||
menu.AddAction(qsTr("Open")).ConnectTriggered(func(ok bool) { frontend.Qml.ShowWindow() })
|
||||
menu.AddAction(qsTr("Help")).ConnectTriggered(func(ok bool) { frontend.Qml.ShowHelp() })
|
||||
menu.AddAction(qsTr("Quit")).ConnectTriggered(func(ok bool) { frontend.Qml.ShowQuit() })
|
||||
return menu
|
||||
}
|
||||
|
||||
func menuPosition(systray *widgets.QSystemTrayIcon) *core.QPoint {
|
||||
var availRec = gui.QGuiApplication_PrimaryScreen().AvailableGeometry()
|
||||
var trayRec = systray.Geometry()
|
||||
var x = max(availRec.Left(), min(trayRec.X(), availRec.Right()-trayRec.Width()))
|
||||
var y = max(availRec.Top(), min(trayRec.Y(), availRec.Bottom()-trayRec.Height()))
|
||||
return core.NewQPoint2(x, y)
|
||||
}
|
||||
|
||||
func showSystray(systrayType string) {
|
||||
path := ":/ProtonUI/images/systray" + systrayType
|
||||
if runtime.GOOS == "darwin" {
|
||||
path += "-mono"
|
||||
}
|
||||
path += ".png"
|
||||
icon := gui.NewQIcon5(path)
|
||||
icon.SetIsMask(true)
|
||||
systray.SetIcon(icon)
|
||||
}
|
||||
|
||||
func NormalSystray() {
|
||||
showSystray(systrayNormal)
|
||||
}
|
||||
|
||||
func HighlightSystray() {
|
||||
showSystray(systrayWarning)
|
||||
}
|
||||
|
||||
func ErrorSystray() {
|
||||
showSystray(systrayError)
|
||||
}
|
||||
|
||||
func HideSystray() {
|
||||
systray.Hide()
|
||||
}
|
||||
@ -1,656 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1">
|
||||
<context>
|
||||
<name>AccountDelegate</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/AccountDelegate.qml" line="107"/>
|
||||
<source>Logout</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/AccountDelegate.qml" line="121"/>
|
||||
<source>Remove</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/AccountDelegate.qml" line="284"/>
|
||||
<source>connected</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/AccountDelegate.qml" line="289"/>
|
||||
<source>Log out</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/AccountDelegate.qml" line="316"/>
|
||||
<location filename="qml/BridgeUI/AccountDelegate.qml" line="346"/>
|
||||
<source>disconnected</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/AccountDelegate.qml" line="321"/>
|
||||
<location filename="qml/BridgeUI/AccountDelegate.qml" line="351"/>
|
||||
<source>Log in</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>AccountView</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/AccountView.qml" line="56"/>
|
||||
<source>No accounts added</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/AccountView.qml" line="67"/>
|
||||
<source>ACCOUNT</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/AccountView.qml" line="78"/>
|
||||
<source>STATUS</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/AccountView.qml" line="89"/>
|
||||
<source>ACTIONS</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>AddAccountBar</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/AddAccountBar.qml" line="20"/>
|
||||
<source>Add Account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/AddAccountBar.qml" line="33"/>
|
||||
<source>Help</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>BubbleMenu</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/BubbleMenu.qml" line="40"/>
|
||||
<source>About</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>BugReportWindow</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/BugReportWindow.qml" line="44"/>
|
||||
<source>Please write a brief description of the bug(s) you have encountered...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/BugReportWindow.qml" line="67"/>
|
||||
<source>Email client:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/BugReportWindow.qml" line="99"/>
|
||||
<source>Contact email:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/BugReportWindow.qml" line="135"/>
|
||||
<source>Bug reports are not end-to-end encrypted!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/BugReportWindow.qml" line="136"/>
|
||||
<source>Please do not send any sensitive information.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/BugReportWindow.qml" line="137"/>
|
||||
<source>Contact us at security@protonmail.com for critical security issues.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/BugReportWindow.qml" line="147"/>
|
||||
<source>Cancel</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/BugReportWindow.qml" line="158"/>
|
||||
<source>Send</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/BugReportWindow.qml" line="173"/>
|
||||
<source>Field required</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DialogAddUser</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogAddUser.qml" line="10"/>
|
||||
<source>Log in to your ProtonMail account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogAddUser.qml" line="53"/>
|
||||
<source>Username:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogAddUser.qml" line="65"/>
|
||||
<source>Cancel</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogAddUser.qml" line="71"/>
|
||||
<location filename="qml/BridgeUI/DialogAddUser.qml" line="146"/>
|
||||
<location filename="qml/BridgeUI/DialogAddUser.qml" line="196"/>
|
||||
<source>Next</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogAddUser.qml" line="91"/>
|
||||
<source>Sign Up for an Account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogAddUser.qml" line="111"/>
|
||||
<source>Password for %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogAddUser.qml" line="176"/>
|
||||
<source>Mailbox password for %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogAddUser.qml" line="127"/>
|
||||
<source>Two Factor Code</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogAddUser.qml" line="140"/>
|
||||
<location filename="qml/BridgeUI/DialogAddUser.qml" line="190"/>
|
||||
<source>Back</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogAddUser.qml" line="163"/>
|
||||
<source>Logging in</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogAddUser.qml" line="218"/>
|
||||
<source>Adding account, please wait ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogAddUser.qml" line="279"/>
|
||||
<source>Required field</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DialogPortChange</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogPortChange.qml" line="20"/>
|
||||
<source>IMAP port</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogPortChange.qml" line="29"/>
|
||||
<source>SMTP port</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogPortChange.qml" line="42"/>
|
||||
<source>Cancel</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogPortChange.qml" line="50"/>
|
||||
<source>Okay</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogPortChange.qml" line="68"/>
|
||||
<source>Settings will be applied after next start. You may need to re-configure your email client.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogPortChange.qml" line="70"/>
|
||||
<source>Bridge will now restart.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DialogYesNo</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="68"/>
|
||||
<source>Additionally delete all stored preferences and data</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="91"/>
|
||||
<source>No</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="99"/>
|
||||
<source>Yes</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="119"/>
|
||||
<source>Waiting...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="131"/>
|
||||
<source>Close Bridge</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="132"/>
|
||||
<source>Are you sure you want to close the Bridge?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="133"/>
|
||||
<source>Closing Bridge...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="141"/>
|
||||
<source>Logout</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="143"/>
|
||||
<source>Logging out...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="151"/>
|
||||
<source>Delete account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="152"/>
|
||||
<source>Are you sure you want to remove this account?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="153"/>
|
||||
<source>Deleting ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="161"/>
|
||||
<source>Clear keychain</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="162"/>
|
||||
<source>Are you sure you want to clear your keychain?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="163"/>
|
||||
<source>Clearing the keychain ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="171"/>
|
||||
<source>Clear cache</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="172"/>
|
||||
<source>Are you sure you want to clear your local cache?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="173"/>
|
||||
<source>Clearing the cache ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="183"/>
|
||||
<source>Checking for updates ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="194"/>
|
||||
<source>Turning on automatic start of Bridge...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="195"/>
|
||||
<source>Turning off automatic start of Bridge...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/DialogYesNo.qml" line="252"/>
|
||||
<source>You have the latest version!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Gui</name>
|
||||
<message>
|
||||
<location filename="qml/Gui.qml" line="95"/>
|
||||
<source>Account %1 has been disconnected. Please log in to continue to use the Bridge with this account.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="qml/Gui.qml" line="118"/>
|
||||
<source>Incorrect username or password.</source>
|
||||
<comment>notification</comment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="qml/Gui.qml" line="119"/>
|
||||
<source>Incorrect mailbox password.</source>
|
||||
<comment>notification</comment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="qml/Gui.qml" line="120"/>
|
||||
<source>Cannot contact server, please check your internet connection.</source>
|
||||
<comment>notification</comment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="qml/Gui.qml" line="121"/>
|
||||
<source>Credentials could not be removed.</source>
|
||||
<comment>notification</comment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="qml/Gui.qml" line="122"/>
|
||||
<source>Unable to submit bug report.</source>
|
||||
<comment>notification</comment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="qml/Gui.qml" line="123"/>
|
||||
<source>Bug report successfully sent.</source>
|
||||
<comment>notification</comment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>HelpView</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/HelpView.qml" line="24"/>
|
||||
<source>Logs</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/HelpView.qml" line="34"/>
|
||||
<source>Report Bug</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/HelpView.qml" line="44"/>
|
||||
<source>Setup Guide</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/HelpView.qml" line="54"/>
|
||||
<source>Check for Updates</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/HelpView.qml" line="83"/>
|
||||
<source>Credits</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>HelperPane</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/HelperPane.qml" line="37"/>
|
||||
<source>Skip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/HelperPane.qml" line="50"/>
|
||||
<source>Back</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/HelperPane.qml" line="60"/>
|
||||
<source>Next</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/HelperPane.qml" line="70"/>
|
||||
<source>You can add, delete, logout account. Expand account to see settings.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/HelperPane.qml" line="73"/>
|
||||
<source>This is settings windows: Clear cache, list logs, ... </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/HelperPane.qml" line="76"/>
|
||||
<source>Welcome to ProtonMail Bridge! Add account to start.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>InfoWindow</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InfoWindow.qml" line="38"/>
|
||||
<source>IMAP SETTINGS</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InfoWindow.qml" line="43"/>
|
||||
<location filename="qml/BridgeUI/InfoWindow.qml" line="56"/>
|
||||
<source>Hostname:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InfoWindow.qml" line="44"/>
|
||||
<location filename="qml/BridgeUI/InfoWindow.qml" line="57"/>
|
||||
<source>Port:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InfoWindow.qml" line="45"/>
|
||||
<location filename="qml/BridgeUI/InfoWindow.qml" line="58"/>
|
||||
<source>Username:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InfoWindow.qml" line="46"/>
|
||||
<location filename="qml/BridgeUI/InfoWindow.qml" line="59"/>
|
||||
<source>Password:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InfoWindow.qml" line="51"/>
|
||||
<source>SMTP SETTINGS</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InfoWindow.qml" line="74"/>
|
||||
<source>Configure Apple Mail</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>InformationBar</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InformationBar.qml" line="27"/>
|
||||
<source>An update is available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InformationBar.qml" line="27"/>
|
||||
<source>No Internet connection</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InformationBar.qml" line="41"/>
|
||||
<source>Install</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InformationBar.qml" line="41"/>
|
||||
<source>Retry</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InformationBar.qml" line="62"/>
|
||||
<source>Dismiss</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>InstanceExistsWindow</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InstanceExistsWindow.qml" line="16"/>
|
||||
<source>ProtonMail Bridge</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InstanceExistsWindow.qml" line="32"/>
|
||||
<source>Warning: Instance exists</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InstanceExistsWindow.qml" line="43"/>
|
||||
<source>An instance of the ProtonMail Bridge is already running.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InstanceExistsWindow.qml" line="44"/>
|
||||
<source>Please close the existing ProtonMail Bridge process before starting a new one.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InstanceExistsWindow.qml" line="45"/>
|
||||
<source>This program will close now.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/InstanceExistsWindow.qml" line="54"/>
|
||||
<source>Okay</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/MainWindow.qml" line="27"/>
|
||||
<source>ProtonMail Bridge</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/MainWindow.qml" line="44"/>
|
||||
<source>Accounts</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/MainWindow.qml" line="45"/>
|
||||
<source>Settings</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/MainWindow.qml" line="46"/>
|
||||
<source>Help</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/MainWindow.qml" line="117"/>
|
||||
<source>Click here to start</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/MainWindow.qml" line="241"/>
|
||||
<source>Credits</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/MainWindow.qml" line="249"/>
|
||||
<source>Information about version</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsView</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/SettingsView.qml" line="23"/>
|
||||
<source>Clear Cache</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/SettingsView.qml" line="26"/>
|
||||
<location filename="qml/BridgeUI/SettingsView.qml" line="44"/>
|
||||
<source>Clear</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/SettingsView.qml" line="41"/>
|
||||
<source>Clear Keychain</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/SettingsView.qml" line="59"/>
|
||||
<source>Automatically Start Bridge</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/SettingsView.qml" line="74"/>
|
||||
<source>Advanced settings</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/SettingsView.qml" line="89"/>
|
||||
<source>Change SMTP/IMAP Ports</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/SettingsView.qml" line="92"/>
|
||||
<source>Change</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>StatusFooter</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/StatusFooter.qml" line="31"/>
|
||||
<source>Quit</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TabLabels</name>
|
||||
<message>
|
||||
<location filename="qml/BridgeUI/TabLabels.qml" line="46"/>
|
||||
<source>Close Bridge</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
@ -1,215 +0,0 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/therecipe/qt/core"
|
||||
)
|
||||
|
||||
// GoQMLInterface between go and qml.
|
||||
//
|
||||
// Here we implement all the signals / methods.
|
||||
type GoQMLInterface struct {
|
||||
core.QObject
|
||||
|
||||
_ func() `constructor:"init"`
|
||||
|
||||
_ bool `property:"isAutoStart"`
|
||||
_ bool `property:"isAutoUpdate"`
|
||||
_ bool `property:"isEarlyAccess"`
|
||||
_ bool `property:"isProxyAllowed"`
|
||||
_ string `property:"currentAddress"`
|
||||
_ string `property:"goos"`
|
||||
_ string `property:"credits"`
|
||||
_ bool `property:"isShownOnStart"`
|
||||
_ bool `property:"isFirstStart"`
|
||||
_ bool `property:"isFreshVersion"`
|
||||
_ bool `property:"isConnectionOK"`
|
||||
_ bool `property:"isDefaultPort"`
|
||||
|
||||
_ string `property:"programTitle"`
|
||||
_ string `property:"fullversion"`
|
||||
_ string `property:"downloadLink"`
|
||||
|
||||
_ string `property:"updateState"`
|
||||
_ string `property:"updateVersion"`
|
||||
_ bool `property:"updateCanInstall"`
|
||||
_ string `property:"updateLandingPage"`
|
||||
_ string `property:"updateReleaseNotesLink"`
|
||||
_ func() `signal:"notifyManualUpdate"`
|
||||
_ func() `signal:"notifyManualUpdateRestartNeeded"`
|
||||
_ func() `signal:"notifyManualUpdateError"`
|
||||
_ func() `signal:"notifyForceUpdate"`
|
||||
_ func() `signal:"notifySilentUpdateRestartNeeded"`
|
||||
_ func() `signal:"notifySilentUpdateError"`
|
||||
_ func() `slot:"checkForUpdates"`
|
||||
_ func() `slot:"checkAndOpenReleaseNotes"`
|
||||
_ func() `signal:"openReleaseNotesExternally"`
|
||||
_ func() `slot:"startManualUpdate"`
|
||||
_ func() `slot:"guiIsReady"`
|
||||
|
||||
_ []string `property:"availableKeychain"`
|
||||
_ string `property:"selectedKeychain"`
|
||||
|
||||
// Translations.
|
||||
_ string `property:"wrongCredentials"`
|
||||
_ string `property:"wrongMailboxPassword"`
|
||||
_ string `property:"canNotReachAPI"`
|
||||
_ string `property:"credentialsNotRemoved"`
|
||||
_ string `property:"versionCheckFailed"`
|
||||
_ string `property:"failedAutostartPerm"`
|
||||
_ string `property:"failedAutostart"`
|
||||
_ string `property:"genericErrSeeLogs"`
|
||||
|
||||
_ float32 `property:"progress"`
|
||||
_ string `property:"progressDescription"`
|
||||
|
||||
_ func(isAvailable bool) `signal:"setConnectionStatus"`
|
||||
|
||||
_ func() `slot:"setToRestart"`
|
||||
|
||||
_ func(systX, systY, systW, systH int) `signal:"toggleMainWin"`
|
||||
|
||||
_ func() `signal:"processFinished"`
|
||||
_ func() `signal:"openManual"`
|
||||
_ func(showMessage bool) `signal:"runCheckVersion"`
|
||||
_ func() `signal:"toggleMainWin"`
|
||||
|
||||
_ func() `signal:"showWindow"`
|
||||
_ func() `signal:"showHelp"`
|
||||
_ func() `signal:"showQuit"`
|
||||
|
||||
_ func() `slot:"toggleAutoStart"`
|
||||
_ func() `slot:"toggleAutoUpdate"`
|
||||
_ func() `slot:"toggleEarlyAccess"`
|
||||
_ func() `slot:"toggleAllowProxy"`
|
||||
_ func() `slot:"loadAccounts"`
|
||||
_ func() `slot:"openLogs"`
|
||||
_ func() `slot:"clearCache"`
|
||||
_ func() `slot:"clearKeychain"`
|
||||
_ func() `slot:"highlightSystray"`
|
||||
_ func() `slot:"errorSystray"`
|
||||
_ func() `slot:"normalSystray"`
|
||||
|
||||
_ func() `slot:"openLicenseFile"`
|
||||
_ func() `slot:"getLocalVersionInfo"`
|
||||
_ func(showMessage bool) `slot:"isNewVersionAvailable"`
|
||||
_ func() string `slot:"getBackendVersion"`
|
||||
_ func() string `slot:"getIMAPPort"`
|
||||
_ func() string `slot:"getSMTPPort"`
|
||||
_ func() string `slot:"getLastMailClient"`
|
||||
_ func(portStr string) int `slot:"isPortOpen"`
|
||||
_ func(imapPort, smtpPort string, useSTARTTLSforSMTP bool) `slot:"setPortsAndSecurity"`
|
||||
_ func() bool `slot:"isSMTPSTARTTLS"`
|
||||
|
||||
_ func(description, client, address string) bool `slot:"sendBug"`
|
||||
|
||||
_ func(tabIndex int, message string) `signal:"notifyBubble"`
|
||||
_ func(tabIndex int, message string) `signal:"silentBubble"`
|
||||
_ func() `signal:"bubbleClosed"`
|
||||
|
||||
_ func(iAccount int, removePreferences bool) `slot:"deleteAccount"`
|
||||
_ func(iAccount int) `slot:"logoutAccount"`
|
||||
_ func(iAccount int, iAddress int) `slot:"configureAppleMail"`
|
||||
_ func(iAccount int) `signal:"switchAddressMode"`
|
||||
|
||||
_ func(login, password string) int `slot:"login"`
|
||||
_ func(twoFacAuth string) int `slot:"auth2FA"`
|
||||
_ func(mailboxPassword string) int `slot:"addAccount"`
|
||||
_ func(message string, changeIndex int) `signal:"setAddAccountWarning"`
|
||||
|
||||
_ func() `signal:"notifyVersionIsTheLatest"`
|
||||
_ func() `signal:"notifyKeychainRebuild"`
|
||||
_ func() `signal:"notifyHasNoKeychain"`
|
||||
_ func(accname string) `signal:"notifyLogout"`
|
||||
_ func(accname string) `signal:"notifyAddressChanged"`
|
||||
_ func(accname string) `signal:"notifyAddressChangedLogout"`
|
||||
_ func(busyPortIMAP, busyPortSMTP bool) `signal:"notifyPortIssue"`
|
||||
_ func(code string) `signal:"failedAutostartCode"`
|
||||
|
||||
_ bool `property:"isReportingOutgoingNoEnc"`
|
||||
_ func() `slot:"toggleIsReportingOutgoingNoEnc"`
|
||||
_ func(messageID string, shouldSend bool) `slot:"shouldSendAnswer"`
|
||||
_ func(messageID, subject string) `signal:"showOutgoingNoEncPopup"`
|
||||
_ func(x, y float32) `signal:"setOutgoingNoEncPopupCoord"`
|
||||
_ func(x, y float32) `slot:"saveOutgoingNoEncPopupCoord"`
|
||||
_ func(recipient string) `signal:"showNoActiveKeyForRecipient"`
|
||||
_ func() `signal:"showCertIssue"`
|
||||
|
||||
_ func(hasError bool) `signal:"updateFinished"`
|
||||
}
|
||||
|
||||
// init is basically the constructor.
|
||||
func (s *GoQMLInterface) init() {}
|
||||
|
||||
// SetFrontend connects all slots and signals from Go to QML.
|
||||
func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
|
||||
s.ConnectToggleAutoStart(f.toggleAutoStart)
|
||||
s.ConnectToggleEarlyAccess(f.toggleEarlyAccess)
|
||||
s.ConnectToggleAutoUpdate(f.toggleAutoUpdate)
|
||||
s.ConnectToggleAllowProxy(f.toggleAllowProxy)
|
||||
s.ConnectLoadAccounts(f.loadAccounts)
|
||||
s.ConnectOpenLogs(f.openLogs)
|
||||
s.ConnectClearCache(f.clearCache)
|
||||
s.ConnectClearKeychain(f.clearKeychain)
|
||||
s.ConnectOpenLicenseFile(f.openLicenseFile)
|
||||
s.ConnectStartManualUpdate(f.startManualUpdate)
|
||||
s.ConnectGuiIsReady(f.setGUIIsReady)
|
||||
s.ConnectGetLocalVersionInfo(f.getLocalVersionInfo)
|
||||
s.ConnectCheckForUpdates(f.checkForUpdates)
|
||||
s.ConnectGetIMAPPort(f.getIMAPPort)
|
||||
s.ConnectGetSMTPPort(f.getSMTPPort)
|
||||
s.ConnectGetLastMailClient(f.getLastMailClient)
|
||||
s.ConnectIsPortOpen(f.isPortOpen)
|
||||
s.ConnectIsSMTPSTARTTLS(f.isSMTPSTARTTLS)
|
||||
|
||||
s.ConnectSendBug(f.sendBug)
|
||||
|
||||
s.ConnectDeleteAccount(f.deleteAccount)
|
||||
s.ConnectLogoutAccount(f.logoutAccount)
|
||||
s.ConnectConfigureAppleMail(f.configureAppleMail)
|
||||
s.ConnectLogin(f.login)
|
||||
s.ConnectAuth2FA(f.auth2FA)
|
||||
s.ConnectAddAccount(f.addAccount)
|
||||
s.ConnectSetPortsAndSecurity(f.setPortsAndSecurity)
|
||||
|
||||
s.ConnectHighlightSystray(HighlightSystray)
|
||||
s.ConnectErrorSystray(ErrorSystray)
|
||||
s.ConnectNormalSystray(NormalSystray)
|
||||
s.ConnectSwitchAddressMode(f.switchAddressModeUser)
|
||||
|
||||
s.SetGoos(runtime.GOOS)
|
||||
s.SetProgramTitle(f.programName)
|
||||
|
||||
s.ConnectGetBackendVersion(func() string {
|
||||
return f.programVer
|
||||
})
|
||||
|
||||
s.ConnectSetToRestart(f.restarter.SetToRestart)
|
||||
|
||||
s.ConnectToggleIsReportingOutgoingNoEnc(f.toggleIsReportingOutgoingNoEnc)
|
||||
s.ConnectShouldSendAnswer(f.shouldSendAnswer)
|
||||
s.ConnectSaveOutgoingNoEncPopupCoord(f.saveOutgoingNoEncPopupCoord)
|
||||
|
||||
s.ConnectSetSelectedKeychain(f.setKeychain)
|
||||
s.ConnectSelectedKeychain(f.getKeychain)
|
||||
}
|
||||
Reference in New Issue
Block a user