File indexing completed on 2024-05-19 05:32:29

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