File indexing completed on 2024-05-19 16:34:57

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 "config-kwin.h"
0013 
0014 #include <QFile>
0015 #include <QStandardPaths>
0016 
0017 #include <KLocalizedString>
0018 
0019 namespace KWin
0020 {
0021 
0022 static bool verifyHeader(const uint8_t *data)
0023 {
0024     if (data[0] != 0x0 || data[7] != 0x0) {
0025         return false;
0026     }
0027 
0028     return std::all_of(data + 1, data + 7,
0029                        [](uint8_t byte) {
0030                            return byte == 0xff;
0031                        });
0032 }
0033 
0034 static QSize parsePhysicalSize(const uint8_t *data)
0035 {
0036     // Convert physical size from centimeters to millimeters.
0037     return QSize(data[0x15], data[0x16]) * 10;
0038 }
0039 
0040 static QByteArray parsePnpId(const uint8_t *data)
0041 {
0042     // Decode PNP ID from three 5 bit words packed into 2 bytes:
0043     //
0044     // | Byte |        Bit                    |
0045     // |      | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
0046     // ----------------------------------------
0047     // |  1   | 0)| (4| 3 | 2 | 1 | 0)| (4| 3 |
0048     // |      | * |    Character 1    | Char 2|
0049     // ----------------------------------------
0050     // |  2   | 2 | 1 | 0)| (4| 3 | 2 | 1 | 0)|
0051     // |      | Character2|      Character 3  |
0052     // ----------------------------------------
0053     const uint offset = 0x8;
0054 
0055     char pnpId[4];
0056     pnpId[0] = 'A' + ((data[offset + 0] >> 2) & 0x1f) - 1;
0057     pnpId[1] = 'A' + (((data[offset + 0] & 0x3) << 3) | ((data[offset + 1] >> 5) & 0x7)) - 1;
0058     pnpId[2] = 'A' + (data[offset + 1] & 0x1f) - 1;
0059     pnpId[3] = '\0';
0060 
0061     return QByteArray(pnpId);
0062 }
0063 
0064 static QByteArray parseEisaId(const uint8_t *data)
0065 {
0066     for (int i = 72; i <= 108; i += 18) {
0067         // Skip the block if it isn't used as monitor descriptor.
0068         if (data[i]) {
0069             continue;
0070         }
0071         if (data[i + 1]) {
0072             continue;
0073         }
0074 
0075         // We have found the EISA ID, it's stored as ASCII.
0076         if (data[i + 3] == 0xfe) {
0077             return QByteArray(reinterpret_cast<const char *>(&data[i + 5]), 13).trimmed();
0078         }
0079     }
0080 
0081     // If there isn't an ASCII EISA ID descriptor, try to decode PNP ID
0082     return parsePnpId(data);
0083 }
0084 
0085 static QByteArray parseMonitorName(const uint8_t *data)
0086 {
0087     for (int i = 72; i <= 108; i += 18) {
0088         // Skip the block if it isn't used as monitor descriptor.
0089         if (data[i]) {
0090             continue;
0091         }
0092         if (data[i + 1]) {
0093             continue;
0094         }
0095 
0096         // We have found the monitor name, it's stored as ASCII.
0097         if (data[i + 3] == 0xfc) {
0098             return QByteArray(reinterpret_cast<const char *>(&data[i + 5]), 13).trimmed();
0099         }
0100     }
0101 
0102     return QByteArray();
0103 }
0104 
0105 static QByteArray parseSerialNumber(const uint8_t *data)
0106 {
0107     for (int i = 72; i <= 108; i += 18) {
0108         // Skip the block if it isn't used as monitor descriptor.
0109         if (data[i]) {
0110             continue;
0111         }
0112         if (data[i + 1]) {
0113             continue;
0114         }
0115 
0116         // We have found the serial number, it's stored as ASCII.
0117         if (data[i + 3] == 0xff) {
0118             return QByteArray(reinterpret_cast<const char *>(&data[i + 5]), 13).trimmed();
0119         }
0120     }
0121 
0122     // Maybe there isn't an ASCII serial number descriptor, so use this instead.
0123     const uint32_t offset = 0xc;
0124 
0125     uint32_t serialNumber = data[offset + 0];
0126     serialNumber |= uint32_t(data[offset + 1]) << 8;
0127     serialNumber |= uint32_t(data[offset + 2]) << 16;
0128     serialNumber |= uint32_t(data[offset + 3]) << 24;
0129     if (serialNumber) {
0130         return QByteArray::number(serialNumber);
0131     }
0132 
0133     return QByteArray();
0134 }
0135 
0136 static QByteArray parseVendor(const uint8_t *data)
0137 {
0138     const auto pnpId = parsePnpId(data);
0139 
0140     // Map to vendor name
0141     QFile pnpFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("hwdata/pnp.ids")));
0142     if (pnpFile.exists() && pnpFile.open(QIODevice::ReadOnly)) {
0143         while (!pnpFile.atEnd()) {
0144             const auto line = pnpFile.readLine();
0145             if (line.startsWith(pnpId)) {
0146                 return line.mid(4).trimmed();
0147             }
0148         }
0149     }
0150 
0151     return {};
0152 }
0153 
0154 Edid::Edid()
0155 {
0156 }
0157 
0158 Edid::Edid(const void *data, uint32_t size)
0159 {
0160     m_raw.resize(size);
0161     memcpy(m_raw.data(), data, size);
0162 
0163     const uint8_t *bytes = static_cast<const uint8_t *>(data);
0164 
0165     if (size < 128) {
0166         return;
0167     }
0168 
0169     if (!verifyHeader(bytes)) {
0170         return;
0171     }
0172 
0173     m_physicalSize = parsePhysicalSize(bytes);
0174     m_eisaId = parseEisaId(bytes);
0175     m_monitorName = parseMonitorName(bytes);
0176     m_serialNumber = parseSerialNumber(bytes);
0177     m_vendor = parseVendor(bytes);
0178 
0179     m_isValid = true;
0180 }
0181 
0182 bool Edid::isValid() const
0183 {
0184     return m_isValid;
0185 }
0186 
0187 QSize Edid::physicalSize() const
0188 {
0189     return m_physicalSize;
0190 }
0191 
0192 QByteArray Edid::eisaId() const
0193 {
0194     return m_eisaId;
0195 }
0196 
0197 QByteArray Edid::monitorName() const
0198 {
0199     return m_monitorName;
0200 }
0201 
0202 QByteArray Edid::serialNumber() const
0203 {
0204     return m_serialNumber;
0205 }
0206 
0207 QByteArray Edid::vendor() const
0208 {
0209     return m_vendor;
0210 }
0211 
0212 QByteArray Edid::raw() const
0213 {
0214     return m_raw;
0215 }
0216 
0217 QString Edid::manufacturerString() const
0218 {
0219     QString manufacturer;
0220     if (!m_vendor.isEmpty()) {
0221         manufacturer = QString::fromLatin1(m_vendor);
0222     } else if (!m_eisaId.isEmpty()) {
0223         manufacturer = QString::fromLatin1(m_eisaId);
0224     }
0225     return manufacturer;
0226 }
0227 
0228 QString Edid::nameString() const
0229 {
0230     if (!m_monitorName.isEmpty()) {
0231         QString m = QString::fromLatin1(m_monitorName);
0232         if (!m_serialNumber.isEmpty()) {
0233             m.append('/');
0234             m.append(QString::fromLatin1(m_serialNumber));
0235         }
0236         return m;
0237     } else if (!m_serialNumber.isEmpty()) {
0238         return QString::fromLatin1(m_serialNumber);
0239     } else {
0240         return i18n("unknown");
0241     }
0242 }
0243 
0244 } // namespace KWin