diff --git a/config/m3ui.conf.example b/config/m3ui.conf.example index 606154b..d8ad385 100644 --- a/config/m3ui.conf.example +++ b/config/m3ui.conf.example @@ -10,6 +10,7 @@ UseAccentColor=False PerScreenDPI=True ForceWaylandDPI=0 EnableFractionalScale=True +UseMonet=True Animations=True Bounce=True SpatialStiffness=2000 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9958e85..7e9b5b5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,6 +29,12 @@ if (ENABLE_DBUS) set(CLASSICUI_LIBS ${CLASSICUI_LIBS} Fcitx5::Module::DBus) endif() +# libmonet, the shared colour code, header only. build.sh fetches it at a pinned +# commit and passes the directory. +if (NOT MONET_INCLUDE_DIR OR NOT EXISTS "${MONET_INCLUDE_DIR}/monet/monet.h") + message(FATAL_ERROR "MONET_INCLUDE_DIR must point at the include directory of libmonet") +endif() + add_fcitx5_addon(m3ui classicui.cpp window.cpp theme.cpp inputwindow.cpp plasmathemewatchdog.cpp colorhelper.cpp ${CLASSICUI_SRCS} ) @@ -37,6 +43,8 @@ if (ENABLE_X11) target_compile_definitions(m3ui PRIVATE -DFCITX_XCB_EWMH) endif() +target_include_directories(m3ui PRIVATE "${MONET_INCLUDE_DIR}") + target_link_libraries(m3ui Fcitx5::Core PkgConfig::Cairo PkgConfig::Pango diff --git a/src/classicui.cpp b/src/classicui.cpp index 83595e6..40ffb8f 100644 --- a/src/classicui.cpp +++ b/src/classicui.cpp @@ -7,6 +7,7 @@ #include "classicui.h" #include +#include #include #include #include @@ -180,6 +181,7 @@ void M3UI::reloadTheme() { auto color = variant.dataAs(); auto oldIsDark = isDark_; isDark_ = (color == 1); + portalSchemeKnown_ = true; if (oldIsDark != isDark_) { CLASSICUI_DEBUG() << "XDG Portal AppearanceChanged " "isDark" @@ -189,8 +191,10 @@ void M3UI::reloadTheme() { } }; + // The portal is also how the colours for UseMonet are learned, over the + // connection Fcitx5 already has, so nothing has to be started for it. if (dbus()) { - if (*config_.useDarkTheme) { + if (*config_.useDarkTheme || *config_.useMonet) { if (!darkModeEntry_ && settingMonitor_) { darkModeEntry_ = settingMonitor_->watch( "org.freedesktop.appearance", "color-scheme", parseMessage); @@ -253,7 +257,7 @@ void M3UI::reloadTheme() { }; if (dbus()) { - if (*config_.useAccentColor) { + if (*config_.useAccentColor || *config_.useMonet) { if (!accentColorEntry_ && settingMonitor_) { accentColorEntry_ = settingMonitor_->watch("org.freedesktop.appearance", @@ -266,7 +270,53 @@ void M3UI::reloadTheme() { } #endif - theme_.populateColor(accentColor_); + if (*config_.useMonet) { + theme_.applyMonet(mapScheme(monetScheme())); + } + // The accent colour is only watched for UseMonet, it must not recolour the + // fields of a classic theme unless the user asked for that. + theme_.populateColor(*config_.useAccentColor ? accentColor_ + : std::nullopt); +} + +monet::Scheme M3UI::monetScheme() const { + // Nothing is ever started from here: a slow program would stall the input + // method. The portal is read through the connection Fcitx5 has, the other + // sources only read files. + monet::Context ctx = monet::Context::system(); + ctx.run = [](const std::vector &, int) { + return std::optional(); + }; + monet::Source portal{ + "fcitx-portal", [this](const monet::Context &) { + monet::Appearance a; + if (accentColor_) { + auto q = [](float c) { + return static_cast(std::lround(c * 255.0f)); + }; + a.accent = monet::Rgb{q(accentColor_->redF()), + q(accentColor_->greenF()), + q(accentColor_->blueF())}; + } + if (portalSchemeKnown_) { + a.mode = isDark_ ? monet::Mode::Dark : monet::Mode::Light; + } + return a; + }}; + // MONET_SEED and MONET_MODE first, then what the portal said, then the rest. + std::vector sources; + for (auto &source : monet::defaultSources(ctx)) { + sources.push_back(std::move(source)); + if (sources.back().name == "override") { + sources.push_back(portal); + } + } + const monet::Appearance a = monet::detect(ctx, sources); + CLASSICUI_DEBUG() << "Monet seed " << monet::hex(*a.accent) << " from " + << a.accentSource << ", " + << (*a.mode == monet::Mode::Dark ? "dark" : "light") + << " from " << a.modeSource; + return monet::generate(*a.accent, *a.mode); } void M3UI::suspend() { diff --git a/src/classicui.h b/src/classicui.h index 4c78be7..8941c77 100644 --- a/src/classicui.h +++ b/src/classicui.h @@ -201,6 +201,11 @@ FCITX_CONFIGURATION( "font DPI. If the value is 0, it means this option is " "disabled.")}}; + Option useMonet{ + this, "UseMonet", + _("Take the colours from the desktop: its accent colour and its light " + "or dark setting"), + true}; Option animations{this, "Animations", _("Enable animations"), true}; Option bounce{this, "Bounce", _("Bouncy overshoot (off: critically damped)"), true}; @@ -244,6 +249,8 @@ public: FCITX_ADDON_DEPENDENCY_LOADER(dbus, instance_->addonManager()); Instance *instance() const { return instance_; } const Configuration *getConfig() const override; + // The scheme for the current desktop colours, see classicui.cpp. + monet::Scheme monetScheme() const; void setConfig(const RawConfig &config) override { config_.load(config, true); safeSaveAsIni(config_, "conf/m3ui.conf"); @@ -330,6 +337,8 @@ private: mutable Theme subconfigTheme_; bool suspended_ = true; bool isDark_ = false; + // The portal has answered, so isDark_ is a value and not just the default. + bool portalSchemeKnown_ = false; std::optional accentColor_ = std::nullopt; std::unique_ptr deferedEnableTray_; diff --git a/src/theme.cpp b/src/theme.cpp index 4a3a438..7d7b0a9 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -1180,6 +1180,25 @@ std::vector Theme::mask(const BackgroundImageConfig &cfg, int width, return result; } +void Theme::applyMonet(const MonetColors &c) { + // A classic theme draws its panel from images, there is nothing to recolour. + if (inputPanel->surfaceColor->alpha() == 0) { + return; + } + auto *panel = inputPanel.mutableValue(); + panel->surfaceColor.setValue(c.surface); + panel->outlineColor.setValue(c.outline); + panel->highlightFillColor.setValue(c.highlightFill); + panel->normalColor.setValue(c.normal); + panel->highlightColor.setValue(c.highlight); + panel->highlightBackgroundColor.setValue(c.highlightBackground); + panel->highlightCandidateColor.setValue(c.highlightCandidate); + panel->candidateLabelColor.setValue(c.candidateLabel); + panel->highlightCandidateLabelColor.setValue(c.highlightCandidateLabel); + panel->candidateCommentColor.setValue(c.candidateComment); + panel->highlightCandidateCommentColor.setValue(c.highlightCandidateComment); +} + void Theme::populateColor(std::optional accent) { inputPanelBackground_ = *inputPanel->background->color; inputPanelBorder_ = *inputPanel->background->borderColor; diff --git a/src/theme.h b/src/theme.h index cda1194..cc0900d 100644 --- a/src/theme.h +++ b/src/theme.h @@ -30,6 +30,7 @@ #include "fcitx-utils/rect.h" #include "fcitx/icontheme.h" #include "common.h" +#include "monettheme.h" namespace fcitx::m3ui { enum class Gravity { @@ -364,6 +365,10 @@ public: const auto &accentColorFields() const { return accentColorFields_; } void populateColor(std::optional accent); + // Replaces the colours of a Material style theme (one with a SurfaceColor) + // with colours that follow the desktop. Other themes are left as they are. + // Call it after load() and before populateColor(). + void applyMonet(const MonetColors &colors); const auto &inputPanelBackground() const { return inputPanelBackground_; } const auto &inputPanelBorder() const { return inputPanelBorder_; }