diff --git a/Makefile b/Makefile index aba363a37..282258ed8 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ release: cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` debug: - cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -S . -B ./build + cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DTESTS=true -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -S . -B ./build cmake --build ./build --config Debug --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` nopch: diff --git a/hyprtester/src/tests/main/colors.cpp b/hyprtester/src/tests/main/colors.cpp new file mode 100644 index 000000000..deb4254ce --- /dev/null +++ b/hyprtester/src/tests/main/colors.cpp @@ -0,0 +1,27 @@ +#include "tests.hpp" +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include "../shared.hpp" + +static int ret = 0; + +static bool test() { + NLog::log("{}Testing hyprctl monitors", Colors::GREEN); + + std::string monitorsSpec = getFromSocket("j/monitors"); + EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "srgb")"); + + EXPECT_CONTAINS(getFromSocket("/keyword monitor HEADLESS-2,1920x1080x60.00000,0x0,1.0,bitdepth,10,cm,wide"), "ok") + monitorsSpec = getFromSocket("j/monitors"); + EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "wide")"); + + EXPECT_CONTAINS(getFromSocket("/keyword monitor HEADLESS-2,1920x1080x60.00000,0x0,1.0,bitdepth,10,cm,srgb,sdrbrightness,1.2,sdrsaturation,0.98"), "ok") + monitorsSpec = getFromSocket("j/monitors"); + EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "srgb")"); + EXPECT_CONTAINS(monitorsSpec, R"("sdrBrightness": 1.20)"); + EXPECT_CONTAINS(monitorsSpec, R"("sdrSaturation": 0.98)"); + + return !ret; +} + +REGISTER_TEST_FN(test) diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 151941fe3..0e88fca31 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -2273,28 +2273,12 @@ bool CMonitorRuleParser::parseBitdepth(const std::string& value) { } bool CMonitorRuleParser::parseCM(const std::string& value) { - if (value == "auto") - m_rule.cmType = CM_AUTO; - else if (value == "srgb") - m_rule.cmType = CM_SRGB; - else if (value == "wide") - m_rule.cmType = CM_WIDE; - else if (value == "edid") - m_rule.cmType = CM_EDID; - else if (value == "hdr") - m_rule.cmType = CM_HDR; - else if (value == "hdredid") - m_rule.cmType = CM_HDR_EDID; - else if (value == "dcip3") - m_rule.cmType = CM_DCIP3; - else if (value == "dp3") - m_rule.cmType = CM_DP3; - else if (value == "adobe") - m_rule.cmType = CM_ADOBE; - else { + auto parsedCM = NCMType::fromString(value); + if (!parsedCM.has_value()) { m_error += "invalid cm "; return false; } + m_rule.cmType = parsedCM.value(); return true; } diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index ebf7acd68..a0ccddd08 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -241,7 +241,12 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer "disabled": {}, "currentFormat": "{}", "mirrorOf": "{}", - "availableModes": [{}] + "availableModes": [{}], + "colorManagementPreset": "{}", + "sdrBrightness": {:.2f}, + "sdrSaturation": {:.2f}, + "sdrMinLuminance": {:.2f}, + "sdrMaxLuminance": {} }},)#", m->m_id, escapeJSONStrings(m->m_name), escapeJSONStrings(m->m_shortDescription), escapeJSONStrings(m->m_output->make), escapeJSONStrings(m->m_output->model), @@ -253,7 +258,8 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer (m->m_dpmsStatus ? "true" : "false"), (m->m_output->state->state().adaptiveSync ? "true" : "false"), rc(m->m_solitaryClient.get()), getSolitaryBlockedReason(m, format), (m->m_tearingState.activelyTearing ? "true" : "false"), getTearingBlockedReason(m, format), rc(m->m_lastScanout.get()), getDSBlockedReason(m, format), (m->m_enabled ? "false" : "true"), formatToString(m->m_output->state->state().drmFormat), - m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format)); + m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format), (NCMType::toString(m->m_cmType)), (m->m_sdrBrightness), + (m->m_sdrSaturation), (m->m_sdrMinLuminance), (m->m_sdrMaxLuminance)); } else { result += std::format( @@ -262,7 +268,7 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer "dpmsStatus: {}\n\tvrr: {}\n\tsolitary: {:x}\n\tsolitaryBlockedBy: {}\n\tactivelyTearing: {}\n\ttearingBlockedBy: {}\n\tdirectScanoutTo: " "{:x}\n\tdirectScanoutBlockedBy: {}\n\tdisabled: " "{}\n\tcurrentFormat: {}\n\tmirrorOf: " - "{}\n\tavailableModes: {}\n\n", + "{}\n\tavailableModes: {}\n\tcolorManagementPreset: {}\n\tsdrBrightness: {:.2f}\n\tsdrSaturation: {:.2f}\n\tsdrMinLuminance: {:.2f}\n\tsdrMaxLuminance: {}\n\n", m->m_name, m->m_id, sc(m->m_pixelSize.x), sc(m->m_pixelSize.y), m->m_refreshRate, sc(m->m_position.x), sc(m->m_position.y), m->m_shortDescription, m->m_output->make, m->m_output->model, sc(m->m_output->physicalSize.x), sc(m->m_output->physicalSize.y), m->m_output->serial, m->activeWorkspaceID(), (!m->m_activeWorkspace ? "" : m->m_activeWorkspace->m_name), m->activeSpecialWorkspaceID(), (m->m_activeSpecialWorkspace ? m->m_activeSpecialWorkspace->m_name : ""), @@ -270,7 +276,8 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer sc(m->m_transform), (m == g_pCompositor->m_lastMonitor ? "yes" : "no"), sc(m->m_dpmsStatus), m->m_output->state->state().adaptiveSync, rc(m->m_solitaryClient.get()), getSolitaryBlockedReason(m, format), m->m_tearingState.activelyTearing, getTearingBlockedReason(m, format), rc(m->m_lastScanout.get()), getDSBlockedReason(m, format), !m->m_enabled, formatToString(m->m_output->state->state().drmFormat), - m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format)); + m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format), (NCMType::toString(m->m_cmType)), (m->m_sdrBrightness), + (m->m_sdrSaturation), (m->m_sdrMinLuminance), (m->m_sdrMaxLuminance)); } return result; diff --git a/src/helpers/CMType.cpp b/src/helpers/CMType.cpp new file mode 100644 index 000000000..db2b73172 --- /dev/null +++ b/src/helpers/CMType.cpp @@ -0,0 +1,23 @@ +#include "CMType.hpp" +#include +#include +#include + +static std::unordered_map const table = {{"auto", NCMType::CM_AUTO}, {"srgb", NCMType::CM_SRGB}, {"wide", NCMType::CM_WIDE}, + {"edid", NCMType::CM_EDID}, {"hdr", NCMType::CM_HDR}, {"hdredid", NCMType::CM_HDR_EDID}, + {"dcip3", NCMType::CM_DCIP3}, {"dp3", NCMType::CM_DP3}, {"adobe", NCMType::CM_ADOBE}}; + +std::optional NCMType::fromString(const std::string cmType) { + auto it = table.find(cmType); + if (it == table.end()) + return std::nullopt; + return it->second; +} + +std::string NCMType::toString(eCMType cmType) { + for (const auto& [key, value] : table) { + if (value == cmType) + return key; + } + return ""; +} diff --git a/src/helpers/CMType.hpp b/src/helpers/CMType.hpp new file mode 100644 index 000000000..8802cca82 --- /dev/null +++ b/src/helpers/CMType.hpp @@ -0,0 +1,20 @@ +#include +#include +#include + +namespace NCMType { + enum eCMType : uint8_t { + CM_AUTO = 0, // subject to change. srgb for 8bpc, wide for 10bpc if supported + CM_SRGB, // default, sRGB primaries + CM_WIDE, // wide color gamut, BT2020 primaries + CM_EDID, // primaries from edid (known to be inaccurate) + CM_HDR, // wide color gamut and HDR PQ transfer function + CM_HDR_EDID, // same as CM_HDR with edid primaries + CM_DCIP3, // movie theatre with greenish white point + CM_DP3, // applle P3 variant with blueish white point + CM_ADOBE, // adobe colorspace + }; + + std::optional fromString(const std::string cmType); + std::string toString(eCMType cmType); +} diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 1bc1c0baa..1ec035fd8 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -465,31 +465,31 @@ void CMonitor::onDisconnect(bool destroy) { std::erase_if(g_pCompositor->m_monitors, [&](PHLMONITOR& el) { return el.get() == this; }); } -void CMonitor::applyCMType(eCMType cmType) { +void CMonitor::applyCMType(NCMType::eCMType cmType) { auto oldImageDescription = m_imageDescription; switch (cmType) { - case CM_SRGB: m_imageDescription = {}; break; // assumes SImageDescirption defaults to sRGB - case CM_WIDE: + case NCMType::CM_SRGB: m_imageDescription = {}; break; // assumes SImageDescirption defaults to sRGB + case NCMType::CM_WIDE: m_imageDescription = {.primariesNameSet = true, .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020)}; break; - case CM_DCIP3: + case NCMType::CM_DCIP3: m_imageDescription = {.primariesNameSet = true, .primariesNamed = NColorManagement::CM_PRIMARIES_DCI_P3, .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DCI_P3)}; break; - case CM_DP3: + case NCMType::CM_DP3: m_imageDescription = {.primariesNameSet = true, .primariesNamed = NColorManagement::CM_PRIMARIES_DISPLAY_P3, .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DISPLAY_P3)}; break; - case CM_ADOBE: + case NCMType::CM_ADOBE: m_imageDescription = {.primariesNameSet = true, .primariesNamed = NColorManagement::CM_PRIMARIES_ADOBE_RGB, .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_ADOBE_RGB)}; break; - case CM_EDID: + case NCMType::CM_EDID: m_imageDescription = {.primariesNameSet = false, .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, .primaries = { @@ -499,14 +499,14 @@ void CMonitor::applyCMType(eCMType cmType) { .white = {.x = m_output->parsedEDID.chromaticityCoords->white.x, .y = m_output->parsedEDID.chromaticityCoords->white.y}, }}; break; - case CM_HDR: + case NCMType::CM_HDR: m_imageDescription = {.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, .primariesNameSet = true, .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), .luminances = {.min = 0, .max = 10000, .reference = 203}}; break; - case CM_HDR_EDID: + case NCMType::CM_HDR_EDID: m_imageDescription = {.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, .primariesNameSet = false, .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, @@ -861,10 +861,10 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { m_cmType = RULE->cmType; switch (m_cmType) { - case CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? CM_WIDE : CM_SRGB; break; - case CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? CM_EDID : CM_SRGB; break; - case CM_HDR: - case CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : CM_SRGB; break; + case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break; + case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break; + case NCMType::CM_HDR: + case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break; default: break; } diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index d112f7476..687fc049f 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -8,6 +8,7 @@ #include "WLClasses.hpp" #include #include "AnimatedVariable.hpp" +#include "CMType.hpp" #include #include "time/Timer.hpp" @@ -35,18 +36,6 @@ enum eAutoDirs : uint8_t { DIR_AUTO_CENTER_RIGHT }; -enum eCMType : uint8_t { - CM_AUTO = 0, // subject to change. srgb for 8bpc, wide for 10bpc if supported - CM_SRGB, // default, sRGB primaries - CM_WIDE, // wide color gamut, BT2020 primaries - CM_EDID, // primaries from edid (known to be inaccurate) - CM_HDR, // wide color gamut and HDR PQ transfer function - CM_HDR_EDID, // same as CM_HDR with edid primaries - CM_DCIP3, // movie theatre with greenish white point - CM_DP3, // applle P3 variant with blueish white point - CM_ADOBE, // adobe colorspace -}; - struct SMonitorRule { eAutoDirs autoDir = DIR_AUTO_NONE; std::string name = ""; @@ -58,7 +47,7 @@ struct SMonitorRule { wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; std::string mirrorOf = ""; bool enable10bit = false; - eCMType cmType = CM_SRGB; + NCMType::eCMType cmType = NCMType::CM_SRGB; float sdrSaturation = 1.0f; // SDR -> HDR float sdrBrightness = 1.0f; // SDR -> HDR @@ -141,7 +130,7 @@ class CMonitor { bool m_dpmsStatus = true; bool m_vrrActive = false; // this can be TRUE even if VRR is not active in the case that this display does not support it. bool m_enabled10bit = false; // as above, this can be TRUE even if 10 bit failed. - eCMType m_cmType = CM_SRGB; + NCMType::eCMType m_cmType = NCMType::CM_SRGB; float m_sdrSaturation = 1.0f; float m_sdrBrightness = 1.0f; float m_sdrMinLuminance = 0.2f; @@ -283,7 +272,7 @@ class CMonitor { // methods void onConnect(bool noRule); void onDisconnect(bool destroy = false); - void applyCMType(eCMType cmType); + void applyCMType(NCMType::eCMType cmType); bool applyMonitorRule(SMonitorRule* pMonitorRule, bool force = false); void addDamage(const pixman_region32_t* rg); void addDamage(const CRegion& rg); diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 446c4bc4d..edf0f7d1d 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -1506,7 +1506,7 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { static bool needsHDRupdate = false; - const bool configuredHDR = (pMonitor->m_cmType == CM_HDR_EDID || pMonitor->m_cmType == CM_HDR); + const bool configuredHDR = (pMonitor->m_cmType == NCMType::CM_HDR_EDID || pMonitor->m_cmType == NCMType::CM_HDR); bool wantHDR = configuredHDR; const auto FS_WINDOW = pMonitor->inFullscreenMode() ? pMonitor->m_activeWorkspace->getFullscreenWindow() : nullptr; @@ -1555,7 +1555,7 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { if (*PAUTOHDR && !(pMonitor->inHDR() && configuredHDR)) { // modify or restore monitor image description for auto-hdr // FIXME ok for now, will need some other logic if monitor image description can be modified some other way - const auto targetCM = wantHDR ? (*PAUTOHDR == 2 ? CM_HDR_EDID : CM_HDR) : pMonitor->m_cmType; + const auto targetCM = wantHDR ? (*PAUTOHDR == 2 ? NCMType::CM_HDR_EDID : NCMType::CM_HDR) : pMonitor->m_cmType; Debug::log(INFO, "[CM] Auto HDR: changing monitor cm to {}", sc(targetCM)); pMonitor->applyCMType(targetCM); pMonitor->m_previousFSWindow.reset(); // trigger CTM update