Skip to content

Commit 0894f79

Browse files
committed
search for dark or light mode stylesheet
summary: ------- This commit adds xdg-desktop-portal support to waybar. If a portal supporting `org.freedesktop.portal.Settings` exists, then it will be queried for the current colorscheme. This colorscheme will then be used to prefer a `style-light.css` or `style-dark.css` over the basic `style.css`. technical details: ----------------- Appearance is provided by several libraries, such as libhandy (mobile) and libadwaita. However, waybar links to neither of these libraries. As the amount of code required to communicate with xdg-desktop portal as a client is rather minimal, I believe doing so is better than linking to an additional library. The Gio library for communicating with dbus is rather messy, Instead of the `Portal` class containing a `Gio::Dbus::Proxy`, it extends it which simplifies signal handling. `Portal` then exposes its own signal, which can be listened to by waybar to update CSS. test plan: --------- If no desktop portal which provides `Settings` exists, then waybar continues with the log line ``` [2023-09-06 14:14:37.754] [info] Unable to receive desktop appearance: GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: No such interface “org.freedesktop.portal.Settings” on object at path /org/freedesktop/portal/desktop ``` Furthermore, if `style-light.css` or `style-dark.css` do not exist, then `style.css` will still be searched for. Finally, waybar has been tested with both light and dark startup. E.g. if the appearance is dark on startup the log lines ``` [2023-09-06 14:27:45.379] [info] Discovered appearance 'dark' [2023-09-06 14:27:45.379] [debug] Try expanding: $XDG_CONFIG_HOME/waybar/style-dark.css [2023-09-06 14:27:45.379] [debug] Found config file: $XDG_CONFIG_HOME/waybar/style-dark.css [2023-09-06 14:27:45.379] [info] Using CSS file /home/pounce/.config/waybar/style-dark.css ``` will be observed. If the color then changes to light during the operation of waybar, it will change css files: ``` [2023-09-06 14:28:17.173] [info] Received new appearance 'dark' [2023-09-06 14:28:17.173] [debug] Try expanding: $XDG_CONFIG_HOME/waybar/style-light.css [2023-09-06 14:28:17.173] [debug] Found config file: $XDG_CONFIG_HOME/waybar/style-light.css [2023-09-06 14:28:17.173] [info] Using CSS file /home/pounce/.config/waybar/style-light.css ``` Finally, tested resetting waybar and toggling style (works, and style is only changed once). fixes: Alexays#1973
1 parent 8eb614f commit 0894f79

File tree

5 files changed

+188
-4
lines changed

5 files changed

+188
-4
lines changed

include/client.hpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "bar.hpp"
99
#include "config.hpp"
10+
#include "util/portal.hpp"
1011

1112
struct zwlr_layer_shell_v1;
1213
struct zwp_idle_inhibitor_v1;
@@ -33,7 +34,7 @@ class Client {
3334

3435
private:
3536
Client() = default;
36-
const std::string getStyle(const std::string &style);
37+
const std::string getStyle(const std::string &style, std::optional<Appearance> appearance);
3738
void bindInterfaces();
3839
void handleOutput(struct waybar_output &output);
3940
auto setupCss(const std::string &css_file) -> void;
@@ -46,12 +47,14 @@ class Client {
4647
static void handleOutputDone(void *, struct zxdg_output_v1 *);
4748
static void handleOutputName(void *, struct zxdg_output_v1 *, const char *);
4849
static void handleOutputDescription(void *, struct zxdg_output_v1 *, const char *);
50+
void handleAppearanceChanged(waybar::Appearance appearance);
4951
void handleMonitorAdded(Glib::RefPtr<Gdk::Monitor> monitor);
5052
void handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor);
5153
void handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor);
5254

5355
Glib::RefPtr<Gtk::StyleContext> style_context_;
5456
Glib::RefPtr<Gtk::CssProvider> css_provider_;
57+
std::unique_ptr<Portal> portal;
5558
std::list<struct waybar_output> outputs_;
5659
};
5760

include/util/portal.hpp

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#include <giomm/dbusproxy.h>
2+
3+
#include <string>
4+
5+
#include "fmt/format.h"
6+
7+
namespace waybar {
8+
9+
using namespace Gio;
10+
11+
enum class Appearance {
12+
UNKNOWN = 0,
13+
DARK = 1,
14+
LIGHT = 2,
15+
};
16+
class Portal : private DBus::Proxy {
17+
public:
18+
Portal();
19+
void refreshAppearance();
20+
Appearance getAppearance();
21+
22+
typedef sigc::signal<void, Appearance> type_signal_appearance_changed;
23+
type_signal_appearance_changed signal_appearance_changed() { return m_signal_appearance_changed; }
24+
25+
private:
26+
type_signal_appearance_changed m_signal_appearance_changed;
27+
Appearance currentMode;
28+
void on_signal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
29+
const Glib::VariantContainerBase& parameters);
30+
};
31+
32+
} // namespace waybar
33+
34+
template <>
35+
struct fmt::formatter<waybar::Appearance> : formatter<fmt::string_view> {
36+
// parse is inherited from formatter<string_view>.
37+
auto format(waybar::Appearance c, format_context& ctx) const;
38+
};

meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ src_files = files(
171171
'src/client.cpp',
172172
'src/config.cpp',
173173
'src/group.cpp',
174+
'src/util/portal.cpp',
174175
'src/util/prepare_for_sleep.cpp',
175176
'src/util/ustring_clen.cpp',
176177
'src/util/sanitize_str.cpp',

src/client.cpp

+32-3
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,26 @@ void waybar::Client::handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> mon
151151
outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; });
152152
}
153153

154-
const std::string waybar::Client::getStyle(const std::string &style) {
155-
auto css_file = style.empty() ? Config::findConfigPath({"style.css"}) : style;
154+
const std::string waybar::Client::getStyle(const std::string &style,
155+
std::optional<Appearance> appearance = std::nullopt) {
156+
std::optional<std::string> css_file;
157+
if (!style.empty()) {
158+
css_file = style;
159+
} else {
160+
std::vector<std::string> search_files;
161+
switch (appearance.value_or(portal->getAppearance())) {
162+
case waybar::Appearance::LIGHT:
163+
search_files.push_back("style-light.css");
164+
break;
165+
case waybar::Appearance::DARK:
166+
search_files.push_back("style-dark.css");
167+
break;
168+
case waybar::Appearance::UNKNOWN:
169+
break;
170+
}
171+
search_files.push_back("style.css");
172+
css_file = Config::findConfigPath(search_files);
173+
}
156174
if (!css_file) {
157175
throw std::runtime_error("Missing required resource files");
158176
}
@@ -235,13 +253,24 @@ int waybar::Client::main(int argc, char *argv[]) {
235253
}
236254
wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj());
237255
config.load(config_opt);
256+
if (!portal) {
257+
portal = std::make_unique<waybar::Portal>();
258+
}
238259
auto css_file = getStyle(style_opt);
239260
setupCss(css_file);
261+
portal->signal_appearance_changed().connect([&](waybar::Appearance appearance) {
262+
auto css_file = getStyle(style_opt, appearance);
263+
setupCss(css_file);
264+
});
240265
bindInterfaces();
241266
gtk_app->hold();
242267
gtk_app->run();
243268
bars.clear();
244269
return 0;
245270
}
246271

247-
void waybar::Client::reset() { gtk_app->quit(); }
272+
void waybar::Client::reset() {
273+
gtk_app->quit();
274+
// delete signal handler for css changes
275+
portal->signal_appearance_changed().clear();
276+
}

src/util/portal.cpp

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#include <iostream>
2+
#include <string>
3+
#include <giomm/dbusproxy.h>
4+
#include <glibmm/variant.h>
5+
#include <spdlog/spdlog.h>
6+
7+
#include "util/portal.hpp"
8+
#include "fmt/format.h"
9+
10+
namespace waybar {
11+
static constexpr const char* PORTAL_BUS_NAME =
12+
"org.freedesktop.portal.Desktop";
13+
static constexpr const char* PORTAL_OBJ_PATH =
14+
"/org/freedesktop/portal/desktop";
15+
static constexpr const char* PORTAL_INTERFACE =
16+
"org.freedesktop.portal.Settings";
17+
static constexpr const char* PORTAL_NAMESPACE =
18+
"org.freedesktop.appearance";
19+
static constexpr const char* PORTAL_KEY = "color-scheme";
20+
} // waybar
21+
22+
using namespace Gio;
23+
24+
25+
auto fmt::formatter<waybar::Appearance>::format(waybar::Appearance c, format_context& ctx) const {
26+
string_view name;
27+
switch (c) {
28+
case waybar::Appearance::LIGHT: name = "light"; break;
29+
case waybar::Appearance::DARK: name = "dark"; break;
30+
default: name = "unknown"; break;
31+
}
32+
return formatter<string_view>::format(name, ctx);
33+
}
34+
35+
waybar::Portal::Portal() :
36+
DBus::Proxy(DBus::Connection::get_sync(DBus::BusType::BUS_TYPE_SESSION),
37+
PORTAL_BUS_NAME,
38+
PORTAL_OBJ_PATH,
39+
PORTAL_INTERFACE),
40+
currentMode(Appearance::UNKNOWN) {
41+
refreshAppearance();
42+
};
43+
44+
void waybar::Portal::refreshAppearance() {
45+
auto params = Glib::Variant<std::tuple<Glib::ustring,Glib::ustring>>::create(
46+
{PORTAL_NAMESPACE,PORTAL_KEY}
47+
);
48+
Glib::VariantBase response;
49+
try {
50+
response = call_sync(
51+
std::string(PORTAL_INTERFACE) + ".Read",
52+
params
53+
);
54+
} catch (const Glib::Error& e) {
55+
spdlog::info("Unable to receive desktop appearance: {}", std::string(e.what()));
56+
return;
57+
}
58+
59+
// unfortunately, the response is triple-nested, with type (v<v<uint32_t>>),
60+
// so we have cast thrice. This is a variation from the freedesktop standard
61+
// (it should only be doubly nested) but all implementations appear to do so.
62+
//
63+
// xdg-desktop-portal 1.17 will fix this issue with a new `ReadOne` method,
64+
// but this version is not yet released.
65+
// TODO(xdg-desktop-portal v1.17): switch to ReadOne
66+
auto container = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(response);
67+
Glib::VariantBase modev;
68+
container.get_child(modev, 0);
69+
auto mode = Glib::VariantBase::cast_dynamic<
70+
Glib::Variant<Glib::Variant<Glib::Variant<uint32_t>>>
71+
>(modev).get().get().get();
72+
auto newMode = Appearance(mode);
73+
if (newMode == currentMode) {
74+
return;
75+
}
76+
spdlog::info("Discovered appearance '{}'", newMode);
77+
currentMode = newMode;
78+
m_signal_appearance_changed.emit(currentMode);
79+
}
80+
81+
waybar::Appearance waybar::Portal::getAppearance() {
82+
return currentMode;
83+
};
84+
85+
void waybar::Portal::on_signal (const Glib::ustring& sender_name,
86+
const Glib::ustring& signal_name,
87+
const Glib::VariantContainerBase& parameters) {
88+
spdlog::debug("Received signal {}", (std::string)signal_name);
89+
if (signal_name != "SettingChanged" || parameters.get_n_children() != 3) {
90+
return;
91+
}
92+
Glib::VariantBase nspcv, keyv, valuev;
93+
parameters.get_child(nspcv, 0);
94+
parameters.get_child(keyv, 1);
95+
parameters.get_child(valuev, 2);
96+
auto nspc =
97+
Glib::VariantBase::cast_dynamic<Glib::Variant<std::string>>(nspcv).get();
98+
auto key =
99+
Glib::VariantBase::cast_dynamic<Glib::Variant<std::string>>(keyv).get();
100+
if (nspc != PORTAL_NAMESPACE || key != PORTAL_KEY) {
101+
return;
102+
}
103+
auto value = Glib::VariantBase::cast_dynamic<
104+
Glib::Variant<Glib::Variant<uint32_t>>
105+
>(valuev).get().get();
106+
auto newMode = Appearance(value);
107+
if (newMode == currentMode) {
108+
return;
109+
}
110+
spdlog::info("Received new appearance '{}'", newMode);
111+
currentMode = newMode;
112+
m_signal_appearance_changed.emit(currentMode);
113+
}

0 commit comments

Comments
 (0)