Implement system-based dark mode for Windows and Linux

This commit is contained in:
Ilya Fedin 2020-07-14 20:36:55 +04:00 committed by John Preston
parent fc3a9d98c0
commit 47a237c924
12 changed files with 185 additions and 20 deletions

View File

@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/local_url_handlers.h"
#include "core/launcher.h"
#include "core/ui_integration.h"
#include "core/core_settings.h"
#include "chat_helpers/emoji_keywords.h"
#include "chat_helpers/stickers_emoji_image_loader.h"
#include "base/platform/base_platform_info.h"
@ -213,6 +214,23 @@ void Application::run() {
startEmojiImageLoader();
Media::Player::start(_audio.get());
const auto darkModeChanged = [] {
const auto darkMode = Core::App().settings().systemDarkMode();
const auto darkModeEnabled = Core::App().settings().systemDarkModeEnabled();
if (darkModeEnabled
&& darkMode.has_value()
&& (*darkMode != Window::Theme::IsNightMode())) {
Window::Theme::ToggleNightMode();
Window::Theme::KeepApplied();
}
};
Core::App().settings().systemDarkModeChanges(
) | rpl::start_with_next(darkModeChanged, _lifetime);
Core::App().settings().systemDarkModeEnabledChanges(
) | rpl::start_with_next(darkModeChanged, _lifetime);
style::ShortAnimationPlaying(
) | rpl::start_with_next([=](bool playing) {
if (playing) {
@ -235,6 +253,7 @@ void Application::run() {
_domain->activeChanges(
) | rpl::start_with_next([=](not_null<Main::Account*> account) {
_window->showAccount(account);
Core::App().settings().setSystemDarkMode(Platform::IsDarkMode());
}, _window->widget()->lifetime());
QCoreApplication::instance()->installEventFilter(this);

View File

@ -106,7 +106,8 @@ QByteArray Settings::serialize() const {
<< qint32(_thirdColumnWidth.current())
<< qint32(_thirdSectionExtendedBy)
<< qint32(_notifyFromAll ? 1 : 0)
<< qint32(_nativeWindowFrame.current() ? 1 : 0);
<< qint32(_nativeWindowFrame.current() ? 1 : 0)
<< qint32(_systemDarkModeEnabled.current() ? 1 : 0);
}
return result;
}
@ -171,6 +172,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 thirdSectionExtendedBy = _thirdSectionExtendedBy;
qint32 notifyFromAll = _notifyFromAll ? 1 : 0;
qint32 nativeWindowFrame = _nativeWindowFrame.current() ? 1 : 0;
qint32 systemDarkModeEnabled = _systemDarkModeEnabled.current() ? 1 : 0;
stream >> themesAccentColors;
if (!stream.atEnd()) {
@ -248,6 +250,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) {
stream >> nativeWindowFrame;
}
if (!stream.atEnd()) {
stream >> systemDarkModeEnabled;
}
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()"));
@ -341,6 +346,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
}
_notifyFromAll = (notifyFromAll == 1);
_nativeWindowFrame = (nativeWindowFrame == 1);
_systemDarkModeEnabled = (systemDarkModeEnabled == 1);
}
bool Settings::chatWide() const {
@ -474,6 +480,7 @@ void Settings::resetOnLastLogout() {
_thirdColumnWidth = kDefaultThirdColumnWidth; // p-w
_notifyFromAll = true;
_tabbedReplacedWithInfo = false; // per-window
_systemDarkModeEnabled = false;
}
bool Settings::ThirdColumnByDefault() {

View File

@ -416,6 +416,30 @@ public:
[[nodiscard]] rpl::producer<bool> nativeWindowFrameChanges() const {
return _nativeWindowFrame.changes();
}
void setSystemDarkMode(std::optional<bool> value) {
_systemDarkMode = value;
}
[[nodiscard]] std::optional<bool> systemDarkMode() const {
return _systemDarkMode.current();
}
[[nodiscard]] rpl::producer<std::optional<bool>> systemDarkModeValue() const {
return _systemDarkMode.value();
}
[[nodiscard]] rpl::producer<std::optional<bool>> systemDarkModeChanges() const {
return _systemDarkMode.changes();
}
void setSystemDarkModeEnabled(bool value) {
_systemDarkModeEnabled = value;
}
[[nodiscard]] bool systemDarkModeEnabled() const {
return _systemDarkModeEnabled.current();
}
[[nodiscard]] rpl::producer<bool> systemDarkModeEnabledValue() const {
return _systemDarkModeEnabled.value();
}
[[nodiscard]] rpl::producer<bool> systemDarkModeEnabledChanges() const {
return _systemDarkModeEnabled.changes();
}
[[nodiscard]] static bool ThirdColumnByDefault();
[[nodiscard]] float64 DefaultDialogsWidthRatio();
@ -484,6 +508,8 @@ private:
rpl::variable<int> _thirdColumnWidth = kDefaultThirdColumnWidth; // p-w
bool _notifyFromAll = true;
rpl::variable<bool> _nativeWindowFrame = false;
rpl::variable<std::optional<bool>> _systemDarkMode = std::nullopt;
rpl::variable<bool> _systemDarkModeEnabled = false;
bool _tabbedReplacedWithInfo = false; // per-window
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window

View File

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/linux/linux_desktop_environment.h"
#include "platform/linux/specific_linux.h"
#include "core/sandbox.h"
#include "core/core_settings.h"
#include "core/application.h"
#include "main/main_domain.h"
#include "mainwindow.h"
@ -22,6 +23,7 @@ namespace Libs {
namespace {
bool gtkTriedToInit = false;
bool gtkLoaded = false;
bool loadLibrary(QLibrary &lib, const char *name, int version) {
#if defined DESKTOP_APP_USE_PACKAGED && !defined DESKTOP_APP_USE_PACKAGED_LAZY
@ -44,21 +46,6 @@ bool loadLibrary(QLibrary &lib, const char *name, int version) {
}
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
template <typename T>
T gtkSetting(const gchar *propertyName) {
GtkSettings *settings = gtk_settings_get_default();
T value;
g_object_get(settings, propertyName, &value, nullptr);
return value;
}
QString gtkSetting(const gchar *propertyName) {
gchararray value = gtkSetting<gchararray>(propertyName);
QString str = QString::fromUtf8(value);
g_free(value);
return str;
}
void gtkMessageHandler(
const gchar *log_domain,
GLogLevelFlags log_level,
@ -75,6 +62,7 @@ void gtkMessageHandler(
bool setupGtkBase(QLibrary &lib_gtk) {
if (!LOAD_SYMBOL(lib_gtk, "gtk_init_check", gtk_init_check)) return false;
if (!LOAD_SYMBOL(lib_gtk, "gtk_check_version", gtk_check_version)) return false;
if (!LOAD_SYMBOL(lib_gtk, "gtk_settings_get_default", gtk_settings_get_default)) return false;
if (!LOAD_SYMBOL(lib_gtk, "gtk_widget_show", gtk_widget_show)) return false;
@ -173,10 +161,12 @@ bool IconThemeShouldBeSet() {
void SetIconTheme() {
Core::Sandbox::Instance().customEnterFromEventLoop([] {
if (IconThemeShouldBeSet()) {
if (GtkSettingSupported()
&& GtkLoaded()
&& IconThemeShouldBeSet()) {
DEBUG_LOG(("Set GTK icon theme"));
QIcon::setThemeName(gtkSetting("gtk-icon-theme-name"));
QIcon::setFallbackThemeName(gtkSetting("gtk-fallback-icon-theme"));
QIcon::setThemeName(GtkSetting("gtk-icon-theme-name"));
QIcon::setFallbackThemeName(GtkSetting("gtk-fallback-icon-theme"));
Platform::SetApplicationIcon(Window::CreateIcon());
if (App::wnd()) {
App::wnd()->setWindowIcon(Window::CreateIcon());
@ -185,12 +175,19 @@ void SetIconTheme() {
}
});
}
void DarkModeChanged() {
Core::Sandbox::Instance().customEnterFromEventLoop([] {
Core::App().settings().setSystemDarkMode(Platform::IsDarkMode());
});
}
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
} // namespace
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
f_gtk_init_check gtk_init_check = nullptr;
f_gtk_check_version gtk_check_version = nullptr;
f_gtk_settings_get_default gtk_settings_get_default = nullptr;
f_gtk_widget_show gtk_widget_show = nullptr;
f_gtk_widget_hide gtk_widget_hide = nullptr;
@ -245,6 +242,10 @@ f_gdk_pixbuf_get_pixels gdk_pixbuf_get_pixels = nullptr;
f_gdk_pixbuf_get_width gdk_pixbuf_get_width = nullptr;
f_gdk_pixbuf_get_height gdk_pixbuf_get_height = nullptr;
f_gdk_pixbuf_get_rowstride gdk_pixbuf_get_rowstride = nullptr;
bool GtkLoaded() {
return gtkLoaded;
}
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
void start() {
@ -255,7 +256,6 @@ void start() {
DEBUG_LOG(("Loading libraries"));
bool gtkLoaded = false;
QLibrary lib_gtk;
lib_gtk.setLoadHints(QLibrary::DeepBindHint);
@ -284,6 +284,11 @@ void start() {
const auto settings = gtk_settings_get_default();
g_signal_connect(settings, "notify::gtk-icon-theme-name", G_CALLBACK(SetIconTheme), nullptr);
g_signal_connect(settings, "notify::gtk-theme-name", G_CALLBACK(DarkModeChanged), nullptr);
if (!gtk_check_version(3, 0, 0)) {
g_signal_connect(settings, "notify::gtk-application-prefer-dark-theme", G_CALLBACK(DarkModeChanged), nullptr);
}
} else {
LOG(("Could not load gtk-3 or gtk-x11-2.0!"));
}

View File

@ -29,6 +29,10 @@ extern "C" {
namespace Platform {
namespace Libs {
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
bool GtkLoaded();
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
void start();
template <typename Function>
@ -50,6 +54,9 @@ bool load(QLibrary &lib, const char *name, Function &func) {
typedef gboolean (*f_gtk_init_check)(int *argc, char ***argv);
extern f_gtk_init_check gtk_init_check;
typedef const gchar* (*f_gtk_check_version)(guint required_major, guint required_minor, guint required_micro);
extern f_gtk_check_version gtk_check_version;
typedef GtkSettings* (*f_gtk_settings_get_default)(void);
extern f_gtk_settings_get_default gtk_settings_get_default;
@ -252,6 +259,25 @@ extern f_gdk_pixbuf_get_height gdk_pixbuf_get_height;
typedef int (*f_gdk_pixbuf_get_rowstride)(const GdkPixbuf *pixbuf);
extern f_gdk_pixbuf_get_rowstride gdk_pixbuf_get_rowstride;
inline bool GtkSettingSupported() {
return gtk_settings_get_default != nullptr;
}
template <typename T>
inline T GtkSetting(const gchar *propertyName) {
GtkSettings *settings = gtk_settings_get_default();
T value;
g_object_get(settings, propertyName, &value, nullptr);
return value;
}
inline QString GtkSetting(const gchar *propertyName) {
gchararray value = GtkSetting<gchararray>(propertyName);
QString str = QString::fromUtf8(value);
g_free(value);
return str;
}
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
} // namespace Libs

View File

@ -869,6 +869,27 @@ std::optional<crl::time> LastUserInputTime() {
return std::nullopt;
}
std::optional<bool> IsDarkMode() {
#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION
if (Libs::GtkSettingSupported()
&& Libs::GtkLoaded()) {
if (Libs::gtk_check_version != nullptr
&& !Libs::gtk_check_version(3, 0, 0)
&& Libs::GtkSetting<gboolean>("gtk-application-prefer-dark-theme")) {
return true;
}
if (Libs::GtkSetting("gtk-theme-name").toLower().endsWith(qsl("-dark"))) {
return true;
}
return false;
}
#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION
return std::nullopt;
}
bool AutostartSupported() {
// snap sandbox doesn't allow creating files in folders with names started with a dot
// and doesn't provide any api to add an app to autostart

View File

@ -18,6 +18,10 @@ namespace Platform {
void RemoveQuarantine(const QString &path);
inline std::optional<bool> IsDarkMode() {
return std::nullopt;
}
inline void FallbackFontConfigCheckBegin() {
}

View File

@ -41,6 +41,11 @@ bool OpenSystemSettings(SystemSettingsType type);
return LastUserInputTime().has_value();
}
[[nodiscard]] std::optional<bool> IsDarkMode();
[[nodiscard]] inline bool IsDarkModeSupported() {
return IsDarkMode().has_value();
}
void IgnoreApplicationActivationRightNow();
bool AutostartSupported();
QImage GetImageFromClipboard();

View File

@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/localstorage.h"
#include "core/crash_reports.h"
#include <QtCore/QOperatingSystemVersion>
#include <QtWidgets/QApplication>
#include <QtWidgets/QDesktopWidget>
#include <QtGui/QDesktopServices>
@ -382,6 +383,30 @@ std::optional<crl::time> LastUserInputTime() {
return LastTrackedWhen;
}
std::optional<bool> IsDarkMode() {
if (QOperatingSystemVersion::current()
< QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 17763)) {
return std::nullopt;
}
LPCWSTR lpKeyName = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
LPCWSTR lpValueName = L"AppsUseLightTheme";
HKEY key;
auto result = RegOpenKeyEx(HKEY_CURRENT_USER, lpKeyName, 0, KEY_READ, &key);
if (result != ERROR_SUCCESS) {
return std::nullopt;
}
DWORD value = 0, type = 0, size = sizeof(value);
result = RegQueryValueEx(key, lpValueName, 0, &type, (LPBYTE)&value, &size);
RegCloseKey(key);
if (result != ERROR_SUCCESS) {
return std::nullopt;
}
return value == 0;
}
bool AutostartSupported() {
return !IsWindowsStoreBuild();
}

View File

@ -8,7 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/win/windows_event_filter.h"
#include "platform/win/windows_dlls.h"
#include "platform/win/specific_win.h"
#include "core/sandbox.h"
#include "core/core_settings.h"
#include "core/application.h"
#include "ui/inactive_press.h"
#include "mainwindow.h"
@ -274,6 +276,10 @@ bool EventFilter::mainWindowEvent(
}
} return true;
case WM_SETTINGCHANGE: {
Core::App().settings().setSystemDarkMode(Platform::IsDarkMode());
} return false;
}
return false;
}

View File

@ -120,6 +120,14 @@ auto GenerateCodes() {
window->showSettings(Settings::Type::Folders);
}
});
codes.emplace(qsl("autodark"), [](SessionController *window) {
auto text = Core::App().settings().systemDarkModeEnabled() ? qsl("Disable system dark mode?") : qsl("Enable system dark mode?");
Ui::show(Box<ConfirmBox>(text, [=] {
Core::App().settings().setSystemDarkModeEnabled(!Core::App().settings().systemDarkModeEnabled());
Core::App().saveSettingsDelayed();
Ui::hideLayer();
}));
});
#ifndef TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME
codes.emplace(qsl("registertg"), [](SessionController *window) {

View File

@ -902,6 +902,19 @@ void MainMenu::refreshMenu() {
*_nightThemeAction = action;
action->setCheckable(true);
action->setChecked(Window::Theme::IsNightMode());
Core::App().settings().systemDarkModeValue(
) | rpl::start_with_next([=](std::optional<bool> darkMode) {
const auto darkModeEnabled = Core::App().settings().systemDarkModeEnabled();
if (darkModeEnabled && darkMode.has_value()) {
action->setChecked(*darkMode);
}
action->setEnabled(!darkModeEnabled || !darkMode.has_value());
}, lifetime());
Core::App().settings().systemDarkModeEnabledChanges(
) | rpl::start_with_next([=](bool darkModeEnabled) {
const auto darkMode = Core::App().settings().systemDarkMode();
action->setEnabled(!darkModeEnabled || !darkMode.has_value());
}, lifetime());
_menu->finishAnimating();
updatePhone();