/**************************************************************************** ** ** Copyright (C) 2017 Pier Luigi Fiorini ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include "qedidparser_p.h" #include "qedidvendortable_p.h" #define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) #define EDID_DESCRIPTOR_ALPHANUMERIC_STRING 0xfe #define EDID_DESCRIPTOR_PRODUCT_NAME 0xfc #define EDID_DESCRIPTOR_SERIAL_NUMBER 0xff #define EDID_OFFSET_DATA_BLOCKS 0x36 #define EDID_OFFSET_LAST_BLOCK 0x6c #define EDID_OFFSET_PNP_ID 0x08 #define EDID_OFFSET_SERIAL 0x0c #define EDID_PHYSICAL_WIDTH 0x15 #define EDID_OFFSET_PHYSICAL_HEIGHT 0x16 QT_BEGIN_NAMESPACE QEdidParser::QEdidParser() { // Cache vendors list from pnp.ids const QString fileName = QLatin1String("/usr/share/hwdata/pnp.ids"); if (QFile::exists(fileName)) { QFile file(fileName); if (file.open(QFile::ReadOnly)) { while (!file.atEnd()) { QString line = QString::fromUtf8(file.readLine()).trimmed(); if (line.startsWith(QLatin1Char('#'))) continue; QStringList parts = line.split(QLatin1Char('\t')); if (parts.count() > 1) { QString pnpId = parts.at(0); parts.removeFirst(); m_vendorCache[pnpId] = parts.join(QLatin1Char(' ')); } } file.close(); } } } bool QEdidParser::parse(const QByteArray &blob) { const quint8 *data = reinterpret_cast(blob.constData()); const size_t length = blob.length(); // Verify header if (length < 128) return false; if (data[0] != 0x00 || data[1] != 0xff) return false; /* Decode the PNP ID from three 5 bit words packed into 2 bytes * /--08--\/--09--\ * 7654321076543210 * |\---/\---/\---/ * R C1 C2 C3 */ char id[3]; id[0] = 'A' + ((data[EDID_OFFSET_PNP_ID] & 0x7c) / 4) - 1; id[1] = 'A' + ((data[EDID_OFFSET_PNP_ID] & 0x3) * 8) + ((data[EDID_OFFSET_PNP_ID + 1] & 0xe0) / 32) - 1; id[2] = 'A' + (data[EDID_OFFSET_PNP_ID + 1] & 0x1f) - 1; identifier = QString::fromLatin1(id, 3); // Clear manufacturer manufacturer = QString(); // Serial number, will be overwritten by an ASCII descriptor // when and if it will be found quint32 serial = data[EDID_OFFSET_SERIAL] + (data[EDID_OFFSET_SERIAL + 1] << 8) + (data[EDID_OFFSET_SERIAL + 2] << 16) + (data[EDID_OFFSET_SERIAL + 3] << 24); if (serial > 0) serialNumber = QString::number(serial); else serialNumber = QString(); // Parse EDID data for (int i = 0; i < 5; ++i) { const uint offset = EDID_OFFSET_DATA_BLOCKS + i * 18; if (data[offset] != 0 || data[offset + 1] != 0 || data[offset + 2] != 0) continue; if (data[offset + 3] == EDID_DESCRIPTOR_PRODUCT_NAME) model = parseEdidString(&data[offset + 5]); else if (data[offset + 3] == EDID_DESCRIPTOR_ALPHANUMERIC_STRING) identifier = parseEdidString(&data[offset + 5]); else if (data[offset + 3] == EDID_DESCRIPTOR_SERIAL_NUMBER) serialNumber = parseEdidString(&data[offset + 5]); } // Try to use cache first because it is potentially more updated if (m_vendorCache.contains(identifier)) { manufacturer = m_vendorCache[identifier]; } else { // Find the manufacturer from the vendor lookup table for (size_t i = 0; i < ARRAY_LENGTH(q_edidVendorTable); i++) { if (strcmp(q_edidVendorTable[i].id, identifier.toLatin1().constData()) == 0) { manufacturer = QString::fromUtf8(q_edidVendorTable[i].name); break; } } } // If we don't know the manufacturer, fallback to PNP ID if (manufacturer.isEmpty()) manufacturer = identifier; // Physical size physicalSize = QSizeF(data[EDID_PHYSICAL_WIDTH], data[EDID_OFFSET_PHYSICAL_HEIGHT]) * 10; return true; } QString QEdidParser::parseEdidString(const quint8 *data) { QByteArray buffer(reinterpret_cast(data), 12); // Erase carriage return and line feed buffer = buffer.replace('\r', '\0').replace('\n', '\0'); // Replace non-printable characters with dash for (int i = 0; i < buffer.count(); ++i) { if (buffer[i] < '\040' && buffer[i] > '\176') buffer[i] = '-'; } return QString::fromLatin1(buffer.trimmed()); } QT_END_NAMESPACE