GCC Code Coverage Report


Directory: ./
File: backends/edid.cpp
Date: 2023-04-20 22:59:23
Exec Total Coverage
Lines: 163 182 89.6%
Branches: 66 104 63.5%

Line Branch Exec Source
1 /*************************************************************************************
2 * Copyright (C) 2012 by Daniel Nicoletti <dantti12@gmail.com> *
3 * (C) 2012 - 2014 by Daniel Vrátil <dvratil@redhat.com> *
4 * *
5 * This library is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU Lesser General Public *
7 * License as published by the Free Software Foundation; either *
8 * version 2.1 of the License, or (at your option) any later version. *
9 * *
10 * This library is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
13 * Lesser General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU Lesser General Public *
16 * License along with this library; if not, write to the Free Software *
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
18 *************************************************************************************/
19 #include "edid.h"
20
21 #include "logging.h"
22
23 #include <math.h>
24
25 #include <QCryptographicHash>
26 #include <QFile>
27 #include <QStringBuilder>
28 #include <QStringList>
29
30 #define GCM_EDID_OFFSET_PNPID 0x08
31 #define GCM_EDID_OFFSET_SERIAL 0x0c
32 #define GCM_EDID_OFFSET_SIZE 0x15
33 #define GCM_EDID_OFFSET_GAMMA 0x17
34 #define GCM_EDID_OFFSET_DATA_BLOCKS 0x36
35 #define GCM_EDID_OFFSET_LAST_BLOCK 0x6c
36 #define GCM_EDID_OFFSET_EXTENSION_BLOCK_COUNT 0x7e
37
38 #define GCM_DESCRIPTOR_DISPLAY_PRODUCT_NAME 0xfc
39 #define GCM_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER 0xff
40 #define GCM_DESCRIPTOR_COLOR_MANAGEMENT_DATA 0xf9
41 #define GCM_DESCRIPTOR_ALPHANUMERIC_DATA_STRING 0xfe
42 #define GCM_DESCRIPTOR_COLOR_POINT 0xfb
43
44 #define PNP_IDS "/usr/share/hwdata/pnp.ids"
45
46 using namespace Disman;
47
48 class Edid::Private
49 {
50 public:
51 56 Private(QByteArray const& data)
52 56 {
53 56 parse(data);
54 56 }
55
56 1 Private(const Private& other)
57 1 : valid(other.valid)
58 1 , monitorName(other.monitorName)
59 1 , vendorName(other.vendorName)
60 1 , serialNumber(other.serialNumber)
61 1 , eisaId(other.eisaId)
62 1 , checksum(other.checksum)
63 1 , pnpId(other.pnpId)
64 1 , width(other.width)
65 1 , height(other.height)
66 1 , gamma(other.gamma)
67 1 , red(other.red)
68 1 , green(other.green)
69 1 , blue(other.blue)
70 1 , white(other.white)
71 {
72 1 }
73
74 bool valid{false};
75 std::string monitorName;
76 std::string vendorName;
77 std::string serialNumber;
78 std::string eisaId;
79 std::string checksum;
80 std::string pnpId;
81 uint width{0};
82 uint height{0};
83 double gamma{0};
84 QQuaternion red;
85 QQuaternion green;
86 QQuaternion blue;
87 QQuaternion white;
88
89 private:
90 bool parse(const QByteArray& data);
91 int edidGetBit(int in, int bit) const;
92 int edidGetBits(int in, int begin, int end) const;
93 float edidDecodeFraction(int high, int low) const;
94 std::string edidParseString(const quint8* data) const;
95 };
96
97 56 Edid::Edid(const QByteArray& data)
98 56 : d_ptr{new Private(data)}
99 {
100 56 }
101
102 1 Edid::Edid(Edid const& edid)
103 1 : d_ptr(new Private(*edid.d_ptr))
104 {
105 1 }
106
107 57 Edid::~Edid() = default;
108
109 57 bool Edid::isValid() const
110 {
111 57 return d_ptr->valid;
112 }
113
114 4 std::string Edid::deviceId() const
115 {
116 4 std::string id = "xrandr";
117 // if no info was added check if the fallbacName is provided
118
5/14
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 4 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 4 times.
✓ Branch 18 taken 4 times.
✗ Branch 19 not taken.
✗ Branch 21 not taken.
✓ Branch 22 taken 4 times.
4 if (!vendor().size() && !name().size() && !serial().size()) {
119 // All info we have are empty strings.
120 id.append("-unknown");
121
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 } else if (d_ptr->valid) {
122
1/2
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
4 if (vendor().size()) {
123 4 id.append('-' + vendor());
124 }
125
2/2
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
4 if (name().size()) {
126 2 id.append('-' + name());
127 }
128
2/2
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
4 if (serial().size()) {
129 2 id.append('-' + serial());
130 }
131 }
132
133 4 return id;
134 }
135
136 30 std::string Edid::name() const
137 {
138
2/2
✓ Branch 1 taken 28 times.
✓ Branch 2 taken 2 times.
30 if (d_ptr->valid) {
139 28 return d_ptr->monitorName;
140 }
141 2 return std::string();
142 }
143
144 30 std::string Edid::vendor() const
145 {
146
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 if (d_ptr->valid) {
147 30 return d_ptr->vendorName;
148 }
149 return std::string();
150 }
151
152 28 std::string Edid::serial() const
153 {
154
1/2
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
28 if (d_ptr->valid) {
155 28 return d_ptr->serialNumber;
156 }
157 return std::string();
158 }
159
160 4 std::string Edid::eisaId() const
161 {
162
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 if (d_ptr->valid) {
163 4 return d_ptr->eisaId;
164 }
165 return std::string();
166 }
167
168 4 std::string Edid::hash() const
169 {
170
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 if (d_ptr->valid) {
171 4 return d_ptr->checksum;
172 }
173 return std::string();
174 }
175
176 4 std::string Edid::pnpId() const
177 {
178
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 if (d_ptr->valid) {
179 4 return d_ptr->pnpId;
180 }
181 return std::string();
182 }
183
184 22 uint Edid::width() const
185 {
186 22 return d_ptr->width;
187 }
188
189 22 uint Edid::height() const
190 {
191 22 return d_ptr->height;
192 }
193
194 4 double Edid::gamma() const
195 {
196 4 return d_ptr->gamma;
197 }
198
199 4 QQuaternion Edid::red() const
200 {
201 4 return d_ptr->red;
202 }
203
204 4 QQuaternion Edid::green() const
205 {
206 4 return d_ptr->green;
207 }
208
209 4 QQuaternion Edid::blue() const
210 {
211 4 return d_ptr->blue;
212 }
213
214 4 QQuaternion Edid::white() const
215 {
216 4 return d_ptr->white;
217 }
218
219 56 bool Edid::Private::parse(const QByteArray& rawData)
220 {
221 56 const quint8* data = reinterpret_cast<const quint8*>(rawData.constData());
222 56 int length = rawData.length();
223
224 /* check header */
225
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 22 times.
56 if (length < 128) {
226
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 33 times.
34 if (length > 0) {
227
2/2
✓ Branch 9 taken 1 times.
✓ Branch 10 taken 1 times.
2 qCWarning(DISMAN_BACKEND) << "Invalid EDID length (" << length << " bytes)";
228 }
229 34 valid = false;
230 34 return valid;
231 }
232
2/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
22 if (data[0] != 0x00 || data[1] != 0xff) {
233 qCWarning(DISMAN_BACKEND) << "Failed to parse EDID header";
234 valid = false;
235 return valid;
236 }
237
238 /* decode the PNP ID from three 5 bit words packed into 2 bytes
239 * /--08--\/--09--\
240 * 7654321076543210
241 * |\---/\---/\---/
242 * R C1 C2 C3 */
243 22 pnpId.resize(3);
244 22 pnpId[0] = 'A' + ((data[GCM_EDID_OFFSET_PNPID + 0] & 0x7c) / 4) - 1;
245 44 pnpId[1] = 'A' + ((data[GCM_EDID_OFFSET_PNPID + 0] & 0x3) * 8)
246 22 + ((data[GCM_EDID_OFFSET_PNPID + 1] & 0xe0) / 32) - 1;
247 22 pnpId[2] = 'A' + (data[GCM_EDID_OFFSET_PNPID + 1] & 0x1f) - 1;
248 22 pnpId.resize(strlen(pnpId.data()));
249
250 // load the PNP_IDS file and load the vendor name
251
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 if (pnpId.size()) {
252 22 QFile pnpIds(QStringLiteral(PNP_IDS));
253
1/2
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 if (pnpIds.open(QIODevice::ReadOnly)) {
254
1/2
✓ Branch 1 taken 26615 times.
✗ Branch 2 not taken.
26615 while (!pnpIds.atEnd()) {
255 26615 QString line = QString::fromUtf8(pnpIds.readLine());
256
2/2
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 26593 times.
26615 if (line.startsWith(QString::fromStdString(pnpId))) {
257 22 QStringList parts = line.split(QLatin1Char('\t'));
258
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 if (parts.size() == 2) {
259 22 vendorName = line.split(QLatin1Char('\t')).at(1).simplified().toStdString();
260 }
261 22 break;
262 22 }
263
2/2
✓ Branch 1 taken 26593 times.
✓ Branch 2 taken 22 times.
26615 }
264 }
265 22 }
266
267 /* maybe there isn't a ASCII serial number descriptor, so use this instead */
268 22 auto serial = static_cast<uint32_t>(data[GCM_EDID_OFFSET_SERIAL + 0]);
269 22 serial += static_cast<uint32_t>(data[GCM_EDID_OFFSET_SERIAL + 1] * 0x100);
270 22 serial += static_cast<uint32_t>(data[GCM_EDID_OFFSET_SERIAL + 2] * 0x10000);
271 22 serial += static_cast<uint32_t>(data[GCM_EDID_OFFSET_SERIAL + 3] * 0x1000000);
272
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 2 times.
22 if (serial > 0) {
273 20 serialNumber = std::to_string(serial);
274 }
275
276 /* get the size */
277 22 width = data[GCM_EDID_OFFSET_SIZE + 0];
278 22 height = data[GCM_EDID_OFFSET_SIZE + 1];
279
280 /* we don't care about aspect */
281
2/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
22 if (width == 0 || height == 0) {
282 width = 0;
283 height = 0;
284 }
285
286 /* get gamma */
287
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 if (data[GCM_EDID_OFFSET_GAMMA] == 0xff) {
288 gamma = 1.0;
289 } else {
290 22 gamma = data[GCM_EDID_OFFSET_GAMMA] / 100.0 + 1.0;
291 }
292
293 /* get color red */
294 22 red.setX(edidDecodeFraction(data[0x1b], edidGetBits(data[0x19], 6, 7)));
295 22 red.setY(edidDecodeFraction(data[0x1c], edidGetBits(data[0x19], 5, 4)));
296
297 /* get color green */
298 22 green.setX(edidDecodeFraction(data[0x1d], edidGetBits(data[0x19], 2, 3)));
299 22 green.setY(edidDecodeFraction(data[0x1e], edidGetBits(data[0x19], 0, 1)));
300
301 /* get color blue */
302 22 blue.setX(edidDecodeFraction(data[0x1f], edidGetBits(data[0x1a], 6, 7)));
303 22 blue.setY(edidDecodeFraction(data[0x20], edidGetBits(data[0x1a], 4, 5)));
304
305 /* get color white */
306 22 white.setX(edidDecodeFraction(data[0x21], edidGetBits(data[0x1a], 2, 3)));
307 22 white.setY(edidDecodeFraction(data[0x22], edidGetBits(data[0x1a], 0, 1)));
308
309 /* parse EDID data */
310
2/2
✓ Branch 0 taken 88 times.
✓ Branch 1 taken 22 times.
110 for (uint i = GCM_EDID_OFFSET_DATA_BLOCKS; i <= GCM_EDID_OFFSET_LAST_BLOCK; i += 18) {
311 /* ignore pixel clock data */
312
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 64 times.
88 if (data[i] != 0) {
313 24 continue;
314 }
315
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 64 times.
64 if (data[i + 2] != 0) {
316 continue;
317 }
318
319 /* any useful blocks? */
320
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 44 times.
64 if (data[i + 3] == GCM_DESCRIPTOR_DISPLAY_PRODUCT_NAME) {
321 20 monitorName = edidParseString(&data[i + 5]);
322
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 24 times.
44 } else if (data[i + 3] == GCM_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER) {
323 20 serialNumber = edidParseString(&data[i + 5]);
324
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 } else if (data[i + 3] == GCM_DESCRIPTOR_COLOR_MANAGEMENT_DATA) {
325 qCWarning(DISMAN_BACKEND) << "failing to parse color management data";
326
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 21 times.
24 } else if (data[i + 3] == GCM_DESCRIPTOR_ALPHANUMERIC_DATA_STRING) {
327 3 eisaId = edidParseString(&data[i + 5]);
328
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
21 } else if (data[i + 3] == GCM_DESCRIPTOR_COLOR_POINT) {
329 if (data[i + 3 + 9] != 0xff) {
330 /* extended EDID block(1) which contains
331 * a better gamma value */
332 gamma = (data[i + 3 + 9] / 100.0) + 1;
333 }
334 if (data[i + 3 + 14] != 0xff) {
335 /* extended EDID block(2) which contains
336 * a better gamma value */
337 gamma = (data[i + 3 + 9] / 100.0) + 1;
338 }
339 }
340 }
341
342 // calculate checksum
343 22 QCryptographicHash hash(QCryptographicHash::Md5);
344 22 hash.addData(reinterpret_cast<const char*>(data), length);
345 22 checksum = QString::fromLatin1(hash.result().toHex()).toStdString();
346
347 22 valid = true;
348 22 return valid;
349 22 }
350
351 1760 int Edid::Private::edidGetBit(int in, int bit) const
352 {
353 1760 return (in & (1 << bit)) >> bit;
354 }
355
356 176 int Edid::Private::edidGetBits(int in, int begin, int end) const
357 {
358 176 int mask = (1 << (end - begin + 1)) - 1;
359
360 176 return (in >> begin) & mask;
361 }
362
363 176 float Edid::Private::edidDecodeFraction(int high, int low) const
364 {
365 176 float result = 0.0;
366
367 176 high = (high << 2) | low;
368
2/2
✓ Branch 0 taken 1760 times.
✓ Branch 1 taken 176 times.
1936 for (int i = 0; i < 10; ++i) {
369 1760 result += edidGetBit(high, i) * pow(2, i - 10);
370 }
371 176 return result;
372 }
373
374 43 std::string Edid::Private::edidParseString(const quint8* data) const
375 {
376 // We know his is always 13 bytes but might not be null-terminated.
377 43 auto text = std::string(reinterpret_cast<const char*>(data), 13);
378
379 43 auto i = text.size();
380
1/2
✓ Branch 0 taken 155 times.
✗ Branch 1 not taken.
155 while (i > 0) {
381 155 i--;
382
6/6
✓ Branch 1 taken 113 times.
✓ Branch 2 taken 42 times.
✓ Branch 4 taken 43 times.
✓ Branch 5 taken 70 times.
✓ Branch 6 taken 43 times.
✓ Branch 7 taken 112 times.
155 if (std::isprint(text.at(i)) && text.at(i) != ' ') {
383 // Found the first printable char. Everything before is cleaned up in the forward loop.
384 43 break;
385 }
386 112 text[i] = '\0';
387 };
388
389 43 text.resize(i + 1);
390
391
2/2
✓ Branch 1 taken 447 times.
✓ Branch 2 taken 43 times.
490 for (i = 0; i < text.size(); ++i) {
392
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 447 times.
447 if (text.at(i) == '\0') {
393 break;
394 }
395
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 446 times.
447 if (!std::isprint(text.at(i))) {
396 1 text[i] = '-';
397 }
398 }
399
400 43 return text;
401 }
402