A header only C++20 library. One seed colour gives a complete Material 3 style scheme, light or dark, with the tones, roles and contrast guarantees of the baseline scheme, computed in OKLCH as a light weight stand in for HCT. The seed colour and the light or dark preference come from a chain of sources: an environment override, the XDG desktop portal (GNOME 47+ and KDE Plasma 6), kdeglobals on KDE, gsettings on GNOME, GTK settings, and a fallback that is always appended and cannot be left out. Desktop specific sources are only asked on their own desktop, so a stale gsettings value on KDE never wins. Programs can put sources of their own in front. The tests run without a desktop through a fake Context. They cover colour conversions, tone accuracy, the contrast of every role pair over 300 random seeds in both modes, every parser against real portal output, the order of the sources per desktop, the fallback and external program timeouts. The test suite was checked by breaking the code 22 ways, all are caught. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
4.1 KiB
libmonet
一个种子色生成一整套 Material 3 风格配色,并且知道从桌面上去哪里取这个种子色和深浅色。 纯 C++20 头文件库,没有第三方依赖。
给 fcitx5-m3ui 和 m3shot 共用,让它们的配色跟着桌面走, KDE Plasma 和 GNOME 都支持。
用法
#include <monet/monet.h>
monet::Scheme s = monet::fromDesktop(); // 种子色和深浅色都来自当前桌面
monet::Rgb surface = s.surfaceContainer; // 角色颜色: primary、onPrimary、surface、outline ...
std::string css = monet::hex(s.primary); // "#00658F"
bool dark = s.dark();
已经有种子色的话:monet::generate(monet::Rgb{61, 174, 233}, monet::Mode::Dark)。
种子色和深浅色从哪来
探测由一串**来源(Source)**完成,按顺序问,每个值取第一个有答案的来源:
| 来源 | 读什么 | 什么时候用 |
|---|---|---|
override |
环境变量 MONET_SEED=#RRGGBB、MONET_MODE=dark|light |
永远排第一,方便手动指定 |
portal |
org.freedesktop.portal.Settings(用 busctl 或 gdbus 读) |
永远第二。GNOME 47+ 和 KDE Plasma 6 都实现了它,所以这是唯一跨桌面通用的来源 |
kde |
~/.config/kdeglobals |
只在 KDE 桌面上 |
gnome |
gsettings |
只在 GNOME 一类桌面上 |
gtk |
gtk-3.0/settings.ini、gtk-4.0/settings.ini |
只能给出深浅色 |
fallback |
固定的粉色种子加深色 | 永远是最后一个,而且去不掉 |
两个细节:
- 桌面专用来源只在对应桌面上才会被问。KDE 机器上残留的旧
gsettings值不会盖过 KDE 自己的答案。 - 回退源永远存在:来源列表为空、全部失败、没有桌面环境,结果都是合法的配色,不会返回空。 回退用的是两个工具在跟随桌面之前的老外观(粉色强调色加深色)。
程序有自己的 D-Bus 连接的话,可以加一个自己的来源放在最前面:
monet::Source mine{"mine", [](const monet::Context &) {
monet::Appearance a;
a.accent = monet::Rgb{...}; // 只填你知道的,其余留空,交给后面的来源
return a;
}};
monet::Scheme s = monet::fromDesktop(monet::Context::system(), {mine});
monet::detect() 的结果里带着 accentSource 和 modeSource,方便打日志,看颜色到底是谁给的。
Context 可以替换,测试里就是用假的 Context 模拟各种桌面。
接入
CMake,固定到某个提交:
include(FetchContent)
FetchContent_Declare(monet
GIT_REPOSITORY https://git.oeo.one/justme/libmonet.git
GIT_TAG <提交哈希>)
FetchContent_MakeAvailable(monet)
target_link_libraries(myapp PRIVATE monet::monet)
不用 CMake 的话,把 include/ 加进头文件搜索路径就行。
配色是怎么算的
颜色在 OKLab/OKLCH 里处理,"色调(tone)"就是结果的 CIE L*,和 Material 3 的 HCT 色调是同一个意思, 所以同一个色调不论什么色相,亮度和对比度都一样。角色和它们用的色调照搬 Material 3 的基线方案 (浅色 primary 40、深色 primary 80,容器 90/30,表面阶梯 100 到 90 等),三色相偏移 60°, 中性色几乎是灰色,错误色固定在红色。
要说清楚的是:这是 HCT 的轻量替代,没有实现 CAM16,所以不是和 Material 官方库逐位一致。 色相和色调是准的,色度的标度和官方不同。灰色种子会得到灰色方案,不会被硬染上颜色。
测试
cmake -S . -B build -G Ninja && ninja -C build && ./build/monet_tests
测试不需要真实桌面,所有来源都用假的 Context 测。覆盖:颜色空间往返、色调精度和单调性、
300 个随机种子加深浅两种模式下每一对前景/背景的对比度(WCAG)、各种格式的解析(包括这台机器上
门户的真实输出和 gdbus 的输出格式)、各桌面下的来源顺序、回退、环境变量覆盖、外部程序的超时。
我还故意把代码改坏了 22 处来检查这些测试抓不抓得到,第一轮有几处漏掉的,补了测试。
没有覆盖的:GNOME 这条路只通过假环境测试过,没有在真实的 GNOME 机器上跑过。
许可证
MIT