forked from Silverfish/proton-bridge
GODT-22: Frontend-backend
- GODT-1246 Implement settings view.
- GODT-1257 GODT-1246: Account and Help view
- GODT-1298: Minimal working build (panics)
- GODT-1298: loading QML (needs Popup window)
- GODT-1298: WARN: Adding PopupWindow not possible!
In therecipe qt the `quickwidgets` classes are within `quick` module, but
forgot to add library and include paths into cgo flags. Therefore
compilation fails and it would be hard to patch therecipe in order to
fix it.
I am not sure if rewrite PopupWindow into go would make any difference,
therefore I decided to use normal QML Window without borders.
- GODT-1298: Rework status window, add backend props, slots and signals.
- GODT-1298: Users
- GODT-1298: Login
- GODT-1298: WIP Help and bug report
- GODT-1178: MacOS dock icon control
- GODT-1298: Help, bug report, update and events
- GODT-1298: Apple Mail config and Settings (without cache on disk)
This commit is contained in:
24
internal/frontend/qt/dockicon/DockIcon.h
Normal file
24
internal/frontend/qt/dockicon/DockIcon.h
Normal file
@ -0,0 +1,24 @@
|
||||
// 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 darwin
|
||||
// +build build_qt
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
void SetDockIconVisibleState(bool visible);
|
||||
bool GetDockIconVisibleState();
|
||||
42
internal/frontend/qt/dockicon/DockIcon.m
Normal file
42
internal/frontend/qt/dockicon/DockIcon.m
Normal file
@ -0,0 +1,42 @@
|
||||
// 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 darwin
|
||||
// +build build_qt
|
||||
|
||||
#include "DockIcon.h"
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
void SetDockIconVisibleState(bool visible) {
|
||||
if (visible) {
|
||||
[NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
|
||||
return;
|
||||
} else {
|
||||
[NSApp setActivationPolicy: NSApplicationActivationPolicyAccessory];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool GetDockIconVisibleState() {
|
||||
switch ([NSApp activationPolicy]) {
|
||||
case NSApplicationActivationPolicyAccessory:
|
||||
case NSApplicationActivationPolicyProhibited:
|
||||
return false;
|
||||
case NSApplicationActivationPolicyRegular:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
33
internal/frontend/qt/dockicon/dockicon_darwin.go
Normal file
33
internal/frontend/qt/dockicon/dockicon_darwin.go
Normal file
@ -0,0 +1,33 @@
|
||||
// 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 darwin
|
||||
// +build build_qt
|
||||
|
||||
package dockicon
|
||||
|
||||
// #cgo CFLAGS: -x objective-c
|
||||
// #cgo LDFLAGS: -framework Cocoa
|
||||
// #include "DockIcon.h"
|
||||
import "C"
|
||||
|
||||
func SetDockIconVisibleState(visible bool) {
|
||||
C.SetDockIconVisibleState(C.bool(visible))
|
||||
}
|
||||
func GetDockIconVisibleState() bool {
|
||||
return bool(C.GetDockIconVisibleState())
|
||||
}
|
||||
26
internal/frontend/qt/dockicon/dockicon_default.go
Normal file
26
internal/frontend/qt/dockicon/dockicon_default.go
Normal file
@ -0,0 +1,26 @@
|
||||
// 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 !darwin
|
||||
// +build build_qt
|
||||
|
||||
package dockicon
|
||||
|
||||
func SetDockIconVisibleState(visible bool) {}
|
||||
func GetDockIconVisibleState() bool {
|
||||
return true
|
||||
}
|
||||
146
internal/frontend/qt/frontend.go
Normal file
146
internal/frontend/qt/frontend.go
Normal file
@ -0,0 +1,146 @@
|
||||
// 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 provides communication between Qt/QML frontend and Go backend
|
||||
package qt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"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/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/therecipe/qt/qml"
|
||||
"github.com/therecipe/qt/widgets"
|
||||
)
|
||||
|
||||
type FrontendQt struct {
|
||||
programName, programVersion string
|
||||
|
||||
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
|
||||
|
||||
authClient pmapi.Client
|
||||
auth *pmapi.Auth
|
||||
password []byte
|
||||
|
||||
newVersionInfo updater.VersionInfo
|
||||
|
||||
log *logrus.Entry
|
||||
usersMtx sync.Mutex
|
||||
|
||||
app *widgets.QApplication
|
||||
engine *qml.QQmlApplicationEngine
|
||||
qml *QMLBackend
|
||||
}
|
||||
|
||||
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,
|
||||
_ types.NoEncConfirmator,
|
||||
autostart *autostart.App,
|
||||
restarter types.Restarter,
|
||||
) *FrontendQt {
|
||||
return &FrontendQt{
|
||||
programName: "Proton Mail Bridge",
|
||||
programVersion: version,
|
||||
log: logrus.WithField("pkg", "frontend/qt"),
|
||||
|
||||
panicHandler: panicHandler,
|
||||
locations: locations,
|
||||
settings: settings,
|
||||
eventListener: eventListener,
|
||||
updater: updater,
|
||||
userAgent: userAgent,
|
||||
bridge: bridge,
|
||||
autostart: autostart,
|
||||
restarter: restarter,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FrontendQt) Loop() error {
|
||||
err := f.initiateQtApplication()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer f.panicHandler.HandlePanic()
|
||||
f.watchEvents()
|
||||
}()
|
||||
|
||||
if ret := f.app.Exec(); ret != 0 {
|
||||
err := fmt.Errorf("Event loop ended with return value: %v", ret)
|
||||
f.log.Warn("App exec", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FrontendQt) NotifyManualUpdate(version updater.VersionInfo, canInstall bool) {
|
||||
if canInstall {
|
||||
f.qml.UpdateManualReady(version.Version.String())
|
||||
} else {
|
||||
f.qml.UpdateManualError()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FrontendQt) SetVersion(version updater.VersionInfo) {
|
||||
f.newVersionInfo = version
|
||||
f.qml.SetReleaseNotesLink(version.ReleaseNotesPage)
|
||||
f.qml.SetLandingPageLink(version.LandingPage)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) NotifySilentUpdateInstalled() {
|
||||
f.qml.UpdateSilentRestartNeeded()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) NotifySilentUpdateError(err error) {
|
||||
f.log.WithError(err).Warn("Update failed, asking for manual.")
|
||||
f.qml.UpdateManualError()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) WaitUntilFrontendIsReady() {
|
||||
// TODO: Implement
|
||||
}
|
||||
85
internal/frontend/qt/frontend_events.go
Normal file
85
internal/frontend/qt/frontend_events.go
Normal file
@ -0,0 +1,85 @@
|
||||
// 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 provides communication between Qt/QML frontend and Go backend
|
||||
package qt
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
)
|
||||
|
||||
func (f *FrontendQt) watchEvents() {
|
||||
f.WaitUntilFrontendIsReady()
|
||||
|
||||
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
|
||||
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
|
||||
noActiveKeyForRecipientCh := f.eventListener.ProvideChannel(events.NoActiveKeyForRecipientEvent)
|
||||
internetOffCh := f.eventListener.ProvideChannel(events.InternetOffEvent)
|
||||
internetOnCh := f.eventListener.ProvideChannel(events.InternetOnEvent)
|
||||
secondInstanceCh := f.eventListener.ProvideChannel(events.SecondInstanceEvent)
|
||||
restartBridgeCh := f.eventListener.ProvideChannel(events.RestartBridgeEvent)
|
||||
addressChangedCh := f.eventListener.ProvideChannel(events.AddressChangedEvent)
|
||||
addressChangedLogoutCh := f.eventListener.ProvideChannel(events.AddressChangedLogoutEvent)
|
||||
logoutCh := f.eventListener.ProvideChannel(events.LogoutEvent)
|
||||
updateApplicationCh := f.eventListener.ProvideChannel(events.UpgradeApplicationEvent)
|
||||
userChangedCh := f.eventListener.ProvideChannel(events.UserRefreshEvent)
|
||||
certIssue := f.eventListener.ProvideChannel(events.TLSCertIssue)
|
||||
|
||||
for {
|
||||
select {
|
||||
case errorDetails := <-errorCh:
|
||||
if strings.Contains(errorDetails, "IMAP failed") {
|
||||
f.qml.PortIssueIMAP()
|
||||
}
|
||||
if strings.Contains(errorDetails, "SMTP failed") {
|
||||
f.qml.PortIssueSMTP()
|
||||
}
|
||||
case <-credentialsErrorCh:
|
||||
f.qml.NotifyHasNoKeychain()
|
||||
case email := <-noActiveKeyForRecipientCh:
|
||||
f.qml.NoActiveKeyForRecipient(email)
|
||||
case <-internetOffCh:
|
||||
f.qml.InternetOff()
|
||||
case <-internetOnCh:
|
||||
f.qml.InternetOn()
|
||||
case <-secondInstanceCh:
|
||||
f.qml.ShowMainWindow()
|
||||
case <-restartBridgeCh:
|
||||
f.restart()
|
||||
case address := <-addressChangedCh:
|
||||
f.qml.AddressChanged(address)
|
||||
case address := <-addressChangedLogoutCh:
|
||||
f.qml.AddressChangedLogout(address)
|
||||
case userID := <-logoutCh:
|
||||
user, err := f.bridge.GetUser(userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
f.qml.UserDisconnected(user.Username())
|
||||
case <-updateApplicationCh:
|
||||
f.updateForce()
|
||||
case userID := <-userChangedCh:
|
||||
f.userChanged(userID)
|
||||
case <-certIssue:
|
||||
f.qml.ApiCertIssue()
|
||||
}
|
||||
}
|
||||
}
|
||||
45
internal/frontend/qt/frontend_help.go
Normal file
45
internal/frontend/qt/frontend_help.go
Normal file
@ -0,0 +1,45 @@
|
||||
// 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
|
||||
|
||||
func (f *FrontendQt) setVersion() {
|
||||
f.qml.SetVersion(f.programVersion)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setLogsPath() {
|
||||
path, err := f.locations.ProvideLogsPath()
|
||||
if err != nil {
|
||||
f.log.WithError(err).Error("Cannot update path folder")
|
||||
return
|
||||
}
|
||||
f.qml.SetLogsPath(path)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setLicensePath() {
|
||||
f.qml.SetLicensePath(f.locations.GetLicenseFilePath())
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setCurrentEmailClient() {
|
||||
f.qml.SetCurrentEmailClient(f.userAgent.String())
|
||||
}
|
||||
|
||||
func (f *FrontendQt) reportBug(description, address, emailClient string, includeLogs bool) {
|
||||
//TODO
|
||||
}
|
||||
71
internal/frontend/qt/frontend_init.go
Normal file
71
internal/frontend/qt/frontend_init.go
Normal file
@ -0,0 +1,71 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
qmlLog "github.com/ProtonMail/proton-bridge/internal/frontend/qt/log"
|
||||
"github.com/therecipe/qt/core"
|
||||
"github.com/therecipe/qt/qml"
|
||||
"github.com/therecipe/qt/quickcontrols2"
|
||||
"github.com/therecipe/qt/widgets"
|
||||
"os"
|
||||
)
|
||||
|
||||
func (f *FrontendQt) initiateQtApplication() error {
|
||||
qmlLog.InstallMessageHandler()
|
||||
|
||||
f.app = widgets.NewQApplication(len(os.Args), os.Args)
|
||||
|
||||
core.QCoreApplication_SetApplicationName(f.programName)
|
||||
core.QCoreApplication_SetApplicationVersion(f.programVersion)
|
||||
|
||||
// High DPI scaling for windows.
|
||||
core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, false)
|
||||
// Software OpenGL: to avoid dedicated GPU.
|
||||
core.QCoreApplication_SetAttribute(core.Qt__AA_UseSoftwareOpenGL, true)
|
||||
|
||||
// Bridge runs background, no window is needed to be opened.
|
||||
f.app.SetQuitOnLastWindowClosed(false)
|
||||
|
||||
// QML Engine and path
|
||||
f.engine = qml.NewQQmlApplicationEngine(f.app)
|
||||
|
||||
f.qml = NewQMLBackend(nil)
|
||||
f.qml.setup(f)
|
||||
f.engine.RootContext().SetContextProperty("go", f.qml)
|
||||
|
||||
f.engine.AddImportPath("qrc:/qml/")
|
||||
f.engine.AddPluginPath("qrc:/qml/")
|
||||
|
||||
// Add style: if colorScheme / style is forgotten we should fallback to
|
||||
// default style and should be Proton
|
||||
quickcontrols2.QQuickStyle_AddStylePath("qrc:/qml/")
|
||||
quickcontrols2.QQuickStyle_SetStyle("Proton")
|
||||
|
||||
f.engine.Load(core.NewQUrl3("qrc:/qml/Bridge.qml", 0))
|
||||
|
||||
// Check QML is loaded properly.
|
||||
if len(f.engine.RootObjects()) == 0 {
|
||||
return errors.New("QML not loaded properly")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
83
internal/frontend/qt/frontend_nogui.go
Normal file
83
internal/frontend/qt/frontend_nogui.go
Normal file
@ -0,0 +1,83 @@
|
||||
// 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 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,
|
||||
) *FrontendHeadless {
|
||||
return &FrontendHeadless{}
|
||||
}
|
||||
|
||||
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() {}
|
||||
151
internal/frontend/qt/frontend_settings.go
Normal file
151
internal/frontend/qt/frontend_settings.go
Normal file
@ -0,0 +1,151 @@
|
||||
// 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 (
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/clientconfig"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/keychain"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/ports"
|
||||
)
|
||||
|
||||
func (f *FrontendQt) setIsDiskCacheEnabled() {
|
||||
//TODO
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setDiskCachePath() {
|
||||
//TODO
|
||||
}
|
||||
|
||||
func (f *FrontendQt) changeLocalCache(enableDiskCache bool, diskCachePath string) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setIsAutostartOn() {
|
||||
f.qml.SetIsAutostartOn(f.autostart.IsEnabled())
|
||||
}
|
||||
|
||||
func (f *FrontendQt) toggleAutostart(makeItEnabled bool) {
|
||||
defer f.qml.ToggleAutostartFinished()
|
||||
if makeItEnabled == f.autostart.IsEnabled() {
|
||||
f.setIsAutostartOn()
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
if makeItEnabled {
|
||||
err = f.autostart.Enable()
|
||||
} else {
|
||||
err = f.autostart.Disable()
|
||||
}
|
||||
f.setIsAutostartOn()
|
||||
|
||||
if err != nil {
|
||||
f.log.
|
||||
WithField("makeItEnabled", makeItEnabled).
|
||||
WithField("isEnabled", f.qml.IsAutostartOn()).
|
||||
WithError(err).
|
||||
Error("Autostart change failed")
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FrontendQt) toggleDoH(makeItEnabled bool) {
|
||||
if f.settings.GetBool(settings.AllowProxyKey) == makeItEnabled {
|
||||
f.qml.SetIsDoHEnabled(makeItEnabled)
|
||||
return
|
||||
}
|
||||
f.settings.SetBool(settings.AllowProxyKey, makeItEnabled)
|
||||
f.restart()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) toggleUseSSLforSMTP(makeItEnabled bool) {
|
||||
if f.settings.GetBool(settings.SMTPSSLKey) == makeItEnabled {
|
||||
f.qml.SetUseSSLforSMTP(makeItEnabled)
|
||||
return
|
||||
}
|
||||
f.settings.SetBool(settings.SMTPPortKey, makeItEnabled)
|
||||
f.restart()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) changePorts(imapPort, smtpPort int) {
|
||||
f.settings.SetInt(settings.IMAPPortKey, imapPort)
|
||||
f.settings.SetInt(settings.SMTPPortKey, smtpPort)
|
||||
f.restart()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) isPortFree(port int) bool {
|
||||
return ports.IsPortFree(port)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) configureAppleMail(userID, address string) {
|
||||
user, err := f.bridge.GetUser(userID)
|
||||
if err != nil {
|
||||
f.log.WithField("userID", userID).Error("Cannot configure AppleMail for user")
|
||||
return
|
||||
}
|
||||
|
||||
needRestart, err := clientconfig.ConfigureAppleMail(user, address, f.settings)
|
||||
if err != nil {
|
||||
f.log.WithError(err).Error("Apple Mail config failed")
|
||||
}
|
||||
|
||||
if needRestart {
|
||||
// There is delay needed for external window to open
|
||||
time.Sleep(2 * time.Second)
|
||||
f.restart()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FrontendQt) triggerReset() {
|
||||
defer f.qml.ResetFinished()
|
||||
//TODO
|
||||
f.restart()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setKeychain() {
|
||||
availableKeychain := []string{}
|
||||
for chain := range keychain.Helpers {
|
||||
availableKeychain = append(availableKeychain, chain)
|
||||
}
|
||||
f.qml.SetAvailableKeychain(availableKeychain)
|
||||
f.qml.SetSelectedKeychain(f.bridge.GetKeychainApp())
|
||||
}
|
||||
|
||||
func (f *FrontendQt) selectKeychain(wantKeychain string) {
|
||||
if f.bridge.GetKeychainApp() == wantKeychain {
|
||||
return
|
||||
}
|
||||
|
||||
f.bridge.SetKeychainApp(wantKeychain)
|
||||
f.restart()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) restart() {
|
||||
f.log.Info("Restarting bridge")
|
||||
f.restarter.SetToRestart()
|
||||
f.app.Exit(0)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) quit() {
|
||||
f.log.Warn("Your wish is my command.. I quit!")
|
||||
f.app.Exit(0)
|
||||
}
|
||||
130
internal/frontend/qt/frontend_updates.go
Normal file
130
internal/frontend/qt/frontend_updates.go
Normal file
@ -0,0 +1,130 @@
|
||||
// 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 (
|
||||
"sync"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
)
|
||||
|
||||
var checkingUpdates = sync.Mutex{}
|
||||
|
||||
func (f *FrontendQt) checkUpdates() error {
|
||||
version, err := f.updater.Check()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.SetVersion(version)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FrontendQt) checkUpdatesAndNotify(isRequestFromUser bool) {
|
||||
checkingUpdates.Lock()
|
||||
defer checkingUpdates.Lock()
|
||||
defer f.qml.CheckUpdatesFinished()
|
||||
|
||||
if err := f.checkUpdates(); err != nil {
|
||||
f.log.WithError(err).Error("An error occurred while checking updates")
|
||||
if isRequestFromUser {
|
||||
f.qml.UpdateManualError()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !f.updater.IsUpdateApplicable(f.newVersionInfo) {
|
||||
f.log.Debug("No need to update")
|
||||
if isRequestFromUser {
|
||||
f.qml.UpdateIsLatestVersion()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !f.updater.CanInstall(f.newVersionInfo) {
|
||||
f.log.Debug("A manual update is required")
|
||||
f.qml.UpdateManualReady(f.newVersionInfo.Version.String())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FrontendQt) updateForce() {
|
||||
checkingUpdates.Lock()
|
||||
defer checkingUpdates.Lock()
|
||||
|
||||
version := ""
|
||||
if err := f.checkUpdates(); err == nil {
|
||||
version = f.newVersionInfo.Version.String()
|
||||
}
|
||||
|
||||
f.qml.UpdateForce(version)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setIsAutomaticUpdateOn() {
|
||||
f.qml.SetIsAutomaticUpdateOn(f.settings.GetBool(settings.AutoUpdateKey))
|
||||
}
|
||||
|
||||
func (f *FrontendQt) toggleAutomaticUpdate(makeItEnabled bool) {
|
||||
f.qml.SetIsAutomaticUpdateOn(makeItEnabled)
|
||||
isEnabled := f.settings.GetBool(settings.AutoUpdateKey)
|
||||
if makeItEnabled == isEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
f.settings.SetBool(settings.AutoUpdateKey, makeItEnabled)
|
||||
|
||||
f.checkUpdatesAndNotify(false)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) setIsBetaEnabled() {
|
||||
channel := f.bridge.GetUpdateChannel()
|
||||
f.qml.SetIsBetaEnabled(channel == updater.EarlyChannel)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) toggleBeta(makeItEnabled bool) {
|
||||
channel := f.bridge.GetUpdateChannel()
|
||||
|
||||
if makeItEnabled == (channel == updater.EarlyChannel) {
|
||||
f.qml.SetIsBetaEnabled(makeItEnabled)
|
||||
return
|
||||
}
|
||||
|
||||
channel = updater.StableChannel
|
||||
if makeItEnabled {
|
||||
channel = updater.EarlyChannel
|
||||
}
|
||||
|
||||
needRestart, err := f.bridge.SetUpdateChannel(channel)
|
||||
f.setIsBetaEnabled()
|
||||
|
||||
if err != nil {
|
||||
f.log.WithError(err).Warn("Switching udpate channel failed.")
|
||||
f.qml.UpdateManualError()
|
||||
return
|
||||
}
|
||||
|
||||
if needRestart {
|
||||
f.restart()
|
||||
return
|
||||
}
|
||||
|
||||
f.checkUpdatesAndNotify(false)
|
||||
}
|
||||
224
internal/frontend/qt/frontend_users.go
Normal file
224
internal/frontend/qt/frontend_users.go
Normal file
@ -0,0 +1,224 @@
|
||||
// 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"
|
||||
"encoding/base64"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func (f *FrontendQt) loadUsers() {
|
||||
f.usersMtx.Lock()
|
||||
defer f.usersMtx.Unlock()
|
||||
|
||||
f.qml.Users().clear()
|
||||
|
||||
for _, user := range f.bridge.GetUsers() {
|
||||
f.qml.Users().addUser(newQMLUserFromBacked(f, user))
|
||||
}
|
||||
|
||||
// If there are no active accounts.
|
||||
if f.qml.Users().Count() == 0 {
|
||||
f.log.Info("No active accounts")
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FrontendQt) userChanged(userID string) {
|
||||
f.usersMtx.Lock()
|
||||
defer f.usersMtx.Unlock()
|
||||
|
||||
fUsers := f.qml.Users()
|
||||
|
||||
index := fUsers.indexByID(userID)
|
||||
user, err := f.bridge.GetUser(userID)
|
||||
|
||||
if user == nil || err != nil {
|
||||
if index >= 0 { // delete existing user
|
||||
fUsers.removeUser(index)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if index < 0 { // add non-existing user
|
||||
fUsers.addUser(newQMLUserFromBacked(f, user))
|
||||
return
|
||||
}
|
||||
|
||||
// update exiting user
|
||||
fUsers.users[index].update(user)
|
||||
}
|
||||
|
||||
func newQMLUserFromBacked(f *FrontendQt, user types.User) *QMLUser {
|
||||
qu := NewQMLUser(nil)
|
||||
qu.ID = user.ID()
|
||||
|
||||
qu.update(user)
|
||||
|
||||
qu.ConnectToggleSplitMode(func(activateSplitMode bool) {
|
||||
go func() {
|
||||
defer qu.ToggleSplitModeFinished()
|
||||
if activateSplitMode == user.IsCombinedAddressMode() {
|
||||
user.SwitchAddressMode()
|
||||
}
|
||||
qu.SetSplitMode(!user.IsCombinedAddressMode())
|
||||
}()
|
||||
})
|
||||
|
||||
qu.ConnectLogout(func() {
|
||||
qu.SetLoggedIn(false)
|
||||
go user.Logout()
|
||||
})
|
||||
|
||||
qu.ConnectConfigureAppleMail(func(address string) {
|
||||
go f.configureAppleMail(qu.ID, address)
|
||||
})
|
||||
|
||||
return qu
|
||||
}
|
||||
|
||||
func (f *FrontendQt) login(username, password string) {
|
||||
var err error
|
||||
f.password, err = base64.StdEncoding.DecodeString(password)
|
||||
if err != nil {
|
||||
f.log.WithError(err).Error("Cannot decode password")
|
||||
f.qml.LoginUsernamePasswordError("Cannot decode password")
|
||||
f.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
f.authClient, f.auth, err = f.bridge.Login(username, f.password)
|
||||
if err != nil {
|
||||
f.qml.LoginUsernamePasswordError(err.Error())
|
||||
f.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
if f.auth.HasTwoFactor() {
|
||||
f.qml.Login2FARequested()
|
||||
return
|
||||
}
|
||||
if f.auth.HasMailboxPassword() {
|
||||
f.qml.Login2PasswordRequested()
|
||||
return
|
||||
}
|
||||
|
||||
f.finishLogin()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) login2FA(username, code string) {
|
||||
if f.auth == nil || f.authClient == nil {
|
||||
f.log.Errorf("Login 2FA: authethication incomplete %p %p", f.auth, f.authClient)
|
||||
f.qml.Login2FAErrorAbort("Missing authentication, try again.")
|
||||
f.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
twoFA, err := base64.StdEncoding.DecodeString(code)
|
||||
if err != nil {
|
||||
f.log.WithError(err).Error("Cannot decode 2fa code")
|
||||
f.qml.LoginUsernamePasswordError("Cannot decode 2fa code")
|
||||
f.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
err = f.authClient.Auth2FA(context.Background(), string(twoFA))
|
||||
if err == pmapi.ErrBad2FACodeTryAgain {
|
||||
f.log.Warn("Login 2FA: retry 2fa")
|
||||
f.qml.Login2FAError("")
|
||||
return
|
||||
}
|
||||
|
||||
if err == pmapi.ErrBad2FACode {
|
||||
f.log.Warn("Login 2FA: abort 2fa")
|
||||
f.qml.Login2FAErrorAbort("")
|
||||
f.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
f.log.WithError(err).Warn("Login 2FA: failed.")
|
||||
f.qml.Login2FAErrorAbort(err.Error())
|
||||
f.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
if f.auth.HasMailboxPassword() {
|
||||
f.qml.Login2PasswordRequested()
|
||||
return
|
||||
}
|
||||
|
||||
f.finishLogin()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) login2Password(username, mboxPassword string) {
|
||||
var err error
|
||||
f.password, err = base64.StdEncoding.DecodeString(mboxPassword)
|
||||
if err != nil {
|
||||
f.log.WithError(err).Error("Cannot decode mbox password")
|
||||
f.qml.LoginUsernamePasswordError("Cannot decode mbox password")
|
||||
f.loginClean()
|
||||
return
|
||||
}
|
||||
|
||||
f.finishLogin()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) finishLogin() {
|
||||
defer f.loginClean()
|
||||
|
||||
if f.auth == nil || f.authClient == nil {
|
||||
f.log.Errorf("Finish login: Authethication incomplete %p %p", f.auth, f.authClient)
|
||||
f.qml.Login2PasswordErrorAbort("Missing authentication, try again.")
|
||||
return
|
||||
}
|
||||
|
||||
user, err := f.bridge.FinishLogin(f.authClient, f.auth, f.password)
|
||||
if err != nil {
|
||||
f.log.Errorf("Authethication incomplete %p %p", f.auth, f.authClient)
|
||||
f.qml.Login2PasswordErrorAbort("Missing authentication, try again.")
|
||||
return
|
||||
}
|
||||
|
||||
index := f.qml.Users().indexByID(user.ID())
|
||||
if index < 0 {
|
||||
qu := newQMLUserFromBacked(f, user)
|
||||
qu.SetSetupGuideSeen(false)
|
||||
f.qml.Users().addUser(qu)
|
||||
return
|
||||
}
|
||||
|
||||
f.qml.Users().users[index].update(user)
|
||||
f.qml.LoginFinished()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) loginAbort(username string) {
|
||||
f.loginClean()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) loginClean() {
|
||||
f.auth = nil
|
||||
f.authClient = nil
|
||||
for i := range f.password {
|
||||
f.password[i] = '\x00'
|
||||
}
|
||||
f.password = f.password[0:0]
|
||||
}
|
||||
70
internal/frontend/qt/helpers.go
Normal file
70
internal/frontend/qt/helpers.go
Normal file
@ -0,0 +1,70 @@
|
||||
// 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 (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/therecipe/qt/core"
|
||||
"github.com/therecipe/qt/gui"
|
||||
)
|
||||
|
||||
// getCursorPos returns current mouse position to be able to use in QML
|
||||
func getCursorPos() *core.QPoint {
|
||||
return gui.QCursor_Pos()
|
||||
}
|
||||
|
||||
// newQByteArrayFromString is a wrapper for new QByteArray from string.
|
||||
func newQByteArrayFromString(name string) *core.QByteArray {
|
||||
return core.NewQByteArray2(name, len(name))
|
||||
}
|
||||
|
||||
var (
|
||||
reMultiSpaces = regexp.MustCompile(`\s{2,}`)
|
||||
reStartWithSymbol = regexp.MustCompile(`^[.,/#!$@%^&*;:{}=\-_` + "`" + `~()]`)
|
||||
)
|
||||
|
||||
// getInitials based on webapp implementation:
|
||||
// https://github.com/ProtonMail/WebClients/blob/55d96a8b4afaaa4372fc5f1ef34953f2070fd7ec/packages/shared/lib/helpers/string.ts#L145
|
||||
func getInitials(fullName string) string {
|
||||
words := strings.Split(
|
||||
reMultiSpaces.ReplaceAllString(fullName, " "),
|
||||
" ",
|
||||
)
|
||||
|
||||
n := 0
|
||||
for _, word := range words {
|
||||
if !reStartWithSymbol.MatchString(word) {
|
||||
words[n] = word
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return "?"
|
||||
}
|
||||
|
||||
initials := words[0][0:1]
|
||||
if n != 1 {
|
||||
initials += words[n-1][0:1]
|
||||
}
|
||||
return strings.ToUpper(initials)
|
||||
}
|
||||
44
internal/frontend/qt/log/log.cpp
Normal file
44
internal/frontend/qt/log/log.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
// 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
|
||||
|
||||
#include "log.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QtGlobal>
|
||||
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
{
|
||||
Q_UNUSED( type )
|
||||
Q_UNUSED( context )
|
||||
QByteArray localMsg = msg.toUtf8().prepend("WHITESPACE");
|
||||
logMsgPacked(
|
||||
const_cast<char*>( (localMsg.constData()) +10 ),
|
||||
localMsg.size()-10
|
||||
);
|
||||
}
|
||||
|
||||
void InstallMessageHandler() {
|
||||
qInstallMessageHandler(messageHandler);
|
||||
}
|
||||
|
||||
46
internal/frontend/qt/log/log.go
Normal file
46
internal/frontend/qt/log/log.go
Normal file
@ -0,0 +1,46 @@
|
||||
// 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 log redirects QML logs to logrus
|
||||
package log
|
||||
|
||||
//#include "log.h"
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/therecipe/qt/core"
|
||||
)
|
||||
|
||||
var logQML = logrus.WithField("pkg", "frontent/qml")
|
||||
|
||||
// InstallMessageHandler is registering logQML as logger for QML calls.
|
||||
func InstallMessageHandler() {
|
||||
C.InstallMessageHandler()
|
||||
}
|
||||
|
||||
//export logMsgPacked
|
||||
func logMsgPacked(data *C.char, len C.int) {
|
||||
logQML.Warn(C.GoStringN(data, len))
|
||||
}
|
||||
|
||||
// logDummy is here to trigger qtmoc to create cgo instructions
|
||||
type logDummy struct {
|
||||
core.QObject
|
||||
}
|
||||
36
internal/frontend/qt/log/log.h
Normal file
36
internal/frontend/qt/log/log.h
Normal file
@ -0,0 +1,36 @@
|
||||
// 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef LOGRUS_QML_LOG_H
|
||||
#define LOGRUS_QML_LOG_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // C++
|
||||
|
||||
void InstallMessageHandler();
|
||||
;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // C++
|
||||
|
||||
#endif // LOGRUS_QML_LOG_H
|
||||
203
internal/frontend/qt/qml_backend.go
Normal file
203
internal/frontend/qt/qml_backend.go
Normal file
@ -0,0 +1,203 @@
|
||||
// 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/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
dockIcon "github.com/ProtonMail/proton-bridge/internal/frontend/qt/dockicon"
|
||||
"github.com/therecipe/qt/core"
|
||||
)
|
||||
|
||||
// QMLBackend connects QML frontend with Go backend.
|
||||
type QMLBackend struct {
|
||||
core.QObject
|
||||
|
||||
_ func() *core.QPoint `slot:"getCursorPos"`
|
||||
_ func() `slot:"quit"`
|
||||
_ func() `slot:"restart"`
|
||||
|
||||
_ bool `property:dockIconVisible`
|
||||
|
||||
_ QMLUserModel `property:"users"`
|
||||
|
||||
// TODO copy stuff from Bridge_test.qml backend object
|
||||
_ string `property:"goos"`
|
||||
|
||||
_ func(username, password string) `slot:"login"`
|
||||
_ func(username, code string) `slot:"login2FA"`
|
||||
_ func(username, password string) `slot:"login2Password"`
|
||||
_ func(username string) `slot:"loginAbort"`
|
||||
_ func(errorMsg string) `signal:"loginUsernamePasswordError"`
|
||||
_ func(errorMsg string) `signal:"loginFreeUserError"`
|
||||
_ func(errorMsg string) `signal:"loginConnectionError"`
|
||||
_ func() `signal:"login2FARequested"`
|
||||
_ func(errorMsg string) `signal:"login2FAError"`
|
||||
_ func(errorMsg string) `signal:"login2FAErrorAbort"`
|
||||
_ func() `signal:"login2PasswordRequested"`
|
||||
_ func(errorMsg string) `signal:"login2PasswordError"`
|
||||
_ func(errorMsg string) `signal:"login2PasswordErrorAbort"`
|
||||
_ func() `signal:"loginFinished"`
|
||||
|
||||
_ func() `signal:"internetOff"`
|
||||
_ func() `signal:"internetOn"`
|
||||
|
||||
_ func(version string) `signal:"updateManualReady"`
|
||||
_ func() `signal:"updateManualRestartNeeded"`
|
||||
_ func() `signal:"updateManualError"`
|
||||
_ func(version string) `signal:"updateForce"`
|
||||
_ func() `signal:"updateForceError"`
|
||||
_ func() `signal:"updateSilentRestartNeeded"`
|
||||
_ func() `signal:"updateSilentError"`
|
||||
_ func() `signal:"updateIsLatestVersion"`
|
||||
_ func() `slot:"checkUpdates"`
|
||||
_ func() `signal:"checkUpdatesFinished"`
|
||||
|
||||
_ bool `property:"isDiskCacheEnabled"`
|
||||
_ string `property:"diskCachePath"`
|
||||
_ func() `signal:"cacheUnavailable"`
|
||||
_ func() `signal:"cacheCantMove"`
|
||||
_ func() `signal:"cacheLocationChangeSuccess"`
|
||||
_ func() `signal:"diskFull"`
|
||||
_ func(enableDiskCache bool, diskCachePath string) `slot:"changeLocalCache"`
|
||||
_ func() `signal:"changeLocalCacheFinished"`
|
||||
|
||||
_ bool `property:"isAutomaticUpdateOn"`
|
||||
_ func(makeItActive bool) `slot:"toggleAutomaticUpdate"`
|
||||
|
||||
_ bool `property:"isAutostartOn"`
|
||||
_ func(makeItActive bool) `slot:"toggleAutostart"`
|
||||
_ func() `signal:"toggleAutostartFinished"`
|
||||
|
||||
_ bool `property:"isBetaEnabled"`
|
||||
_ func(makeItActive bool) `slot:"toggleBeta"`
|
||||
|
||||
_ bool `property:"isDoHEnabled"`
|
||||
_ func(makeItActive bool) `slot:"toggleDoH"`
|
||||
|
||||
_ bool `property:"useSSLforSMTP"`
|
||||
_ func(makeItActive bool) `slot:"toggleUseSSLforSMTP"`
|
||||
_ func() `signal:"toggleUseSSLFinished"`
|
||||
|
||||
_ string `property:"hostname"`
|
||||
_ int `property:"portIMAP"`
|
||||
_ int `property:"portSMTP"`
|
||||
_ func(imapPort, smtpPort int) `slot:"changePorts"`
|
||||
_ func(port int) bool `slot:"isPortFree"`
|
||||
_ func() `signal:"changePortFinished"`
|
||||
_ func() `signal:"portIssueIMAP"`
|
||||
_ func() `signal:"portIssueSMTP"`
|
||||
|
||||
_ func() `slot:"triggerReset"`
|
||||
_ func() `signal:"resetFinished"`
|
||||
|
||||
_ string `property:"version"`
|
||||
_ string `property:"logsPath"`
|
||||
_ string `property:"licensePath"`
|
||||
_ string `property:"releaseNotesLink"`
|
||||
_ string `property:"landingPageLink"`
|
||||
|
||||
_ string `property:"currentEmailClient"`
|
||||
_ func() `slot:"updateCurrentMailClient"`
|
||||
_ func(description, address, emailClient string, includeLogs bool) `slot:"reportBug"`
|
||||
_ func() `signal:"reportBugFinished"`
|
||||
_ func() `signal:"bugReportSendSuccess"`
|
||||
_ func() `signal:"bugReportSendError"`
|
||||
|
||||
_ []string `property:"availableKeychain"`
|
||||
_ string `property:"selectedKeychain"`
|
||||
_ func(keychain string) `slot:"selectKeychain"`
|
||||
_ func() `signal:"notifyHasNoKeychain"`
|
||||
|
||||
_ func(email string) `signal:noActiveKeyForRecipient`
|
||||
_ func() `signal:showMainWindow`
|
||||
|
||||
_ func(address string) `signal:addressChanged`
|
||||
_ func(address string) `signal:addressChangedLogout`
|
||||
_ func(username string) `signal:userDisconnected`
|
||||
_ func() `signal:apiCertIssue`
|
||||
}
|
||||
|
||||
func (q *QMLBackend) setup(f *FrontendQt) {
|
||||
q.ConnectGetCursorPos(getCursorPos)
|
||||
q.ConnectQuit(f.quit)
|
||||
q.ConnectRestart(f.restart)
|
||||
|
||||
q.ConnectIsDockIconVisible(func() bool {
|
||||
return dockIcon.GetDockIconVisibleState()
|
||||
})
|
||||
q.ConnectSetDockIconVisible(func(visible bool) {
|
||||
dockIcon.SetDockIconVisibleState(visible)
|
||||
})
|
||||
|
||||
q.SetUsers(NewQMLUserModel(nil))
|
||||
f.loadUsers()
|
||||
|
||||
q.SetGoos(runtime.GOOS)
|
||||
|
||||
q.ConnectLogin(func(u, p string) { go f.login(u, p) })
|
||||
q.ConnectLogin2FA(func(u, p string) { go f.login2FA(u, p) })
|
||||
q.ConnectLogin2Password(func(u, p string) { go f.login2Password(u, p) })
|
||||
q.ConnectLoginAbort(func(u string) { go f.loginAbort(u) })
|
||||
|
||||
go f.checkUpdatesAndNotify(false)
|
||||
q.ConnectCheckUpdates(func() { go f.checkUpdatesAndNotify(true) })
|
||||
|
||||
f.setIsDiskCacheEnabled()
|
||||
f.setDiskCachePath()
|
||||
q.ConnectChangeLocalCache(f.changeLocalCache)
|
||||
|
||||
f.setIsAutomaticUpdateOn()
|
||||
q.ConnectToggleAutomaticUpdate(func(m bool) { go f.toggleAutomaticUpdate(m) })
|
||||
|
||||
f.setIsAutostartOn()
|
||||
q.ConnectToggleAutostart(f.toggleAutostart)
|
||||
|
||||
f.setIsBetaEnabled()
|
||||
q.ConnectToggleBeta(func(m bool) { go f.toggleBeta(m) })
|
||||
|
||||
q.SetIsDoHEnabled(f.settings.GetBool(settings.AllowProxyKey))
|
||||
q.ConnectToggleDoH(f.toggleDoH)
|
||||
|
||||
q.SetUseSSLforSMTP(f.settings.GetBool(settings.SMTPSSLKey))
|
||||
q.ConnectToggleUseSSLforSMTP(f.toggleUseSSLforSMTP)
|
||||
|
||||
q.SetHostname(bridge.Host)
|
||||
q.SetPortIMAP(f.settings.GetInt(settings.IMAPPortKey))
|
||||
q.SetPortSMTP(f.settings.GetInt(settings.SMTPPortKey))
|
||||
q.ConnectChangePorts(f.changePorts)
|
||||
q.ConnectIsPortFree(f.isPortFree)
|
||||
|
||||
q.ConnectTriggerReset(func() { go f.triggerReset() })
|
||||
|
||||
f.setVersion()
|
||||
f.setLogsPath()
|
||||
// release notes link is set by update
|
||||
f.setLicensePath()
|
||||
|
||||
f.setCurrentEmailClient()
|
||||
q.ConnectUpdateCurrentMailClient(func() { go f.setCurrentEmailClient() })
|
||||
q.ConnectReportBug(func(d, a, e string, i bool) { go f.reportBug(d, a, e, i) })
|
||||
|
||||
f.setKeychain()
|
||||
q.ConnectSelectKeychain(func(k string) { go f.selectKeychain(k) })
|
||||
}
|
||||
135
internal/frontend/qt/qml_users.go
Normal file
135
internal/frontend/qt/qml_users.go
Normal file
@ -0,0 +1,135 @@
|
||||
// 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 (
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/therecipe/qt/core"
|
||||
)
|
||||
|
||||
// QMLUserModel stores list of of users
|
||||
type QMLUserModel struct {
|
||||
core.QAbstractListModel
|
||||
|
||||
_ map[int]*core.QByteArray `property:"roles"`
|
||||
_ int `property:"count"`
|
||||
_ func() `constructor:"init"`
|
||||
_ func(row int) *core.QVariant `slot:"get"`
|
||||
|
||||
users []*QMLUser
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) init() {
|
||||
um.SetRoles(map[int]*core.QByteArray{
|
||||
int(core.Qt__UserRole + 1): newQByteArrayFromString("object"),
|
||||
})
|
||||
um.ConnectRowCount(um.rowCount)
|
||||
um.ConnectData(um.data)
|
||||
um.ConnectGet(um.get)
|
||||
um.users = []*QMLUser{}
|
||||
um.setCount()
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) data(index *core.QModelIndex, property int) *core.QVariant {
|
||||
if !index.IsValid() {
|
||||
return core.NewQVariant()
|
||||
}
|
||||
return um.get(index.Row())
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) get(index int) *core.QVariant {
|
||||
if index < 0 || index >= um.rowCount(nil) {
|
||||
return core.NewQVariant()
|
||||
}
|
||||
return um.users[index].ToVariant()
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) rowCount(*core.QModelIndex) int {
|
||||
return len(um.users)
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) setCount() {
|
||||
um.SetCount(len(um.users))
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) addUser(user *QMLUser) {
|
||||
um.BeginInsertRows(core.NewQModelIndex(), um.rowCount(nil), um.rowCount(nil))
|
||||
um.users = append(um.users, user)
|
||||
um.setCount()
|
||||
um.EndInsertRows()
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) removeUser(row int) {
|
||||
um.BeginRemoveRows(core.NewQModelIndex(), row, row)
|
||||
um.users = append(um.users[:row], um.users[row+1:]...)
|
||||
um.setCount()
|
||||
um.EndRemoveRows()
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) clear() {
|
||||
um.BeginRemoveRows(core.NewQModelIndex(), 0, um.rowCount(nil))
|
||||
um.users = []*QMLUser{}
|
||||
um.setCount()
|
||||
um.EndRemoveRows()
|
||||
}
|
||||
|
||||
func (um *QMLUserModel) indexByID(id string) int {
|
||||
for i, qu := range um.users {
|
||||
if id == qu.ID {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// QMLUser holds data, slots and signals and for user.
|
||||
type QMLUser struct {
|
||||
core.QObject
|
||||
|
||||
_ string `property:"username"`
|
||||
_ string `property:"avatarText"`
|
||||
_ bool `property:"loggedIn"`
|
||||
_ bool `property:"splitMode"`
|
||||
_ bool `property:"setupGuideSeen"`
|
||||
_ float32 `property:"usedBytes"`
|
||||
_ float32 `property:"totalBytes"`
|
||||
_ string `property:"password"`
|
||||
_ []string `property:"addresses"`
|
||||
|
||||
_ func(makeItActive bool) `slot:"toggleSplitMode"`
|
||||
_ func() `signal:"toggleSplitModeFinished"`
|
||||
_ func() `slot:"logout"`
|
||||
_ func(address string) `slot:"configureAppleMail"`
|
||||
|
||||
ID string
|
||||
}
|
||||
|
||||
func (qu *QMLUser) update(user types.User) {
|
||||
username := user.Username()
|
||||
qu.SetAvatarText(getInitials(username))
|
||||
qu.SetUsername(username)
|
||||
qu.SetLoggedIn(user.IsConnected())
|
||||
qu.SetSplitMode(!user.IsCombinedAddressMode())
|
||||
qu.SetSetupGuideSeen(true)
|
||||
qu.SetUsedBytes(1.0) // TODO
|
||||
qu.SetTotalBytes(10000.0) // TODO
|
||||
qu.SetPassword(user.GetBridgePassword())
|
||||
qu.SetAddresses(user.GetAddresses())
|
||||
}
|
||||
Reference in New Issue
Block a user