master ohmu/fastfetch-openbsd / src / common / impl / edidHelper.c
ohmu  ·  2026-10-15
  1#include "common/edidHelper.h"
  2
  3void ffEdidGetPhysicalResolution(const uint8_t edid[128], uint32_t* width, uint32_t* height) {
  4    const int dtd = 54;
  5    *width = (((uint32_t) edid[dtd + 4] >> 4) << 8) | edid[dtd + 2];
  6    *height = (((uint32_t) edid[dtd + 7] >> 4) << 8) | edid[dtd + 5];
  7}
  8
  9void ffEdidGetPreferredResolutionAndRefreshRate(const uint8_t edid[128], uint32_t* width, uint32_t* height, double* refreshRate) {
 10    for (uint32_t i = 0x36; i < 0x7E; i += 0x12) {    // read through descriptor blocks...
 11        if (edid[i] != 0x00 && edid[i + 1] != 0x00) { // a dtd
 12            uint32_t hactive = edid[i + 2] + (uint32_t) ((edid[i + 4] & 0xf0) << 4);
 13            uint32_t hblank = edid[i + 3] + (uint32_t) ((edid[i + 4] & 0x0f) << 8);
 14            uint32_t vactive = edid[i + 5] + (uint32_t) ((edid[i + 7] & 0xf0) << 4);
 15            uint32_t vblank = edid[i + 6] + (uint32_t) ((edid[i + 7] & 0x0f) << 8);
 16            uint32_t pixclk = ((uint32_t) edid[i + 1] << 8) | (edid[i]);
 17            *width = hactive;
 18            *height = vactive;
 19            *refreshRate = (double) pixclk * 10000 / (double) (hactive + hblank) / (double) (vactive + vblank);
 20            return;
 21        }
 22    }
 23}
 24
 25void ffEdidGetVendorAndModel(const uint8_t edid[128], FFstrbuf* result) {
 26    // https://github.com/jinksong/read_edid/blob/master/parse-edid/parse-edid.c
 27    ffStrbufAppendF(result, "%c%c%c%04X", (char) (((uint32_t) edid[8] >> 2 & 0x1f) + 'A' - 1), (char) (((((uint32_t) edid[8] & 0x3) << 3) | (((uint32_t) edid[9] & 0xe0) >> 5)) + 'A' - 1), (char) (((uint32_t) edid[9] & 0x1f) + 'A' - 1), (uint32_t) (edid[10] + (uint32_t) (edid[11] << 8)));
 28}
 29
 30bool ffEdidGetName(const uint8_t edid[128], FFstrbuf* name) {
 31    // https://github.com/jinksong/read_edid/blob/master/parse-edid/parse-edid.c
 32    for (uint32_t i = 0x36; i < 0x7E; i += 0x12) { // read through descriptor blocks...
 33        if (edid[i] == 0x00) {                     // not a timing descriptor
 34            if (edid[i + 3] == 0xfc) {             // Model Name tag
 35                for (uint32_t j = 0; j < 13; j++) {
 36                    if (edid[i + 5 + j] == 0x0a) {
 37                        ffStrbufAppendNS(name, j, (const char*) &edid[i + 5]);
 38                        return true;
 39                    }
 40                }
 41            }
 42        }
 43    }
 44
 45    // use manufacturer + model number as monitor name
 46    ffEdidGetVendorAndModel(edid, name);
 47    return false;
 48}
 49
 50void ffEdidGetPhysicalSize(const uint8_t edid[128], uint32_t* width, uint32_t* height) {
 51    // Detailed Timing Descriptors
 52    uint32_t dw = (((uint32_t) edid[68] & 0xF0) << 4) + edid[66];
 53    uint32_t dh = (((uint32_t) edid[68] & 0x0F) << 8) + edid[67];
 54
 55    // Basic Display Parameters
 56    uint32_t bw = edid[21] * 10;
 57    uint32_t bh = edid[22] * 10;
 58
 59    // Some monitors report invalid data in DTD. See #1406
 60    if (abs((int) dw - (int) bw) < 10 && abs((int) dh - (int) bh) < 10) {
 61        *width = dw;
 62        *height = dh;
 63    } else {
 64        *width = bw;
 65        *height = bh;
 66    }
 67}
 68
 69void ffEdidGetManufactureDate(const uint8_t edid[128], uint16_t* year, uint16_t* week) {
 70    if (edid[17] > 0 && edid[17] < 0xFF) {
 71        *year = (uint16_t) (edid[17] + 1990);
 72        *week = (uint16_t) edid[16];
 73        if (*week == 0xFF) {
 74            *week = 0;
 75        }
 76    } else {
 77        *year = *week = 0;
 78    }
 79}
 80
 81bool ffEdidGetSerial(const uint8_t edid[128], FFstrbuf* result) {
 82    int descriptor_offsets[4] = {54, 72, 90, 108};
 83
 84    for (int i = 0; i < 4; i++) {
 85        int offset = descriptor_offsets[i];
 86        const uint8_t* desc = edid + offset;
 87
 88        if (desc[0] == 0x00 && desc[1] == 0x00) { // Monitor Descriptor
 89            uint8_t tag = desc[3]; // Descriptor Type Tag
 90
 91            if (tag == 0xFF) { // Display Product Serial Number
 92                ffStrbufClear(result);
 93                for (int j = 0; j < 13; j++) {
 94                    uint8_t ch = desc[5 + j];
 95
 96                    if (ch == 0x0A || ch == 0x00) {
 97                        break;
 98                    }
 99
100                    ffStrbufAppendC(result, (char) ch);
101                }
102
103                ffStrbufTrim(result, ' ');
104                if (result->length > 0) {
105                    return true;
106                }
107            }
108        }
109    }
110
111    uint32_t int_serial = 0;
112    int_serial |= (uint32_t)edid[12];
113    int_serial |= (uint32_t)edid[13] << 8;
114    int_serial |= (uint32_t)edid[14] << 16;
115    int_serial |= (uint32_t)edid[15] << 24;
116
117    if (int_serial != 0 && int_serial != 0xFFFFFFFF) {
118        ffStrbufSetF(result, "0x%08X", int_serial);
119        return true;
120    }
121    return false;
122}
123
124bool ffEdidGetHdrCompatible(const uint8_t* edid, uint32_t length) {
125    if (length <= 128) {
126        return false;
127    }
128    for (const uint8_t* cta = &edid[128]; cta < &edid[length]; cta += 128) {
129        // https://en.wikipedia.org/wiki/Extended_Display_Identification_Data#CTA_EDID_Timing_Extension_Block
130        if (cta[0] != 0x02 /* CTA EDID */) {
131            continue;
132        }
133        if (cta[1] < 0x03 /* Version 3 */) {
134            continue;
135        }
136        const uint8_t offset = cta[2];
137        if (offset <= 4) {
138            continue;
139        }
140        for (uint8_t i = 4; i < offset;) {
141            uint8_t blkLen = cta[i] & 0x1f;
142            if (blkLen > 0) {
143                uint8_t blkTag = (cta[i] & 0xe0) >> 5;
144                if (blkTag == 0x07 /* Extended Block Type Tag */) {
145                    uint8_t extendedTag = cta[i + 1];
146                    if (extendedTag == 6 /* HDR SMDB */ || extendedTag == 7 /* HDR DMDB */) {
147                        return true;
148                    }
149                }
150            }
151            i += (uint8_t) (blkLen + 1);
152        }
153    }
154    return false;
155}
156
157bool ffEdidIsValid(const uint8_t edid[128], uint32_t length) {
158    if (length < 128 || length % 128 != 0) {
159        return false;
160    }
161
162    static const uint8_t edidHeader[] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 };
163    if (memcmp(edid, edidHeader, sizeof(edidHeader)) != 0) {
164        return false;
165    }
166
167    uint8_t sum = 0;
168    for (uint32_t i = 0; i < 128; i++) {
169        sum += edid[i];
170    }
171
172    return sum == 0;
173}