diff --git a/internal/config/settings/settings.go b/internal/config/settings/settings.go
index feb76b29..be2a85a9 100644
--- a/internal/config/settings/settings.go
+++ b/internal/config/settings/settings.go
@@ -53,6 +53,7 @@ const (
IMAPWorkers = "imap_workers"
FetchWorkers = "fetch_workers"
AttachmentWorkers = "attachment_workers"
+ ColorScheme = "color_scheme"
)
type Settings struct {
@@ -100,6 +101,7 @@ func (s *Settings) setDefaultValues() {
s.setDefault(IMAPWorkers, "16")
s.setDefault(FetchWorkers, "16")
s.setDefault(AttachmentWorkers, "16")
+ s.setDefault(ColorScheme, "")
s.setDefault(APIPortKey, DefaultAPIPort)
s.setDefault(IMAPPortKey, DefaultIMAPPort)
diff --git a/internal/frontend/qml/Bridge.qml b/internal/frontend/qml/Bridge.qml
index 36c605ea..0f61c094 100644
--- a/internal/frontend/qml/Bridge.qml
+++ b/internal/frontend/qml/Bridge.qml
@@ -20,6 +20,7 @@ import QtQuick 2.13
import QtQuick.Window 2.13
import Qt.labs.platform 1.1
+import Proton 4.0
import Notifications 1.0
QtObject {
@@ -58,6 +59,7 @@ QtObject {
onCacheUnavailable: {
mainWindow.showAndRise()
}
+ onColorSchemeNameChanged: root.setColorScheme()
}
}
@@ -206,15 +208,15 @@ QtObject {
switch (reason) {
case SystemTrayIcon.Unknown:
- break;
+ break;
case SystemTrayIcon.Context:
case SystemTrayIcon.Trigger:
case SystemTrayIcon.DoubleClick:
case SystemTrayIcon.MiddleClick:
- calcStatusWindowPosition()
- toggleWindow(statusWindow)
+ calcStatusWindowPosition()
+ toggleWindow(statusWindow)
break;
- default:
+ default:
break;
}
}
@@ -225,6 +227,9 @@ QtObject {
console.log("backend not loaded")
}
+ root.setColorScheme()
+
+
if (!root.backend.users) {
console.log("users not loaded")
}
@@ -253,4 +258,9 @@ QtObject {
root.backend.guiReady()
}
+
+ function setColorScheme() {
+ if (root.backend.colorSchemeName == "light") ProtonStyle.currentStyle = ProtonStyle.lightStyle
+ if (root.backend.colorSchemeName == "dark") ProtonStyle.currentStyle = ProtonStyle.darkStyle
+ }
}
diff --git a/internal/frontend/qml/Bridge_test.qml b/internal/frontend/qml/Bridge_test.qml
index 4890c540..41b62bc0 100644
--- a/internal/frontend/qml/Bridge_test.qml
+++ b/internal/frontend/qml/Bridge_test.qml
@@ -321,7 +321,7 @@ Window {
onCheckedChanged: {
if (checked && ProtonStyle.currentStyle !== ProtonStyle.lightStyle) {
- ProtonStyle.currentStyle = ProtonStyle.lightStyle
+ root.colorSchemeName = "light"
}
}
}
@@ -336,7 +336,7 @@ Window {
onCheckedChanged: {
if (checked && ProtonStyle.currentStyle !== ProtonStyle.darkStyle) {
- ProtonStyle.currentStyle = ProtonStyle.darkStyle
+ root.colorSchemeName = "dark"
}
}
}
@@ -777,6 +777,12 @@ Window {
property url releaseNotesLink: Qt.resolvedUrl("https://protonmail.com/download/bridge/early_releases.html")
property url landingPageLink: Qt.resolvedUrl("https://protonmail.com/bridge")
+ property string colorSchemeName: "light"
+ function changeColorScheme(newScheme){
+ root.colorSchemeName = newScheme
+ }
+
+
property string currentEmailClient: "" // "Apple Mail 14.0"
function updateCurrentMailClient(){
currentEmailClient = "Apple Mail 14.0"
diff --git a/internal/frontend/qml/GeneralSettings.qml b/internal/frontend/qml/GeneralSettings.qml
index 9549b7ea..c52122db 100644
--- a/internal/frontend/qml/GeneralSettings.qml
+++ b/internal/frontend/qml/GeneralSettings.qml
@@ -129,6 +129,19 @@ SettingsView {
Layout.fillWidth: true
}
+ SettingsItem {
+ id: darkMode
+ visible: root._isAdvancedShown
+ colorScheme: root.colorScheme
+ text: qsTr("Dark mode")
+ description: qsTr("Choose dark color theme.")
+ type: SettingsItem.Toggle
+ checked: root.backend.colorSchemeName == "dark"
+ onClicked: root.backend.changeColorScheme( darkMode.checked ? "light" : "dark")
+
+ Layout.fillWidth: true
+ }
+
SettingsItem {
id: ports
visible: root._isAdvancedShown
diff --git a/internal/frontend/qml/Proton/Style.qml b/internal/frontend/qml/Proton/Style.qml
index 49a48239..083e2cf8 100644
--- a/internal/frontend/qml/Proton/Style.qml
+++ b/internal/frontend/qml/Proton/Style.qml
@@ -36,7 +36,7 @@ QtObject {
property ColorScheme lightStyle: ColorScheme {
id: _lightStyle
- prominent: prominentStyle
+ prominent: lightProminentStyle
// Primary
primay_norm: "#657EE4"
@@ -107,8 +107,8 @@ QtObject {
welcome_img: "icons/img-welcome.png"
}
- property ColorScheme prominentStyle: ColorScheme {
- id: _prominentStyle
+ property ColorScheme lightProminentStyle: ColorScheme {
+ id: _lightProminentStyle
prominent: this
@@ -184,7 +184,7 @@ QtObject {
property ColorScheme darkStyle: ColorScheme {
id: _darkStyle
- prominent: prominentStyle
+ prominent: darkProminentStyle
// Primary
primay_norm: "#657EE4"
@@ -245,8 +245,82 @@ QtObject {
signal_info_active: "#3D99EB"
// Shadows
- shadow_norm: "#262A33"
- shadow_lifted: "#262A33"
+ shadow_norm: "#262A33" // #000000 32% x+0 y+1 blur:4
+ shadow_lifted: "#262A33" // #000000 40% x+0 y+8 blur:24
+
+ // Backdrop
+ backdrop_norm: "#52000000"
+
+ // Images
+ welcome_img: "icons/img-welcome-dark.png"
+ }
+
+ property ColorScheme darkProminentStyle: ColorScheme {
+ id: _darkProminentStyle
+
+ prominent: this
+
+ // Primary
+ primay_norm: "#657EE4"
+
+ // Interaction-norm
+ interaction_norm: "#657EE4"
+ interaction_norm_hover: "#7D92E8"
+ interaction_norm_active: "#98A9EE"
+
+ // Text
+ text_norm: "#FFFFFF"
+ text_weak: "#A4A9B5"
+ text_hint: "#696F7D"
+ text_disabled: "#575D6B"
+ text_invert: "#262A33"
+
+ // Field
+ field_norm: "#575D6B"
+ field_hover: "#696F7D"
+ field_disabled: "#464B58"
+
+ // Border
+ border_norm: "#464B58"
+ border_weak: "#363A46"
+
+ // Background
+ background_norm: "#1A1D24"
+ background_weak: "#2E323C"
+ background_strong: "#363A46"
+ background_avatar: "#575D6B"
+
+ // Interaction-weak
+ interaction_weak: "#464B58"
+ interaction_weak_hover: "#575D6B"
+ interaction_weak_active: "#696F7D"
+
+ // Interaction-default
+ interaction_default: "#00000000"
+ interaction_default_hover: "#33575D6B"
+ interaction_default_active: "#4D575D6B"
+
+ // Scrollbar
+ scrollbar_norm: "#464B58"
+ scrollbar_hover: "#575D6B"
+
+ // Signal
+ signal_danger: "#ED4C51"
+ signal_danger_hover: "#F7595E"
+ signal_danger_active: "#FF666B"
+ signal_warning: "#F5930A"
+ signal_warning_hover: "#F5A716"
+ signal_warning_active: "#F5B922"
+ signal_success: "#349172"
+ signal_success_hover: "#339C79"
+ signal_success_active: "#31A67F"
+ signal_info: "#2C89DB"
+ signal_info_hover: "#3491E3"
+ signal_info_active: "#3D99EB"
+
+ // Shadows
+ shadow_norm: "#262A33" // #000000 32% x+0 y+1 blur:4
+ shadow_lifted: "#262A33" // #000000 40% x+0 y+8 blur:24
// Backdrop
backdrop_norm: "#52000000"
@@ -255,8 +329,6 @@ QtObject {
welcome_img: "icons/img-welcome-dark.png"
}
- // TODO: if default style should be loaded from somewhere
- // (i.e. from preferencies file) - it should be loaded here
property ColorScheme currentStyle: lightStyle
property string font_family: {
diff --git a/internal/frontend/qt/frontend_settings.go b/internal/frontend/qt/frontend_settings.go
index ab56970b..2e25a255 100644
--- a/internal/frontend/qt/frontend_settings.go
+++ b/internal/frontend/qt/frontend_settings.go
@@ -26,6 +26,7 @@ import (
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/frontend/clientconfig"
+ "github.com/ProtonMail/proton-bridge/internal/frontend/theme"
"github.com/ProtonMail/proton-bridge/pkg/keychain"
"github.com/ProtonMail/proton-bridge/pkg/ports"
"github.com/therecipe/qt/core"
@@ -184,3 +185,21 @@ func (f *FrontendQt) quit() {
func (f *FrontendQt) guiReady() {
f.initializationDone.Do(f.initializing.Done)
}
+
+func (f *FrontendQt) setColorScheme() {
+ current := f.settings.Get(settings.ColorScheme)
+ if !theme.IsAvailable(theme.Theme(current)) {
+ current = string(theme.DefaultTheme())
+ f.settings.Set(settings.ColorScheme, current)
+ }
+ f.qml.SetColorSchemeName(current)
+}
+
+func (f *FrontendQt) changeColorScheme(newScheme string) {
+ if !theme.IsAvailable(theme.Theme(newScheme)) {
+ f.log.WithField("scheme", newScheme).Warn("Color scheme not available")
+ return
+ }
+ f.settings.Set(settings.ColorScheme, newScheme)
+ f.setColorScheme()
+}
diff --git a/internal/frontend/qt/qml_backend.go b/internal/frontend/qt/qml_backend.go
index 62b229b7..41c67956 100644
--- a/internal/frontend/qt/qml_backend.go
+++ b/internal/frontend/qt/qml_backend.go
@@ -128,6 +128,8 @@ type QMLBackend struct {
_ core.QUrl `property:"releaseNotesLink"`
_ core.QUrl `property:"landingPageLink"`
+ _ string `property:"colorSchemeName"`
+ _ func(string) `slot:"changeColorScheme"`
_ string `property:"currentEmailClient"`
_ func() `slot:"updateCurrentMailClient"`
_ func(description, address, emailClient string, includeLogs bool) `slot:"reportBug"`
@@ -262,6 +264,14 @@ func (q *QMLBackend) setup(f *FrontendQt) {
// release notes link is set by update
f.setLicensePath()
+ f.setColorScheme()
+ q.ConnectChangeColorScheme(func(newScheme string) {
+ go func() {
+ defer f.panicHandler.HandlePanic()
+ f.changeColorScheme(newScheme)
+ }()
+ })
+
f.setCurrentEmailClient()
q.ConnectUpdateCurrentMailClient(func() {
go func() {
diff --git a/internal/frontend/theme/detect_darwin.go b/internal/frontend/theme/detect_darwin.go
new file mode 100644
index 00000000..fabf10fc
--- /dev/null
+++ b/internal/frontend/theme/detect_darwin.go
@@ -0,0 +1,34 @@
+// 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 .
+
+//go:build darwin
+// +build darwin
+
+package theme
+
+import (
+ "os/exec"
+ "strings"
+)
+
+func detectSystemTheme() Theme {
+ out, err := exec.Command("defaults", "read", "-g", "AppleInterfaceStyle").Output() //nolint[gosec]
+ if err == nil && strings.TrimSpace(string(out)) == "Dark" {
+ return Dark
+ }
+ return Light
+}
diff --git a/internal/frontend/theme/detect_default.go b/internal/frontend/theme/detect_default.go
new file mode 100644
index 00000000..834df4c9
--- /dev/null
+++ b/internal/frontend/theme/detect_default.go
@@ -0,0 +1,25 @@
+// 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 .
+
+//go:build !windows && !darwin
+// +build !windows,!darwin
+
+package theme
+
+func detectSystemTheme() Theme {
+ return Light
+}
diff --git a/internal/frontend/theme/detect_windows.go b/internal/frontend/theme/detect_windows.go
new file mode 100644
index 00000000..0157dcb7
--- /dev/null
+++ b/internal/frontend/theme/detect_windows.go
@@ -0,0 +1,53 @@
+// 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 .
+
+//go:build windows
+// +build windows
+
+package theme
+
+import (
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/windows/registry"
+)
+
+func detectSystemTheme() Theme {
+ log := logrus.WithField("pkg", "theme")
+ k, err := registry.OpenKey(
+ registry.CURRENT_USER,
+ `SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize`,
+ registry.QUERY_VALUE,
+ )
+
+ if err != nil {
+ log.WithError(err).Error("Not able to open register")
+ return Light
+ }
+ defer k.Close()
+
+ i, _, err := k.GetIntegerValue("AppsUseLightTheme")
+ if err != nil {
+ log.WithError(err).Error("Cannot get value")
+ return Light
+ }
+
+ if i == 0 {
+ return Dark
+ }
+
+ return Light
+}
diff --git a/internal/frontend/theme/theme.go b/internal/frontend/theme/theme.go
new file mode 100644
index 00000000..e416aef6
--- /dev/null
+++ b/internal/frontend/theme/theme.go
@@ -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 .
+
+package theme
+
+import (
+ "runtime"
+)
+
+type Theme string
+
+const (
+ Light = Theme("light")
+ Dark = Theme("dark")
+)
+
+func IsAvailable(have Theme) bool {
+ return have == Light || have == Dark
+}
+
+func DefaultTheme() Theme {
+ switch runtime.GOOS {
+ case "darwin", "windows":
+ return detectSystemTheme()
+ default:
+ return Light
+ }
+}
diff --git a/internal/frontend/theme/theme_test.go b/internal/frontend/theme/theme_test.go
new file mode 100644
index 00000000..69e0b160
--- /dev/null
+++ b/internal/frontend/theme/theme_test.go
@@ -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 .
+
+// Package settings provides access to persistent user settings.
+package theme
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestIsAvailable(t *testing.T) {
+ r := require.New(t)
+
+ want := "dark"
+
+ r.True(IsAvailable("dark"))
+ r.True(IsAvailable(Dark))
+ r.True(IsAvailable(Theme(want)))
+
+ want = "light"
+ r.True(IsAvailable("light"))
+ r.True(IsAvailable(Light))
+ r.True(IsAvailable(Theme(want)))
+
+ want = "molokai"
+ r.False(IsAvailable(""))
+ r.False(IsAvailable("molokai"))
+ r.False(IsAvailable(Theme(want)))
+}