File indexing completed on 2024-11-10 04:57:22
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2015 Martin Flöser <mgraesslin@kde.org> 0006 SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 #include "edid.h" 0011 0012 #include "c_ptr.h" 0013 #include "common.h" 0014 #include "config-kwin.h" 0015 0016 #include <QFile> 0017 #include <QStandardPaths> 0018 0019 #include <KLocalizedString> 0020 #include <QCryptographicHash> 0021 0022 extern "C" { 0023 #include <libdisplay-info/cta.h> 0024 #include <libdisplay-info/edid.h> 0025 #include <libdisplay-info/info.h> 0026 } 0027 0028 namespace KWin 0029 { 0030 0031 static QByteArray parsePnpId(const uint8_t *data) 0032 { 0033 // Decode PNP ID from three 5 bit words packed into 2 bytes: 0034 // 0035 // | Byte | Bit | 0036 // | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 0037 // ---------------------------------------- 0038 // | 1 | 0)| (4| 3 | 2 | 1 | 0)| (4| 3 | 0039 // | | * | Character 1 | Char 2| 0040 // ---------------------------------------- 0041 // | 2 | 2 | 1 | 0)| (4| 3 | 2 | 1 | 0)| 0042 // | | Character2| Character 3 | 0043 // ---------------------------------------- 0044 const uint offset = 0x8; 0045 0046 char pnpId[4]; 0047 pnpId[0] = 'A' + ((data[offset + 0] >> 2) & 0x1f) - 1; 0048 pnpId[1] = 'A' + (((data[offset + 0] & 0x3) << 3) | ((data[offset + 1] >> 5) & 0x7)) - 1; 0049 pnpId[2] = 'A' + (data[offset + 1] & 0x1f) - 1; 0050 pnpId[3] = '\0'; 0051 0052 return QByteArray(pnpId); 0053 } 0054 0055 static QByteArray parseEisaId(const uint8_t *data) 0056 { 0057 for (int i = 72; i <= 108; i += 18) { 0058 // Skip the block if it isn't used as monitor descriptor. 0059 if (data[i]) { 0060 continue; 0061 } 0062 if (data[i + 1]) { 0063 continue; 0064 } 0065 0066 // We have found the EISA ID, it's stored as ASCII. 0067 if (data[i + 3] == 0xfe) { 0068 return QByteArray(reinterpret_cast<const char *>(&data[i + 5]), 13).trimmed(); 0069 } 0070 } 0071 0072 // If there isn't an ASCII EISA ID descriptor, try to decode PNP ID 0073 return parsePnpId(data); 0074 } 0075 0076 static QByteArray parseVendor(const uint8_t *data) 0077 { 0078 const auto pnpId = parsePnpId(data); 0079 0080 // Map to vendor name 0081 QFile pnpFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("hwdata/pnp.ids"))); 0082 if (pnpFile.exists() && pnpFile.open(QIODevice::ReadOnly)) { 0083 while (!pnpFile.atEnd()) { 0084 const auto line = pnpFile.readLine(); 0085 if (line.startsWith(pnpId)) { 0086 return line.mid(4).trimmed(); 0087 } 0088 } 0089 } 0090 0091 return {}; 0092 } 0093 0094 Edid::Edid() 0095 { 0096 } 0097 0098 Edid::Edid(const void *data, uint32_t size) 0099 { 0100 m_raw.resize(size); 0101 memcpy(m_raw.data(), data, size); 0102 0103 const uint8_t *bytes = static_cast<const uint8_t *>(data); 0104 0105 auto info = di_info_parse_edid(data, size); 0106 if (!info) { 0107 qCWarning(KWIN_CORE, "parsing edid failed"); 0108 return; 0109 } 0110 const di_edid *edid = di_info_get_edid(info); 0111 const di_edid_vendor_product *productInfo = di_edid_get_vendor_product(edid); 0112 const di_edid_screen_size *screenSize = di_edid_get_screen_size(edid); 0113 0114 // basic output information 0115 m_physicalSize = QSize(screenSize->width_cm, screenSize->height_cm) * 10; 0116 m_eisaId = parseEisaId(bytes); 0117 UniqueCPtr<char> monitorName{di_info_get_model(info)}; 0118 m_monitorName = QByteArray(monitorName.get()); 0119 m_serialNumber = QByteArray::number(productInfo->serial); 0120 m_vendor = parseVendor(bytes); 0121 QCryptographicHash hash(QCryptographicHash::Md5); 0122 hash.addData(m_raw); 0123 m_hash = QString::fromLatin1(hash.result().toHex()); 0124 0125 m_identifier = QByteArray(productInfo->manufacturer, 3) + " " + QByteArray::number(productInfo->product) + " " + QByteArray::number(productInfo->serial) + " " 0126 + QByteArray::number(productInfo->manufacture_week) + " " + QByteArray::number(productInfo->manufacture_year) + " " + QByteArray::number(productInfo->model_year); 0127 0128 // colorimetry and HDR metadata 0129 const auto chromaticity = di_edid_get_chromaticity_coords(edid); 0130 if (chromaticity) { 0131 m_colorimetry = Colorimetry{ 0132 QVector2D{chromaticity->red_x, chromaticity->red_y}, 0133 QVector2D{chromaticity->green_x, chromaticity->green_y}, 0134 QVector2D{chromaticity->blue_x, chromaticity->blue_y}, 0135 QVector2D{chromaticity->white_x, chromaticity->white_y}, 0136 }; 0137 } else { 0138 m_colorimetry.reset(); 0139 } 0140 0141 const di_edid_cta *cta = nullptr; 0142 const di_edid_ext *const *exts = di_edid_get_extensions(edid); 0143 const di_cta_hdr_static_metadata_block *hdr_static_metadata = nullptr; 0144 const di_cta_colorimetry_block *colorimetry = nullptr; 0145 for (; *exts != nullptr; exts++) { 0146 if ((cta = di_edid_ext_get_cta(*exts))) { 0147 break; 0148 } 0149 } 0150 if (cta) { 0151 const di_cta_data_block *const *blocks = di_edid_cta_get_data_blocks(cta); 0152 for (; *blocks != nullptr; blocks++) { 0153 if (!hdr_static_metadata && (hdr_static_metadata = di_cta_data_block_get_hdr_static_metadata(*blocks))) { 0154 continue; 0155 } 0156 if (!colorimetry && (colorimetry = di_cta_data_block_get_colorimetry(*blocks))) { 0157 continue; 0158 } 0159 } 0160 if (hdr_static_metadata) { 0161 m_hdrMetadata = HDRMetadata{ 0162 .desiredContentMinLuminance = hdr_static_metadata->desired_content_min_luminance, 0163 .desiredContentMaxLuminance = hdr_static_metadata->desired_content_max_luminance > 0 ? std::make_optional(hdr_static_metadata->desired_content_max_luminance) : std::nullopt, 0164 .desiredMaxFrameAverageLuminance = hdr_static_metadata->desired_content_max_frame_avg_luminance > 0 ? std::make_optional(hdr_static_metadata->desired_content_max_frame_avg_luminance) : std::nullopt, 0165 .supportsPQ = hdr_static_metadata->eotfs->pq, 0166 .supportsBT2020 = colorimetry && colorimetry->bt2020_rgb, 0167 }; 0168 } 0169 } 0170 0171 m_isValid = true; 0172 di_info_destroy(info); 0173 } 0174 0175 bool Edid::isValid() const 0176 { 0177 return m_isValid; 0178 } 0179 0180 QSize Edid::physicalSize() const 0181 { 0182 return m_physicalSize; 0183 } 0184 0185 QByteArray Edid::eisaId() const 0186 { 0187 return m_eisaId; 0188 } 0189 0190 QByteArray Edid::monitorName() const 0191 { 0192 return m_monitorName; 0193 } 0194 0195 QByteArray Edid::serialNumber() const 0196 { 0197 return m_serialNumber; 0198 } 0199 0200 QByteArray Edid::vendor() const 0201 { 0202 return m_vendor; 0203 } 0204 0205 QByteArray Edid::raw() const 0206 { 0207 return m_raw; 0208 } 0209 0210 QString Edid::manufacturerString() const 0211 { 0212 QString manufacturer; 0213 if (!m_vendor.isEmpty()) { 0214 manufacturer = QString::fromLatin1(m_vendor); 0215 } else if (!m_eisaId.isEmpty()) { 0216 manufacturer = QString::fromLatin1(m_eisaId); 0217 } 0218 return manufacturer; 0219 } 0220 0221 QString Edid::nameString() const 0222 { 0223 if (!m_monitorName.isEmpty()) { 0224 QString m = QString::fromLatin1(m_monitorName); 0225 if (!m_serialNumber.isEmpty()) { 0226 m.append('/'); 0227 m.append(QString::fromLatin1(m_serialNumber)); 0228 } 0229 return m; 0230 } else if (!m_serialNumber.isEmpty()) { 0231 return QString::fromLatin1(m_serialNumber); 0232 } else { 0233 return i18n("unknown"); 0234 } 0235 } 0236 0237 QString Edid::hash() const 0238 { 0239 return m_hash; 0240 } 0241 0242 std::optional<Colorimetry> Edid::colorimetry() const 0243 { 0244 return m_colorimetry; 0245 } 0246 0247 double Edid::desiredMinLuminance() const 0248 { 0249 return m_hdrMetadata ? m_hdrMetadata->desiredContentMinLuminance : 0; 0250 } 0251 0252 std::optional<double> Edid::desiredMaxFrameAverageLuminance() const 0253 { 0254 return m_hdrMetadata ? m_hdrMetadata->desiredMaxFrameAverageLuminance : std::nullopt; 0255 } 0256 0257 std::optional<double> Edid::desiredMaxLuminance() const 0258 { 0259 return m_hdrMetadata ? m_hdrMetadata->desiredContentMaxLuminance : std::nullopt; 0260 } 0261 0262 bool Edid::supportsPQ() const 0263 { 0264 return m_hdrMetadata && m_hdrMetadata->supportsPQ; 0265 } 0266 0267 bool Edid::supportsBT2020() const 0268 { 0269 return m_hdrMetadata && m_hdrMetadata->supportsBT2020; 0270 } 0271 0272 QByteArray Edid::identifier() const 0273 { 0274 return m_identifier; 0275 } 0276 0277 } // namespace KWin