diff options
| author | Burt P <pburt0@gmail.com> | 2019-08-18 02:32:58 -0500 | 
|---|---|---|
| committer | Leandro A. F. Pereira <leandro@hardinfo.org> | 2019-08-18 23:49:29 +0200 | 
| commit | 0e0797a9257c8ee039746da9903138c19615e7a0 (patch) | |
| tree | 9844f60b23370c1bf42692ce086a4ba7c935acff /deps/sysobj_early/src | |
| parent | e49e438270cdffc4a80a2676c73ad95bedce33d4 (diff) | |
monitors: updated util_edid
Signed-off-by: Burt P <pburt0@gmail.com>
Diffstat (limited to 'deps/sysobj_early/src')
| -rw-r--r-- | deps/sysobj_early/src/appf.c | 2 | ||||
| -rw-r--r-- | deps/sysobj_early/src/util_edid.c | 624 | 
2 files changed, 438 insertions, 188 deletions
| diff --git a/deps/sysobj_early/src/appf.c b/deps/sysobj_early/src/appf.c index 5969d945..e63e77d0 100644 --- a/deps/sysobj_early/src/appf.c +++ b/deps/sysobj_early/src/appf.c @@ -28,7 +28,7 @@  /* FIXME: if this isn't here, hardinfo will crash,   * I don't have the slightest idea why */  void wtf() { -    edid_fill(NULL, NULL, 0); +    void *e = edid_new(NULL, 0);  }  char *appf(char *str, const char *sep, const char *fmt, ...) { diff --git a/deps/sysobj_early/src/util_edid.c b/deps/sysobj_early/src/util_edid.c index 3f953191..80a71002 100644 --- a/deps/sysobj_early/src/util_edid.c +++ b/deps/sysobj_early/src/util_edid.c @@ -25,12 +25,12 @@  #include <math.h>  #include <endian.h>  #include <stdio.h> +#include "gettext.h"  #include "util_edid.h"  #include "util_sysobj.h" -#include "gettext.h"  /* block must be 128 bytes */ -int block_check(const void *edid_block_bytes) { +static int block_check(const void *edid_block_bytes) {      if (!edid_block_bytes) return 0;      uint8_t sum = 0;      uint8_t *data = (uint8_t*)edid_block_bytes; @@ -40,148 +40,238 @@ int block_check(const void *edid_block_bytes) {      return sum == 0 ? 1 : 0;  } -int edid_fill(struct edid *id_out, const void *edid_bytes, int edid_len) { +char *hex_bytes(uint8_t *bytes, int count) { +    char *buffer = malloc(count*3+1), *p = buffer; +    memset(buffer, 0, count*3+1);      int i; +    for(i = 0; i < count; i++) { +        sprintf(p, "%02x ", (unsigned int)bytes[i]); +        p += 3; +    } +    return buffer; +} -    if (!id_out) return 0; - -    memset(id_out, 0, sizeof(struct edid)); -    id_out->size = edid_len; - -    if (edid_len >= 128) { -        uint8_t *u8 = (uint8_t*)edid_bytes; -        uint16_t *u16 = (uint16_t*)edid_bytes; -        uint32_t *u32 = (uint32_t*)edid_bytes; - -        id_out->ver_major = u8[18];        /* byte 18 */ -        id_out->ver_minor = u8[19];        /* byte 19 */ - -        /* checksum first block */ -        id_out->checksum_ok = block_check(edid_bytes); -        if (edid_len > 128) { -            int blocks = edid_len / 128; -            if (blocks > EDID_MAX_EXT_BLOCKS) blocks = EDID_MAX_EXT_BLOCKS; -            blocks--; -            id_out->ext_blocks = blocks; -            for(; blocks; blocks--) { -                id_out->ext[blocks-1][0] = *(u8 + (blocks * 128)); -                int r = block_check(u8 + (blocks * 128)); -                id_out->ext[blocks-1][1] = (uint8_t)r; -                if (r) id_out->ext_blocks_ok++; -                else id_out->ext_blocks_fail++; -            } +static void cea_block_decode(struct edid_cea_block *blk) { +    struct edid_cea_audio *blk_audio = (void*)blk; +    struct edid_cea_video *blk_video = (void*)blk; +    struct edid_cea_speaker *blk_speaker = (void*)blk; +    struct edid_cea_vendor_spec *blk_vendor_spec = (void*)blk; +    if (blk) { +        switch(blk->header.type) { +            case 0x1: +                blk_audio->format = (blk->header.ptr[1] & 0x78) >> 3; +                blk_audio->channels = 1 + blk->header.ptr[1] & 0x7; +                blk_audio->freq_bits = blk->header.ptr[2]; +                if (blk_audio->format == 1) { +                    blk_audio->depth_bits = blk->header.ptr[3]; +                } else if (blk_audio->format >= 2 +                        && blk_audio->format <= 8) { +                    blk_audio->max_kbps = blk->header.ptr[3] * 8; +                } +                break; +            case 0x4: +                blk_speaker->alloc_bits = blk->header.ptr[1]; +                break; +            case 0x3: +            case 0x2: +            default: +                break;          } +    } +} -        /* expect 1.3 */ - -        uint16_t vid = be16toh(u16[4]); /* bytes 8-9 */ -        id_out->ven[2] = 64 + (vid & 0x1f); -        id_out->ven[1] = 64 + ((vid >> 5) & 0x1f); -        id_out->ven[0] = 64 + ((vid >> 10) & 0x1f); - -        id_out->product = le16toh(u16[5]); /* bytes 10-11 */ -        id_out->n_serial = le32toh(u32[3]);/* bytes 12-15 */ -        id_out->week = u8[16];             /* byte 16 */ -        id_out->year = u8[17] + 1990;      /* byte 17 */ - -        if (id_out->week >= 52) -            id_out->week = 0; - -        id_out->a_or_d = (u8[20] & 0x80) ? 1 : 0; -        if (id_out->a_or_d == 1) { -            /* digital */ -            switch((u8[20] >> 4) & 0x7) { -                case 0x1: id_out->bpc = 6; break; -                case 0x2: id_out->bpc = 8; break; -                case 0x3: id_out->bpc = 10; break; -                case 0x4: id_out->bpc = 12; break; -                case 0x5: id_out->bpc = 14; break; -                case 0x6: id_out->bpc = 16; break; -            } -        } +edid *edid_new(const char *data, unsigned int len) { +    if (len < 128) return NULL; -        if (u8[21] && u8[22]) { -            id_out->horiz_cm = u8[21]; -            id_out->vert_cm = u8[22]; -            id_out->diag_cm = -                sqrt( (id_out->horiz_cm * id_out->horiz_cm) -                 + (id_out->vert_cm * id_out->vert_cm) ); -            id_out->diag_in = id_out->diag_cm / 2.54; +    int i; +    edid *e = malloc(sizeof(edid)); +    memset(e, 0, sizeof(edid)); +    e->data = malloc(len); +    memcpy(e->data, data, len); +    e->len = len; +    e->ver_major = e->u8[18]; +    e->ver_minor = e->u8[19]; + +    e->dtds = malloc(sizeof(struct edid_dtd) * 1000); +    e->cea_blocks = malloc(sizeof(struct edid_cea_block) * 1000); + +    uint16_t vid = be16toh(e->u16[4]); /* bytes 8-9 */ +    e->ven[2] = 64 + (vid & 0x1f); +    e->ven[1] = 64 + ((vid >> 5) & 0x1f); +    e->ven[0] = 64 + ((vid >> 10) & 0x1f); +    e->product = le16toh(e->u16[5]); /* bytes 10-11 */ +    e->n_serial = le32toh(e->u32[3]);/* bytes 12-15 */ +    e->week = e->u8[16];             /* byte 16 */ +    e->year = e->u8[17] + 1990;      /* byte 17 */ + +    if (e->week >= 52) +        e->week = 0; + +    e->a_or_d = (e->u8[20] & 0x80) ? 1 : 0; +    if (e->a_or_d == 1) { +        /* digital */ +        switch((e->u8[20] >> 4) & 0x7) { +            case 0x1: e->bpc = 6; break; +            case 0x2: e->bpc = 8; break; +            case 0x3: e->bpc = 10; break; +            case 0x4: e->bpc = 12; break; +            case 0x5: e->bpc = 14; break; +            case 0x6: e->bpc = 16; break;          } +    } -        uint16_t dh, dl; +    if (e->u8[21] && e->u8[22]) { +        e->horiz_cm = e->u8[21]; +        e->vert_cm = e->u8[22]; +        e->diag_cm = +            sqrt( (e->horiz_cm * e->horiz_cm) +             + (e->vert_cm * e->vert_cm) ); +        e->diag_in = e->diag_cm / 2.54; +    } -        /* descriptor at byte 54 */ -        dh = be16toh(u16[54/2]); -        dl = be16toh(u16[54/2+1]); -        id_out->d_type[0] = (dh << 16) + dl; -        switch(id_out->d_type[0]) { -            case 0xfc: case 0xff: case 0xfe: -                strncpy(id_out->d_text[0], (char*)u8+54+5, 13); -        } +    uint16_t dh, dl; +#define CHECK_DESCRIPTOR(index, offset)       \ +    if (e->u8[offset] == 0) {                 \ +        dh = be16toh(e->u16[offset/2]);       \ +        dl = be16toh(e->u16[offset/2+1]);     \ +        e->d_type[index] = (dh << 16) + dl;   \ +        switch(e->d_type[index]) {            \ +            case 0xfc: case 0xff: case 0xfe:  \ +                strncpy(e->d_text[index], (char*)e->u8+offset+5, 13);  \ +        } \ +    } else e->dtds[e->dtd_count++].ptr = &e->u8[offset]; + +    CHECK_DESCRIPTOR(0, 54); +    CHECK_DESCRIPTOR(1, 72); +    CHECK_DESCRIPTOR(2, 90); +    CHECK_DESCRIPTOR(3, 108); + +    e->checksum_ok = block_check(e->data); /* first 128-byte block only */ +    if (len > 128) { +        /* check extension blocks */ +        int blocks = len / 128; +        blocks--; +        e->ext_blocks = blocks; +        e->ext_ok = malloc(sizeof(uint8_t) * blocks); +        for(; blocks; blocks--) { +            uint8_t *u8 = e->u8 + (blocks * 128); +            int r = block_check(u8); +            e->ext_ok[blocks-1] = r; +            if (r) e->ext_blocks_ok++; +            else e->ext_blocks_fail++; + +            if (u8[0] == 0x70) { +                e->std = MAX(e->std, 2); +                /* DisplayID */ +                // TODO: ... +            } -        /* descriptor at byte 72 */ -        dh = be16toh(u16[72/2]); -        dl = be16toh(u16[72/2+1]); -        id_out->d_type[1] = (dh << 16) + dl; -        switch(id_out->d_type[1]) { -            case 0xfc: case 0xff: case 0xfe: -                strncpy(id_out->d_text[1], (char*)u8+72+5, 13); +            if (u8[0] == 0x02) { +                e->std = MAX(e->std, 1); +                /* CEA extension */ +                int db_end = u8[2]; +                //printf("db_end: %d\n", db_end); +                if (db_end) { +                    int b = 4; +                    while(b < db_end) { +                        int db_type = (u8[b] & 0xe0) >> 5; +                        int db_size = u8[b] & 0x1f; +                        //printf("CEA BLK: %s\n", hex_bytes(&u8[b], db_size+1)); +                        e->cea_blocks[e->cea_block_count].header.ptr = &u8[b]; +                        e->cea_blocks[e->cea_block_count].header.type = db_type; +                        e->cea_blocks[e->cea_block_count].header.len = db_size; +                        cea_block_decode(&e->cea_blocks[e->cea_block_count]); +                        e->cea_block_count++; +                        b += db_size + 1; +                    } +                    if (b > db_end) b = db_end; +                    //printf("dtd start: %d\n", b); +                    /* DTDs */ +                    while(b < 127) { +                        if (u8[b]) { +                            //printf("DTD: %s\n", hex_bytes(&u8[b], 18)); +                            e->dtds[e->dtd_count].ptr = &u8[b]; +                            e->dtd_count++; +                        } +                        b += 18; +                    } +                } +            }          } +    } -        /* descriptor at byte 90 */ -        dh = be16toh(u16[90/2]); -        dl = be16toh(u16[90/2+1]); -        id_out->d_type[2] = (dh << 16) + dl; -        switch(id_out->d_type[2]) { -            case 0xfc: case 0xff: case 0xfe: -                strncpy(id_out->d_text[2], (char*)u8+90+5, 13); +    /* strings */ +    for(i = 0; i < 4; i++) { +        g_strstrip(e->d_text[i]); +        switch(e->d_type[i]) { +            case 0xfc: +                e->name = e->d_text[i]; +                break; +            case 0xff: +                e->serial = e->d_text[i]; +                break; +            case 0xfe: +                if (e->ut1) +                    e->ut2 = e->d_text[i]; +                else +                    e->ut1 = e->d_text[i]; +                break;          } +    } -        /* descriptor at byte 108 */ -        dh = be16toh(u16[108/2]); -        dl = be16toh(u16[108/2+1]); -        id_out->d_type[3] = (dh << 16) + dl; -        switch(id_out->d_type[3]) { -            case 0xfc: case 0xff: case 0xfe: -                strncpy(id_out->d_text[3], (char*)u8+108+5, 13); +    /* quirks */ +    if (!e->name) { +        if (SEQ(e->ut1, "LG Display") && e->ut2) +            /* LG may use "uspecified text" for name and model */ +            e->name = e->ut2; +        else if (SEQ(e->ut1, "AUO") && e->ut2) +            /* Same with AUO */ +            e->name = e->ut2; +        else { +            if (e->ut1) e->name = e->ut1; +            if (e->ut2 && !e->serial) e->serial = e->ut2;          } +    } -        for(i = 0; i < 4; i++) { -            g_strstrip(id_out->d_text[i]); -            switch(id_out->d_type[i]) { -                case 0xfc: -                    id_out->name = id_out->d_text[i]; -                    break; -                case 0xff: -                    id_out->serial = id_out->d_text[i]; -                    break; -                case 0xfe: -                    if (id_out->ut1) -                        id_out->ut2 = id_out->d_text[i]; -                    else -                        id_out->ut1 = id_out->d_text[i]; -                    break; -            } -        } +    /* dtds */ +    for(i = 0; i < e->dtd_count; i++) { +        uint8_t *u8 = e->dtds[i].ptr; +        e->dtds[i].pixel_clock_khz = u8[0] * 10; +        e->dtds[i].horiz_pixels = +            ((u8[4] & 0xf0) << 4) + u8[2]; +        e->dtds[i].vert_lines = +            ((u8[7] & 0xf0) << 4) + u8[5]; +    } -        if (!id_out->name) { -            if (SEQ(id_out->ut1, "LG Display") && id_out->ut2) -                /* LG may use "uspecified text" for name and model */ -                id_out->name = id_out->ut2; -            else if (SEQ(id_out->ut1, "AUO") && id_out->ut2) -                /* Same with AUO */ -                id_out->name = id_out->ut2; -            else { -                if (id_out->ut1) id_out->name = id_out->ut1; -                if (id_out->ut2 && !id_out->serial) id_out->serial = id_out->ut2; -            } -        } +    /* squeeze lists */ +    if (!e->dtd_count) { +        free(e->dtds); +        e->dtds = NULL; +    } else { +        e->dtds = realloc(e->dtds, sizeof(struct edid_dtd) * e->dtd_count); +    } +    if (!e->cea_block_count) { +        if (e->cea_blocks) +            free(e->cea_blocks); +        e->cea_blocks = NULL; +    } else { +        e->cea_blocks = realloc(e->cea_blocks, sizeof(struct edid_cea_block) * e->cea_block_count); +    } + +    return e; +} + +void edid_free(edid *e) { +    if (e) { +        g_free(e->ext_ok); +        g_free(e->cea_blocks); +        g_free(e->dtds); +        g_free(e->data); +        g_free(e);      } -    return 1;  } -int edid_hex_to_bin(void **edid_bytes, int *edid_len, const char *hex_string) { +edid *edid_new_from_hex(const char *hex_string) {      int blen = strlen(hex_string) / 2;      uint8_t *buffer = malloc(blen), *n = buffer;      memset(buffer, 0, blen); @@ -202,24 +292,110 @@ int edid_hex_to_bin(void **edid_bytes, int *edid_len, const char *hex_string) {              p++;      } -    *edid_bytes = (void *)buffer; -    *edid_len = len; -    return 1; +    edid *e = edid_new((char*)buffer, len); +    free(buffer); +    return e;  } -int edid_fill_xrandr(struct edid *id_out, const char *xrandr_edid_dump) { -    void *buffer = NULL; -    int len = 0; -    edid_hex_to_bin(&buffer, &len, xrandr_edid_dump); -    return edid_fill(id_out, buffer, len); +char *edid_dump_hex(edid *e, int tabs, int breaks) { +    if (!e) return NULL; +    int lines = e->len / 16; +    int blen = lines * 35 + 1; +    unsigned int pc = 0; +    char *ret = malloc(blen); +    memset(ret, 0, blen); +    uint8_t *u8 = e->u8; +    char *p = ret; +    for(; lines; lines--) { +        int i; +        for(i = 0; i < tabs; i++) +            sprintf(p++, "\t"); +        for(i = 0; i < 16; i++) { +            sprintf(p, "%02x", (unsigned int)*u8); +            p+=2; +            u8++; +            pc++; +            if (pc == e->len) { +                if (breaks) sprintf(p++, "\n"); +                goto edid_dump_hex_done; +            } +        } +        if (breaks) sprintf(p++, "\n"); +    } +edid_dump_hex_done: +    return ret; +} + +const char *edid_standard(int std) { +    switch(std) { +        case 0: return N_("VESA EDID"); +        case 1: return N_("EIA/CEA-861"); +        case 2: return N_("VESA DisplayID"); +    }; +    return N_("unknown"); +} + +const char *edid_cea_audio_type(int type) { +    switch(type) { +        case 0: case 15: return N_("reserved"); +        case 1: return N_("LPCM"); +        case 2: return N_("AC-3"); +        case 3: return N_("MPEG1 (layers 1 and 2)"); +        case 4: return N_("MPEG1 layer 3"); +        case 5: return N_("MPEG2"); +        case 6: return N_("AAC"); +        case 7: return N_("DTS"); +        case 8: return N_("ATRAC"); +        case 9: return N_("DSD"); +        case 10: return N_("DD+"); +        case 11: return N_("DTS-HD"); +        case 12: return N_("MLP/Dolby TrueHD"); +        case 13: return N_("DST Audio"); +        case 14: return N_("WMA Pro"); +    } +    return N_("unknown type"); +} + +const char *edid_cea_block_type(int type) { +    switch(type) { +        case 0x01: +            return N_("audio"); +        case 0x02: +            return N_("video"); +        case 0x03: +            return N_("vendor specific"); +        case 0x04: +            return N_("speaker allocation"); +    } +    return N_("unknown type");  }  const char *edid_ext_block_type(int type) {      switch(type) { -        case 0xf0: -            return N_("extension block map"); +        case 0x00: +            return N_("timing extension");          case 0x02:              return N_("EIA/CEA-861 extension block"); +        case 0x10: +            return N_("video timing block"); +        case 0x20: +            return N_("EDID 2.0 extension"); +        case 0x40: +            return N_("DVI feature data/display information"); +        case 0x50: +            return N_("localized strings"); +        case 0x60: +            return N_("microdisplay interface"); +        case 0x70: +            return N_("DisplayID"); +        case 0xa7: +        case 0xaf: +        case 0xbf: +            return N_("display transfer characteristics data block"); +        case 0xf0: +            return N_("extension block map"); +        case 0xff: +            return N_("manufacturer-defined extension/display device data block");      }      return N_("unknown block type");  } @@ -247,73 +423,147 @@ const char *edid_descriptor_type(int type) {          case 0x10:              return N_("dummy");      } -    if (type < 0x0f) return N_("manufacturer reserved descriptor"); +    if (type && type < 0x0f) +        return N_("manufacturer reserved descriptor");      return N_("detailed timing descriptor");  } -char *edid_dump(struct edid *id) { +char *edid_cea_block_describe(struct edid_cea_block *blk) { +    gchar *ret = NULL; +    gchar *tmp[3] = {}; +    struct edid_cea_audio *blk_audio = (void*)blk; +    struct edid_cea_video *blk_video = (void*)blk; +    struct edid_cea_speaker *blk_speaker = (void*)blk; +    struct edid_cea_vendor_spec *blk_vendor_spec = (void*)blk; + +    if (blk) { +        char *hb = hex_bytes(blk->header.ptr, blk->header.len+1); +        switch(blk->header.type) { +            case 0x1: + +#define appfreq(b, f) if (blk_audio->freq_bits & (1 << b)) tmp[0] = appf(tmp[0], ", ", "%d", f); +#define appdepth(b, d) if (blk_audio->depth_bits & (1 << b)) tmp[1] = appf(tmp[1], ", ", "%d%s", d, _("-bit")); +                appfreq(0, 32); +                appfreq(1, 44); +                appfreq(2, 48); +                appfreq(3, 88); +                appfreq(4, 96); +                appfreq(5, 176); +                appfreq(6, 192); + +                if (blk_audio->format == 1) { +                    appdepth(0, 16); +                    appdepth(1, 20); +                    appdepth(2, 24); +                    tmp[2] = g_strdup_printf("depths: %s", tmp[1]); +                } else if (blk_audio->format >= 2 +                            && blk_audio->format <= 8 ) { +                    tmp[2] = g_strdup_printf("max_bitrate: %d %s", blk_audio->max_kbps, _("kbps")); +                } else +                    tmp[2] = g_strdup(""); + +                ret = g_strdup_printf("([%x] %s) len:%d format:([%x] %s) channels:%d rates:%s %s %s -- %s", +                    blk->header.type, _(edid_cea_block_type(blk->header.type)), +                    blk->header.len, +                    blk_audio->format, _(edid_cea_audio_type(blk_audio->format)), +                    blk_audio->channels, tmp[0], _("kHz"), +                    tmp[2], +                    hb); +                g_free(tmp[0]); +                g_free(tmp[1]); +                g_free(tmp[2]); +                break; +            case 0x4: +#define appspk(b, s) if (blk_speaker->alloc_bits & (1 << b)) tmp[0] = appf(tmp[0], ", ", "%s", s); +                appspk(0, _("LF+LR: Front left and right")); +                appspk(1, _("LFE: Low-frequency effects")); +                appspk(2, _("FC: Front center")); +                appspk(3, _("LR+LR: Rear left and right")); +                appspk(4, _("RC: Rear center")); +                appspk(5, _("???")); +                appspk(6, _("???")); +                ret = g_strdup_printf("([%x] %s) len:%d %s -- %s", +                    blk->header.type, _(edid_cea_block_type(blk->header.type)), +                    blk->header.len, +                    tmp[0], +                    hb); +                g_free(tmp[0]); +                break; +            case 0x3: +            case 0x2: +            default: +                ret = g_strdup_printf("([%x] %s) len:%d -- %s", +                    blk->header.type, _(edid_cea_block_type(blk->header.type)), +                    blk->header.len, +                    hb); +                break; +        } +        free(hb); +    } +    return ret; +} + +char *edid_dtd_describe(struct edid_dtd *dtd) { +    gchar *ret = NULL; +    if (dtd) { +        char *hb = hex_bytes(dtd->ptr, 18); +        ret = g_strdup_printf("%dx%d %s", +            dtd->horiz_pixels, dtd->vert_lines, +            hb); +        free(hb); +    } +    return ret; +} + +char *edid_dump2(edid *e) {      char *ret = NULL;      int i; +    if (!e) return NULL; -    if (!id) return NULL; -    ret = appfnl(ret, "edid_version: %d.%d (%d bytes)", id->ver_major, id->ver_minor, id->size); +    ret = appfnl(ret, "edid_version: %d.%d (%d bytes)", e->ver_major, e->ver_minor, e->len); +    ret = appfnl(ret, "standard: %s", _(edid_standard(e->std)) ); -    ret = appfnl(ret, "mfg: %s, model: %u, n_serial: %u", id->ven, id->product, id->n_serial); -    if (id->week && id->year) -        ret = appf(ret, "", ", dom: week %d of %d", id->week, id->year); -    else if (id->year) -        ret = appf(ret, "", ", dom: %d", id->year); +    ret = appfnl(ret, "mfg: %s, model: %u, n_serial: %u", e->ven, e->product, e->n_serial); +    if (e->week && e->year) +        ret = appf(ret, "", ", dom: week %d of %d", e->week, e->year); +    else if (e->year) +        ret = appf(ret, "", ", dom: %d", e->year); -    ret = appfnl(ret, "type: %s", id->a_or_d ? "digital" : "analog"); -    if (id->bpc) -        ret = appfnl(ret, "bits per color channel: %d", id->bpc); +    ret = appfnl(ret, "type: %s", e->a_or_d ? "digital" : "analog"); +    if (e->bpc) +        ret = appfnl(ret, "bits per color channel: %d", e->bpc); -    if (id->horiz_cm && id->vert_cm) -        ret = appfnl(ret, "size: %d cm × %d cm", id->horiz_cm, id->vert_cm); -    if (id->diag_cm) -        ret = appfnl(ret, "diagonal: %0.2f cm (%0.2f in)", id->diag_cm, id->diag_in); +    if (e->horiz_cm && e->vert_cm) +        ret = appfnl(ret, "size: %d cm × %d cm", e->horiz_cm, e->vert_cm); +    if (e->diag_cm) +        ret = appfnl(ret, "diagonal: %0.2f cm (%0.2f in)", e->diag_cm, e->diag_in); -    ret = appfnl(ret, "checksum %s", id->checksum_ok ? "ok" : "failed!"); -    if (id->ext_blocks_ok || id->ext_blocks_fail) -        ret = appf(ret, "", ", extension blocks: %d of %d ok", id->ext_blocks_ok, id->ext_blocks_ok + id->ext_blocks_fail); +    ret = appfnl(ret, "checksum %s", e->checksum_ok ? "ok" : "failed!"); +    if (e->ext_blocks_ok || e->ext_blocks_fail) +        ret = appf(ret, "", ", extension blocks: %d of %d ok", e->ext_blocks_ok, e->ext_blocks_ok + e->ext_blocks_fail);      for(i = 0; i < 4; i++) -        ret = appfnl(ret, "descriptor[%d] (%s): %s", i, _(edid_descriptor_type(id->d_type[i])), *id->d_text[i] ? id->d_text[i] : "{...}"); +        ret = appfnl(ret, "descriptor[%d] ([%02x] %s): %s", i, e->d_type[i], _(edid_descriptor_type(e->d_type[i])), *e->d_text[i] ? e->d_text[i] : "{...}"); + +    for(i = 0; i < e->ext_blocks; i++) { +        int type = e->u8[(i+1)*128]; +        int version = e->u8[(i+1)*128 + 1]; +        ret = appfnl(ret, "ext[%d] ([%02x:v%02x] %s) %s", i, +            type, version, _(edid_ext_block_type(type)), +            e->ext_ok[i] ? "ok" : "fail" +        ); +    } -    for(i = 0; i < EDID_MAX_EXT_BLOCKS; i++) { -        if (id->ext[i][0]) { -            ret = appfnl(ret, "ext_block[%d]: (%s): %s", i, _(edid_ext_block_type(id->ext[i][0])), id->ext[i][1] ? "ok" : "fail"); -        } +    for(i = 0; i < e->dtd_count; i++) { +        char *desc = edid_dtd_describe(&e->dtds[i]); +        ret = appfnl(ret, "dtd[%d] %s", i, desc); +        free(desc); +    } +    for(i = 0; i < e->cea_block_count; i++) { +        char *desc = edid_cea_block_describe(&e->cea_blocks[i]); +        ret = appfnl(ret, "cea_block[%d] %s", i, desc);      }      return ret;  } -char *edid_bin_to_hex(const void *edid_bytes, int edid_len) { -    int lines = edid_len / 16; -    int blen = lines * 35 + 1; -    int pc = 0; -    char *ret = malloc(blen); -    memset(ret, 0, blen); -    uint8_t *u8 = (uint8_t*)edid_bytes; -    char *p = ret; -    for(; lines; lines--) { -        sprintf(p, "\t\t"); -        p+=2; -        int i; -        for(i = 0; i < 16; i++) { -            sprintf(p, "%02x", (unsigned int)*u8); -            p+=2; -            u8++; -            pc++; -            if (pc == blen-1) { -                sprintf(p, "\n"); -                goto edid_print_done; -            } -        } -        sprintf(p, "\n"); -        p++; -    } -edid_print_done: -    return ret; -} | 
