Compare commits
3
Commits
74959cd892
...
99fe8f589f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99fe8f589f | ||
|
|
5bea01e224 | ||
|
|
064ca6b4d4 |
No files matched your search
@@ -47,7 +47,7 @@ sudo dnf install fcitx5-devel extra-cmake-modules cairo-devel pango-devel \
|
||||
然后:
|
||||
|
||||
```
|
||||
scripts/build.sh # 下载 Fcitx5 和 yoga 源码,叠加 src/,构建 m3ui
|
||||
scripts/build.sh # 下载 Fcitx5、yoga 和 libmonet 源码,叠加 src/,构建 m3ui
|
||||
scripts/install.sh # 安装到 ~/.local,不改动 $HOME 以外的任何东西
|
||||
```
|
||||
|
||||
@@ -56,6 +56,12 @@ scripts/install.sh # 安装到 ~/.local,不改动 $HOME 以外的任何东西
|
||||
没有固定提交的版本会被拒绝,除非你自己用 `FCITX_COMMIT=<sha>` 指定。这个插件跑在输入法进程里,
|
||||
看得到你打的所有字,所以构建来源值得认真对待。
|
||||
|
||||
## 莫奈取色
|
||||
|
||||
候选窗的颜色由 [libmonet](https://git.oeo.one/justme/libmonet) 从桌面强调色生成(`UseMonet=True`,默认开),
|
||||
深浅色跟随系统。输入法进程内不会启动任何子进程:直接复用 Fcitx5 自己监听的 XDG portal
|
||||
(KDE Plasma 6、GNOME 47+ 都有),取不到时回退到默认的粉色。`UseMonet=False` 回到主题文件里的固定颜色。
|
||||
|
||||
## 使用
|
||||
|
||||
手动试用:
|
||||
|
||||
@@ -10,6 +10,7 @@ UseAccentColor=False
|
||||
PerScreenDPI=True
|
||||
ForceWaylandDPI=0
|
||||
EnableFractionalScale=True
|
||||
UseMonet=True
|
||||
Animations=True
|
||||
Bounce=True
|
||||
SpatialStiffness=2000
|
||||
|
||||
@@ -27,6 +27,8 @@ commit=${FCITX_COMMIT:-${known_commits[$version]:-}}
|
||||
[ -n "$commit" ] || die "no pinned commit for Fcitx5 $version. Look up the commit of its tag and pass it as FCITX_COMMIT=<sha>"
|
||||
# Commit of the yoga submodule pinned by Fcitx5 (third_party/yoga).
|
||||
yoga_commit=${YOGA_COMMIT:-042f5013152eb81c1552dec945b88f7b95ca350f}
|
||||
# libmonet (header-only) supplies the colours derived from the desktop accent.
|
||||
monet_commit=${MONET_COMMIT:-b97a55ac75ae82435628dc585cccadb45e4d2feb}
|
||||
|
||||
# fetch_commit <url> <dir> <commit>, leaves <dir> checked out at exactly <commit>
|
||||
fetch_commit() {
|
||||
@@ -48,6 +50,8 @@ echo ">> fetching Fcitx5 $version ($commit)"
|
||||
fetch_commit https://github.com/fcitx/fcitx5 "$srcdir" "$commit"
|
||||
echo ">> fetching yoga ($yoga_commit)"
|
||||
fetch_commit https://github.com/facebook/yoga "$srcdir/third_party/yoga" "$yoga_commit"
|
||||
echo ">> fetching libmonet ($monet_commit)"
|
||||
fetch_commit https://git.oeo.one/justme/libmonet.git "$work/libmonet" "$monet_commit"
|
||||
[ -z "${FETCH_ONLY:-}" ] || { echo ">> sources are in place"; exit 0; }
|
||||
|
||||
echo ">> overlaying m3ui"
|
||||
@@ -61,6 +65,7 @@ fi
|
||||
echo ">> configuring"
|
||||
cmake -S "$srcdir" -B "$work/cmake" -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DMONET_INCLUDE_DIR="$work/libmonet/include" \
|
||||
-DCMAKE_INSTALL_PREFIX="${PREFIX:-$HOME/.local/m3ui}" \
|
||||
-DENABLE_X11=On -DENABLE_WAYLAND=On \
|
||||
-DENABLE_TEST=Off -DENABLE_TESTING_ADDONS=Off -DENABLE_ENCHANT=Off \
|
||||
|
||||
@@ -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
|
||||
|
||||
+53
-3
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "classicui.h"
|
||||
#include <fcntl.h>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
@@ -180,6 +181,7 @@ void M3UI::reloadTheme() {
|
||||
auto color = variant.dataAs<uint32_t>();
|
||||
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<std::string> &, int) {
|
||||
return std::optional<std::string>();
|
||||
};
|
||||
monet::Source portal{
|
||||
"fcitx-portal", [this](const monet::Context &) {
|
||||
monet::Appearance a;
|
||||
if (accentColor_) {
|
||||
auto q = [](float c) {
|
||||
return static_cast<std::uint8_t>(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<monet::Source> 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() {
|
||||
|
||||
@@ -201,6 +201,11 @@ FCITX_CONFIGURATION(
|
||||
"font DPI. If the value is 0, it means this option is "
|
||||
"disabled.")}};
|
||||
|
||||
Option<bool> useMonet{
|
||||
this, "UseMonet",
|
||||
_("Take the colours from the desktop: its accent colour and its light "
|
||||
"or dark setting"),
|
||||
true};
|
||||
Option<bool> animations{this, "Animations", _("Enable animations"), true};
|
||||
Option<bool> 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<Color> accentColor_ = std::nullopt;
|
||||
|
||||
std::unique_ptr<EventSource> deferedEnableTray_;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <monet/monet.h>
|
||||
|
||||
#include "fcitx-utils/color.h"
|
||||
|
||||
namespace fcitx::m3ui {
|
||||
|
||||
// The colours of the candidate window that follow the desktop, taken from a
|
||||
// libmonet scheme. Only colours are decided here, the radii and the spacing stay
|
||||
// with the theme file.
|
||||
struct MonetColors {
|
||||
Color surface; // the panel
|
||||
Color outline; // its border
|
||||
Color highlightFill; // the pill behind the current candidate
|
||||
Color normal; // candidate text and the typed pinyin
|
||||
Color highlight; // the part of the typed pinyin being converted
|
||||
Color highlightBackground;
|
||||
Color highlightCandidate; // text on the pill
|
||||
Color candidateLabel; // the number in front of a candidate
|
||||
Color highlightCandidateLabel;
|
||||
Color candidateComment;
|
||||
Color highlightCandidateComment;
|
||||
};
|
||||
|
||||
inline Color toColor(monet::Rgb c, int alpha = 255) {
|
||||
return Color(c.r, c.g, c.b, static_cast<unsigned short>(alpha));
|
||||
}
|
||||
|
||||
inline MonetColors mapScheme(const monet::Scheme &s) {
|
||||
MonetColors c;
|
||||
c.surface = toColor(s.surfaceContainer, 245);
|
||||
c.outline = toColor(s.outlineVariant);
|
||||
c.highlightFill = toColor(s.primaryContainer);
|
||||
c.normal = toColor(s.onSurface);
|
||||
c.highlight = toColor(s.primary);
|
||||
c.highlightBackground = toColor(s.primaryContainer);
|
||||
c.highlightCandidate = toColor(s.onPrimaryContainer);
|
||||
c.candidateLabel = toColor(s.onSurfaceVariant);
|
||||
c.highlightCandidateLabel = toColor(s.onPrimaryContainer);
|
||||
c.candidateComment = toColor(s.onSurfaceVariant);
|
||||
c.highlightCandidateComment = toColor(s.onPrimaryContainer);
|
||||
return c;
|
||||
}
|
||||
|
||||
} // namespace fcitx::m3ui
|
||||
@@ -1180,6 +1180,25 @@ std::vector<Rect> 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<Color> accent) {
|
||||
inputPanelBackground_ = *inputPanel->background->color;
|
||||
inputPanelBorder_ = *inputPanel->background->borderColor;
|
||||
|
||||
@@ -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<Color> 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_; }
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// The candidate window colours taken from a libmonet scheme must stay readable
|
||||
// for any seed, in both modes.
|
||||
// g++ -std=c++20 -Isrc -I<libmonet>/include tests/monettheme_test.cpp
|
||||
// (add the flags from: pkg-config --cflags --libs Fcitx5Utils)
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
#include "monettheme.h"
|
||||
|
||||
using namespace fcitx::m3ui;
|
||||
|
||||
static int failures = 0;
|
||||
#define CHECK_MSG(cond, ...) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::printf("FAIL line %d: %s ", __LINE__, #cond); \
|
||||
std::printf(__VA_ARGS__); \
|
||||
std::printf("\n"); \
|
||||
failures++; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static monet::Rgb rgb(const fcitx::Color &c) {
|
||||
return {static_cast<std::uint8_t>(std::lround(c.redF() * 255)), static_cast<std::uint8_t>(std::lround(c.greenF() * 255)),
|
||||
static_cast<std::uint8_t>(std::lround(c.blueF() * 255))};
|
||||
}
|
||||
|
||||
int main() {
|
||||
unsigned state = 4242;
|
||||
auto next = [&] { state = state * 1664525u + 1013904223u; return static_cast<std::uint8_t>(state >> 24); };
|
||||
std::vector<monet::Rgb> seeds = {{61, 174, 233}, {255, 176, 201}, {0, 0, 0}, {255, 255, 255}, {128, 128, 128},
|
||||
{255, 0, 0}, {0, 255, 0}, {0, 0, 255}, {255, 255, 0}, {0x67, 0x50, 0xA4}};
|
||||
for (int i = 0; i < 300; i++) seeds.push_back({next(), next(), next()});
|
||||
for (auto seed : seeds) {
|
||||
for (auto mode : {monet::Mode::Light, monet::Mode::Dark}) {
|
||||
const MonetColors c = mapScheme(monet::generate(seed, mode));
|
||||
const char *m = mode == monet::Mode::Dark ? "dark" : "light";
|
||||
const auto surface = rgb(c.surface), fill = rgb(c.highlightFill);
|
||||
auto pair = [&](const char *name, monet::Rgb bg, const fcitx::Color &fg, double min) {
|
||||
CHECK_MSG(monet::contrast(bg, rgb(fg)) >= min, "%s %s, seed %s: %.2f < %.1f", m, name, monet::hex(seed).c_str(),
|
||||
monet::contrast(bg, rgb(fg)), min);
|
||||
};
|
||||
pair("candidate text", surface, c.normal, 7.0);
|
||||
pair("candidate number", surface, c.candidateLabel, 4.5);
|
||||
pair("candidate comment", surface, c.candidateComment, 4.5);
|
||||
pair("typed pinyin being converted", surface, c.highlight, 4.5);
|
||||
pair("text on the current candidate", fill, c.highlightCandidate, 7.0);
|
||||
pair("number on the current candidate", fill, c.highlightCandidateLabel, 7.0);
|
||||
pair("comment on the current candidate", fill, c.highlightCandidateComment, 7.0);
|
||||
// the panel border can be seen against the panel
|
||||
CHECK_MSG(monet::contrast(surface, rgb(c.outline)) >= 1.1, "%s border of seed %s", m, monet::hex(seed).c_str());
|
||||
// the pill can be told apart from the panel, by tone since a container is only a few tones off the surface
|
||||
CHECK_MSG(std::fabs(monet::toneOf(fill) - monet::toneOf(surface)) >= 3.0, "%s pill of seed %s: tones %.1f and %.1f", m,
|
||||
monet::hex(seed).c_str(), monet::toneOf(fill), monet::toneOf(surface));
|
||||
CHECK_MSG(c.surface.alpha() == 245 && c.normal.alpha() == 255 && c.highlightFill.alpha() == 255, "%s alpha of seed %s", m, monet::hex(seed).c_str());
|
||||
// dark panel in dark mode, light panel in light mode
|
||||
CHECK_MSG((monet::toneOf(surface) < 50) == (mode == monet::Mode::Dark), "%s panel tone of seed %s", m, monet::hex(seed).c_str());
|
||||
}
|
||||
}
|
||||
std::printf(failures ? "%d check(s) failed\n" : "monettheme: all checks passed\n", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
Reference in new issue
Block a user