From ec1d664b0c59da5edd25653cf29e13bdaa12bc75 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Thu, 25 Aug 2022 17:02:14 -0400
Subject: [PATCH] gnulinux: add support for system theme

Use 'color-scheme' from gsettings if available to check if dark theme
is preferred.  Otherwise, fall back on theme name from 'gtk-theme'.

The default is to follow the system theme (if supported), and
'EnableDarkTheme' would be unused.  'AppTheme' can currently have
one of three values: 'System', 'Dark', 'Light'.

Change-Id: I8008c8fe0f4750c97e71838fff0031f80b685f5e
GitLab: #723
---
 src/app/appsettingsmanager.h                  |  1 +
 src/app/constant/JamiStrings.qml              |  4 +-
 src/app/constant/JamiTheme.qml                |  6 +-
 .../components/SystemSettings.qml             | 50 ++++++++++---
 src/app/utilsadapter.cpp                      | 71 +++++++++++++++++++
 src/app/utilsadapter.h                        | 20 ++++++
 6 files changed, 141 insertions(+), 11 deletions(-)

diff --git a/src/app/appsettingsmanager.h b/src/app/appsettingsmanager.h
index 074fb0cf8..b0f17939c 100644
--- a/src/app/appsettingsmanager.h
+++ b/src/app/appsettingsmanager.h
@@ -45,6 +45,7 @@ extern const QString defaultDownloadPath;
     X(DisplayHyperlinkPreviews, true) \
     X(EnableExperimentalSwarm, false) \
     X(EnableDarkTheme, false) \
+    X(AppTheme, "System") \
     X(BaseZoom, 1.0) \
     X(ParticipantsSide, false) \
     X(HideSelf, false) \
diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml
index 32af891a0..adf297180 100644
--- a/src/app/constant/JamiStrings.qml
+++ b/src/app/constant/JamiStrings.qml
@@ -422,9 +422,11 @@ Item {
 
     // SystemSettings
     property string system: qsTr("System")
+    property string dark: qsTr("Dark")
+    property string light: qsTr("Light")
     property string selectFolder: qsTr("Select a folder")
     property string enableNotifications: qsTr("Enable notifications")
-    property string enableDarkTheme: qsTr("Enable dark theme")
+    property string applicationTheme: qsTr("Application theme")
     property string enableDesktopNotifications: qsTr("Enable desktop notifications")
     property string keepMinimized: qsTr("Keep minimized on close")
     property string tipRunStartup: qsTr("Run application on system startup")
diff --git a/src/app/constant/JamiTheme.qml b/src/app/constant/JamiTheme.qml
index 5a1cb4a73..f1b41da91 100644
--- a/src/app/constant/JamiTheme.qml
+++ b/src/app/constant/JamiTheme.qml
@@ -25,7 +25,7 @@ import net.jami.Adapters 1.1
 import net.jami.Enums 1.1
 
 Item {
-    property bool darkTheme: UtilsAdapter.getAppValue(Settings.EnableDarkTheme)
+    property bool darkTheme: UtilsAdapter.useApplicationTheme()
 
     Connections {
         target: UtilsAdapter
@@ -33,6 +33,10 @@ Item {
         function onChangeFontSize() {
             baseZoom = UtilsAdapter.getAppValue(Settings.BaseZoom)
         }
+
+        function onAppThemeChanged() {
+            darkTheme = UtilsAdapter.useApplicationTheme()
+        }
     }
 
     // Jami theme colors
diff --git a/src/app/settingsview/components/SystemSettings.qml b/src/app/settingsview/components/SystemSettings.qml
index ec520c434..80ffcc492 100644
--- a/src/app/settingsview/components/SystemSettings.qml
+++ b/src/app/settingsview/components/SystemSettings.qml
@@ -64,21 +64,53 @@ ColumnLayout {
         verticalAlignment: Text.AlignVCenter
     }
 
-    ToggleSwitch {
-        id: darkThemeCheckBox
+
+
+    SettingsComboBox {
+        id: themeComboBoxSettings
+
         Layout.fillWidth: true
         Layout.leftMargin: JamiTheme.preferredMarginSize
 
-        checked: UtilsAdapter.getAppValue(Settings.EnableDarkTheme)
-
-        labelText: JamiStrings.enableDarkTheme
+        labelText: JamiStrings.applicationTheme
         fontPointSize: JamiTheme.settingsFontSize
 
-        tooltipText: JamiStrings.enableDarkTheme
+        comboModel: ListModel {
+            Component.onCompleted: {
+                append({ textDisplay: JamiStrings.dark })
+                append({ textDisplay: JamiStrings.light })
+                if (UtilsAdapter.hasNativeDarkTheme())
+                    append({ textDisplay: JamiStrings.system })
+            }
+        }
+        widthOfComboBox: itemWidth
+        tipText: JamiStrings.selectAudioInputDevice
+        role: "textDisplay"
+
+        modelIndex: {
+            if (UtilsAdapter.hasNativeDarkTheme()) {
+                var theme = UtilsAdapter.getAppValue(Settings.Key.AppTheme)
+                if (theme === "Dark") {
+                    return 0
+                } else if (theme === "Light") {
+                    return 1
+                }
+                return 2
+            }
+            return UtilsAdapter.getAppValue(Settings.Key.EnableDarkTheme) ? 0 : 1
+        }
 
-        onSwitchToggled: {
-            JamiTheme.setTheme(checked)
-            UtilsAdapter.setAppValue(Settings.Key.EnableDarkTheme, checked)
+        onActivated: {
+            if (UtilsAdapter.hasNativeDarkTheme()) {
+                if (modelIndex === 0)
+                    UtilsAdapter.setAppValue(Settings.Key.AppTheme, "Dark")
+                else if (modelIndex === 1)
+                    UtilsAdapter.setAppValue(Settings.Key.AppTheme, "Light")
+                else if (modelIndex === 2)
+                    UtilsAdapter.setAppValue(Settings.Key.AppTheme, "System")
+            } else {
+                UtilsAdapter.setAppValue(Settings.Key.EnableDarkTheme, modelIndex === 0)
+            }
         }
     }
 
diff --git a/src/app/utilsadapter.cpp b/src/app/utilsadapter.cpp
index 6a21e1c16..d27c0f709 100644
--- a/src/app/utilsadapter.cpp
+++ b/src/app/utilsadapter.cpp
@@ -375,6 +375,8 @@ UtilsAdapter::setAppValue(const Settings::Key key, const QVariant& value)
         Q_EMIT changeFontSize();
     else if (key == Settings::Key::ShowChatviewHorizontally)
         Q_EMIT chatviewPositionChanged();
+    else if (key == Settings::Key::AppTheme)
+        Q_EMIT appThemeChanged();
 }
 
 QString
@@ -584,3 +586,72 @@ UtilsAdapter::luma(const QColor& color) const
     return (0.2126 * color.red() + 0.7152 * color.green() + 0.0722 * color.blue())
            < 153 /* .6 * 256 */;
 }
+
+#if __has_include(<gio/gio.h>)
+void
+settingsCallback(GSettings* self, gchar* key, gpointer user_data)
+{
+    QString keyString = key;
+    if (keyString == "color-scheme" || keyString == "gtk-theme") {
+        Q_EMIT((UtilsAdapter*) (user_data))->appThemeChanged();
+    }
+}
+#endif
+
+bool
+UtilsAdapter::isSystemThemeDark()
+{
+#if __has_include(<gio/gio.h>)
+    if (!settings) {
+        settings = g_settings_new("org.gnome.desktop.interface");
+        if (!settings)
+            return false;
+        g_signal_connect(settings, "changed", G_CALLBACK(settingsCallback), this);
+    }
+    if (!schema) {
+        g_object_get(settings, "settings-schema", &schema, nullptr);
+        if (!schema)
+            return false;
+    }
+    std::vector<std::string> keys = {"gtk-color-scheme", "color-scheme", "gtk-theme"};
+    auto** gtk_keys = g_settings_schema_list_keys(schema);
+    for (const auto& key : keys) {
+        auto hasKey = false;
+        for (int i = 0; gtk_keys[i]; i++) {
+            if (key == gtk_keys[i]) {
+                hasKey = true;
+                break;
+            }
+        }
+        if (hasKey) {
+            if (auto* valueCstr = g_settings_get_string(settings, key.c_str())) {
+                QString value = valueCstr;
+                if (!value.isEmpty()) {
+                    return value.contains("dark", Qt::CaseInsensitive)
+                           || value.contains("black", Qt::CaseInsensitive);
+                }
+            }
+        }
+    }
+    return false;
+#else
+    qWarning("System theme detection is not implemented");
+    return false;
+#endif
+}
+
+bool
+UtilsAdapter::useApplicationTheme()
+{
+    if (hasNativeDarkTheme()) {
+        QString theme = getAppValue(Settings::Key::AppTheme).toString();
+        if (theme == "Dark")
+            return true;
+        else if (theme == "Light")
+            return false;
+        return isSystemThemeDark();
+    }
+    bool enableDark = getAppValue(Settings::Key::EnableDarkTheme).toBool();
+    setAppValue(Settings::Key::AppTheme, enableDark ? "Dark" : "Light");
+    return enableDark;
+}
\ No newline at end of file
diff --git a/src/app/utilsadapter.h b/src/app/utilsadapter.h
index 9d7af80a8..b1c659e79 100644
--- a/src/app/utilsadapter.h
+++ b/src/app/utilsadapter.h
@@ -29,6 +29,10 @@
 #include "appsettingsmanager.h"
 #include "qtutils.h"
 
+#if __has_include(<gio/gio.h>)
+#include <gio/gio.h>
+#endif
+
 class QClipboard;
 class SystemTray;
 
@@ -107,12 +111,22 @@ public:
                                                           const QString& convId,
                                                           const QString& uri);
     Q_INVOKABLE bool luma(const QColor& color) const;
+    Q_INVOKABLE bool useApplicationTheme();
+    Q_INVOKABLE bool hasNativeDarkTheme() const
+    {
+#if __has_include(<gio/gio.h>)
+        return true;
+#else
+        return false;
+#endif
+    }
 
 Q_SIGNALS:
     void debugMessageReceived(const QString& message);
     void showExperimentalSwarm();
     void changeFontSize();
     void chatviewPositionChanged();
+    void appThemeChanged();
 
 private:
     QClipboard* clipboard_;
@@ -121,5 +135,11 @@ private:
 
     QMetaObject::Connection debugMessageReceivedConnection_;
     QString getDefaultRecordPath() const;
+
+    bool isSystemThemeDark();
+#if __has_include(<gio/gio.h>)
+    GSettings* settings {nullptr};
+    GSettingsSchema* schema {nullptr};
+#endif
 };
 Q_DECLARE_METATYPE(UtilsAdapter*)
-- 
GitLab