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. For a reference implementation, please see another one of my projects: https://github.com/4e554c4c/darkman.nvim/blob/main/portal.go 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. 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/Waybar#1973pull/2470/head
parent
8eb614f69e
commit
09873f0ed9
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "bar.hpp"
|
#include "bar.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
#include "util/portal.hpp"
|
||||||
|
|
||||||
struct zwlr_layer_shell_v1;
|
struct zwlr_layer_shell_v1;
|
||||||
struct zwp_idle_inhibitor_v1;
|
struct zwp_idle_inhibitor_v1;
|
||||||
|
@ -33,7 +34,7 @@ class Client {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Client() = default;
|
Client() = default;
|
||||||
const std::string getStyle(const std::string &style);
|
const std::string getStyle(const std::string &style, std::optional<Appearance> appearance);
|
||||||
void bindInterfaces();
|
void bindInterfaces();
|
||||||
void handleOutput(struct waybar_output &output);
|
void handleOutput(struct waybar_output &output);
|
||||||
auto setupCss(const std::string &css_file) -> void;
|
auto setupCss(const std::string &css_file) -> void;
|
||||||
|
@ -46,12 +47,14 @@ class Client {
|
||||||
static void handleOutputDone(void *, struct zxdg_output_v1 *);
|
static void handleOutputDone(void *, struct zxdg_output_v1 *);
|
||||||
static void handleOutputName(void *, struct zxdg_output_v1 *, const char *);
|
static void handleOutputName(void *, struct zxdg_output_v1 *, const char *);
|
||||||
static void handleOutputDescription(void *, struct zxdg_output_v1 *, const char *);
|
static void handleOutputDescription(void *, struct zxdg_output_v1 *, const char *);
|
||||||
|
void handleAppearanceChanged(waybar::Appearance appearance);
|
||||||
void handleMonitorAdded(Glib::RefPtr<Gdk::Monitor> monitor);
|
void handleMonitorAdded(Glib::RefPtr<Gdk::Monitor> monitor);
|
||||||
void handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor);
|
void handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor);
|
||||||
void handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor);
|
void handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor);
|
||||||
|
|
||||||
Glib::RefPtr<Gtk::StyleContext> style_context_;
|
Glib::RefPtr<Gtk::StyleContext> style_context_;
|
||||||
Glib::RefPtr<Gtk::CssProvider> css_provider_;
|
Glib::RefPtr<Gtk::CssProvider> css_provider_;
|
||||||
|
std::unique_ptr<Portal> portal;
|
||||||
std::list<struct waybar_output> outputs_;
|
std::list<struct waybar_output> outputs_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
#include <giomm/dbusproxy.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#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<void, Appearance> 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<waybar::Appearance> : formatter<fmt::string_view> {
|
||||||
|
// parse is inherited from formatter<string_view>.
|
||||||
|
auto format(waybar::Appearance c, format_context& ctx) const;
|
||||||
|
};
|
|
@ -171,6 +171,7 @@ src_files = files(
|
||||||
'src/client.cpp',
|
'src/client.cpp',
|
||||||
'src/config.cpp',
|
'src/config.cpp',
|
||||||
'src/group.cpp',
|
'src/group.cpp',
|
||||||
|
'src/util/portal.cpp',
|
||||||
'src/util/prepare_for_sleep.cpp',
|
'src/util/prepare_for_sleep.cpp',
|
||||||
'src/util/ustring_clen.cpp',
|
'src/util/ustring_clen.cpp',
|
||||||
'src/util/sanitize_str.cpp',
|
'src/util/sanitize_str.cpp',
|
||||||
|
|
|
@ -151,8 +151,26 @@ void waybar::Client::handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> mon
|
||||||
outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; });
|
outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; });
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string waybar::Client::getStyle(const std::string &style) {
|
const std::string waybar::Client::getStyle(const std::string &style,
|
||||||
auto css_file = style.empty() ? Config::findConfigPath({"style.css"}) : style;
|
std::optional<Appearance> appearance = std::nullopt) {
|
||||||
|
std::optional<std::string> css_file;
|
||||||
|
if (!style.empty()) {
|
||||||
|
css_file = style;
|
||||||
|
} else {
|
||||||
|
std::vector<std::string> 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);
|
||||||
|
}
|
||||||
if (!css_file) {
|
if (!css_file) {
|
||||||
throw std::runtime_error("Missing required resource files");
|
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());
|
wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj());
|
||||||
config.load(config_opt);
|
config.load(config_opt);
|
||||||
|
if (!portal) {
|
||||||
|
portal = std::make_unique<waybar::Portal>();
|
||||||
|
}
|
||||||
auto css_file = getStyle(style_opt);
|
auto css_file = getStyle(style_opt);
|
||||||
setupCss(css_file);
|
setupCss(css_file);
|
||||||
|
portal->signal_appearance_changed().connect([&](waybar::Appearance appearance) {
|
||||||
|
auto css_file = getStyle(style_opt, appearance);
|
||||||
|
setupCss(css_file);
|
||||||
|
});
|
||||||
bindInterfaces();
|
bindInterfaces();
|
||||||
gtk_app->hold();
|
gtk_app->hold();
|
||||||
gtk_app->run();
|
gtk_app->run();
|
||||||
|
@ -244,4 +269,8 @@ int waybar::Client::main(int argc, char *argv[]) {
|
||||||
return 0;
|
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();
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
#include "util/portal.hpp"
|
||||||
|
|
||||||
|
#include <giomm/dbusproxy.h>
|
||||||
|
#include <glibmm/variant.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#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<waybar::Appearance>::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<string_view>::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<std::tuple<Glib::ustring, Glib::ustring>>::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<v<uint32_t>>),
|
||||||
|
// 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<Glib::VariantContainerBase>(response);
|
||||||
|
Glib::VariantBase modev;
|
||||||
|
container.get_child(modev, 0);
|
||||||
|
auto mode =
|
||||||
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::Variant<Glib::Variant<uint32_t>>>>(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<Glib::Variant<std::string>>(nspcv).get();
|
||||||
|
auto key = Glib::VariantBase::cast_dynamic<Glib::Variant<std::string>>(keyv).get();
|
||||||
|
if (nspc != PORTAL_NAMESPACE || key != PORTAL_KEY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto value =
|
||||||
|
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::Variant<uint32_t>>>(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);
|
||||||
|
}
|
Loading…
Reference in New Issue