File indexing completed on 2024-04-21 16:17:34
0001 /* 0002 * SPDX-FileCopyrightText: 2012 Daniel Nicoletti <dantti12@gmail.com> 0003 * SPDX-FileCopyrightText: 2012-2014 Daniel Vrátil <dvratil@redhat.com> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.1-or-later 0006 */ 0007 0008 #include "edid.h" 0009 #include "kscreen_debug_edid.h" 0010 0011 #include <math.h> 0012 0013 #include <QCryptographicHash> 0014 #include <QFile> 0015 #include <QStringBuilder> 0016 #include <QStringList> 0017 0018 #define GCM_EDID_OFFSET_PNPID 0x08 0019 #define GCM_EDID_OFFSET_SERIAL 0x0c 0020 #define GCM_EDID_OFFSET_SIZE 0x15 0021 #define GCM_EDID_OFFSET_GAMMA 0x17 0022 #define GCM_EDID_OFFSET_DATA_BLOCKS 0x36 0023 #define GCM_EDID_OFFSET_LAST_BLOCK 0x6c 0024 #define GCM_EDID_OFFSET_EXTENSION_BLOCK_COUNT 0x7e 0025 0026 #define GCM_DESCRIPTOR_DISPLAY_PRODUCT_NAME 0xfc 0027 #define GCM_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER 0xff 0028 #define GCM_DESCRIPTOR_COLOR_MANAGEMENT_DATA 0xf9 0029 #define GCM_DESCRIPTOR_ALPHANUMERIC_DATA_STRING 0xfe 0030 #define GCM_DESCRIPTOR_COLOR_POINT 0xfb 0031 0032 #define PNP_IDS "/usr/share/hwdata/pnp.ids" 0033 0034 using namespace KScreen; 0035 0036 class Q_DECL_HIDDEN Edid::Private 0037 { 0038 public: 0039 Private() 0040 : valid(false) 0041 , width(0) 0042 , height(0) 0043 , gamma(0) 0044 { 0045 } 0046 0047 Private(const Private &other) 0048 : valid(other.valid) 0049 , monitorName(other.monitorName) 0050 , vendorName(other.vendorName) 0051 , serialNumber(other.serialNumber) 0052 , eisaId(other.eisaId) 0053 , checksum(other.checksum) 0054 , pnpId(other.pnpId) 0055 , width(other.width) 0056 , height(other.height) 0057 , gamma(other.gamma) 0058 , red(other.red) 0059 , green(other.green) 0060 , blue(other.blue) 0061 , white(other.white) 0062 { 0063 } 0064 0065 bool parse(const QByteArray &data); 0066 int edidGetBit(int in, int bit) const; 0067 int edidGetBits(int in, int begin, int end) const; 0068 float edidDecodeFraction(int high, int low) const; 0069 QString edidParseString(const quint8 *data) const; 0070 0071 bool valid; 0072 QString monitorName; 0073 QString vendorName; 0074 QString serialNumber; 0075 QString eisaId; 0076 QString checksum; 0077 QString pnpId; 0078 uint width; 0079 uint height; 0080 qreal gamma; 0081 QQuaternion red; 0082 QQuaternion green; 0083 QQuaternion blue; 0084 QQuaternion white; 0085 }; 0086 0087 Edid::Edid() 0088 : QObject() 0089 , d(new Private()) 0090 { 0091 } 0092 0093 Edid::Edid(const QByteArray &data, QObject *parent) 0094 : QObject(parent) 0095 , d(new Private()) 0096 { 0097 d->parse(data); 0098 } 0099 0100 Edid::Edid(Edid::Private *dd) 0101 : QObject() 0102 , d(dd) 0103 { 0104 } 0105 0106 Edid::~Edid() 0107 { 0108 delete d; 0109 } 0110 0111 Edid *Edid::clone() const 0112 { 0113 return new Edid(new Private(*d)); 0114 } 0115 0116 bool Edid::isValid() const 0117 { 0118 return d->valid; 0119 } 0120 0121 QString Edid::deviceId(const QString &fallbackName) const 0122 { 0123 QString id = QStringLiteral("xrandr"); 0124 // if no info was added check if the fallbacName is provided 0125 if (vendor().isNull() && name().isNull() && serial().isNull()) { 0126 if (!fallbackName.isEmpty()) { 0127 id.append(QLatin1Char('-') % fallbackName); 0128 } else { 0129 // all info we have are empty strings 0130 id.append(QLatin1String("-unknown")); 0131 } 0132 } else if (d->valid) { 0133 if (!vendor().isNull()) { 0134 id.append(QLatin1Char('-') % vendor()); 0135 } 0136 if (!name().isNull()) { 0137 id.append(QLatin1Char('-') % name()); 0138 } 0139 if (!serial().isNull()) { 0140 id.append(QLatin1Char('-') % serial()); 0141 } 0142 } 0143 0144 return id; 0145 } 0146 0147 QString Edid::name() const 0148 { 0149 if (d->valid) { 0150 return d->monitorName; 0151 } 0152 return QString(); 0153 } 0154 0155 QString Edid::vendor() const 0156 { 0157 if (d->valid) { 0158 return d->vendorName; 0159 } 0160 return QString(); 0161 } 0162 0163 QString Edid::serial() const 0164 { 0165 if (d->valid) { 0166 return d->serialNumber; 0167 } 0168 return QString(); 0169 } 0170 0171 QString Edid::eisaId() const 0172 { 0173 if (d->valid) { 0174 return d->eisaId; 0175 } 0176 return QString(); 0177 } 0178 0179 QString Edid::hash() const 0180 { 0181 if (d->valid) { 0182 return d->checksum; 0183 } 0184 return QString(); 0185 } 0186 0187 QString Edid::pnpId() const 0188 { 0189 if (d->valid) { 0190 return d->pnpId; 0191 } 0192 return QString(); 0193 } 0194 0195 uint Edid::width() const 0196 { 0197 return d->width; 0198 } 0199 0200 uint Edid::height() const 0201 { 0202 return d->height; 0203 } 0204 0205 qreal Edid::gamma() const 0206 { 0207 return d->gamma; 0208 } 0209 0210 QQuaternion Edid::red() const 0211 { 0212 return d->red; 0213 } 0214 0215 QQuaternion Edid::green() const 0216 { 0217 return d->green; 0218 } 0219 0220 QQuaternion Edid::blue() const 0221 { 0222 return d->blue; 0223 } 0224 0225 QQuaternion Edid::white() const 0226 { 0227 return d->white; 0228 } 0229 0230 bool Edid::Private::parse(const QByteArray &rawData) 0231 { 0232 quint32 serial; 0233 const quint8 *data = reinterpret_cast<const quint8 *>(rawData.constData()); 0234 int length = rawData.length(); 0235 0236 /* check header */ 0237 if (length < 128) { 0238 if (length > 0) { 0239 qCWarning(KSCREEN_EDID) << "Invalid EDID length (" << length << " bytes)"; 0240 } 0241 valid = false; 0242 return valid; 0243 } 0244 if (data[0] != 0x00 || data[1] != 0xff) { 0245 qCWarning(KSCREEN_EDID) << "Failed to parse EDID header"; 0246 valid = false; 0247 return valid; 0248 } 0249 0250 /* decode the PNP ID from three 5 bit words packed into 2 bytes 0251 * /--08--\/--09--\ 0252 * 7654321076543210 0253 * |\---/\---/\---/ 0254 * R C1 C2 C3 */ 0255 pnpId.resize(3); 0256 pnpId[0] = QLatin1Char('A' + ((data[GCM_EDID_OFFSET_PNPID + 0] & 0x7c) / 4) - 1); 0257 pnpId[1] = QLatin1Char('A' + ((data[GCM_EDID_OFFSET_PNPID + 0] & 0x3) * 8) + ((data[GCM_EDID_OFFSET_PNPID + 1] & 0xe0) / 32) - 1); 0258 pnpId[2] = QLatin1Char('A' + (data[GCM_EDID_OFFSET_PNPID + 1] & 0x1f) - 1); 0259 0260 // load the PNP_IDS file and load the vendor name 0261 QFile pnpIds(QStringLiteral(PNP_IDS)); 0262 if (pnpIds.open(QIODevice::ReadOnly)) { 0263 while (!pnpIds.atEnd()) { 0264 QString line = QString::fromUtf8(pnpIds.readLine()); 0265 if (line.startsWith(pnpId)) { 0266 const QStringList parts = line.split(QLatin1Char('\t')); 0267 if (parts.size() == 2) { 0268 vendorName = parts.at(1).simplified(); 0269 } 0270 break; 0271 } 0272 } 0273 } 0274 0275 /* maybe there isn't a ASCII serial number descriptor, so use this instead */ 0276 serial = static_cast<quint32>(data[GCM_EDID_OFFSET_SERIAL + 0]); 0277 serial += static_cast<quint32>(data[GCM_EDID_OFFSET_SERIAL + 1] * 0x100); 0278 serial += static_cast<quint32>(data[GCM_EDID_OFFSET_SERIAL + 2] * 0x10000); 0279 serial += static_cast<quint32>(data[GCM_EDID_OFFSET_SERIAL + 3] * 0x1000000); 0280 if (serial > 0) { 0281 serialNumber = QString::number(serial); 0282 } 0283 0284 /* get the size */ 0285 width = data[GCM_EDID_OFFSET_SIZE + 0]; 0286 height = data[GCM_EDID_OFFSET_SIZE + 1]; 0287 0288 /* we don't care about aspect */ 0289 if (width == 0 || height == 0) { 0290 width = 0; 0291 height = 0; 0292 } 0293 0294 /* get gamma */ 0295 if (data[GCM_EDID_OFFSET_GAMMA] == 0xff) { 0296 gamma = 1.0; 0297 } else { 0298 gamma = data[GCM_EDID_OFFSET_GAMMA] / 100.0 + 1.0; 0299 } 0300 0301 /* get color red */ 0302 red.setX(edidDecodeFraction(data[0x1b], edidGetBits(data[0x19], 6, 7))); 0303 red.setY(edidDecodeFraction(data[0x1c], edidGetBits(data[0x19], 5, 4))); 0304 0305 /* get color green */ 0306 green.setX(edidDecodeFraction(data[0x1d], edidGetBits(data[0x19], 2, 3))); 0307 green.setY(edidDecodeFraction(data[0x1e], edidGetBits(data[0x19], 0, 1))); 0308 0309 /* get color blue */ 0310 blue.setX(edidDecodeFraction(data[0x1f], edidGetBits(data[0x1a], 6, 7))); 0311 blue.setY(edidDecodeFraction(data[0x20], edidGetBits(data[0x1a], 4, 5))); 0312 0313 /* get color white */ 0314 white.setX(edidDecodeFraction(data[0x21], edidGetBits(data[0x1a], 2, 3))); 0315 white.setY(edidDecodeFraction(data[0x22], edidGetBits(data[0x1a], 0, 1))); 0316 0317 /* parse EDID data */ 0318 for (uint i = GCM_EDID_OFFSET_DATA_BLOCKS; i <= GCM_EDID_OFFSET_LAST_BLOCK; i += 18) { 0319 /* ignore pixel clock data */ 0320 if (data[i] != 0) { 0321 continue; 0322 } 0323 if (data[i + 2] != 0) { 0324 continue; 0325 } 0326 0327 /* any useful blocks? */ 0328 if (data[i + 3] == GCM_DESCRIPTOR_DISPLAY_PRODUCT_NAME) { 0329 QString tmp = edidParseString(&data[i + 5]); 0330 if (!tmp.isEmpty()) { 0331 monitorName = tmp; 0332 } 0333 } else if (data[i + 3] == GCM_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER) { 0334 QString tmp = edidParseString(&data[i + 5]); 0335 if (!tmp.isEmpty()) { 0336 serialNumber = tmp; 0337 } 0338 } else if (data[i + 3] == GCM_DESCRIPTOR_COLOR_MANAGEMENT_DATA) { 0339 qCWarning(KSCREEN_EDID) << "failing to parse color management data"; 0340 } else if (data[i + 3] == GCM_DESCRIPTOR_ALPHANUMERIC_DATA_STRING) { 0341 QString tmp = edidParseString(&data[i + 5]); 0342 if (!tmp.isEmpty()) { 0343 eisaId = tmp; 0344 } 0345 } else if (data[i + 3] == GCM_DESCRIPTOR_COLOR_POINT) { 0346 if (data[i + 3 + 9] != 0xff) { 0347 /* extended EDID block(1) which contains 0348 * a better gamma value */ 0349 gamma = (data[i + 3 + 9] / 100.0) + 1; 0350 } 0351 if (data[i + 3 + 14] != 0xff) { 0352 /* extended EDID block(2) which contains 0353 * a better gamma value */ 0354 gamma = (data[i + 3 + 9] / 100.0) + 1; 0355 } 0356 } 0357 } 0358 0359 // calculate checksum 0360 QCryptographicHash hash(QCryptographicHash::Md5); 0361 hash.addData(reinterpret_cast<const char *>(data), length); 0362 checksum = QString::fromLatin1(hash.result().toHex()); 0363 0364 valid = true; 0365 return valid; 0366 } 0367 0368 int Edid::Private::edidGetBit(int in, int bit) const 0369 { 0370 return (in & (1 << bit)) >> bit; 0371 } 0372 0373 int Edid::Private::edidGetBits(int in, int begin, int end) const 0374 { 0375 int mask = (1 << (end - begin + 1)) - 1; 0376 0377 return (in >> begin) & mask; 0378 } 0379 0380 float Edid::Private::edidDecodeFraction(int high, int low) const 0381 { 0382 float result = 0.0; 0383 0384 high = (high << 2) | low; 0385 for (int i = 0; i < 10; ++i) { 0386 result += edidGetBit(high, i) * pow(2, i - 10); 0387 } 0388 return result; 0389 } 0390 0391 QString Edid::Private::edidParseString(const quint8 *data) const 0392 { 0393 /* this is always 13 bytes, but we can't guarantee it's null 0394 * terminated or not junk. */ 0395 auto text = QString::fromLatin1(reinterpret_cast<const char *>(data), 13).simplified(); 0396 0397 for (int i = 0; i < text.length(); ++i) { 0398 if (!text.at(i).isPrint()) { 0399 text[i] = QLatin1Char('-'); 0400 } 0401 } 0402 return text; 0403 }