File indexing completed on 2025-04-27 11:33:52
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