Skip to content

Commit

Permalink
Auto dark mode (#529,#537)
Browse files Browse the repository at this point in the history
  • Loading branch information
rcaelers committed Sep 17, 2024
1 parent 44811aa commit 949884c
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 74 deletions.
14 changes: 11 additions & 3 deletions changes.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
---
releases:
- version: 1.11.0-beta.15
date: 2024-09-22T00:00:00+02:00
short: |-
Workrave 1.11.0-beta.15 has been released.
changes:
- Follow OS dark/light mode on Windows (#529)
- Fix dark mode on Windows (#537)

- version: 1.11.0-beta.14
date: 2024-06-15T14:29:23+02:00
short: |-
Expand All @@ -13,7 +21,7 @@ releases:
short: |-
Workrave 1.11.0-beta.13 has been released.
changes:
- Improved handling of hibernate/suspend (#539, #540)
- Improved handling of hibernate/suspend (#539,#540)

- version: 1.11.0-beta.12
date: 2024-03-21T19:48:51+01:00
Expand Down Expand Up @@ -165,11 +173,11 @@ releases:
- Major internal code cleanup and code modernization.
- Initial (incomplete) port to macOS using Qt6
- The default sound theme was changed to the "Subtle" theme (#362)
- You can now change the operation mode for a limited amount of time (#98, #305)
- You can now change the operation mode for a limited amount of time (#98,#305)
- A portable version is now available for Windows and Linux (#180)
- Workrave now follows the XDG basedirectory specification (#192)
- The Windows version is now 64 bit (#295)
- Workrave can now automatically change the operation mode based on Windows Focus Assist (#255, #226)
- Workrave can now automatically change the operation mode based on Windows Focus Assist (#255,#226)
- A dark UI theme is now supported on Windows (#214)
- |-
Bug fixes:
Expand Down
92 changes: 48 additions & 44 deletions ui/app/GUIConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,44 +26,44 @@
#include "core/ICore.hh"
#include "core/IBreak.hh"

using namespace std;
using namespace workrave::config;

const string GUIConfig::CFG_KEY_BREAK_IGNORABLE = "gui/breaks/%b/ignorable_break";
const string GUIConfig::CFG_KEY_BREAK_SKIPPABLE = "gui/breaks/%b/skippable_break";
const string GUIConfig::CFG_KEY_BREAK_ENABLE_SHUTDOWN = "gui/breaks/%b/enable_shutdown";
const string GUIConfig::CFG_KEY_BREAK_EXERCISES = "gui/breaks/%b/exercises";
const string GUIConfig::CFG_KEY_BREAK_AUTO_NATURAL = "gui/breaks/%b/auto_natural";
const string GUIConfig::CFG_KEY_BLOCK_MODE = "gui/breaks/block_mode";
const string GUIConfig::CFG_KEY_FOCUS_MODE = "gui/breaks/focus_mode";
const string GUIConfig::CFG_KEY_FOLLOW_FOCUS_ASSIST_ENABLED = "gui/breaks/follow_focus_assist_enabled";
const string GUIConfig::CFG_KEY_LOCALE = "gui/locale";
const string GUIConfig::CFG_KEY_TRAYICON_ENABLED = "gui/trayicon_enabled";
const string GUIConfig::CFG_KEY_CLOSEWARN_ENABLED = "gui/closewarn_enabled";
const string GUIConfig::CFG_KEY_AUTOSTART = "gui/autostart";
const string GUIConfig::CFG_KEY_ICONTHEME = "gui/icontheme";
const string GUIConfig::CFG_KEY_THEME_NAME = "gui/theme_name";
const string GUIConfig::CFG_KEY_THEME_DARK = "gui/theme_dark";
const string GUIConfig::CFG_KEY_FORCE_X11 = "gui/force_x11";

const string GUIConfig::CFG_KEY_MAIN_WINDOW = "gui/main_window";
const string GUIConfig::CFG_KEY_MAIN_WINDOW_ALWAYS_ON_TOP = "gui/main_window/always_on_top";
const string GUIConfig::CFG_KEY_MAIN_WINDOW_START_IN_TRAY = "gui/main_window/start_in_tray";
const string GUIConfig::CFG_KEY_MAIN_WINDOW_X = "gui/main_window/x";
const string GUIConfig::CFG_KEY_MAIN_WINDOW_Y = "gui/main_window/y";
const string GUIConfig::CFG_KEY_MAIN_WINDOW_HEAD = "gui/main_window/head";

const string GUIConfig::CFG_KEY_APPLET_FALLBACK_ENABLED = "gui/applet/fallback_enabled";
const string GUIConfig::CFG_KEY_APPLET_ICON_ENABLED = "gui/applet/icon_enabled";

const string GUIConfig::CFG_KEY_TIMERBOX = "gui/";
const string GUIConfig::CFG_KEY_TIMERBOX_CYCLE_TIME = "/cycle_time";
const string GUIConfig::CFG_KEY_TIMERBOX_ENABLED = "/enabled";
const string GUIConfig::CFG_KEY_TIMERBOX_POSITION = "/position";
const string GUIConfig::CFG_KEY_TIMERBOX_FLAGS = "/flags";
const string GUIConfig::CFG_KEY_TIMERBOX_IMMINENT = "/imminent";

string
const std::string GUIConfig::CFG_KEY_BREAK_IGNORABLE = "gui/breaks/%b/ignorable_break";
const std::string GUIConfig::CFG_KEY_BREAK_SKIPPABLE = "gui/breaks/%b/skippable_break";
const std::string GUIConfig::CFG_KEY_BREAK_ENABLE_SHUTDOWN = "gui/breaks/%b/enable_shutdown";
const std::string GUIConfig::CFG_KEY_BREAK_EXERCISES = "gui/breaks/%b/exercises";
const std::string GUIConfig::CFG_KEY_BREAK_AUTO_NATURAL = "gui/breaks/%b/auto_natural";
const std::string GUIConfig::CFG_KEY_BLOCK_MODE = "gui/breaks/block_mode";
const std::string GUIConfig::CFG_KEY_FOCUS_MODE = "gui/breaks/focus_mode";
const std::string GUIConfig::CFG_KEY_FOLLOW_FOCUS_ASSIST_ENABLED = "gui/breaks/follow_focus_assist_enabled";
const std::string GUIConfig::CFG_KEY_LOCALE = "gui/locale";
const std::string GUIConfig::CFG_KEY_TRAYICON_ENABLED = "gui/trayicon_enabled";
const std::string GUIConfig::CFG_KEY_CLOSEWARN_ENABLED = "gui/closewarn_enabled";
const std::string GUIConfig::CFG_KEY_AUTOSTART = "gui/autostart";
const std::string GUIConfig::CFG_KEY_ICONTHEME = "gui/icontheme";
const std::string GUIConfig::CFG_KEY_THEME_NAME = "gui/theme_name";
const std::string GUIConfig::CFG_KEY_THEME_DARK = "gui/theme_dark";
const std::string GUIConfig::CFG_KEY_LIGHT_DARK_MODE = "gui/light_dark_mode";
const std::string GUIConfig::CFG_KEY_FORCE_X11 = "gui/force_x11";

const std::string GUIConfig::CFG_KEY_MAIN_WINDOW = "gui/main_window";
const std::string GUIConfig::CFG_KEY_MAIN_WINDOW_ALWAYS_ON_TOP = "gui/main_window/always_on_top";
const std::string GUIConfig::CFG_KEY_MAIN_WINDOW_START_IN_TRAY = "gui/main_window/start_in_tray";
const std::string GUIConfig::CFG_KEY_MAIN_WINDOW_X = "gui/main_window/x";
const std::string GUIConfig::CFG_KEY_MAIN_WINDOW_Y = "gui/main_window/y";
const std::string GUIConfig::CFG_KEY_MAIN_WINDOW_HEAD = "gui/main_window/head";

const std::string GUIConfig::CFG_KEY_APPLET_FALLBACK_ENABLED = "gui/applet/fallback_enabled";
const std::string GUIConfig::CFG_KEY_APPLET_ICON_ENABLED = "gui/applet/icon_enabled";

const std::string GUIConfig::CFG_KEY_TIMERBOX = "gui/";
const std::string GUIConfig::CFG_KEY_TIMERBOX_CYCLE_TIME = "/cycle_time";
const std::string GUIConfig::CFG_KEY_TIMERBOX_ENABLED = "/enabled";
const std::string GUIConfig::CFG_KEY_TIMERBOX_POSITION = "/position";
const std::string GUIConfig::CFG_KEY_TIMERBOX_FLAGS = "/flags";
const std::string GUIConfig::CFG_KEY_TIMERBOX_IMMINENT = "/imminent";

std::string
GUIConfig::get_break_name(workrave::BreakId id)
{
std::array<const char *, 3> names{"micro_pause", "rest_break", "daily_limit"};
Expand Down Expand Up @@ -101,16 +101,20 @@ GUIConfig::init(std::shared_ptr<workrave::config::IConfigurator> config)
config->set_value(CFG_KEY_CLOSEWARN_ENABLED, true, workrave::config::CONFIG_FLAG_INITIAL);
config->set_value(CFG_KEY_AUTOSTART, true, CONFIG_FLAG_INITIAL);
config->set_value(CFG_KEY_LOCALE, "", CONFIG_FLAG_INITIAL);

bool dark = false;
config->get_value_with_default(CFG_KEY_THEME_DARK, dark, false);
config->set_value(CFG_KEY_LIGHT_DARK_MODE, dark ? 1 : 0, CONFIG_FLAG_INITIAL);
}

std::string
GUIConfig::expand(const std::string &key, workrave::BreakId id)
{
string str = key;
string::size_type pos = 0;
string name = GUIConfig::get_break_name(id);
std::string str = key;
std::string::size_type pos = 0;
std::string name = GUIConfig::get_break_name(id);

while ((pos = str.find("%b", pos)) != string::npos)
while ((pos = str.find("%b", pos)) != std::string::npos)
{
str.replace(pos, 2, name);
pos++;
Expand Down Expand Up @@ -198,15 +202,15 @@ GUIConfig::icon_theme() -> Setting<std::string> &
}

auto
GUIConfig::theme_dark() -> workrave::config::Setting<bool> &
GUIConfig::theme_name() -> workrave::config::Setting<std::string> &
{
return SettingCache::get<bool>(config, CFG_KEY_THEME_DARK, false);
return SettingCache::get<std::string>(config, CFG_KEY_THEME_NAME, std::string());
}

auto
GUIConfig::theme_name() -> workrave::config::Setting<std::string> &
GUIConfig::light_dark_mode() -> workrave::config::Setting<int, LightDarkTheme> &
{
return SettingCache::get<std::string>(config, CFG_KEY_THEME_NAME, std::string());
return SettingCache::get<int, LightDarkTheme>(config, CFG_KEY_LIGHT_DARK_MODE, LightDarkTheme::Auto);
}

auto
Expand Down
14 changes: 10 additions & 4 deletions ui/app/include/ui/GUIConfig.hh
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,9 @@
#include <iostream>

#include "config/IConfigurator.hh"
#include "core/ICore.hh"
#include "core/CoreTypes.hh"
#include "config/Setting.hh"

#include "ui/IApplicationContext.hh"

enum class BlockMode
{
Off = 0,
Expand All @@ -39,6 +37,13 @@ enum class FocusMode
Quiet
};

enum class LightDarkTheme
{
Light,
Dark,
Auto
};

class GUIConfig
{
public:
Expand All @@ -65,8 +70,8 @@ public:
static workrave::config::Setting<bool> &closewarn_enabled();
static workrave::config::Setting<bool> &autostart_enabled();
static workrave::config::Setting<std::string> &icon_theme();
static workrave::config::Setting<bool> &theme_dark();
static workrave::config::Setting<std::string> &theme_name();
static workrave::config::Setting<int, LightDarkTheme> &light_dark_mode();
static workrave::config::Setting<bool> &force_x11();

static workrave::config::Setting<bool> &main_window_always_on_top();
Expand Down Expand Up @@ -105,6 +110,7 @@ private:
static const std::string CFG_KEY_ICONTHEME;
static const std::string CFG_KEY_THEME_NAME;
static const std::string CFG_KEY_THEME_DARK;
static const std::string CFG_KEY_LIGHT_DARK_MODE;
static const std::string CFG_KEY_FORCE_X11;

static const std::string CFG_KEY_MAIN_WINDOW;
Expand Down
17 changes: 16 additions & 1 deletion ui/app/toolkits/gtkmm/AutoUpdateDialog.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#if defined(PLATFORM_OS_WINDOWS)
# include "cmark.h"
# include "Edge.hh"
# include "ToolkitWindows.hh"

static constexpr const char *doc =
R"(<!DOCTYPE html>
Expand Down Expand Up @@ -171,7 +172,21 @@ AutoUpdateDialog::AutoUpdateDialog(std::shared_ptr<unfold::UpdateInfo> info, Aut
}
}

web->set_content(fmt::format(doc, GUIConfig::theme_dark()() ? darkstyle : lightstyle, body));
bool dark = false;
switch (GUIConfig::light_dark_mode()())
{
case LightDarkTheme::Light:
dark = false;
break;
case LightDarkTheme::Dark:
dark = true;
break;
case LightDarkTheme::Auto:
dark = ToolkitWindows::is_windows_app_theme_dark();
break;
}

web->set_content(fmt::format(doc, dark ? darkstyle : lightstyle, body));
notes_frame->add(*web);
}
else
Expand Down
76 changes: 62 additions & 14 deletions ui/app/toolkits/gtkmm/ToolkitWindows.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

#include "ToolkitWindows.hh"

#include <cstddef>
#include <gdk/gdk.h>
#include <gdk/gdkwin32.h>

#ifndef PLATFORM_OS_WINDOWS_NATIVE
Expand Down Expand Up @@ -58,7 +60,8 @@ ToolkitWindows::init(std::shared_ptr<IApplicationContext> app)
// No auto hide scrollbars
g_setenv("GTK_OVERLAY_SCROLLING", "0", TRUE);
// No Windows-7 style client-side decorations on Windows 10...
g_setenv("GTK_CSD", "0", TRUE);
// TODO: check if still needed.
// g_setenv("GTK_CSD", "0", TRUE);

Toolkit::init(app);

Expand Down Expand Up @@ -96,22 +99,39 @@ void
ToolkitWindows::init_gui()
{
auto settings = Gtk::Settings::get_default();
settings->property_gtk_application_prefer_dark_theme().set_value(GUIConfig::theme_dark()());
std::string theme_name = GUIConfig::theme_name()();
if (!theme_name.empty())
{
settings->property_gtk_theme_name().set_value(theme_name);
}

settings->property_gtk_application_prefer_dark_theme().signal_changed().connect(
[settings]() { GUIConfig::theme_dark().set(settings->property_gtk_application_prefer_dark_theme().get_value()); });
settings->property_gtk_theme_name().signal_changed().connect(
[settings]() { GUIConfig::theme_name().set(settings->property_gtk_theme_name().get_value()); });
GUIConfig::light_dark_mode().attach(tracker, [settings](auto dark) {
switch (dark)

{
case LightDarkTheme::Light:
settings->property_gtk_application_prefer_dark_theme().set_value(false);
break;
case LightDarkTheme::Dark:
settings->property_gtk_application_prefer_dark_theme().set_value(true);
break;
case LightDarkTheme::Auto:
settings->property_gtk_application_prefer_dark_theme().set_value(is_windows_app_theme_dark());
break;
}
});

GUIConfig::theme_name().attach(tracker, [settings](auto name) {
if (!name.empty())
{
settings->property_gtk_theme_name().set_value(name);
}
});

GUIConfig::theme_dark().connect(tracker, [settings](auto dark) {
settings->property_gtk_application_prefer_dark_theme().set_value(dark);
settings->property_gtk_application_prefer_dark_theme().signal_changed().connect([settings]() {
if (GUIConfig::light_dark_mode()() != LightDarkTheme::Auto)
{
GUIConfig::light_dark_mode().set(
settings->property_gtk_application_prefer_dark_theme().get_value() ? LightDarkTheme::Dark : LightDarkTheme::Light);
}
});
GUIConfig::theme_name().connect(tracker, [settings](auto name) { settings->property_gtk_theme_name().set_value(name); });
settings->property_gtk_theme_name().signal_changed().connect(
[settings]() { GUIConfig::theme_name().set(settings->property_gtk_theme_name().get_value()); });
}

void
Expand Down Expand Up @@ -229,6 +249,19 @@ ToolkitWindows::filter_func(MSG *msg)
}
break;

case WM_SETTINGCHANGE:
{
if (msg->lParam != 0 && _wcsicmp(L"ImmersiveColorSet", reinterpret_cast<wchar_t *>(msg->lParam)) == 0)
{
if (GUIConfig::light_dark_mode()() == LightDarkTheme::Auto)
{
logger->info("Theme change detected: switching to {} theme", is_windows_app_theme_dark() ? "dark" : "light");
auto settings = Gtk::Settings::get_default();
settings->property_gtk_application_prefer_dark_theme().set_value(is_windows_app_theme_dark());
}
}
}

case WM_DEVICECHANGE:
{
TRACE_MSG("WM_DEVICECHANGE {} {}", msg->wParam, msg->lParam);
Expand Down Expand Up @@ -268,3 +301,18 @@ ToolkitWindows::get_locker()
{
return locker;
}

bool
ToolkitWindows::is_windows_app_theme_dark()
{
DWORD value = 1; // Default to light theme
DWORD dataSize = sizeof(value);
HKEY hKey = nullptr;
if (RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", 0, KEY_READ, &hKey)
== ERROR_SUCCESS)
{
RegQueryValueExW(hKey, L"AppsUseLightTheme", NULL, NULL, (LPBYTE)&value, &dataSize);
RegCloseKey(hKey);
}
return value == 0;
}
2 changes: 2 additions & 0 deletions ui/app/toolkits/gtkmm/ToolkitWindows.hh
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public:
boost::signals2::signal<bool(MSG *msg), IToolkitWindows::event_combiner> &hook_event() override;
HWND get_event_hwnd() const override;

static bool is_windows_app_theme_dark();

private:
void init_filter();
void init_gui();
Expand Down
Loading

0 comments on commit 949884c

Please sign in to comment.