File indexing completed on 2024-04-14 14:32:49
0001 /*************************************************************************** 0002 * Copyright (C) 2012-2016 by Daniel Nicoletti <dantti12@gmail.com> * 0003 * * 0004 * This program is free software; you can redistribute it and/or modify * 0005 * it under the terms of the GNU General Public License as published by * 0006 * the Free Software Foundation; either version 2 of the License, or * 0007 * (at your option) any later version. * 0008 * * 0009 * This program is distributed in the hope that it will be useful, * 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0012 * GNU General Public License for more details. * 0013 * * 0014 * You should have received a copy of the GNU General Public License * 0015 * along with this program; see the file COPYING. If not, write to * 0016 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 0017 * Boston, MA 02110-1301, USA. * 0018 ***************************************************************************/ 0019 0020 #include "Edid.h" 0021 0022 #include <math.h> 0023 0024 #include <QCryptographicHash> 0025 #include <QFile> 0026 #include <QStringList> 0027 0028 #include <QLoggingCategory> 0029 0030 #define GCM_EDID_OFFSET_PNPID 0x08 0031 #define GCM_EDID_OFFSET_SERIAL 0x0c 0032 #define GCM_EDID_OFFSET_SIZE 0x15 0033 #define GCM_EDID_OFFSET_GAMMA 0x17 0034 #define GCM_EDID_OFFSET_DATA_BLOCKS 0x36 0035 #define GCM_EDID_OFFSET_LAST_BLOCK 0x6c 0036 #define GCM_EDID_OFFSET_EXTENSION_BLOCK_COUNT 0x7e 0037 0038 #define GCM_DESCRIPTOR_DISPLAY_PRODUCT_NAME 0xfc 0039 #define GCM_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER 0xff 0040 #define GCM_DESCRIPTOR_COLOR_MANAGEMENT_DATA 0xf9 0041 #define GCM_DESCRIPTOR_ALPHANUMERIC_DATA_STRING 0xfe 0042 #define GCM_DESCRIPTOR_COLOR_POINT 0xfb 0043 0044 #define PNP_IDS "/usr/share/hwdata/pnp.ids" 0045 0046 Q_DECLARE_LOGGING_CATEGORY(COLORD) 0047 0048 Edid::Edid() 0049 { 0050 } 0051 0052 Edid::Edid(const quint8 *data, size_t length) 0053 { 0054 parse(data, length); 0055 } 0056 0057 bool Edid::isValid() const 0058 { 0059 return m_valid; 0060 } 0061 0062 QString Edid::deviceId(const QString &fallbackName) const 0063 { 0064 QString id = QStringLiteral("xrandr"); 0065 // if no info was added check if the fallbacName is provided 0066 if (vendor().isNull() && name().isNull() && serial().isNull()) { 0067 if (!fallbackName.isEmpty()) { 0068 id.append(QLatin1Char('-') % fallbackName); 0069 } else { 0070 // all info we have are empty strings 0071 id.append(QLatin1String("-unknown")); 0072 } 0073 } else if (m_valid) { 0074 if (!vendor().isNull()) { 0075 id.append(QLatin1Char('-') % vendor()); 0076 } 0077 if (!name().isNull()) { 0078 id.append(QLatin1Char('-') % name()); 0079 } 0080 if (!serial().isNull()) { 0081 id.append(QLatin1Char('-') % serial()); 0082 } 0083 } 0084 0085 return id; 0086 } 0087 0088 QString Edid::name() const 0089 { 0090 if (m_valid) { 0091 return m_monitorName; 0092 } 0093 return QString(); 0094 } 0095 0096 QString Edid::vendor() const 0097 { 0098 if (m_valid) { 0099 return m_vendorName; 0100 } 0101 return QString(); 0102 } 0103 0104 QString Edid::serial() const 0105 { 0106 if (m_valid) { 0107 return m_serialNumber; 0108 } 0109 return QString(); 0110 } 0111 0112 QString Edid::eisaId() const 0113 { 0114 if (m_valid) { 0115 return m_eisaId; 0116 } 0117 return QString(); 0118 } 0119 0120 QString Edid::hash() const 0121 { 0122 if (m_valid) { 0123 return m_checksum; 0124 } 0125 return QString(); 0126 } 0127 0128 QString Edid::pnpId() const 0129 { 0130 if (m_valid) { 0131 return m_pnpId; 0132 } 0133 return QString(); 0134 } 0135 0136 uint Edid::width() const 0137 { 0138 return m_width; 0139 } 0140 0141 uint Edid::height() const 0142 { 0143 return m_height; 0144 } 0145 0146 qreal Edid::gamma() const 0147 { 0148 return m_gamma; 0149 } 0150 0151 QQuaternion Edid::red() const 0152 { 0153 return m_red; 0154 } 0155 0156 QQuaternion Edid::green() const 0157 { 0158 return m_green; 0159 } 0160 0161 QQuaternion Edid::blue() const 0162 { 0163 return m_blue; 0164 } 0165 0166 QQuaternion Edid::white() const 0167 { 0168 return m_white; 0169 } 0170 0171 bool Edid::parse(const quint8 *data, size_t length) 0172 { 0173 quint32 serial; 0174 0175 /* check header */ 0176 if (length < 128) { 0177 qCWarning(COLORD) << "EDID length is too small"; 0178 m_valid = false; 0179 return m_valid; 0180 } 0181 if (data[0] != 0x00 || data[1] != 0xff) { 0182 qCWarning(COLORD) << "Failed to parse EDID header"; 0183 m_valid = false; 0184 return m_valid; 0185 } 0186 0187 /* decode the PNP ID from three 5 bit words packed into 2 bytes 0188 * /--08--\/--09--\ 0189 * 7654321076543210 0190 * |\---/\---/\---/ 0191 * R C1 C2 C3 */ 0192 m_pnpId.resize(3); 0193 m_pnpId[0] = QChar::fromLatin1('A' + ((data[GCM_EDID_OFFSET_PNPID + 0] & 0x7c) / 4) - 1); 0194 m_pnpId[1] = QChar::fromLatin1('A' + ((data[GCM_EDID_OFFSET_PNPID + 0] & 0x3) * 8) + ((data[GCM_EDID_OFFSET_PNPID + 1] & 0xe0) / 32) - 1); 0195 m_pnpId[2] = QChar::fromLatin1('A' + (data[GCM_EDID_OFFSET_PNPID + 1] & 0x1f) - 1); 0196 0197 // load the PNP_IDS file and load the vendor name 0198 if (!m_pnpId.isEmpty()) { 0199 QFile pnpIds(QLatin1String(PNP_IDS)); 0200 if (pnpIds.open(QIODevice::ReadOnly)) { 0201 while (!pnpIds.atEnd()) { 0202 QString line = pnpIds.readLine(); 0203 if (line.startsWith(m_pnpId)) { 0204 QStringList parts = line.split(QLatin1Char('\t')); 0205 if (parts.size() == 2) { 0206 m_vendorName = line.split(QLatin1Char('\t')).at(1).simplified(); 0207 } 0208 break; 0209 } 0210 } 0211 qCDebug(COLORD) << "PNP ID" << m_pnpId << "Vendor Name" << m_vendorName; 0212 } 0213 } 0214 0215 /* maybe there isn't a ASCII serial number descriptor, so use this instead */ 0216 serial = static_cast<quint32>(data[GCM_EDID_OFFSET_SERIAL + 0]); 0217 serial += static_cast<quint32>(data[GCM_EDID_OFFSET_SERIAL + 1] * 0x100); 0218 serial += static_cast<quint32>(data[GCM_EDID_OFFSET_SERIAL + 2] * 0x10000); 0219 serial += static_cast<quint32>(data[GCM_EDID_OFFSET_SERIAL + 3] * 0x1000000); 0220 if (serial > 0) { 0221 m_serialNumber = QString::number(serial); 0222 } 0223 0224 /* get the size */ 0225 m_width = data[GCM_EDID_OFFSET_SIZE + 0]; 0226 m_height = data[GCM_EDID_OFFSET_SIZE + 1]; 0227 0228 /* we don't care about aspect */ 0229 if (m_width == 0 || m_height == 0) { 0230 m_width = 0; 0231 m_height = 0; 0232 } 0233 0234 /* get gamma */ 0235 if (data[GCM_EDID_OFFSET_GAMMA] == 0xff) { 0236 m_gamma = 1.0f; 0237 } else { 0238 m_gamma = (static_cast<float>(data[GCM_EDID_OFFSET_GAMMA] / 100) + 1); 0239 } 0240 0241 /* get color red */ 0242 m_red.setX(edidDecodeFraction(data[0x1b], edidGetBits(data[0x19], 6, 7))); 0243 m_red.setY(edidDecodeFraction(data[0x1c], edidGetBits(data[0x19], 5, 4))); 0244 0245 /* get color green */ 0246 m_green.setX(edidDecodeFraction(data[0x1d], edidGetBits(data[0x19], 2, 3))); 0247 m_green.setY(edidDecodeFraction(data[0x1e], edidGetBits(data[0x19], 0, 1))); 0248 0249 /* get color blue */ 0250 m_blue.setX(edidDecodeFraction(data[0x1f], edidGetBits(data[0x1a], 6, 7))); 0251 m_blue.setY(edidDecodeFraction(data[0x20], edidGetBits(data[0x1a], 4, 5))); 0252 0253 /* get color white */ 0254 m_white.setX(edidDecodeFraction(data[0x21], edidGetBits(data[0x1a], 2, 3))); 0255 m_white.setY(edidDecodeFraction(data[0x22], edidGetBits(data[0x1a], 0, 1))); 0256 0257 /* parse EDID data */ 0258 for (uint i = GCM_EDID_OFFSET_DATA_BLOCKS; i <= GCM_EDID_OFFSET_LAST_BLOCK; i += 18) { 0259 /* ignore pixel clock data */ 0260 if (data[i] != 0) { 0261 continue; 0262 } 0263 if (data[i + 2] != 0) { 0264 continue; 0265 } 0266 0267 /* any useful blocks? */ 0268 if (data[i + 3] == GCM_DESCRIPTOR_DISPLAY_PRODUCT_NAME) { 0269 QString tmp = edidParseString(&data[i + 5]); 0270 if (!tmp.isEmpty()) { 0271 m_monitorName = tmp; 0272 } 0273 } else if (data[i + 3] == GCM_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER) { 0274 QString tmp = edidParseString(&data[i + 5]); 0275 if (!tmp.isEmpty()) { 0276 m_serialNumber = tmp; 0277 } 0278 } else if (data[i + 3] == GCM_DESCRIPTOR_COLOR_MANAGEMENT_DATA) { 0279 qCWarning(COLORD) << "failing to parse color management data"; 0280 } else if (data[i + 3] == GCM_DESCRIPTOR_ALPHANUMERIC_DATA_STRING) { 0281 QString tmp = edidParseString(&data[i + 5]); 0282 if (!tmp.isEmpty()) { 0283 m_eisaId = tmp; 0284 } 0285 } else if (data[i + 3] == GCM_DESCRIPTOR_COLOR_POINT) { 0286 if (data[i + 3 + 9] != 0xff) { 0287 /* extended EDID block(1) which contains 0288 * a better gamma value */ 0289 m_gamma = ((float)data[i + 3 + 9] / 100) + 1; 0290 } 0291 if (data[i + 3 + 14] != 0xff) { 0292 /* extended EDID block(2) which contains 0293 * a better gamma value */ 0294 m_gamma = ((float)data[i + 3 + 9] / 100) + 1; 0295 } 0296 } 0297 } 0298 0299 // calculate checksum 0300 QCryptographicHash hash(QCryptographicHash::Md5); 0301 hash.addData(reinterpret_cast<const char *>(data), length); 0302 m_checksum = hash.result().toHex(); 0303 0304 m_valid = true; 0305 return m_valid; 0306 } 0307 0308 int Edid::edidGetBit(int in, int bit) const 0309 { 0310 return (in & (1 << bit)) >> bit; 0311 } 0312 0313 int Edid::edidGetBits(int in, int begin, int end) const 0314 { 0315 int mask = (1 << (end - begin + 1)) - 1; 0316 0317 return (in >> begin) & mask; 0318 } 0319 0320 double Edid::edidDecodeFraction(int high, int low) const 0321 { 0322 double result = 0.0; 0323 int i; 0324 0325 high = (high << 2) | low; 0326 for (i = 0; i < 10; ++i) { 0327 result += edidGetBit(high, i) * pow(2, i - 10); 0328 } 0329 return result; 0330 } 0331 0332 QString Edid::edidParseString(const quint8 *data) const 0333 { 0334 QString text; 0335 0336 /* this is always 13 bytes, but we can't guarantee it's null 0337 * terminated or not junk. */ 0338 text = QString::fromLatin1((const char *)data, 13); 0339 0340 // Remove newlines, extra spaces and stuff 0341 text = text.simplified(); 0342 0343 return text; 0344 }