diff --git a/include/client.hpp b/include/client.hpp index aaba3b6b..9ec29ef3 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -7,6 +7,7 @@ #include "bar.hpp" #include "config.hpp" +#include "util/portal.hpp" struct zwlr_layer_shell_v1; struct zwp_idle_inhibitor_v1; @@ -33,7 +34,7 @@ class Client { private: Client() = default; - const std::string getStyle(const std::string &style); + const std::string getStyle(const std::string &style, std::optional appearance); void bindInterfaces(); void handleOutput(struct waybar_output &output); auto setupCss(const std::string &css_file) -> void; @@ -52,6 +53,7 @@ class Client { Glib::RefPtr style_context_; Glib::RefPtr css_provider_; + std::unique_ptr portal; std::list outputs_; }; diff --git a/include/util/portal.hpp b/include/util/portal.hpp new file mode 100644 index 00000000..23619169 --- /dev/null +++ b/include/util/portal.hpp @@ -0,0 +1,38 @@ +#include + +#include + +#include "fmt/format.h" + +namespace waybar { + +using namespace Gio; + +enum class Appearance { + UNKNOWN = 0, + DARK = 1, + LIGHT = 2, +}; +class Portal : private DBus::Proxy { + public: + Portal(); + void refreshAppearance(); + Appearance getAppearance(); + + typedef sigc::signal type_signal_appearance_changed; + type_signal_appearance_changed signal_appearance_changed() { return m_signal_appearance_changed; } + + private: + type_signal_appearance_changed m_signal_appearance_changed; + Appearance currentMode; + void on_signal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, + const Glib::VariantContainerBase& parameters); +}; + +} // namespace waybar + +template <> +struct fmt::formatter : formatter { + // parse is inherited from formatter. + auto format(waybar::Appearance c, format_context& ctx) const; +}; diff --git a/meson.build b/meson.build index b27bc05b..9ccd83d8 100644 --- a/meson.build +++ b/meson.build @@ -171,6 +171,7 @@ src_files = files( 'src/client.cpp', 'src/config.cpp', 'src/group.cpp', + 'src/util/portal.cpp', 'src/util/enum.cpp', 'src/util/prepare_for_sleep.cpp', 'src/util/ustring_clen.cpp', diff --git a/src/client.cpp b/src/client.cpp index a815e2fe..cd0fa55b 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -151,8 +151,26 @@ void waybar::Client::handleDeferredMonitorRemoval(Glib::RefPtr mon outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; }); } -const std::string waybar::Client::getStyle(const std::string &style) { - auto css_file = style.empty() ? Config::findConfigPath({"style.css"}) : style; +const std::string waybar::Client::getStyle(const std::string &style, + std::optional appearance = std::nullopt) { + std::optional css_file; + if (style.empty()) { + std::vector search_files; + switch (appearance.value_or(portal->getAppearance())) { + case waybar::Appearance::LIGHT: + search_files.push_back("style-light.css"); + break; + case waybar::Appearance::DARK: + search_files.push_back("style-dark.css"); + break; + case waybar::Appearance::UNKNOWN: + break; + } + search_files.push_back("style.css"); + css_file = Config::findConfigPath(search_files); + } else { + css_file = style; + } if (!css_file) { throw std::runtime_error("Missing required resource files"); } @@ -235,8 +253,15 @@ int waybar::Client::main(int argc, char *argv[]) { } wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj()); config.load(config_opt); + if (!portal) { + portal = std::make_unique(); + } auto css_file = getStyle(style_opt); setupCss(css_file); + portal->signal_appearance_changed().connect([&](waybar::Appearance appearance) { + auto css_file = getStyle(style_opt, appearance); + setupCss(css_file); + }); bindInterfaces(); gtk_app->hold(); gtk_app->run(); @@ -244,4 +269,8 @@ int waybar::Client::main(int argc, char *argv[]) { return 0; } -void waybar::Client::reset() { gtk_app->quit(); } +void waybar::Client::reset() { + gtk_app->quit(); + // delete signal handler for css changes + portal->signal_appearance_changed().clear(); +} diff --git a/src/util/portal.cpp b/src/util/portal.cpp new file mode 100644 index 00000000..50c646c5 --- /dev/null +++ b/src/util/portal.cpp @@ -0,0 +1,106 @@ +#include "util/portal.hpp" + +#include +#include +#include + +#include +#include + +#include "fmt/format.h" + +namespace waybar { +static constexpr const char* PORTAL_BUS_NAME = "org.freedesktop.portal.Desktop"; +static constexpr const char* PORTAL_OBJ_PATH = "/org/freedesktop/portal/desktop"; +static constexpr const char* PORTAL_INTERFACE = "org.freedesktop.portal.Settings"; +static constexpr const char* PORTAL_NAMESPACE = "org.freedesktop.appearance"; +static constexpr const char* PORTAL_KEY = "color-scheme"; +} // namespace waybar + +using namespace Gio; + +auto fmt::formatter::format(waybar::Appearance c, format_context& ctx) const { + string_view name; + switch (c) { + case waybar::Appearance::LIGHT: + name = "light"; + break; + case waybar::Appearance::DARK: + name = "dark"; + break; + default: + name = "unknown"; + break; + } + return formatter::format(name, ctx); +} + +waybar::Portal::Portal() + : DBus::Proxy(DBus::Connection::get_sync(DBus::BusType::BUS_TYPE_SESSION), PORTAL_BUS_NAME, + PORTAL_OBJ_PATH, PORTAL_INTERFACE), + currentMode(Appearance::UNKNOWN) { + refreshAppearance(); +}; + +void waybar::Portal::refreshAppearance() { + auto params = Glib::Variant>::create( + {PORTAL_NAMESPACE, PORTAL_KEY}); + Glib::VariantBase response; + try { + response = call_sync(std::string(PORTAL_INTERFACE) + ".Read", params); + } catch (const Glib::Error& e) { + spdlog::info("Unable to receive desktop appearance: {}", std::string(e.what())); + return; + } + + // unfortunately, the response is triple-nested, with type (v>), + // so we have cast thrice. This is a variation from the freedesktop standard + // (it should only be doubly nested) but all implementations appear to do so. + // + // xdg-desktop-portal 1.17 will fix this issue with a new `ReadOne` method, + // but this version is not yet released. + // TODO(xdg-desktop-portal v1.17): switch to ReadOne + auto container = Glib::VariantBase::cast_dynamic(response); + Glib::VariantBase modev; + container.get_child(modev, 0); + auto mode = + Glib::VariantBase::cast_dynamic>>>(modev) + .get() + .get() + .get(); + auto newMode = Appearance(mode); + if (newMode == currentMode) { + return; + } + spdlog::info("Discovered appearance '{}'", newMode); + currentMode = newMode; + m_signal_appearance_changed.emit(currentMode); +} + +waybar::Appearance waybar::Portal::getAppearance() { return currentMode; }; + +void waybar::Portal::on_signal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, + const Glib::VariantContainerBase& parameters) { + spdlog::debug("Received signal {}", (std::string)signal_name); + if (signal_name != "SettingChanged" || parameters.get_n_children() != 3) { + return; + } + Glib::VariantBase nspcv, keyv, valuev; + parameters.get_child(nspcv, 0); + parameters.get_child(keyv, 1); + parameters.get_child(valuev, 2); + auto nspc = Glib::VariantBase::cast_dynamic>(nspcv).get(); + auto key = Glib::VariantBase::cast_dynamic>(keyv).get(); + if (nspc != PORTAL_NAMESPACE || key != PORTAL_KEY) { + return; + } + auto value = + Glib::VariantBase::cast_dynamic>>(valuev).get().get(); + auto newMode = Appearance(value); + if (newMode == currentMode) { + return; + } + spdlog::info("Received new appearance '{}'", newMode); + currentMode = newMode; + m_signal_appearance_changed.emit(currentMode); +}