aboutsummaryrefslogtreecommitdiff
path: root/deps/sysobj_early/src
diff options
context:
space:
mode:
authorLucas de Castro Borges <lucas@gnuabordo.com.br>2024-04-22 00:35:56 -0300
committerLucas de Castro Borges <lucas@gnuabordo.com.br>2024-04-22 00:35:56 -0300
commit754b5d1114f096778e483f8a6f3a5dc333225e26 (patch)
tree30911ec9da4cfd2f5572c27f7288fcbfa4cd212d /deps/sysobj_early/src
parent35c2857da302ab8b3c308052f2cd1674fb4141a6 (diff)
parent5f01c706267c595de92406a32e7f31ef5056c2d0 (diff)
Update upstream source from tag 'upstream/2.0.3pre'
Update to upstream version '2.0.3pre' with Debian dir 6683980bf6b5c02f6847fd56765833301f75f4f3
Diffstat (limited to 'deps/sysobj_early/src')
-rw-r--r--deps/sysobj_early/src/appf.c63
-rw-r--r--deps/sysobj_early/src/auto_free.c219
-rw-r--r--deps/sysobj_early/src/cpubits.c132
-rw-r--r--deps/sysobj_early/src/format_early.c160
-rw-r--r--deps/sysobj_early/src/gg_slist.c49
-rw-r--r--deps/sysobj_early/src/nice_name.c203
-rw-r--r--deps/sysobj_early/src/strstr_word.c80
-rw-r--r--deps/sysobj_early/src/util_edid.c1470
-rw-r--r--deps/sysobj_early/src/util_edid_svd_table.c164
-rw-r--r--deps/sysobj_early/src/util_ids.c316
-rw-r--r--deps/sysobj_early/src/util_sysobj.c303
11 files changed, 3159 insertions, 0 deletions
diff --git a/deps/sysobj_early/src/appf.c b/deps/sysobj_early/src/appf.c
new file mode 100644
index 00000000..8e6991e3
--- /dev/null
+++ b/deps/sysobj_early/src/appf.c
@@ -0,0 +1,63 @@
+/*
+ * sysobj - https://github.com/bp0/verbose-spork
+ * Copyright (C) 2018 Burt P. <pburt0@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#define _GNU_SOURCE /* for vasprintf() */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include "appf.h"
+
+char *appf(char *str, const char *sep, const char *fmt, ...) {
+ char *buf = NULL;
+ int inlen, seplen, len;
+ va_list args;
+ va_start(args, fmt);
+ len = vasprintf(&buf, fmt, args);
+ va_end(args);
+ if (len < 0) return str;
+ if (!str) return buf;
+ inlen = strlen(str);
+ seplen = (inlen && sep) ? strlen(sep) : 0;
+ str = realloc(str, inlen + seplen + len + 1);
+ if (seplen) strcpy(str + inlen, sep);
+ strcpy(str + inlen + seplen, buf);
+ free(buf);
+ return str;
+}
+
+char *appfdup(const char *str, const char *sep, const char *fmt, ...) {
+ char *buf = NULL, *ret = NULL;
+ int inlen, seplen, len;
+ va_list args;
+ va_start(args, fmt);
+ len = vasprintf(&buf, fmt, args);
+ va_end(args);
+ if (len < 0) return NULL;
+ if (!str) return buf;
+ inlen = strlen(str);
+ seplen = (inlen && sep) ? strlen(sep) : 0;
+ ret = malloc(inlen + seplen + len + 1);
+ strcpy(ret, str);
+ if (seplen) strcpy(ret + inlen, sep);
+ strcpy(ret + inlen + seplen, buf);
+ free(buf);
+ return ret;
+}
diff --git a/deps/sysobj_early/src/auto_free.c b/deps/sysobj_early/src/auto_free.c
new file mode 100644
index 00000000..9a10e27a
--- /dev/null
+++ b/deps/sysobj_early/src/auto_free.c
@@ -0,0 +1,219 @@
+/*
+ * sysobj - https://github.com/bp0/verbose-spork
+ * Copyright (C) 2018 Burt P. <pburt0@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "auto_free.h"
+#if (AF_USE_SYSOBJ)
+#include "sysobj.h"
+#else
+#include <stdio.h>
+#define sysobj_elapsed() af_elapsed()
+#define sysobj_stats af_stats
+static struct {
+ double auto_free_next;
+ unsigned long long
+ auto_freed,
+ auto_free_len;
+} af_stats;
+#endif
+
+//Compatibility
+#ifndef G_SOURCE_REMOVE
+ #define G_SOURCE_REMOVE FALSE
+#endif
+
+#ifndef G_SOURCE_CONTINUE
+ #define G_SOURCE_CONTINUE TRUE
+#endif
+
+
+static GMutex *free_lock = NULL;
+static GSList *free_list = NULL;
+static gboolean free_final = FALSE;
+static GTimer *auto_free_timer = NULL;
+static guint free_event_source = 0;
+#define af_elapsed() (auto_free_timer ? g_timer_elapsed(auto_free_timer, NULL) : 0)
+
+#define auto_free_msg(msg, ...) fprintf (stderr, "[%s] " msg "\n", __FUNCTION__, ##__VA_ARGS__) /**/
+
+typedef struct {
+ gpointer ptr;
+ GThread *thread;
+ GDestroyNotify f_free;
+ double stamp;
+
+ const char *file;
+ int line;
+ const char *func;
+} auto_free_item;
+
+gboolean free_auto_free_sf(gpointer trash) {
+ (void)trash;
+ if (free_final) {
+ free_event_source = 0;
+ return G_SOURCE_REMOVE;
+ }
+ free_auto_free();
+ sysobj_stats.auto_free_next = sysobj_elapsed() + AF_SECONDS;
+ return G_SOURCE_CONTINUE;
+}
+
+gpointer auto_free_ex_(gpointer p, GDestroyNotify f, const char *file, int line, const char *func) {
+ if (!p) return p;
+
+ /* an auto_free() after free_auto_free_final()?
+ * Changed mind, I guess, just go with it. */
+ if (free_final)
+ free_final = FALSE;
+
+ if (!auto_free_timer) {
+ auto_free_timer = g_timer_new();
+ g_timer_start(auto_free_timer);
+ }
+
+ if (!free_event_source) {
+ /* if there is a main loop, then this will call
+ * free_auto_free() in idle time every AF_SECONDS seconds.
+ * If there is no main loop, then free_auto_free()
+ * will be called at sysobj_cleanup() and when exiting
+ * threads, as in sysobj_foreach(). */
+ free_event_source = g_timeout_add_seconds(AF_SECONDS, (GSourceFunc)free_auto_free_sf, NULL);
+ sysobj_stats.auto_free_next = sysobj_elapsed() + AF_SECONDS;
+ }
+
+ auto_free_item *z = g_new0(auto_free_item, 1);
+ z->ptr = p;
+ z->f_free = f;
+ z->thread = g_thread_self();
+ z->file = file;
+ z->line = line;
+ z->func = func;
+ z->stamp = af_elapsed();
+#if GLIB_CHECK_VERSION(2,32,0)
+ if(free_lock==NULL) {free_lock=g_new(GMutex,1);g_mutex_init(free_lock);}
+#else
+ if(free_lock==NULL) free_lock=g_mutex_new();
+#endif
+ g_mutex_lock(free_lock);
+ free_list = g_slist_prepend(free_list, z);
+ sysobj_stats.auto_free_len++;
+ g_mutex_unlock(free_lock);
+ return p;
+}
+
+gpointer auto_free_on_exit_ex_(gpointer p, GDestroyNotify f, const char *file, int line, const char *func) {
+ if (!p) return p;
+
+ auto_free_item *z = g_new0(auto_free_item, 1);
+ z->ptr = p;
+ z->f_free = f;
+ z->thread = g_thread_self();
+ z->file = file;
+ z->line = line;
+ z->func = func;
+ z->stamp = -1.0;
+#if GLIB_CHECK_VERSION(2,32,0)
+ if(free_lock==NULL) g_mutex_init(free_lock);
+#else
+ if(free_lock==NULL) free_lock=g_mutex_new();
+#endif
+ g_mutex_lock(free_lock);
+ free_list = g_slist_prepend(free_list, z);
+ sysobj_stats.auto_free_len++;
+ g_mutex_unlock(free_lock);
+ return p;
+}
+
+static struct { GDestroyNotify fptr; char *name; }
+ free_function_tab[] = {
+ { (GDestroyNotify) g_free, "g_free" },
+#if (AF_USE_SYSOBJ)
+ { (GDestroyNotify) sysobj_free, "sysobj_free" },
+ { (GDestroyNotify) class_free, "class_free" },
+ { (GDestroyNotify) sysobj_filter_free, "sysobj_filter_free" },
+ { (GDestroyNotify) sysobj_virt_free, "sysobj_virt_free" },
+#endif
+ { NULL, "(null)" },
+};
+
+static void free_auto_free_ex(gboolean thread_final) {
+ GThread *this_thread = g_thread_self();
+ GSList *l = NULL, *n = NULL;
+ long long unsigned fc = 0;
+ double now = af_elapsed();
+
+ if (!free_list) return;
+
+#if GLIB_CHECK_VERSION(2,32,0)
+ if(free_lock==NULL) {free_lock=g_new(GMutex,1);g_mutex_init(free_lock);}
+#else
+ if(free_lock==NULL) free_lock=g_mutex_new();
+#endif
+ g_mutex_lock(free_lock);
+ if (DEBUG_AUTO_FREE)
+ auto_free_msg("%llu total items in queue, but will free from thread %p only... ", sysobj_stats.auto_free_len, this_thread);
+ for(l = free_list; l; l = n) {
+ auto_free_item *z = (auto_free_item*)l->data;
+ n = l->next;
+ if (!free_final && z->stamp < 0) continue;
+ double age = now - z->stamp;
+ if (free_final || (z->thread == this_thread && (thread_final || age > AF_DELAY_SECONDS) ) ) {
+ if (DEBUG_AUTO_FREE == 2) {
+ char fptr[128] = "", *fname;
+ for(int i = 0; i < (int)G_N_ELEMENTS(free_function_tab); i++)
+ if (z->f_free == free_function_tab[i].fptr)
+ fname = free_function_tab[i].name;
+ if (!fname) {
+ snprintf(fname, 127, "%p", z->f_free);
+ fname = fptr;
+ }
+ if (z->file || z->func)
+ auto_free_msg("free: %s(%p) age:%lfs from %s:%d %s()", fname, z->ptr, age, z->file, z->line, z->func);
+ else
+ auto_free_msg("free: %s(%p) age:%lfs", fname, z->ptr, age);
+ }
+
+ z->f_free(z->ptr);
+ g_free(z);
+ free_list = g_slist_delete_link(free_list, l);
+ fc++;
+ }
+ }
+ if (DEBUG_AUTO_FREE)
+ auto_free_msg("... freed %llu (from thread %p)", fc, this_thread);
+ sysobj_stats.auto_freed += fc;
+ sysobj_stats.auto_free_len -= fc;
+ g_mutex_unlock(free_lock);
+}
+
+void free_auto_free_thread_final() {
+ free_auto_free_ex(TRUE);
+}
+
+void free_auto_free_final() {
+ free_final = TRUE;
+ free_auto_free_ex(TRUE);
+ if (auto_free_timer)
+ g_timer_destroy(auto_free_timer);
+ auto_free_timer = NULL;
+}
+
+void free_auto_free() {
+ free_auto_free_ex(FALSE);
+}
diff --git a/deps/sysobj_early/src/cpubits.c b/deps/sysobj_early/src/cpubits.c
new file mode 100644
index 00000000..fe8ba207
--- /dev/null
+++ b/deps/sysobj_early/src/cpubits.c
@@ -0,0 +1,132 @@
+/*
+ * rpiz - https://github.com/bp0/rpiz
+ * Copyright (C) 2017 Burt P. <pburt0@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "cpubits.h"
+
+uint32_t cpubits_count(cpubits *b) {
+ static const uint32_t max = CPUBITS_SIZE * 8;
+ uint32_t count = 0, i = 0;
+ while (i < max) {
+ count += CPUBIT_GET(b, i);
+ i++;
+ }
+ return count;
+}
+
+int cpubits_min(cpubits *b) {
+ int i = 0;
+ while (i < CPUBITS_SIZE * 8) {
+ if (CPUBIT_GET(b, i))
+ return i;
+ i++;
+ }
+ return -1;
+}
+
+int cpubits_max(cpubits *b) {
+ int i = CPUBITS_SIZE * 8 - 1;
+ while (i >= 0) {
+ if (CPUBIT_GET(b, i))
+ return i;
+ i--;
+ }
+ return i;
+}
+
+int cpubits_next(cpubits *b, int start, int end) {
+ start++; /* not including the start bit */
+ if (start >= 0) {
+ int i = start;
+ if (end == -1)
+ end = CPUBITS_SIZE * 8;
+ while (i < end) {
+ if (CPUBIT_GET(b, i))
+ return i;
+ i++;
+ }
+ }
+ return -1;
+}
+
+cpubits *cpubits_from_str(char *str) {
+ char *v, *nv, *hy;
+ int r0, r1;
+ cpubits *newbits = malloc(CPUBITS_SIZE);
+ if (newbits) {
+ memset(newbits, 0, CPUBITS_SIZE);
+ if (str != NULL) {
+ v = (char*)str;
+ while ( *v != 0 ) {
+ nv = strchr(v, ','); /* strchrnul() */
+ if (nv == NULL) nv = strchr(v, 0); /* equivalent */
+ hy = strchr(v, '-');
+ if (hy && hy < nv) {
+ r0 = strtol(v, NULL, 0);
+ r1 = strtol(hy + 1, NULL, 0);
+ } else {
+ r0 = r1 = strtol(v, NULL, 0);
+ }
+ for (; r0 <= r1; r0++) {
+ CPUBIT_SET(newbits, r0);
+ }
+ v = (*nv == ',') ? nv + 1 : nv;
+ }
+ }
+ }
+ return newbits;
+}
+
+char *cpubits_to_str(cpubits *bits, char *str, int max_len) {
+ static const uint32_t max = CPUBITS_SIZE * 8;
+ uint32_t i = 1, seq_start = 0, seq_last = 0, seq = 0, l = 0;
+ char buffer[65536] = "";
+ if (CPUBIT_GET(bits, 0)) {
+ seq = 1;
+ strcpy(buffer, "0");
+ }
+ while (i < max) {
+ if (CPUBIT_GET(bits, i) ) {
+ seq_last = i;
+ if (!seq) {
+ seq = 1;
+ seq_start = i;
+ l = strlen(buffer);
+ sprintf(buffer + l, "%s%d", l ? "," : "", i);
+ }
+ } else {
+ if (seq && seq_last != seq_start) {
+ l = strlen(buffer);
+ sprintf(buffer + l, "-%d", seq_last);
+ }
+ seq = 0;
+ }
+ i++;
+ }
+ if (str == NULL)
+ return strdup(buffer);
+ else {
+ strncpy(str, buffer, max_len);
+ return str;
+ }
+}
diff --git a/deps/sysobj_early/src/format_early.c b/deps/sysobj_early/src/format_early.c
new file mode 100644
index 00000000..0220cd8d
--- /dev/null
+++ b/deps/sysobj_early/src/format_early.c
@@ -0,0 +1,160 @@
+/*
+ * sysobj - https://github.com/bp0/verbose-spork
+ * Copyright (C) 2018 Burt P. <pburt0@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "format_early.h"
+#include <string.h>
+#include <stdlib.h>
+
+#define ANSI_COLOR_RESET "\x1b[0m"
+
+const gchar *color_lookup(int ansi_color) {
+ static struct { int ansi; const gchar *html; } tab[] = {
+ { 30, "#010101" }, { 31, "#de382b" }, { 32, "#39b54a" }, { 33, "#ffc706" },
+ { 34, "#006fb8" }, { 35, "#762671" }, { 36, "#2cb5e9" }, { 37, "#cccccc" },
+ { 90, "#808080" }, { 91, "#ff0000" }, { 92, "#00ff00" }, { 93, "#ffff00" },
+ { 94, "#0000ff" }, { 95, "#ff00ff" }, { 96, "#00ffff" }, { 97, "#ffffff" },
+ { 40, "#010101" }, { 41, "#de382b" }, { 42, "#39b54a" }, { 43, "#ffc706" },
+ { 44, "#006fb8" }, { 45, "#762671" }, { 46, "#2cb5e9" }, { 47, "#cccccc" },
+ { 100, "#808080" }, { 101, "#ff0000" }, { 102, "#00ff00" }, { 103, "#ffff00" },
+ { 104, "#0000ff" }, { 105, "#ff00ff" }, { 106, "#00ffff" }, { 107, "#ffffff" },
+ };
+ for (int i = 0; i<(int)G_N_ELEMENTS(tab); i++)
+ if (tab[i].ansi == ansi_color)
+ return tab[i].html;
+ return NULL;
+}
+
+gchar *safe_ansi_color(gchar *ansi_color, gboolean free_in) {
+ if (!ansi_color) return NULL;
+ gchar *ret = NULL;
+ gchar **codes = g_strsplit(ansi_color, ";", -1);
+ if (free_in)
+ g_free(ansi_color);
+ int len = g_strv_length(codes);
+ for(int i = 0; i < len; i++) {
+ int c = atoi(codes[i]);
+ if (c == 0 || c == 1
+ || ( c >= 30 && c <= 37)
+ || ( c >= 40 && c <= 47)
+ || ( c >= 90 && c <= 97)
+ || ( c >= 100 && c <= 107) ) {
+ ret = appf(ret, ";", "%s", codes[i]);
+ }
+ }
+ g_strfreev(codes);
+ return ret;
+}
+
+gchar *format_with_ansi_color(const gchar *str, const gchar *ansi_color, int fmt_opts) {
+ gchar *ret = NULL;
+
+ gchar *safe_color = g_strdup(ansi_color);
+ util_strstrip_double_quotes_dumb(safe_color);
+
+ if (fmt_opts & FMT_OPT_ATERM) {
+ safe_color = safe_ansi_color(safe_color, TRUE);
+ ret = g_strdup_printf("\x1b[%sm%s" ANSI_COLOR_RESET, safe_color, str);
+ goto format_with_ansi_color_end;
+ }
+
+ if (fmt_opts & FMT_OPT_PANGO || fmt_opts & FMT_OPT_HTML) {
+ int fgc = 37, bgc = 40;
+ gchar **codes = g_strsplit(safe_color, ";", -1);
+ int len = g_strv_length(codes);
+ for(int i = 0; i < len; i++) {
+ int c = atoi(codes[i]);
+ if ( (c >= 30 && c <= 37)
+ || ( c >= 90 && c <= 97 ) ) {
+ fgc = c;
+ }
+ if ( (c >= 40 && c <= 47)
+ || ( c >= 100 && c <= 107) ) {
+ bgc = c;
+ }
+ }
+ g_strfreev(codes);
+ const gchar *html_color_fg = color_lookup(fgc);
+ const gchar *html_color_bg = color_lookup(bgc);
+ if (fmt_opts & FMT_OPT_PANGO)
+ ret = g_strdup_printf("<span background=\"%s\" color=\"%s\"><b> %s </b></span>", html_color_bg, html_color_fg, str);
+ else if (fmt_opts & FMT_OPT_HTML)
+ ret = g_strdup_printf("<span style=\"background-color: %s; color: %s;\"><b>&nbsp;%s&nbsp;</b></span>", html_color_bg, html_color_fg, str);
+ }
+
+format_with_ansi_color_end:
+ g_free(safe_color);
+ if (!ret)
+ ret = g_strdup(str);
+ return ret;
+}
+
+void tag_vendor(gchar **str, guint offset, const gchar *vendor_str, const char *ansi_color, int fmt_opts) {
+ if (!str || !*str) return;
+ if (!vendor_str || !ansi_color) return;
+ gchar *work = *str, *new = NULL;
+ if (g_str_has_prefix(work + offset, vendor_str)
+ || strncasecmp(work + offset, vendor_str, strlen(vendor_str)) == 0) {
+ gchar *cvs = format_with_ansi_color(vendor_str, ansi_color, fmt_opts);
+ *(work+offset) = 0;
+ new = g_strdup_printf("%s%s%s", work, cvs, work + offset + strlen(vendor_str) );
+ g_free(work);
+ *str = new;
+ g_free(cvs);
+ }
+}
+
+gchar *vendor_match_tag(const gchar *vendor_str, int fmt_opts) {
+ const Vendor *v = vendor_match(vendor_str, NULL);
+ if (v) {
+ gchar *ven_tag = v->name_short ? g_strdup(v->name_short) : g_strdup(v->name);
+ tag_vendor(&ven_tag, 0, ven_tag, v->ansi_color, fmt_opts);
+ return ven_tag;
+ }
+ return NULL;
+}
+
+gchar *vendor_list_ribbon(const vendor_list vl_in, int fmt_opts) {
+ gchar *ret = NULL;
+ vendor_list vl = g_slist_copy(vl_in); /* shallow is fine */
+ vl = vendor_list_remove_duplicates(vl);
+ if (vl) {
+ GSList *l = vl, *n = l ? l->next : NULL;
+ /* replace each vendor with the vendor tag */
+ for(; l; l = n) {
+ n = l->next;
+ const Vendor *v = l->data;
+ if (!v) {
+ vl = g_slist_delete_link(vl, l);
+ continue;
+ }
+ gchar *ven_tag = v->name_short ? g_strdup(v->name_short) : g_strdup(v->name);
+ if(ven_tag) {
+ tag_vendor(&ven_tag, 0, ven_tag, v->ansi_color, fmt_opts);
+ l->data = ven_tag;
+ }
+ }
+ /* vl is now a regular GSList of formatted vendor tag strings */
+ vl = gg_slist_remove_duplicates_custom(vl, (GCompareFunc)g_strcmp0);
+ for(l = vl; l; l = l->next)
+ ret = appfsp(ret, "%s", (gchar*)l->data);
+ }
+ g_slist_free_full(vl, g_free);
+ return ret;
+}
diff --git a/deps/sysobj_early/src/gg_slist.c b/deps/sysobj_early/src/gg_slist.c
new file mode 100644
index 00000000..595dbc46
--- /dev/null
+++ b/deps/sysobj_early/src/gg_slist.c
@@ -0,0 +1,49 @@
+/*
+ * sysobj - https://github.com/bp0/verbose-spork
+ * Copyright (C) 2018 Burt P. <pburt0@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <glib.h>
+
+GSList *gg_slist_remove_duplicates(GSList *sl) {
+ for (GSList *l = sl; l; l = l->next) {
+ GSList *d = NULL;
+ while(d = g_slist_find(l->next, l->data) )
+ sl = g_slist_delete_link(sl, d);
+ }
+ return sl;
+}
+
+GSList *gg_slist_remove_duplicates_custom(GSList *sl, GCompareFunc func) {
+ for (GSList *l = sl; l; l = l->next) {
+ GSList *d = NULL;
+ while(d = g_slist_find_custom(l->next, l->data, func) )
+ sl = g_slist_delete_link(sl, d);
+ }
+ return sl;
+}
+
+GSList *gg_slist_remove_null(GSList *sl) {
+ GSList *n = sl ? sl->next : NULL;
+ for (GSList *l = sl; l; l = n) {
+ n = l->next;
+ if (l->data == NULL)
+ sl = g_slist_delete_link(sl, l);
+ }
+ return sl;
+}
diff --git a/deps/sysobj_early/src/nice_name.c b/deps/sysobj_early/src/nice_name.c
new file mode 100644
index 00000000..e5e15a46
--- /dev/null
+++ b/deps/sysobj_early/src/nice_name.c
@@ -0,0 +1,203 @@
+/*
+ * sysobj - https://github.com/bp0/verbose-spork
+ * Copyright (C) 2018 Burt P. <pburt0@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <ctype.h>
+
+#include "nice_name.h"
+#include "util_sysobj.h"
+
+/* export */
+/* replaces the extra chars with spaces, then when done with a series of
+ * str_shorten()s, use util_compress_space() to squeeze. */
+gboolean str_shorten(gchar *str, const gchar *find, const gchar *replace) {
+ if (!str || !find || !replace) return FALSE;
+ long unsigned lf = strlen(find);
+ long unsigned lr = strlen(replace);
+ gchar *p = strstr(str, find);
+ if (p) {
+ if (lr > lf) lr = lf;
+ gchar *buff = g_strnfill(lf, ' ');
+ strncpy(buff, replace, lr);
+ strncpy(p, buff, lf);
+ g_free(buff);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+gboolean str_shorten_anycase(gchar *str, const gchar *find, const gchar *replace) {
+ if (!str || !find || !replace) return FALSE;
+ long unsigned lf = strlen(find);
+ long unsigned lr = strlen(replace);
+ gchar *p = strcasestr(str, find);
+ if (p) {
+ if (lr > lf) lr = lf;
+ gchar *buff = g_strnfill(lf, ' ');
+ strncpy(buff, replace, lr);
+ strncpy(p, buff, lf);
+ g_free(buff);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void nice_name_x86_cpuid_model_string(char *cpuid_model_string) {
+ static gboolean move_vendor_to_front = TRUE;
+ static gboolean remove_long_core_count = TRUE;
+ static gboolean remove_amd_compute_cores = TRUE;
+ static gboolean remove_amd_xn_ncore_redundancy = TRUE;
+ static gboolean remove_processor_cpu_apu_etc = TRUE;
+ static gboolean remove_mhz_ghz = TRUE;
+ static gboolean remove_radeon = TRUE;
+
+ if (!cpuid_model_string) return;
+ g_strstrip(cpuid_model_string);
+
+ while(str_shorten(cpuid_model_string, "Genuine Intel", "Intel")) {};
+ while(str_shorten_anycase(cpuid_model_string, "(R)", "")) {};
+ while(str_shorten_anycase(cpuid_model_string, "(TM)", "")) {};
+ while(str_shorten_anycase(cpuid_model_string, "(C)", "")) {};
+ while(str_shorten(cpuid_model_string, "@", "")) {};
+
+ if (move_vendor_to_front) {
+ /* vendor not at the beginning, try to move there.
+ * ex: Mobile AMD Sempron(tm) Processor 3600+
+ * ex: Dual Core AMD Opteron(tm) Processor 165 */
+ char *intel = strstr(cpuid_model_string, "Intel ");
+ char *amd = strstr(cpuid_model_string, "AMD ");
+ if (amd || intel) {
+ if (amd && !intel) {
+ if (amd != cpuid_model_string) {
+ int l = amd - cpuid_model_string;
+ memmove(cpuid_model_string+4, cpuid_model_string, l);
+ memcpy(cpuid_model_string, "AMD ", 4);
+ }
+ } else if (intel && !amd) {
+ int l = intel - cpuid_model_string;
+ memmove(cpuid_model_string+6, cpuid_model_string, l);
+ memcpy(cpuid_model_string, "Intel ", 6);
+ }
+ }
+ }
+
+ if (g_str_has_prefix(cpuid_model_string, "AMD")) {
+ while(str_shorten(cpuid_model_string, "Mobile Technology", "Mobile")) {};
+
+ if (remove_radeon) {
+ char *radeon = strcasestr(cpuid_model_string, "with Radeon");
+ if (!radeon)
+ radeon = strcasestr(cpuid_model_string, "Radeon");
+ if (radeon) *radeon = 0;
+ }
+
+ if (remove_amd_compute_cores) {
+ if (strcasestr(cpuid_model_string, " COMPUTE CORES ")) {
+ /* ex: AMD FX-9800P RADEON R7, 12 COMPUTE CORES 4C+8G */
+ char *comma = strchr(cpuid_model_string, ',');
+ if (comma) *comma = 0;
+ }
+ }
+
+ if (remove_amd_xn_ncore_redundancy) {
+ /* remove Xn n-core redundancy */
+ if (strstr(cpuid_model_string, "X2")) {
+ str_shorten(cpuid_model_string, "Dual Core", "");
+ str_shorten(cpuid_model_string, "Dual-Core", "");
+ }
+ if (strstr(cpuid_model_string, "X3"))
+ str_shorten(cpuid_model_string, "Triple-Core", "");
+ if (strstr(cpuid_model_string, "X4"))
+ str_shorten(cpuid_model_string, "Quad-Core", "");
+ }
+ }
+
+ if (g_str_has_prefix(cpuid_model_string, "Cyrix")) {
+ /* ex: Cyrix MediaGXtm MMXtm Enhanced */
+ while(str_shorten(cpuid_model_string, "tm ", "")) {};
+ }
+
+ if (remove_processor_cpu_apu_etc) {
+ while(str_shorten(cpuid_model_string, " CPU", "")) {};
+ while(str_shorten(cpuid_model_string, " APU", "")) {};
+ while(str_shorten_anycase(cpuid_model_string, " Integrated Processor", "")) {};
+ while(str_shorten_anycase(cpuid_model_string, " Processor", "")) {};
+ } else {
+ while(str_shorten(cpuid_model_string, " processor", " Processor")) {};
+ }
+
+ if (remove_mhz_ghz) {
+ /* 1400MHz, 1.6+ GHz, etc */
+ char *u = NULL;
+ while((u = strcasestr(cpuid_model_string, "GHz"))
+ || (u = strcasestr(cpuid_model_string, "MHz")) ) {
+ if (u[3] == '+') u[3] = ' ';
+ strncpy(u, " ", 3);
+ while(isspace(*u)) {u--;}
+ while (isdigit(*u) || *u == '.' || *u == '+')
+ { *u = ' '; u--;}
+ }
+ }
+
+ if (remove_long_core_count) {
+ /* note: "Intel Core 2 Duo" and "Intel Core 2 Quad"
+ * are the marketing names, don't remove those. */
+ static char *count_strings[] = {
+ "Dual", "Triple", "Quad", "Six",
+ "Eight", "Octal", "Twelve", "Sixteen",
+ "8", "16", "24", "32", "48", "56", "64",
+ };
+ char buffer[] = "Enough room for the longest... -Core";
+ char *dash = strchr(buffer, '-');
+ char *m = NULL;
+
+ unsigned int i = 0;
+ for(; i < G_N_ELEMENTS(count_strings); i++) {
+ int l = strlen(count_strings[i]);
+ m = dash-l;
+ memcpy(m, count_strings[i], l);
+ *dash = '-';
+ while(str_shorten_anycase(cpuid_model_string, m, "")) {};
+ *dash = ' ';
+ while(str_shorten_anycase(cpuid_model_string, m, "")) {};
+ }
+ }
+
+ /* finalize */
+ util_compress_space(cpuid_model_string);
+ g_strstrip(cpuid_model_string);
+}
+
+/* Intel Graphics may have very long names,
+ * like "Intel Corporation Seventh Generation Something Core Something Something Integrated Graphics Processor Revision Ninety-four" */
+void nice_name_intel_gpu_device(char *pci_ids_device_string) {
+ while(str_shorten_anycase(pci_ids_device_string, "(R)", "")) {}; /* Intel(R) -> Intel */
+ str_shorten(pci_ids_device_string, "Graphics Controller", "Graphics");
+ str_shorten(pci_ids_device_string, "Graphics Device", "Graphics");
+ str_shorten(pci_ids_device_string, "Generation", "Gen");
+ str_shorten(pci_ids_device_string, "Core Processor", "Core");
+ str_shorten(pci_ids_device_string, "Atom Processor", "Atom");
+ str_shorten(pci_ids_device_string, "Xeon Processor", "Xeon");
+ str_shorten(pci_ids_device_string, "Celeron Processor", "Celeron");
+ str_shorten(pci_ids_device_string, "Pentium Processor", "Pentium");
+ util_compress_space(pci_ids_device_string);
+ g_strstrip(pci_ids_device_string);
+}
diff --git a/deps/sysobj_early/src/strstr_word.c b/deps/sysobj_early/src/strstr_word.c
new file mode 100644
index 00000000..0b51e4ac
--- /dev/null
+++ b/deps/sysobj_early/src/strstr_word.c
@@ -0,0 +1,80 @@
+/*
+ * sysobj - https://github.com/bp0/verbose-spork
+ * Copyright (C) 2018 Burt P. <pburt0@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/* versions of strstr() and strcasestr() where the match must be preceded and
+ * succeded by a non-alpha-numeric character. */
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <ctype.h>
+
+static char *_strstr(const char *haystack, const char *needle, int anycase) {
+ return anycase
+ ? strcasestr(haystack, needle)
+ : strstr(haystack, needle);
+}
+
+static char *_strstr_word(const char *haystack, const char *needle,
+ int anycase, int prefix_ok, int suffix_ok) {
+
+ if (!haystack || !needle)
+ return NULL;
+
+ char *c;
+ const char *p = haystack;
+ size_t l = strlen(needle);
+ while((c = _strstr(p, needle, anycase))) {
+ const char *before = (c == haystack) ? NULL : c-1;
+ const char *after = c + l;
+ int ok = 1, wbs = 1, wbe = 1;
+ if (isalnum(*after)) wbe = 0;
+ if (before && isalnum(*before)) wbs = 0;
+ if (!wbe && !prefix_ok) ok = 0;
+ if (!wbs && !suffix_ok) ok = 0;
+ if (!(wbs || wbe)) ok = 0;
+ if (ok) return c;
+ p++;
+ }
+ return NULL;
+}
+
+char *strstr_word(const char *haystack, const char *needle) {
+ return _strstr_word(haystack, needle, 0, 0, 0);
+}
+
+char *strcasestr_word(const char *haystack, const char *needle) {
+ return _strstr_word(haystack, needle, 1, 0, 0);
+}
+
+char *strstr_word_prefix(const char *haystack, const char *needle) {
+ return _strstr_word(haystack, needle, 0, 1, 0);
+}
+
+char *strcasestr_word_prefix(const char *haystack, const char *needle) {
+ return _strstr_word(haystack, needle, 1, 1, 0);
+}
+
+char *strstr_word_suffix(const char *haystack, const char *needle) {
+ return _strstr_word(haystack, needle, 0, 0, 1);
+}
+
+char *strcasestr_word_suffix(const char *haystack, const char *needle) {
+ return _strstr_word(haystack, needle, 1, 0, 1);
+}
diff --git a/deps/sysobj_early/src/util_edid.c b/deps/sysobj_early/src/util_edid.c
new file mode 100644
index 00000000..06c5534e
--- /dev/null
+++ b/deps/sysobj_early/src/util_edid.c
@@ -0,0 +1,1470 @@
+/*
+ * sysobj - https://github.com/bp0/verbose-spork
+ * Copyright (C) 2018 Burt P. <pburt0@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+#include <endian.h>
+#include <stdio.h>
+#include "gettext.h"
+#include "util_edid.h"
+#include "util_sysobj.h"
+
+#include "util_edid_svd_table.c"
+
+// TODO: find a better fix, I've seen a few EDID strings with bogus chars
+#if !GLIB_CHECK_VERSION(2,52,0)
+__attribute__ ((weak))
+gchar *g_utf8_make_valid(const gchar *s, const gssize l) {
+ if (l < 0)
+ return g_strdup(s);
+ else
+ return g_strndup(s, (gsize)l);
+}
+#endif
+
+#define NOMASK (~0U)
+#define BFMASK(LSB, MASK) (MASK << LSB)
+
+#define DPTR(ADDY) (uint8_t*)(&((ADDY).e->u8[(ADDY).offset]))
+#define OFMT "@%03d" /* for addy.offset */
+
+#define EDID_MSG_STDERR 0
+#define edid_msg(e, msg, ...) {\
+ if (EDID_MSG_STDERR) fprintf (stderr, ">[%s;L%d] " msg "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__); \
+ g_string_append_printf(e->msg_log, "[%s;L%d] " msg "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__); }
+
+static int str_make_printable(char *str) {
+ int rc = 0;
+ char *p;
+ for(p = str; *p; p++) {
+ if (!isprint(*p)) {
+ *p = '.';
+ rc++;
+ }
+ }
+ return rc;
+}
+
+static inline
+uint32_t bf_value(uint32_t value, uint32_t mask) {
+ uint32_t result = value & mask;
+ if (result)
+ while(!(mask & 1)) {
+ result >>= 1;
+ mask >>= 1;
+ }
+ return result;
+}
+
+static inline
+uint8_t bounds_check(edid *e, uint32_t offset) {
+ if (!e) return 0;
+ if (offset > e->len) return 0;
+ return 1;
+}
+
+static inline
+char *rstr(edid *e, uint32_t offset, uint32_t len) {
+ if (!bounds_check(e, offset+len)) return NULL;
+ char *raw = malloc(len+1), *ret = NULL;
+ strncpy(raw, (char*)&e->u8[offset], len);
+ raw[len] = 0;
+ ret = g_utf8_make_valid(raw, len);
+ g_free(raw);
+ return ret;
+}
+
+static inline
+char *rstr_strip(edid *e, uint32_t offset, uint32_t len) {
+ if (!bounds_check(e, offset+len)) return NULL;
+ char *raw = malloc(len+1), *ret = NULL;
+ strncpy(raw, (char*)&e->u8[offset], len);
+ raw[len] = 0;
+ ret = g_strstrip(g_utf8_make_valid(raw, len));
+ g_free(raw);
+ return ret;
+}
+
+static inline
+uint32_t r8(edid *e, uint32_t offset, uint32_t mask) {
+ if (!bounds_check(e, offset)) return 0;
+ return bf_value(e->u8[offset], mask);
+}
+
+static inline
+uint32_t r16le(edid *e, uint32_t offset, uint32_t mask) {
+ if (!bounds_check(e, offset+1)) return 0;
+ uint32_t v = (e->u8[offset+1] << 8) + e->u8[offset];
+ return bf_value(v, mask);
+}
+
+static inline
+uint32_t r16be(edid *e, uint32_t offset, uint32_t mask) {
+ if (!bounds_check(e, offset+1)) return 0;
+ uint32_t v = (e->u8[offset] << 8) + e->u8[offset+1];
+ return bf_value(v, mask);
+}
+
+static inline
+uint32_t r24le(edid *e, uint32_t offset, uint32_t mask) {
+ if (!bounds_check(e, offset+2)) return 0;
+ uint32_t v = (e->u8[offset+2] << 16) + (e->u8[offset+1] << 8) + e->u8[offset];
+ return bf_value(v, mask);
+}
+
+static inline
+uint32_t r24be(edid *e, uint32_t offset, uint32_t mask) {
+ if (!bounds_check(e, offset+2)) return 0;
+ uint32_t v = (e->u8[offset] << 16) + (e->u8[offset+1] << 8) + e->u8[offset+2];
+ return bf_value(v, mask);
+}
+
+static inline
+uint32_t r32le(edid *e, uint32_t offset, uint32_t mask) {
+ if (!bounds_check(e, offset+3)) return 0;
+ uint32_t v = (e->u8[offset+3] << 24) + (e->u8[offset+2] << 16)
+ + (e->u8[offset+1] << 8) + e->u8[offset];
+ return bf_value(v, mask);
+}
+
+static inline
+uint32_t r32be(edid *e, uint32_t offset, uint32_t mask) {
+ if (!bounds_check(e, offset+3)) return 0;
+ uint32_t v = (e->u8[offset] << 24) + (e->u8[offset+1] << 16)
+ + (e->u8[offset+2] << 8) + e->u8[offset+3];
+ return bf_value(v, mask);
+}
+
+static inline
+int rpnpcpy(edid_ven *dest, edid *e, uint32_t offset) {
+ uint32_t pnp = r16be(e, offset, NOMASK);
+ edid_ven ret = {.type = VEN_TYPE_INVALID};
+ if (pnp) {
+ ret.type = VEN_TYPE_PNP;
+ ret.pnp[2] = 64 + (pnp & 0x1f);
+ ret.pnp[1] = 64 + ((pnp >> 5) & 0x1f);
+ ret.pnp[0] = 64 + ((pnp >> 10) & 0x1f);
+ *dest = ret;
+ return 1;
+ }
+ return 0;
+}
+
+static inline
+int rouicpy(edid_ven *dest, edid *e, uint32_t offset) {
+ edid_ven ret = {.type = VEN_TYPE_OUI};
+ ret.oui = r24le(e, offset, NOMASK);
+ sprintf(ret.oui_str, "%02x%02x%02x",
+ (ret.oui >> 16) & 0xff,
+ (ret.oui >> 8) & 0xff,
+ ret.oui & 0xff );
+ if (ret.oui) {
+ *dest = ret;
+ return 1;
+ }
+ return 0;
+}
+
+static int _block_check_n(const void *bytes, int len) {
+ if (!bytes) return 0;
+ uint8_t sum = 0;
+ uint8_t *data = (uint8_t*)bytes;
+ int i;
+ for(i=0; i<len; i++)
+ sum += data[i];
+ return sum == 0 ? 1 : 0;
+}
+
+static int block_check_n(edid *e, uint32_t offset, int len) {
+ if (!bounds_check(e, offset+len)) return 0;
+ return _block_check_n(e->u8, len);
+}
+
+static int block_check(edid *e, uint32_t offset) {
+ if (!bounds_check(e, offset+128)) return 0;
+ return _block_check_n(e->u8, 128);
+}
+
+static 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;
+}
+
+#define OUTPUT_CPY_SIZE(DEST, SRC) \
+ (DEST).horiz_cm = (SRC).horiz_cm; \
+ (DEST).vert_cm = (SRC).vert_cm; \
+ (DEST).diag_cm = (SRC).diag_cm; \
+ (DEST).diag_in = (SRC).diag_in; \
+ edid_output_fill(&(DEST));
+
+static void edid_output_fill(edid_output *out) {
+ out->diag_cm =
+ sqrt( (out->horiz_cm * out->horiz_cm)
+ + (out->vert_cm * out->vert_cm) );
+ out->diag_in = out->diag_cm / 2.54;
+
+ if (out->is_interlaced) {
+ if (out->vert_lines)
+ out->vert_pixels = out->vert_lines * 2;
+ else
+ out->vert_lines = out->vert_pixels / 2;
+ } else {
+ if (out->vert_lines)
+ out->vert_pixels = out->vert_lines;
+ else
+ out->vert_lines = out->vert_pixels;
+ }
+
+ if (!out->vert_freq_hz && out->pixel_clock_khz) {
+ uint64_t h = out->horiz_pixels + out->horiz_blanking;
+ uint64_t v = out->vert_lines + out->vert_blanking;
+ if (h && v) {
+ uint64_t work = out->pixel_clock_khz * 1000;
+ work /= (h*v);
+ out->vert_freq_hz = work;
+ }
+ }
+
+ out->pixels = out->horiz_pixels;
+ out->pixels *= out->vert_pixels;
+
+ if (out->diag_in) {
+ static const char *inlbl = "\u2033"; /* double prime */
+ sprintf(out->class_inch, "%0.1f%s", out->diag_in, inlbl);
+ util_strchomp_float(out->class_inch);
+ }
+}
+
+static void cea_block_decode(struct edid_cea_block *blk) {
+ if (!blk) return;
+ if (!blk->bounds_ok)
+ blk->bounds_ok =
+ bounds_check(blk->addy.e, blk->addy.offset + 1 + blk->len);
+ if (!blk->bounds_ok) return;
+
+ edid *e = blk->addy.e;
+ static uint32_t h = 1; /* header size */
+ uint32_t a = blk->addy.offset; /* start of block, includes header */
+ uint8_t *ptr = DPTR(blk->addy);
+ int i;
+ switch(blk->type) {
+ case 0x1: /* SADS */
+ for(i = h; i <= blk->len; i+=3) {
+ struct edid_sad *sad = &e->sads[e->sad_count];
+ sad->v[0] = ptr[i];
+ sad->v[1] = ptr[i+1];
+ sad->v[2] = ptr[i+2];
+ sad->format = bf_value(sad->v[0], 0x78);
+ sad->channels = 1 + bf_value(sad->v[0], 0x07);
+ sad->freq_bits = sad->v[1];
+ if (sad->format == 1) {
+ sad->depth_bits = sad->v[2];
+ } else if (sad->format >= 2
+ && sad->format <= 8) {
+ sad->max_kbps = 8 * sad->v[2];
+ }
+ e->sad_count++;
+ }
+ break;
+ case 0x4: /* Speaker allocation */
+ e->speaker_alloc_bits = ptr[h];
+ break;
+ case 0x2: /* SVDs */
+ for(i = h; i <= blk->len; i++)
+ e->svds[e->svd_count++].v = ptr[i];
+ break;
+ case 0x3: /* Vendor-specific */
+ rouicpy(&blk->ven, e, a+h);
+ // TODO:
+ break;
+ default:
+ break;
+ }
+}
+
+static void did_block_decode(DisplayIDBlock *blk) {
+ if (!blk) return;
+
+ //printf("did_block_decode: %s\n", hex_bytes(DPTR(blk->addy), blk->len+3));
+
+ if (!blk->bounds_ok)
+ blk->bounds_ok =
+ bounds_check(blk->addy.e, blk->addy.offset + 3 + blk->len);
+ if (!blk->bounds_ok) return;
+
+ edid *e = blk->addy.e;
+ static uint32_t h = 3; /* header size */
+ uint32_t a = blk->addy.offset; /* start of block, includes header */
+ uint8_t *u8 = DPTR(blk->addy);
+ int b = h;
+ edid_ven ven;// = {};
+ edid_output out;// = {};
+ memset(&ven,0,sizeof(edid_ven));
+ memset(&out,0,sizeof(edid_output));
+ if (blk) {
+ switch(blk->type) {
+ case 0: /* Product ID (1.x) */
+ /* UNTESTED */
+ if (rpnpcpy(&ven, e, a+h) )
+ e->ven = ven;
+ if (u8[12] || u8[13]) {
+ e->dom.week = u8[12];
+ e->dom.year = u8[13] + 2000;
+ e->dom.is_model_year = (e->dom.week == 255);
+ e->dom.std = STD_DISPLAYID;
+ }
+ e->did_strings[e->did_string_count].is_product_name = 1;
+ e->did_strings[e->did_string_count].len = blk->len;
+ e->did_strings[e->did_string_count].str = rstr_strip(e, a+h+12, u8[b+11]);
+ e->name = e->did_strings[e->did_string_count].str;
+ e->did_string_count++;
+ break;
+ case 0x20: /* Product ID */
+ /* UNTESTED */
+ if (rouicpy(&ven, e, a+h) )
+ e->ven = ven;
+ if (u8[12] || u8[13]) {
+ e->dom.week = u8[12];
+ e->dom.year = u8[13] + 2000;
+ e->dom.is_model_year = (e->dom.week == 255);
+ e->dom.std = STD_DISPLAYID20;
+ }
+ e->did_strings[e->did_string_count].is_product_name = 1;
+ e->did_strings[e->did_string_count].len = blk->len;
+ e->did_strings[e->did_string_count].str = rstr_strip(e, a+h+12, u8[b+11]);
+ e->name = e->did_strings[e->did_string_count].str;
+ e->did_string_count++;
+ break;
+ case 0x0a: /* Serial Number (ASCII String) */
+ e->did_strings[e->did_string_count].is_serial = 1;
+ e->did_strings[e->did_string_count].len = blk->len;
+ e->did_strings[e->did_string_count].str = rstr_strip(e, a+h, blk->len);
+ e->serial = e->did_strings[e->did_string_count].str;
+ e->did_string_count++;
+ break;
+ case 0x0b: /* General Purpose ASCII String */
+ e->did_strings[e->did_string_count].len = blk->len;
+ e->did_strings[e->did_string_count].str = rstr(e, a+h, blk->len);
+ e->did_string_count++;
+ break;
+ case 0x03: /* Type I Detailed timings */
+ out.pixel_clock_khz = 10 * r24le(e, a+h, NOMASK);
+ out.horiz_pixels = 1 + (u8[b+5] << 8) + u8[b+4];
+ out.horiz_blanking = (u8[b+7] << 8) + u8[b+6];
+ out.vert_lines = 1 + (u8[b+13] << 8) + u8[b+12];
+ out.vert_blanking = (u8[b+15] << 8) + u8[b+14];
+ out.is_interlaced = bf_value(u8[b+3], BFMASK(4, 0x1));
+ out.stereo_mode = bf_value(u8[b+3], BFMASK(5, 0x3));
+ out.is_preferred = bf_value(u8[b+3], BFMASK(7, 0x1));
+ out.src = OUTSRC_DID_TYPE_I;
+ edid_output_fill(&out);
+ e->didts[e->didt_count++] = out;
+ break;
+ case 0x13: /* Type VI Detailed timings (super 0x03) */
+ /* UNTESTED */
+ out.pixel_clock_khz = (u8[b+2] << 16) & 0xa0;
+ out.pixel_clock_khz += u8[b+1] << 8;
+ out.pixel_clock_khz += u8[b];
+ out.horiz_pixels = ((u8[b+5] << 8) + u8[b+3]) & 0x7fff;
+ out.vert_lines = ((u8[b+6] << 8) + u8[b+5]) & 0x7fff;
+ // TODO: blanking...
+ out.is_interlaced = (u8[b+13] >> 7) & 0x1;
+ out.stereo_mode = (u8[b+13] >> 5) & 0x2;
+ out.src = OUTSRC_DID_TYPE_VI;
+ edid_output_fill(&out);
+ e->didts[e->didt_count++] = out;
+ break;
+ case 0x22: /* Type VII Detailed timings (super 0x13) */
+ /* UNTESTED */
+ out.pixel_clock_khz = u8[b+2] << 16;
+ out.pixel_clock_khz += u8[b+1] << 8;
+ out.pixel_clock_khz += u8[b];
+ out.horiz_pixels = (u8[b+5] << 8) + u8[b+4];
+ out.horiz_blanking = (u8[b+7] << 8) + u8[b+6];
+ out.vert_lines = (u8[b+13] << 8) + u8[b+12];
+ out.vert_blanking = (u8[b+15] << 8) + u8[b+14];
+ out.is_interlaced = (u8[b+3] >> 4) & 0x1;
+ out.stereo_mode = (u8[b+3] >> 5) & 0x2;
+ out.is_preferred = (u8[b+3] >> 7) & 0x1;
+ out.src = OUTSRC_DID_TYPE_VII;
+ edid_output_fill(&out);
+ e->didts[e->didt_count++] = out;
+ break;
+ case 0x7e: /* vendor specific data */
+ case 0x7f: /* vendor specific data */
+ rouicpy(&blk->ven, e, a+h);
+ // TODO:
+ break;
+ case 0x81: /* CTA DisplayID, ... Embedded CEA Blocks */
+ while(b < blk->len) {
+ int db_type = (u8[b] & 0xe0) >> 5;
+ int db_size = u8[b] & 0x1f;
+ e->cea_blocks[e->cea_block_count].addy.e = blk->addy.e;
+ e->cea_blocks[e->cea_block_count].addy.offset = blk->addy.offset + b;
+ e->cea_blocks[e->cea_block_count].type = db_type;
+ e->cea_blocks[e->cea_block_count].len = db_size;
+ cea_block_decode(&e->cea_blocks[e->cea_block_count]);
+ e->cea_block_count++;
+ b += db_size + 1;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static edid_output edid_output_from_svd(uint8_t index) {
+ int i;
+ if (index >= 128 && index <= 192) index &= 0x7f; /* "native" flag for 0-64 */
+ for(i = 0; i < (int)G_N_ELEMENTS(cea_standard_timings); i++) {
+ if (cea_standard_timings[i].index == index) {
+ edid_output out;// = {};
+ memset(&out,0,sizeof(edid_output));
+ out.horiz_pixels = cea_standard_timings[i].horiz_active;
+ out.vert_lines = cea_standard_timings[i].vert_active;
+ if (strchr(cea_standard_timings[i].short_name, 'i'))
+ out.is_interlaced = 1;
+ out.pixel_clock_khz = cea_standard_timings[i].pixel_clock_mhz * 1000;
+ out.vert_freq_hz = cea_standard_timings[i].vert_freq_hz;
+ out.src = OUTSRC_SVD;
+ edid_output_fill(&out);
+ return out;
+ }
+ }
+ return (edid_output){.src = OUTSRC_INVALID};
+}
+
+edid *edid_new(const char *data, unsigned int len) {
+ if (len < 128) return NULL;
+
+ 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->msg_log = g_string_new(NULL);
+
+#define RESERVE_COUNT 300
+ e->dtds = malloc(sizeof(struct edid_dtd) * RESERVE_COUNT);
+ e->cea_blocks = malloc(sizeof(struct edid_cea_block) * RESERVE_COUNT);
+ e->svds = malloc(sizeof(struct edid_svd) * RESERVE_COUNT);
+ e->sads = malloc(sizeof(struct edid_sad) * RESERVE_COUNT);
+ e->did_blocks = malloc(sizeof(DisplayIDBlock) * RESERVE_COUNT);
+ e->didts = malloc(sizeof(edid_output) * RESERVE_COUNT);
+ e->did_strings = malloc(sizeof(edid_output) * RESERVE_COUNT);
+ memset(e->dtds, 0, sizeof(struct edid_dtd) * RESERVE_COUNT);
+ memset(e->cea_blocks, 0, sizeof(struct edid_cea_block) * RESERVE_COUNT);
+ memset(e->svds, 0, sizeof(struct edid_svd) * RESERVE_COUNT);
+ memset(e->sads, 0, sizeof(struct edid_sad) * RESERVE_COUNT);
+ memset(e->did_blocks, 0, sizeof(DisplayIDBlock) * RESERVE_COUNT);
+ memset(e->didts, 0, sizeof(edid_output) * RESERVE_COUNT);
+ memset(e->did_strings, 0, sizeof(edid_output) * RESERVE_COUNT);
+
+ /* base product information */
+ rpnpcpy(&e->ven, e, 8); /* bytes 8-9 */
+ e->product = r16le(e, 10, NOMASK); /* bytes 10-11 */
+ e->n_serial = r32le(e, 12, NOMASK); /* bytes 12-15 */
+ e->dom.week = e->u8[16]; /* byte 16 */
+ e->dom.year = e->u8[17] + 1990; /* byte 17 */
+ e->dom.is_model_year = (e->dom.week == 255);
+ e->dom.std = STD_EDID;
+
+ 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;
+ }
+ e->interface = e->u8[20] & 0xf;
+ }
+
+ if (e->u8[21] && e->u8[22]) {
+ e->img.horiz_cm = e->u8[21];
+ e->img.vert_cm = e->u8[22];
+ edid_output_fill(&e->img);
+ e->img_max = e->img;
+ }
+
+ /* established timing bitmap */
+#define ETB_CHECK(BYT, BIT, HP, VP, RF, IL) \
+ if (e->u8[BYT] & (1<<BIT)) { \
+ e->etbs[e->etb_count] = e->img; \
+ e->etbs[e->etb_count].horiz_pixels = HP; \
+ e->etbs[e->etb_count].vert_pixels = VP; \
+ e->etbs[e->etb_count].vert_freq_hz = RF; \
+ e->etbs[e->etb_count].is_interlaced = IL;\
+ e->etbs[e->etb_count].src = OUTSRC_ETB; \
+ edid_output_fill(&e->etbs[e->etb_count]);\
+ e->etb_count++; };
+ ETB_CHECK(35, 7, 720, 400, 70, 0); //(VGA)
+ ETB_CHECK(35, 6, 720, 400, 88, 0); //(XGA)
+ ETB_CHECK(35, 5, 640, 480, 60, 0); //(VGA)
+ ETB_CHECK(35, 4, 640, 480, 67, 0); //(Apple Macintosh II)
+ ETB_CHECK(35, 3, 640, 480, 72, 0);
+ ETB_CHECK(35, 2, 640, 480, 75, 0);
+ ETB_CHECK(35, 1, 800, 600, 56, 0);
+ ETB_CHECK(35, 0, 800, 600, 60, 0);
+ ETB_CHECK(36, 7, 800, 600, 72, 0);
+ ETB_CHECK(36, 6, 800, 600, 75, 0);
+ ETB_CHECK(36, 5, 832, 624, 75, 0); //(Apple Macintosh II)
+ ETB_CHECK(36, 4, 1024, 768, 87, 1); //(1024×768i)
+ ETB_CHECK(36, 3, 1024, 768, 60, 0);
+ ETB_CHECK(36, 2, 1024, 768, 70, 0);
+ ETB_CHECK(36, 1, 1024, 768, 75, 0);
+ ETB_CHECK(36, 0, 1280, 1024, 75, 0);
+ ETB_CHECK(37, 7, 1152, 870, 75, 0); //(Apple Macintosh II)
+
+ /* standard timings */
+ for(i = 38; i < 53; i+=2) {
+ /* 0101 is unused */
+ if (e->u8[i] == 0x01 && e->u8[i+1] == 0x01)
+ continue;
+ /* 00.. is invalid/"reserved" */
+ if (e->u8[i] == 0x00)
+ continue;
+ double xres = (e->u8[i] + 31) * 8;
+ double yres = 0;
+ int iar = (e->u8[i+1] >> 6) & 0x3;
+ int vf = (e->u8[i+1] & 0x3f) + 60;
+ switch(iar) {
+ case 0: /* 16:10 (v<1.3 1:1) */
+ if (e->ver_major == 1 && e->ver_minor < 3)
+ yres = xres;
+ else
+ yres = xres*10/16;
+ break;
+ case 0x1: /* 4:3 */
+ yres = xres*4/3;
+ break;
+ case 0x2: /* 5:4 */
+ yres = xres*4/5;
+ break;
+ case 0x3: /* 16:9 */
+ yres = xres*9/16;
+ break;
+ }
+ e->stds[e->std_count].ptr = &e->u8[i];
+ e->stds[e->std_count].out = e->img; /* inherit */
+ e->stds[e->std_count].out.horiz_pixels = xres;
+ e->stds[e->std_count].out.vert_pixels = yres;
+ e->stds[e->std_count].out.vert_freq_hz = vf;
+ e->stds[e->std_count].out.src = OUTSRC_STD;
+ edid_output_fill(&e->stds[e->std_count].out);
+ e->std_count++;
+ }
+
+ uint16_t dh, dl;
+#define CHECK_DESCRIPTOR(INDEX, OFFSET) \
+ e->d[INDEX].addy.e = e; \
+ e->d[INDEX].addy.offset = OFFSET; \
+ if (e->u8[OFFSET] == 0) { \
+ dh = be16toh(e->u16[OFFSET/2]); \
+ dl = be16toh(e->u16[OFFSET/2+1]); \
+ e->d[INDEX].type = (dh << 16) + dl; \
+ switch(e->d[INDEX].type) { \
+ case 0xfc: case 0xff: case 0xfe: \
+ strncpy(e->d[INDEX].text, (char*)e->u8+OFFSET+5, 13); \
+ } \
+ } else e->dtds[e->dtd_count++].addy = e->d[INDEX].addy;
+
+ CHECK_DESCRIPTOR(0, 54);
+ CHECK_DESCRIPTOR(1, 72);
+ CHECK_DESCRIPTOR(2, 90);
+ CHECK_DESCRIPTOR(3, 108);
+
+ e->checksum_ok = block_check(e, 0); /* 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--) {
+ uint32_t offset = blocks * 128;
+ uint8_t *u8 = e->u8 + offset;
+ int r = block_check(e, offset);
+ e->ext_ok[blocks-1] = r;
+ if (r) e->ext_blocks_ok++;
+ else e->ext_blocks_fail++;
+
+ if (u8[0] == 0x40) {
+ /* DI-EXT */
+ e->di.exists = 1;
+ e->di.addy.e = e;
+ e->di.addy.offset = offset;
+
+ e->di.interface = r8(e, offset + 2, NOMASK);
+ e->di.supports_hdcp = r8(e, offset + 3, 0x8);
+ }
+
+ if (u8[0] == 0x70) {
+ /* DisplayID */
+ e->did.version = u8[1];
+ if (e->did.version >= 0x20)
+ e->std = MAX(e->std, STD_DISPLAYID20);
+ else e->std = MAX(e->std, STD_DISPLAYID);
+ e->did.extension_length = u8[2];
+ e->did.primary_use_case = u8[3];
+ e->did.extension_count = u8[4];
+ e->did.checksum_ok = block_check_n(e, offset, e->did.extension_length + 5);
+ int db_end = u8[2] + 5;
+ int b = 5;
+ while(b < db_end) {
+ if (r24le(e, offset + b, NOMASK) == 0) break;
+ int db_type = u8[b];
+ int db_revision = u8[b+1] & 0x7;
+ int db_size = u8[b+2];
+ e->did_blocks[e->did_block_count].addy.e = e;
+ e->did_blocks[e->did_block_count].addy.offset = offset + b;
+ e->did_blocks[e->did_block_count].type = db_type;
+ e->did_blocks[e->did_block_count].revision = db_revision;
+ e->did_blocks[e->did_block_count].len = db_size;
+ did_block_decode(&e->did_blocks[e->did_block_count]);
+ e->did_block_count++;
+ e->did.blocks++;
+ b += db_size + 3;
+ }
+ if (b > db_end)
+ edid_msg(e, "DID block overrun [in ext " OFMT "], expect to end at +%d, but last ends at +%d" , offset, db_end-1, b-1);
+ //printf("DID: v:%02x el:%d uc:%d ec:%d, blocks:%d ok:%d\n",
+ // e->did.version, e->did.extension_length,
+ // e->did.primary_use_case, e->did.extension_count,
+ // e->did.blocks, e->checksum_ok);
+ }
+
+ if (u8[0] == 0x02) {
+ e->std = MAX(e->std, STD_EIACEA861);
+ /* CEA extension */
+ int db_end = u8[2];
+ if (db_end) {
+ int b = 4;
+ while(b < db_end) {
+ int db_type = (u8[b] & 0xe0) >> 5;
+ int db_size = u8[b] & 0x1f;
+ e->cea_blocks[e->cea_block_count].addy.e = e;
+ e->cea_blocks[e->cea_block_count].addy.offset = offset + b;
+ e->cea_blocks[e->cea_block_count].type = db_type;
+ e->cea_blocks[e->cea_block_count].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;
+ edid_msg(e, "CEA block overrun [in ext " OFMT "], expect to end at +%d, but last ends at +%d" , offset, db_end-1, b-1);
+ }
+ /* DTDs */
+ while(b < 127) {
+ if (u8[b]) {
+ e->dtds[e->dtd_count].addy.e = e;
+ e->dtds[e->dtd_count].addy.offset = offset + b;
+ e->dtds[e->dtd_count].cea_ext = 1;
+ e->dtd_count++;
+ }
+ b += 18;
+ }
+ }
+ }
+ }
+ }
+ if (e->ext_blocks_ok) {
+ e->std = MAX(e->std, STD_EEDID);
+ }
+
+ /* strings */
+ for(i = 0; i < 4; i++) {
+ g_strstrip(e->d[i].text);
+ str_make_printable(e->d[i].text);
+ switch(e->d[i].type) {
+ case 0xfc:
+ e->name = e->d[i].text;
+ break;
+ case 0xff:
+ e->serial = e->d[i].text;
+ break;
+ case 0xfe:
+ if (e->ut1)
+ e->ut2 = e->d[i].text;
+ else
+ e->ut1 = e->d[i].text;
+ break;
+ }
+ }
+
+ /* 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;
+ }
+ }
+ if (!e->interface && e->di.interface) {
+ if (e->di.interface >= 1
+ && e->di.interface <= 5)
+ e->interface = 1; /* DVI */
+ }
+
+ /* largest in ETB */
+ for (i = 0; i < e->etb_count; i++) {
+ if (e->etbs[i].pixels > e->img.pixels)
+ e->img = e->etbs[i];
+ if (e->etbs[i].pixels > e->img_max.pixels)
+ e->img_max = e->etbs[i];
+ }
+
+ /* largest in STDs */
+ for (i = 0; i < e->std_count; i++) {
+ if (e->stds[i].out.pixels > e->img.pixels)
+ e->img = e->stds[i].out;
+ if (e->stds[i].out.pixels > e->img_max.pixels)
+ e->img_max = e->stds[i].out;
+ }
+
+ /* dtds */
+ for(i = 0; i < e->dtd_count; i++) {
+ edid_addy a = e->dtds[i].addy;
+ if (!e->dtds[i].bounds_ok)
+ e->dtds[i].bounds_ok = bounds_check(a.e, a.offset + 18);
+ if (!e->dtds[i].bounds_ok) {
+ printf("bounds fail\n");
+ exit(0);
+ }
+ uint8_t *u8 = DPTR(a);
+ edid_output *out = &e->dtds[i].out;
+ if (e->dtds[i].cea_ext) out->src = OUTSRC_CEA_DTD;
+ else out->src = OUTSRC_DTD;
+ out->pixel_clock_khz = 10 * r16le(a.e, a.offset, NOMASK);
+ out->horiz_pixels =
+ ((u8[4] & 0xf0) << 4) + u8[2];
+ out->vert_lines =
+ ((u8[7] & 0xf0) << 4) + u8[5];
+
+ out->horiz_blanking =
+ ((u8[4] & 0x0f) << 8) + u8[3];
+ out->vert_blanking =
+ ((u8[7] & 0x0f) << 8) + u8[6];
+
+ out->horiz_cm =
+ ((u8[14] & 0xf0) << 4) + u8[12];
+ out->horiz_cm /= 10;
+ out->vert_cm =
+ ((u8[14] & 0x0f) << 8) + u8[13];
+ out->vert_cm /= 10;
+ out->is_interlaced = (u8[17] & 0x80) >> 7;
+ out->stereo_mode = (u8[17] & 0x60) >> 4;
+ out->stereo_mode += u8[17] & 0x01;
+ edid_output_fill(out);
+ }
+
+ if (e->dtd_count) {
+ /* first DTD is "preferred" */
+ e->img_max = e->dtds[0].out;
+ }
+
+ /* svds */
+ for(i = 0; i < e->svd_count; i++) {
+ e->svds[i].out = edid_output_from_svd(e->svds[i].v);
+ if (e->svds[i].out.src == OUTSRC_INVALID)
+ continue;
+
+ if (e->svds[i].v >= 128 &&
+ e->svds[i].v <= 192) {
+ e->svds[i].is_native = 1;
+
+ edid_output tmp = e->img_max;
+ /* native res is max real res, right? */
+ e->img_max = e->svds[i].out;
+ e->img_max.is_preferred = 1;
+ OUTPUT_CPY_SIZE(e->img_max, tmp);
+ }
+ if (e->svds[i].out.pixels > e->img_svd.pixels
+ || e->svds[i].is_native) {
+ e->img_svd = e->svds[i].out;
+ if (e->svds[i].is_native)
+ e->img_svd.is_preferred = 1;
+ OUTPUT_CPY_SIZE(e->img_svd, e->img_max);
+ }
+ }
+ /* remove invalid SVDs */
+ int d = 0;
+ for(i = 0; i < e->svd_count; i++) {
+ if (d != i)
+ e->svds[d].out = e->svds[i].out;
+ if (e->svds[i].out.src != OUTSRC_INVALID)
+ d++;
+ }
+ e->svd_count -= (i-d);
+
+ /* didts */
+ for(i = 0; i < e->didt_count; i++) {
+ int pref = e->didts[i].is_preferred;
+ int max_pref = e->img_max.is_preferred;
+ int bigger = (e->didts[i].pixels > e->img_max.pixels);
+ int better = (e->didts[i].src > e->img_max.src);
+ if ((bigger && !max_pref)
+ || (pref && !max_pref)
+ || (better)) {
+ edid_output tmp = e->img_max;
+ e->img_max = e->didts[i];
+ OUTPUT_CPY_SIZE(e->img_max, tmp);
+ }
+ }
+
+ if (!e->speaker_alloc_bits && e->sad_count) {
+ /* make an assumption */
+ if (e->sads[0].channels == 2)
+ e->speaker_alloc_bits = 0x1;
+ }
+
+ /* squeeze lists */
+#define SQUEEZE(C, L) \
+ if (!e->C) { free(e->L); e->L = NULL; } \
+ else { e->L = realloc(e->L, sizeof(e->L[0]) * (e->C)); }
+
+ SQUEEZE(dtd_count, dtds);
+ SQUEEZE(cea_block_count, cea_blocks);
+ SQUEEZE(svd_count, svds);
+ SQUEEZE(sad_count, sads);
+ SQUEEZE(did_block_count, did_blocks);
+ SQUEEZE(didt_count, didts);
+ SQUEEZE(did_string_count, did_strings);
+ return e;
+}
+
+void edid_free(edid *e) {
+ int i;
+ if (e) {
+ g_free(e->ext_ok);
+ g_free(e->cea_blocks);
+ g_free(e->dtds);
+ g_free(e->svds);
+ g_free(e->sads);
+ g_free(e->did_blocks);
+ g_free(e->didts);
+ for(i = 0; i < e->did_string_count; i++)
+ g_free(e->did_strings[i].str);
+ g_free(e->did_strings);
+ g_free(e->data);
+ g_string_free(e->msg_log, TRUE);
+ g_free(e);
+ }
+}
+
+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);
+ int len = 0;
+
+ const char *p = hex_string;
+ char byte[3] = "..";
+
+ while(p && *p) {
+ if (isxdigit(p[0]) && isxdigit(p[1])) {
+ byte[0] = p[0];
+ byte[1] = p[1];
+ *n = strtol(byte, NULL, 16);
+ n++;
+ len++;
+ p += 2;
+ } else
+ p++;
+ }
+
+ edid *e = edid_new((char*)buffer, len);
+ free(buffer);
+ return e;
+}
+
+edid *edid_new_from_file(const char *path) {
+ char *bin = NULL;
+ gsize len = 0;
+ if (g_file_get_contents(path, &bin, &len, NULL) ) {
+ edid *ret = edid_new(bin, len);
+ g_free(bin);
+ return ret;
+ }
+ return NULL;
+}
+
+char *edid_dump_hex(edid *e, int tabs, int breaks) {
+ if (!e) return NULL;
+ int lines = 1 + (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, d = MIN(16, (e->len - pc));
+ if (!d) break;
+ for(i = 0; i < tabs; i++)
+ sprintf(p++, "\t");
+ for(i = d; i; 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 STD_EDID: return N_("VESA EDID");
+ case STD_EEDID: return N_("VESA E-EDID");
+ case STD_EIACEA861: return N_("EIA/CEA-861");
+ case STD_DISPLAYID: return N_("VESA DisplayID");
+ case STD_DISPLAYID20: return N_("VESA DisplayID 2.0");
+ };
+ return N_("unknown");
+}
+
+const char *edid_output_src(int src) {
+ switch(src) {
+ case OUTSRC_EDID: return N_("VESA EDID");
+ case OUTSRC_ETB: return N_("VESA EDID ETB");
+ case OUTSRC_STD: return N_("VESA EDID STD");
+ case OUTSRC_DTD: return N_("VESA EDID DTD");
+ case OUTSRC_CEA_DTD: return N_("EIA/CEA-861 DTD");
+ case OUTSRC_SVD: return N_("EIA/CEA-861 SVD");
+ case OUTSRC_DID_TYPE_I: return N_("DisplayID Type I");
+ case OUTSRC_DID_TYPE_VI: return N_("DisplayID Type VI");
+ case OUTSRC_DID_TYPE_VII: return N_("DisplayID Type VII");
+ };
+ return N_("unknown");
+}
+
+const char *edid_interface(int type) {
+ switch(type) {
+ case 0: return N_("undefined");
+ case 0x1: return N_("DVI");
+ case 0x2: return N_("HDMIa");
+ case 0x3: return N_("HDMIb");
+ case 0x4: return N_("MDDI");
+ case 0x5: return N_("DisplayPort");
+ };
+ return N_("unknown");
+}
+
+const char *edid_di_interface(int type) {
+ switch(type) {
+ case 0: return N_("Analog");
+ case 0x1: return N_("DVI");
+ case 0x2: return N_("DVI - Single Link");
+ case 0x3: return N_("DVI - Dual Link (Hi-Resolution)");
+ case 0x4: return N_("DVI - Dual Link (Hi-Color)");
+ case 0x5: return N_("DVI for Consumer Electronics");
+ case 0x6: return N_("PnD");
+ case 0x7: return N_("DFP");
+ case 0x8: return N_("OpenLDI - Single Link");
+ case 0x9: return N_("OpenLDI - Dual Link");
+ case 0xa: return N_("OpenLDI for Consumer Electronics");
+ };
+ 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_did_block_type(int type) {
+ switch(type) {
+ /* 1.x */
+ case 0x00: return N_("Product Identification (1.x)");
+ case 0x01: return N_("Display Parameters (1.x)");
+ case 0x02: return N_("Color Characteristics (1.x)");
+ case 0x03: return N_("Type I Timing - Detailed (1.x)");
+ case 0x04: return N_("Type II Timing - Detailed (1.x)");
+ case 0x05: return N_("Type III Timing - Short (1.x)");
+ case 0x06: return N_("Type IV Timing - DMT ID Code (1.x)");
+ case 0x07: return N_("VESA Timing Standard (1.x)");
+ case 0x08: return N_("CEA Timing Standard (1.x)");
+ case 0x09: return N_("Video Timing Range (1.x)");
+ case 0x0A: return N_("Product Serial Number (1.x)");
+ case 0x0B: return N_("General Purpose ASCII String (1.x)");
+ case 0x0C: return N_("Display Device Data (1.x)");
+ case 0x0D: return N_("Interface Power Sequencing (1.x)");
+ case 0x0E: return N_("Transfer Characteristics (1.x)");
+ case 0x0F: return N_("Display Interface Data (1.x)");
+ case 0x10: return N_("Stereo Display Interface (1.x)");
+ case 0x11: return N_("Type V Timing - Short (1.x)");
+ case 0x12: return N_("Tiled Display Topology (1.x)");
+ case 0x13: return N_("Type VI Timing - Detailed (1.x)");
+ case 0x7F: return N_("Vendor specific (1.x)");
+ /* 2.x */
+ case 0x20: return N_("Product Identification");
+ case 0x21: return N_("Display Parameters");
+ case 0x22: return N_("Type VII - Detailed Timing");
+ case 0x23: return N_("Type VIII - Enumerated Timing Code");
+ case 0x24: return N_("Type IX - Formula-based Timing");
+ case 0x25: return N_("Dynamic Video Timing Range Limits");
+ case 0x26: return N_("Display Interface Features");
+ case 0x27: return N_("Stereo Display Interface");
+ case 0x28: return N_("Tiled Display Topology");
+ case 0x29: return N_("ContainerID");
+ case 0x7E: return N_("Vendor specific");
+ case 0x81: return N_("CTA DisplayID");
+ }
+ return N_("unknown type");
+}
+
+const char *edid_ext_block_type(int type) {
+ switch(type) {
+ case 0x00:
+ return N_("timing extension");
+ case 0x02:
+ return N_("EIA/CEA-861 extension (CEA-EXT)");
+ case 0x10:
+ return N_("Video Timing Block extension (VTB-EXT)");
+ case 0x20:
+ return N_("EDID 2.0 extension");
+ case 0x40:
+ return N_("Display Information extension (DI-EXT)");
+ case 0x50:
+ return N_("Localized String extension (LS-EXT)");
+ case 0x60:
+ return N_("Digital Packet Video Link extension (DPVL-EXT)");
+ 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");
+}
+
+const char *edid_descriptor_type(int type) {
+ switch(type) {
+ case 0xff:
+ return N_("display serial number");
+ case 0xfe:
+ return N_("unspecified text");
+ case 0xfd:
+ return N_("display range limits");
+ case 0xfc:
+ return N_("display name");
+ case 0xfb:
+ return N_("additional white point");
+ case 0xfa:
+ return N_("additional standard timing identifiers");
+ case 0xf9:
+ return N_("Display Color Management");
+ case 0xf8:
+ return N_("CVT 3-byte timing codes");
+ case 0xf7:
+ return N_("additional standard timing");
+ case 0x10:
+ return N_("dummy");
+ }
+ if (type && type < 0x0f)
+ return N_("manufacturer reserved descriptor");
+ return N_("detailed timing descriptor");
+}
+
+char *edid_cea_audio_describe(struct edid_sad *sad) {
+ if (!sad) return NULL;
+
+ if (!sad->format)
+ return g_strdup_printf("format:([%x] %s)",
+ sad->format, _(edid_cea_audio_type(sad->format)) );
+
+ gchar *ret = NULL;
+ gchar *tmp[3] = {NULL,NULL,NULL};
+#define appfreq(b, f) if (sad->freq_bits & (1 << b)) tmp[0] = appf(tmp[0], ", ", "%d", f);
+#define appdepth(b, d) if (sad->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 (sad->format == 1) {
+ appdepth(0, 16);
+ appdepth(1, 20);
+ appdepth(2, 24);
+ tmp[2] = g_strdup_printf("depths: %s", tmp[1]);
+ } else if (sad->format >= 2
+ && sad->format <= 8 ) {
+ tmp[2] = g_strdup_printf("max_bitrate: %d %s", sad->max_kbps, _("kbps"));
+ } else
+ tmp[2] = g_strdup("");
+
+ ret = g_strdup_printf("format:([%x] %s) channels:%d rates:%s %s %s",
+ sad->format, _(edid_cea_audio_type(sad->format)),
+ sad->channels, tmp[0], _("kHz"),
+ tmp[2]);
+ g_free(tmp[0]);
+ g_free(tmp[1]);
+ g_free(tmp[2]);
+ return ret;
+}
+
+char *edid_cea_speaker_allocation_describe(int bitfield, int short_version) {
+ gchar *spk_list = NULL;
+#define appspk(b, sv, fv) if (bitfield & (1 << b)) \
+ spk_list = appf(spk_list, short_version ? ", " : "\n", "%s", short_version ? sv : fv);
+
+ appspk(0, "FL+FR", _("Front left and right"));
+ appspk(1, "LFE", _("Low-frequency effects"));
+ appspk(2, "FC", _("Front center"));
+ appspk(3, "RL+RR", _("Rear left and right"));
+ appspk(4, "RC", _("Rear center"));
+ appspk(5, "???", _(""));
+ appspk(6, "???", _(""));
+
+ return spk_list;
+}
+
+char *edid_cea_block_describe(struct edid_cea_block *blk) {
+ gchar *ret = NULL;
+ if (blk) {
+ char *hb = hex_bytes(DPTR(blk->addy), blk->len+1);
+ switch(blk->type) {
+ case 0x1: /* SAD list */
+ ret = g_strdup_printf("([%x] %s) sads:%d",
+ blk->type, _(edid_cea_block_type(blk->type)),
+ blk->len/3);
+ break;
+ case 0x2: /* SVD list */
+ ret = g_strdup_printf("([%x] %s) svds:%d",
+ blk->type, _(edid_cea_block_type(blk->type)),
+ blk->len);
+ break;
+ case 0x4: /* speaker allocation */
+ ret = g_strdup_printf("([%x] %s) len:%d",
+ blk->type, _(edid_cea_block_type(blk->type)),
+ blk->len);
+ break;
+ case 0x3: /* vendor specific */
+ ret = g_strdup_printf("([%x] %s) len:%d (OUI:%s) -- %s",
+ blk->type, _(edid_cea_block_type(blk->type)),
+ blk->len, blk->ven.oui_str,
+ hb);
+ break;
+ default:
+ ret = g_strdup_printf("([%x] %s) len:%d -- %s",
+ blk->type, _(edid_cea_block_type(blk->type)),
+ blk->len,
+ hb);
+ break;
+ }
+ free(hb);
+ }
+ return ret;
+}
+
+char *edid_base_descriptor_describe(struct edid_descriptor *d) {
+ gchar *ret = NULL;
+ if (d) {
+ char *hb = hex_bytes(DPTR(d->addy), 18);
+ char *txt = NULL;
+ switch(d->type) {
+ case 0: /* DTD */
+ txt = "{...}"; /* displayed elsewhere */
+ break;
+ case 0x10: /* dummy */
+ txt = "";
+ break;
+ default:
+ txt = (*d->text) ? d->text : hb;
+ break;
+ };
+ ret = g_strdup_printf("([%02x] %s) %s",
+ d->type, _(edid_descriptor_type(d->type)),
+ txt);
+ free(hb);
+ }
+ return ret;
+}
+
+char *edid_did_block_describe(DisplayIDBlock *blk) {
+ if (!blk) return NULL;
+
+ gchar *ret = NULL;
+ edid *e = blk->addy.e;
+ uint32_t a = blk->addy.offset + 3;
+ char *str = NULL;
+
+ //printf("edid_did_block_describe: ((%d)) t:%02x a{%p, %d}...\n", blk->addy.e->did.extension_count, blk->type, blk->addy.e, blk->addy.offset);
+ char *hb = hex_bytes(DPTR(blk->addy), blk->len+3);
+ switch(blk->type) {
+ case 0x0a: /* Product Serial ASCII string */
+ str = rstr_strip(e, a, blk->len);
+ ret = g_strdup_printf("([%02x:r%02x] %s) len:%d \"%s\"",
+ blk->type, blk->revision, _(edid_did_block_type(blk->type)),
+ blk->len,
+ str);
+ break;
+ case 0x0b: /* ASCII string */
+ str = rstr(e, a, blk->len);
+ ret = g_strdup_printf("([%02x:r%02x] %s) len:%d \"%s\"",
+ blk->type, blk->revision, _(edid_did_block_type(blk->type)),
+ blk->len,
+ str);
+ break;
+ default:
+ ret = g_strdup_printf("([%02x:r%02x] %s) len:%d -- %s",
+ blk->type, blk->revision, _(edid_did_block_type(blk->type)),
+ blk->len,
+ hb);
+ break;
+ }
+ free(hb);
+
+ return ret;
+}
+
+char *edid_output_describe(edid_output *out) {
+ gchar *ret = NULL;
+ if (out) {
+ ret = g_strdup_printf("%dx%d@%.0f%s",
+ out->horiz_pixels, out->vert_pixels, out->vert_freq_hz, _("Hz") );
+ if (out->diag_cm)
+ ret = appfsp(ret, "%0.1fx%0.1f%s (%0.1f\")",
+ out->horiz_cm, out->vert_cm, _("cm"), out->diag_in );
+ ret = appfsp(ret, "%s", out->is_interlaced ? "interlaced" : "progressive");
+ ret = appfsp(ret, "%s", out->stereo_mode ? "stereo" : "normal");
+ }
+ return ret;
+}
+
+char *edid_dtd_describe(struct edid_dtd *dtd, int dump_bytes) {
+ gchar *ret = NULL;
+ if (dtd) {
+ edid_output *out = &dtd->out;
+ char *hb = hex_bytes(DPTR(dtd->addy), 18);
+ ret = g_strdup_printf("%dx%d@%.0f%s, %0.1fx%0.1f%s (%0.1f\") %s %s (%s)%s%s",
+ out->horiz_pixels, out->vert_lines, out->vert_freq_hz, _("Hz"),
+ out->horiz_cm, out->vert_cm, _("cm"), out->diag_in,
+ out->is_interlaced ? "interlaced" : "progressive",
+ out->stereo_mode ? "stereo" : "normal",
+ _(edid_output_src(out->src)),
+ dump_bytes ? " -- " : "",
+ dump_bytes ? hb : "");
+ free(hb);
+ }
+ return ret;
+}
+
+char *edid_manf_date_describe(struct edid_manf_date dom) {
+ if (!dom.year) return g_strdup("unspecified");
+ if (dom.is_model_year)
+ return g_strdup_printf(_("model year %d"), dom.year);
+ if (dom.week && dom.week <= 53)
+ return g_strdup_printf(_("week %d of %d"), dom.week, dom.year);
+ return g_strdup_printf("%d", dom.year);
+}
+
+char *edid_dump2(edid *e) {
+ char *ret = NULL;
+ int i;
+ if (!e) return NULL;
+
+ ret = appfnl(ret, "edid version: %d.%d (%d bytes)", e->ver_major, e->ver_minor, e->len);
+ if (e->std)
+ ret = appfnl(ret, "extended to: %s", _(edid_standard(e->std)) );
+
+ ret = appfnl(ret, "mfg: %s, model: [%04x-%08x] %u-%u", e->ven.pnp, e->product, e->n_serial, e->product, e->n_serial);
+ char *dom_desc = edid_manf_date_describe(e->dom);
+ ret = appfnl(ret, "date: %s", dom_desc);
+ g_free(dom_desc);
+
+ if (e->name)
+ ret = appfnl(ret, "product: %s", e->name);
+ if (e->serial)
+ ret = appfnl(ret, "serial: %s", e->serial);
+
+ 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 (e->interface)
+ ret = appfnl(ret, "interface: %s", _(edid_interface(e->interface)));
+ if (e->di.exists) {
+ ret = appfnl(ret, "interface_ext: %s", _(edid_di_interface(e->di.interface)));
+ ret = appfnl(ret, "hdcp: %s", e->di.supports_hdcp ? "supported" : "no");
+ }
+
+ char *desc = edid_output_describe(&e->img);
+ char *desc_svd = edid_output_describe(&e->img_svd);
+ char *desc_max = edid_output_describe(&e->img_max);
+ ret = appfnl(ret, "base(%s): %s", _(edid_output_src(e->img.src)), desc);
+ if (e->svd_count)
+ ret = appfnl(ret, "svd(%s): %s", _(edid_output_src(e->img_svd.src)), desc_svd);
+ ret = appfnl(ret, "max(%s): %s", _(edid_output_src(e->img_max.src)), desc_max);
+ g_free(desc);
+ g_free(desc_svd);
+ g_free(desc_max);
+
+ if (e->speaker_alloc_bits) {
+ char *desc = edid_cea_speaker_allocation_describe(e->speaker_alloc_bits, 1);
+ ret = appfnl(ret, "speakers: %s", desc);
+ g_free(desc);
+ }
+
+ for(i = 0; i < e->etb_count; i++) {
+ char *desc = edid_output_describe(&e->etbs[i]);
+ ret = appfnl(ret, "etb[%d]: %s", i, desc);
+ g_free(desc);
+ }
+
+ for(i = 0; i < e->std_count; i++) {
+ char *desc = edid_output_describe(&e->stds[i].out);
+ ret = appfnl(ret, "std[%d]: %s", i, desc);
+ g_free(desc);
+ }
+
+ 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++) {
+ char *desc = edid_base_descriptor_describe(&e->d[i]);
+ ret = appfnl(ret, "descriptor[%d] %s", i, desc);
+ g_free(desc);
+ }
+
+ 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 < e->dtd_count; i++) {
+ char *desc = edid_dtd_describe(&e->dtds[i], 0);
+ 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);
+ free(desc);
+ }
+
+ for(i = 0; i < e->svd_count; i++) {
+ char *desc = edid_output_describe(&e->svds[i].out);
+ ret = appfnl(ret, "svd[%d] [%02x] %s", i, e->svds[i].v, desc);
+ free(desc);
+ }
+
+ for(i = 0; i < e->sad_count; i++) {
+ char *desc = edid_cea_audio_describe(&e->sads[i]);
+ ret = appfnl(ret, "sad[%d] [%02x%02x%02x] %s", i,
+ e->sads[i].v[0], e->sads[i].v[1], e->sads[i].v[2],
+ desc);
+ free(desc);
+ }
+
+ for(i = 0; i < e->did_block_count; i++) {
+ char *desc = edid_did_block_describe(&e->did_blocks[i]);
+ ret = appfnl(ret, "did_block[%d] %s", i, desc);
+ free(desc);
+ }
+
+ for(i = 0; i < e->didt_count; i++) {
+ char *desc = edid_output_describe(&e->didts[i]);
+ ret = appfnl(ret, "did_timing[%d]: %s", i, desc);
+ g_free(desc);
+ }
+
+ for(i = 0; i < e->did_string_count; i++) {
+ ret = appfnl(ret, "did_string[%d]: %s", i, e->did_strings[i].str);
+ }
+
+ ret = appfnl(ret, "parse messages:\n%s---", e->msg_log->str);
+
+ return ret;
+}
+
diff --git a/deps/sysobj_early/src/util_edid_svd_table.c b/deps/sysobj_early/src/util_edid_svd_table.c
new file mode 100644
index 00000000..4833c77d
--- /dev/null
+++ b/deps/sysobj_early/src/util_edid_svd_table.c
@@ -0,0 +1,164 @@
+
+struct {
+ int index;
+ const char *short_name;
+ const char *disp_ratio, *pixel_ratio;
+ float pixel_clock_mhz, vert_freq_hz, horiz_freq_khz;
+ float horiz_active, vert_active, horiz_total, vert_total;
+ float field_rate_hz;
+} cea_standard_timings[] = {
+{ 1, "DMT0659", "4:3", "1:1", 25.175, 59.94, 31.469, 640, 480, 800, 525, 60 },
+{ 2, "480p", "4:3", "8:9", 27.0, 59.94, 31.469, 720, 480, 858, 525, 60 },
+{ 3, "480pH", "16:9", "32:37", 27.0, 59.94, 31.469, 720, 480, 858, 525, 60 },
+{ 4, "720p", "16:9", "1:1", 74.25, 60, 45.0, 1280, 720, 1650, 750, 60 },
+{ 5, "1080i", "16:9", "1:1", 74.25, 60, 33.75, 1920, 540, 2200, 562.5, 60 },
+{ 6, "480i", "4:3", "8:9", 27.0, 59.94, 15.734, 1440, 240, 1716, 262.5, 60 },
+{ 7, "480iH", "16:9", "32:37", 27.0, 59.94, 15.734, 1440, 240, 1716, 262.5, 60 },
+{ 8, "240p", "4:3", "4:9", 27.0, 59.826, 15.734, 1440, 240, 1716, 262.5, 60 },
+{ 9, "240pH", "16:9", "16:27", 27.0, 59.826, 15.734, 1440, 240, 1716, 262.5, 60 },
+{ 10, "480i4x", "4:3", "2:9-20:9", 54.0, 59.94, 15.734, 2880, 240, 3432, 262.5, 60 },
+{ 11, "480i4xH", "16:9", "8:27-80:27", 54.0, 59.94, 15.734, 2880, 240, 3432, 262.5, 60 },
+{ 12, "240p4x", "4:3", "1:9-10:9", 54.0, 60, 15.734, 2880, 240, 3432, 262.5, 60 },
+{ 13, "240p4xH", "16:9", "4:27-40:37", 54.0, 60, 15.734, 2880, 240, 3432, 262.5, 60 },
+{ 14, "480p2x", "4:3", "4:9 or 8:9", 54.0, 59.94, 31.469, 1440, 480, 1716, 525, 60 },
+{ 15, "480p2xH", "16:9", "16:27 or 32:37", 54.0, 59.94, 31.469, 1440, 480, 1716, 525, 60 },
+{ 16, "1080p", "16:9", "1:1", 148.5, 60, 67.5, 1920, 1080, 2200, 1125, 60 },
+{ 17, "576p", "4:3", "16:15", 27.0, 50, 31.25, 720, 576, 864, 625, 50 },
+{ 18, "576pH", "16:9", "64:45", 27.0, 50, 31.25, 720, 576, 864, 625, 50 },
+{ 19, "720p50", "16:9", "1:1", 74.25, 50, 37.5, 1280, 720, 1980, 750, 50 },
+{ 20, "1080i25", "16:9", "1:1", 74.25, 50, 28.125, 1920, 540, 2640, 562.5, 50 },
+{ 21, "576i", "4:3", "16:15", 27.0, 50, 15.625, 1440, 288, 1728, 312.5, 50 },
+{ 22, "576iH", "16:9", "64:45", 27.0, 50, 15.625, 1440, 288, 1728, 312.5, 50 },
+{ 23, "288p", "4:3", "8:15", 27.0, 50, 15.625, 1440, 288, 1728, 313, 50 },
+{ 24, "288pH", "16:9", "32:45", 27.0, 50, 15.625, 1440, 288, 1728, 313, 50 },
+{ 25, "576i4x", "4:3", "2:15-20:15", 54.0, 50, 15.625, 2880, 288, 3456, 312.5, 50 },
+{ 26, "576i4xH", "16:9", "16:45-160:45", 54.0, 50, 15.625, 2880, 288, 3456, 312.5, 50 },
+{ 27, "288p4x", "4:3", "1:15-10:15", 54.0, 50, 15.625, 2880, 288, 3456, 313, 50 },
+{ 28, "288p4xH", "16:9", "8:45-80:45", 54.0, 50, 15.625, 2880, 288, 3456, 313, 50 },
+{ 29, "576p2x", "4:3", "8:15 or 16:15", 54.0, 50, 31.25, 1440, 576, 1728, 625, 50 },
+{ 30, "576p2xH", "16:9", "32:45 or 64:45", 54.0, 50, 31.25, 1440, 576, 1728, 625, 50 },
+{ 31, "1080p50", "16:9", "1:1", 148.5, 50, 56.25, 1920, 1080, 2640, 1125, 50 },
+{ 32, "1080p24", "16:9", "1:1", 74.25, 23.98, 27.0, 1920, 1080, 2750, 1125, 0 },
+{ 33, "1080p25", "16:9", "1:1", 74.25, 25, 28.125, 1920, 1080, 2640, 1125, 0 },
+{ 34, "1080p30", "16:9", "1:1", 74.25, 29.97, 33.75, 1920, 1080, 2500, 1125, 0 },
+{ 35, "480p4x", "4:3", "2:9, 4:9 or 8:9", 108.0, 59.94, 31.469, 2880, 240, 3432, 262.5, 60 },
+{ 36, "480p4xH", "16:9", "8:27, 16:27 or 32:27", 108.0, 59.94, 31.469, 2880, 240, 3432, 262.5, 60 },
+{ 37, "576p4x", "4:3", "4:15, 8:15, or 16:15", 108.0, 50, 31.25, 2880, 576, 3456, 625, 50 },
+{ 38, "576p4xH", "16:9", "16:45, 32:45 or 64:45", 108.0, 50, 31.25, 2880, 576, 3456, 625, 50 },
+{ 39, "1080i25", "16:9", "1:1", 72.0, 50, 31.25, 1920, 540, 2304, 625, 50 },
+{ 40, "1080i50", "16:9", "1:1", 148.5, 100, 56.25, 1920, 540, 2640, 562.5, 100 },
+{ 41, "720p100", "16:9", "1:1", 148.5, 100, 45.0, 1280, 720, 1980, 750, 100 },
+{ 42, "576p100", "4:3", "16:15", 54.0, 100, 62.5, 720, 576, 864, 625, 100 },
+{ 43, "576p100H", "16:9", "64:45", 54.0, 100, 62.5, 720, 576, 864, 625, 100 },
+{ 44, "576i50", "4:3", "16:15", 54.0, 100, 31.25, 1440, 576, 1728, 625, 100 },
+{ 45, "576i50H", "16:9", "64:45", 54.0, 100, 31.25, 1440, 576, 1728, 625, 100 },
+{ 46, "1080i60", "16:9", "1:1", 148.5, 119.88, 67.5, 1920, 540, 2200, 562.5, 120 },
+{ 47, "720p120", "16:9", "1:1", 148.5, 119.88, 90.0, 1280, 720, 1650, 750, 120 },
+{ 48, "480p119", "4:3", "8:9", 54.0, 119.88, 62.937, 720, 576, 858, 525, 120 },
+{ 49, "480p119H", "16:9", "32:37", 54.0, 119.88, 62.937, 720, 576, 858, 525, 120 },
+{ 50, "480i59", "4:3", "16:15", 54.0, 119.88, 31.469, 1440, 576, 1716, 525, 120 },
+{ 51, "480i59H", "16:9", "64:45", 54.0, 119.88, 31.469, 1440, 576, 1716, 525, 120 },
+{ 52, "576p200", "4:3", "16:15", 108.0, 200, 125.0, 720, 576, 864, 625, 200 },
+{ 53, "576p200H", "16:9", "64:45", 108.0, 200, 125.0, 720, 576, 864, 625, 200 },
+{ 54, "576i100", "4:3", "16:15", 108.0, 200, 62.5, 1440, 288, 1728, 312.5, 200 },
+{ 55, "576i100H", "16:9", "64:45", 108.0, 200, 62.5, 1440, 288, 1728, 312.5, 200 },
+{ 56, "480p239", "4:3", "8:9", 108.0, 239.76, 125.874, 720, 480, 858, 525, 240 },
+{ 57, "480p239H", "16:9", "32:37", 108.0, 239.76, 125.874, 720, 480, 858, 525, 240 },
+{ 58, "480i119", "4:3", "8:9", 108.0, 239.76, 62.937, 1440, 240, 1716, 262.5, 240 },
+{ 59, "480i119H", "16:9", "32:37", 108.0, 239.76, 62.937, 1440, 240, 1716, 262.5, 240 },
+{ 60, "720p24", "16:9", "1:1", 59.4, 23.98, 18.0, 1280, 720, 3300, 750, 0 },
+{ 61, "720p25", "16:9", "1:1", 74.25, 25, 18.75, 1280, 720, 3960, 750, 0 },
+{ 62, "720p30", "16:9", "1:1", 74.25, 29.97, 22.5, 1280, 720, 3300, 750, 0 },
+{ 63, "1080p120", "16:9", "1:1", 297.0, 119.88, 135.0, 1920, 1080, 2200, 1125, 120 },
+{ 64, "1080p100", "16:9", "1:1", 297.0, 100, 112.5, 1920, 1080, 2640, 1125, 100 },
+{ 65, "720p24", "64:27", "4:3", 59.4, 23.98, 18.0, 1280, 720, 3300, 750, 0 },
+{ 66, "720p25", "64:27", "4:3", 74.25, 25, 18.75, 1280, 720, 3960, 750, 0 },
+{ 67, "720p30", "64:27", "4:3", 74.25, 29.97, 22.5, 1280, 720, 3300, 750, 0 },
+{ 68, "720p50", "64:27", "4:3", 74.25, 50, 37.5, 1280, 720, 1980, 750, 50 },
+{ 69, "720p", "64:27", "4:3", 74.25, 60, 45.0, 1650, 750, 1650, 750, 60 },
+{ 70, "720p100", "64:27", "4:3", 148.5, 100, 75.0, 1280, 720, 1980, 750, 100 },
+{ 71, "720p120", "64:27", "4:3", 148.5, 119.88, 90.0, 1280, 720, 1650, 750, 120 },
+{ 72, "1080p24", "64:27", "4:3", 74.25, 23.98, 27.0, 1920, 1080, 2750, 1125, 0 },
+{ 73, "1080p25", "64:27", "4:3", 74.25, 25, 28.125, 1920, 1080, 2640, 1125, 0 },
+{ 74, "1080p30", "64:27", "4:3", 74.25, 29.97, 33.75, 1920, 1080, 2500, 1125, 0 },
+{ 75, "1080p50", "64:27", "4:3", 148.5, 50, 56.25, 1920, 1080, 2640, 1125, 50 },
+{ 76, "1080p", "64:27", "4:3", 148.5, 60, 67.5, 1920, 1080, 2200, 1125, 60 },
+{ 77, "1080p100", "64:27", "4:3", 297.0, 100, 112.5, 1920, 1080, 2640, 1125, 100 },
+{ 78, "1080p120", "64:27", "4:3", 297.0, 119.88, 135.0, 1920, 1080, 2200, 1125, 120 },
+{ 79, "720p2x24", "64:27", "64:63", 59.4, 23.98, 18.0, 1680, 720, 3300, 750, 0 },
+{ 80, "720p2x25", "64:27", "64:63", 59.4, 25, 18.75, 1680, 720, 3168, 750, 0 },
+{ 81, "720p2x30", "64:27", "64:63", 59.4, 29.97, 22.5, 1680, 720, 2640, 750, 0 },
+{ 82, "720p2x50", "64:27", "64:63", 82.5, 50, 37.5, 1680, 720, 2200, 750, 50 },
+{ 83, "720p2x", "64:27", "64:63", 99.0, 60, 45.0, 1680, 720, 2200, 750, 60 },
+{ 84, "720p2x100", "64:27", "64:63", 165.0, 100, 82.5, 1680, 720, 2000, 825, 100 },
+{ 85, "720p2x120", "64:27", "64:63", 198.0, 119.88, 99.0, 1680, 720, 2000, 825, 120 },
+{ 86, "1080p2x24", "64:27", "1:1", 99.0, 23.98, 26.4, 2560, 1080, 3750, 1100, 0 },
+{ 87, "1080p2x25", "64:27", "1:1", 90.0, 25, 28.125, 2560, 1080, 3200, 1125, 0 },
+{ 88, "1080p2x30", "64:27", "1:1", 118.8, 29.97, 33.75, 2560, 1080, 3520, 1125, 0 },
+{ 89, "1080p2x50", "64:27", "1:1", 185.625, 50, 56.25, 2560, 1080, 3000, 1125, 50 },
+{ 90, "1080p2x", "64:27", "1:1", 198.0, 60, 66.0, 2560, 1080, 3000, 1100, 60 },
+{ 91, "1080p2x100", "64:27", "1:1", 371.25, 100, 125.0, 2560, 1080, 2970, 1250, 100 },
+{ 92, "1080p2x120", "64:27", "1:1", 495.0, 119.88, 150.0, 2560, 1080, 3300, 1250, 120 },
+{ 93, "2160p24", "16:9", "1:1", 297.0, 23.98, 54.0, 3840, 2160, 5500, 2250, 0 },
+{ 94, "2160p25", "16:9", "1:1", 297.0, 25, 56.25, 3840, 2160, 5280, 2250, 0 },
+{ 95, "2160p30", "16:9", "1:1", 297.0, 29.97, 67.5, 3840, 2160, 4400, 2250, 0 },
+{ 96, "2160p50", "16:9", "1:1", 594.0, 50, 112.5, 3840, 2160, 5280, 2250, 50 },
+{ 97, "2160p60", "16:9", "1:1", 594.0, 60, 135.0, 3840, 2160, 4400, 2250, 60 },
+{ 98, "2160p24", "256:135", "1:1", 297.0, 23.98, 67.5, 4096, 2160, 5500, 2250, 0 },
+{ 99, "2160p25", "256:135", "1:1", 297.0, 25, 112.5, 4096, 2160, 5280, 2250, 0 },
+{ 100, "2160p30", "256:135", "1:1", 297.0, 29.97, 135.0, 4096, 2160, 4400, 2250, 0 },
+{ 101, "2160p50", "256:135", "1:1", 594.0, 50, 112.5, 4096, 2160, 5280, 2250, 50 },
+{ 102, "2160p", "256:135", "1:1", 594.0, 60, 135.0, 4096, 2160, 4400, 2250, 60 },
+{ 103, "2160p24", "64:27", "4:3", 297.0, 23.98, 67.5, 3840, 2160, 5500, 2250, 0 },
+{ 104, "2160p25", "64:27", "4:3", 297.0, 25, 112.5, 3840, 2160, 5280, 2250, 0 },
+{ 105, "2160p30", "64:27", "4:3", 297.0, 29.97, 135.0, 3840, 2160, 4400, 2250, 0 },
+{ 106, "2160p50", "64:27", "4:3", 594.0, 50, 112.5, 3840, 2160, 5280, 2250, 50 },
+{ 107, "2160p", "64:27", "4:3", 594.0, 60, 135.0, 3840, 2160, 4400, 2250, 60 },
+{ 108, "720p48", "16:9", "1:1", 90.0, 47.96, 36.0, 1280, 720, 2500, 750, 0 },
+{ 109, "720p48", "64:27", "4:3", 90.0, 47.96, 36.0, 1280, 720, 2500, 750, 0 },
+{ 110, "720p2x48", "64:27", "64:63", 99.0, 47.96, 36.0, 1680, 720, 2750, 825, 0 },
+{ 111, "1080p48", "16:9", "1:1", 148.5, 47.96, 54.0, 1920, 1080, 2750, 1125, 0 },
+{ 112, "1080p48", "64:27", "4:3", 148.5, 47.96, 54.0, 1920, 1080, 2750, 1125, 0 },
+{ 113, "1080p2x48", "64:27", "1:1", 198.0, 47.96, 52.8, 2560, 1080, 3750, 1100, 0 },
+{ 114, "2160p48", "16:9", "1:1", 594.0, 47.96, 108.0, 3840, 2160, 5500, 2250, 0 },
+{ 115, "2160p48", "256:135", "1:1", 594.0, 47.96, 108.0, 4096, 2160, 5500, 2250, 0 },
+{ 116, "2160p48", "64:27", "4:3", 594.0, 47.96, 108.0, 3840, 2160, 5500, 2250, 0 },
+{ 117, "2160p100", "16:9", "1:1", 1188.0, 100, 225.0, 3840, 2160, 5280, 2250, 100 },
+{ 118, "2160p120", "16:9", "1:1", 1188.0, 119.88, 270.0, 3840, 2160, 4400, 2250, 120 },
+{ 119, "2160p100", "64:27", "4:3", 1188.0, 100, 225.0, 3840, 2160, 5280, 2250, 100 },
+{ 120, "2160p120", "64:27", "4:3", 1188.0, 119.88, 270.0, 3840, 2160, 4400, 2250, 120 },
+{ 121, "2160p2x24", "64:27", "1:1", 396.0, 23.98, 52.8, 5120, 2160, 7500, 2200, 0 },
+{ 122, "2160p2x25", "64:27", "1:1", 396.0, 25, 55.0, 5120, 2160, 7200, 2200, 0 },
+{ 123, "2160p2x30", "64:27", "1:1", 396.0, 29.97, 66.0, 5120, 2160, 6000, 2200, 0 },
+{ 124, "2160p2x48", "64:27", "1:1", 742.5, 47.96, 118.8, 5120, 2160, 6250, 2450, 0 },
+{ 125, "2160p2x50", "64:27", "1:1", 742.5, 50, 112.5, 5120, 2160, 6600, 2250, 50 },
+{ 126, "2160p2x", "64:27", "1:1", 742.5, 60, 135.0, 5120, 2160, 5500, 2250, 60 },
+{ 127, "2160p2x100", "64:27", "1:1", 1485.0, 100, 225.0, 5120, 2160, 6600, 2250, 100 },
+{ 193, "2160p2x120", "64:27", "1:1", 1485.0, 119.88, 270.0, 5120, 2160, 5500, 2250, 120 },
+{ 194, "4320p24", "16:9", "1:1", 1188.0, 23.98, 108.0, 7680, 4320, 11000, 4500, 0 },
+{ 195, "4320p25", "16:9", "1:1", 1188.0, 25, 110.0, 7680, 4320, 10800, 4400, 0 },
+{ 196, "4320p30", "16:9", "1:1", 1188.0, 29.97, 132.0, 7680, 4320, 9000, 4400, 0 },
+{ 197, "4320p48", "16:9", "1:1", 2376.0, 47.96, 216.0, 7680, 4320, 11000, 4500, 0 },
+{ 198, "4320p50", "16:9", "1:1", 2376.0, 50, 220.0, 7680, 4320, 10800, 4400, 50 },
+{ 199, "4320p", "16:9", "1:1", 2376.0, 60, 264.0, 7680, 4320, 9000, 4400, 60 },
+{ 200, "4320p100", "16:9", "1:1", 4752.0, 100, 450.0, 7680, 4320, 10560, 4500, 100 },
+{ 201, "4320p120", "16:9", "1:1", 4752.0, 119.88, 540.0, 7680, 4320, 8800, 4500, 120 },
+{ 202, "4320p24", "64:27", "4:3", 1188.0, 23.98, 108.0, 7680, 4320, 11000, 4500, 0 },
+{ 203, "4320p25", "64:27", "4:3", 1188.0, 25, 110.0, 7680, 4320, 10800, 4400, 0 },
+{ 204, "4320p30", "64:27", "4:3", 1188.0, 29.97, 132.0, 7680, 4320, 9000, 4400, 0 },
+{ 205, "4320p48", "64:27", "4:3", 2376.0, 47.96, 216.0, 7680, 4320, 11000, 4500, 0 },
+{ 206, "4320p50", "64:27", "4:3", 2376.0, 50, 220.0, 7680, 4320, 10800, 4400, 50 },
+{ 207, "4320p", "64:27", "4:3", 2376.0, 60, 264.0, 7680, 4320, 9000, 4400, 60 },
+{ 208, "4320p100", "64:27", "4:3", 4752.0, 100, 450.0, 7680, 4320, 10560, 4500, 100 },
+{ 209, "4320p120", "64:27", "4:3", 4752.0, 119.88, 540.0, 7680, 4320, 8800, 4500, 120 },
+{ 210, "4320p2x24", "64:27", "1:1", 1485.0, 23.98, 118.8, 10240, 4320, 12500, 4950, 0 },
+{ 211, "4320p2x25", "64:27", "1:1", 1485.0, 25, 110.0, 10240, 4320, 13500, 4400, 0 },
+{ 212, "4320p2x30", "64:27", "1:1", 1485.0, 29.97, 135.0, 10240, 4320, 11000, 4500, 0 },
+{ 213, "4320p2x48", "64:27", "1:1", 2970.0, 47.96, 237.6, 10240, 4320, 12500, 4950, 0 },
+{ 214, "4320p2x50", "64:27", "1:1", 2970.0, 50, 220.0, 10240, 4320, 13500, 4400, 50 },
+{ 215, "4320p2x", "64:27", "1:1", 2970.0, 60, 270.0, 10240, 4320, 11000, 4400, 60 },
+{ 216, "4320p2x100", "64:27", "1:1", 5940.0, 100, 450.0, 10240, 4320, 13200, 4500, 100 },
+{ 217, "4320p2x120", "64:27", "1:1", 5940.0, 119.88, 540.0, 10240, 4320, 11000, 4500, 120 },
+{ 218, "2160p100", "256:135", "1:1", 1188.0, 100, 225.0, 4096, 2160, 5280, 2250, 100 },
+{ 219, "2160p120", "256:135", "1:1", 1188.0, 119.88, 270.0, 4096, 2160, 4400, 2250, 120 },
+};
diff --git a/deps/sysobj_early/src/util_ids.c b/deps/sysobj_early/src/util_ids.c
new file mode 100644
index 00000000..197b1ed3
--- /dev/null
+++ b/deps/sysobj_early/src/util_ids.c
@@ -0,0 +1,316 @@
+/*
+ * sysobj - https://github.com/bp0/verbose-spork
+ * Copyright (C) 2018 Burt P. <pburt0@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "util_ids.h"
+#include <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#define ids_msg(msg, ...) fprintf (stderr, "[%s] " msg "\n", __FUNCTION__, ##__VA_ARGS__) /**/
+static int ids_tracing = 0;
+void ids_trace_start() { ids_tracing = 1; }
+void ids_trace_stop() { ids_tracing = 0; }
+
+ids_query *ids_query_new(const gchar *qpath) {
+ ids_query *s = g_new0(ids_query, 1);
+ s->qpath = qpath ? g_strdup(qpath) : NULL;
+ return s;
+}
+
+void ids_query_free(ids_query *s) {
+ if (s) g_free(s->qpath);
+ g_free(s);
+}
+
+void ids_query_result_cpy(ids_query_result *dest, ids_query_result *src) {
+ if (!dest || !src) return;
+ memcpy(dest, src, sizeof(ids_query_result));
+ for(int i = 0; dest->results[i]; i++)
+ dest->results[i] = dest->_strs + (src->results[i] - src->_strs);
+}
+
+/* c001 < C 01 */
+//TODO: compare more than the first char
+static int ids_cmp(const char *s1, const char *s2) {
+ int cmp = (s2 ? 1 : 0) - (s1 ? 1 : 0);
+ if (cmp == 0 && isalpha(*s1) && isalpha(*s2))
+ cmp = (islower(*s2) ? 1 : 0) - (islower(*s1) ? 1 : 0);
+ if (cmp == 0)
+ return g_strcmp0(s1, s2);
+ else
+ return cmp;
+}
+
+static void ids_query_result_set_str(ids_query_result *ret, int tabs, gchar *p) {
+ if (!p) {
+ ret->results[tabs] = p;
+ } else {
+ if (tabs == 0) {
+ ret->results[tabs] = ret->_strs;
+ strncpy(ret->results[tabs], p, IDS_LOOKUP_BUFF_SIZE-1);
+ } else {
+ ret->results[tabs] = ret->results[tabs-1] + strlen(ret->results[tabs-1]) + 1;
+ strncpy(ret->results[tabs], p, IDS_LOOKUP_BUFF_SIZE-1);
+ }
+ }
+ /* all following strings become invalid */
+ while(tabs < IDS_LOOKUP_MAX_DEPTH)
+ ret->results[++tabs] = NULL;
+}
+
+/* Given a qpath "/X/Y/Z", find names as:
+ * X <name> ->result[0]
+ * \tY <name> ->result[1]
+ * \t\tZ <name> ->result[2]
+ *
+ * Works with:
+ * - pci.ids "<vendor>/<device>/<subvendor> <subdevice>" or "C <class>/<subclass>/<prog-if>"
+ * - arm.ids "<implementer>/<part>"
+ * - sdio.ids "<vendor>/<device>", "C <class>"
+ * - usb.ids "<vendor>/<device>", "C <class>" etc
+ * - edid.ids "<3letter_vendor>"
+ */
+long scan_ids_file(const gchar *file, const gchar *qpath, ids_query_result *result, long start_offset) {
+ gchar **qparts = NULL;
+ gchar buff[IDS_LOOKUP_BUFF_SIZE] = "";
+ ids_query_result ret;// = {};
+ gchar *p = NULL;
+
+ FILE *fd;
+ int tabs;
+ int qdepth;
+ int qpartlen[IDS_LOOKUP_MAX_DEPTH];
+ long last_root_fpos = -1, fpos, line = -1;
+
+ memset(&ret,0,sizeof(ids_query_result));
+
+ if (!qpath)
+ return -1;
+
+ fd = fopen(file, "r");
+ if (!fd) {
+ ids_msg("file could not be read: %s", file);
+ return -1;
+ }
+
+ qparts = g_strsplit(qpath, "/", -1);
+ qdepth = g_strv_length(qparts);
+ if (qdepth > IDS_LOOKUP_MAX_DEPTH) {
+ ids_msg("qdepth (%d) > ids_max_depth (%d) for %s", qdepth, IDS_LOOKUP_MAX_DEPTH, qpath);
+ qdepth = IDS_LOOKUP_MAX_DEPTH;
+ }
+ for(int i = 0; i < qdepth; i++)
+ qpartlen[i] = strlen(qparts[i]);
+
+ if (start_offset > 0)
+ fseek(fd, start_offset, SEEK_SET);
+
+ for (fpos = ftell(fd); fgets(buff, IDS_LOOKUP_BUFF_SIZE, fd); fpos = ftell(fd)) {
+ p = strchr(buff, '\n');
+ if (!p)
+ ids_msg("line longer than IDS_LOOKUP_BUFF_SIZE (%d), file: %s, offset: %ld", IDS_LOOKUP_BUFF_SIZE, file, fpos);
+ line++;
+
+ /* line ends at comment */
+ p = strchr(buff, '#');
+ if (p) *p = 0;
+ /* trim trailing white space */
+ if (!p) p = buff + strlen(buff);
+ p--;
+ while(p > buff && isspace((unsigned char)*p)) p--;
+ *(p+1) = 0;
+ p = buff;
+
+ if (buff[0] == 0) continue; /* empty line */
+ if (buff[0] == '\n') continue; /* empty line */
+
+ /* scan for fields */
+ tabs = 0;
+ while(*p == '\t') { tabs++; p++; }
+
+ if (tabs >= qdepth) continue; /* too deep */
+ if (tabs != 0 && !ret.results[tabs-1])
+ continue; /* not looking at this depth, yet */
+ if (ret.results[tabs])
+ goto ids_lookup_done; /* answered at this level */
+
+ if (ids_tracing) ids_msg("[%s] looking at (%d) %s...", file, tabs, p);
+
+ if (g_str_has_prefix(p, qparts[tabs])
+ && isspace(*(p + qpartlen[tabs])) ) {
+ /* found */
+ p += qpartlen[tabs];
+ while(isspace((unsigned char)*p)) p++; /* ffwd */
+
+ if (tabs == 0) last_root_fpos = fpos;
+ ids_query_result_set_str(&ret, tabs, p);
+
+ if (ids_tracing) {
+ int i = 0;
+ for(; i < IDS_LOOKUP_MAX_DEPTH; i++) {
+ if (!qparts[i]) break;
+ ids_msg(" ...[%d]: %s\t--> %s", i, qparts[i], ret.results[i]);
+ }
+ }
+ continue;
+ }
+
+ if (ids_cmp(p, qparts[tabs]) == 1) {
+ if (ids_tracing)
+ ids_msg("will not be found qparts[tabs] = %s, p = %s", qparts[tabs], p);
+ goto ids_lookup_done; /* will not be found */
+ }
+
+ } /* for each line */
+
+ids_lookup_done:
+ if (ids_tracing)
+ ids_msg("bailed at line %ld...", line);
+ fclose(fd);
+
+ if (result) {
+ ids_query_result_cpy(result, &ret);
+ return last_root_fpos;
+ }
+ return last_root_fpos;
+}
+
+static gint _ids_query_list_cmp(const ids_query *ql1, const ids_query *ql2) {
+ return g_strcmp0(ql1->qpath, ql2->qpath);
+}
+
+long scan_ids_file_list(const gchar *file, ids_query_list query_list, long start_offset) {
+ GSList *tmp = g_slist_copy(query_list);
+ tmp = g_slist_sort(tmp, (GCompareFunc)_ids_query_list_cmp);
+
+ long offset = start_offset;
+ for (GSList *l = query_list; l; l = l->next) {
+ ids_query *q = l->data;
+ offset = scan_ids_file(file, q->qpath, &(q->result), offset);
+ if (offset == -1)
+ break;
+ }
+ g_slist_free(tmp);
+ return offset;
+}
+
+int query_list_count_found(ids_query_list query_list) {
+ long count = 0;
+ for (GSList *l = query_list; l; l = l->next) {
+ ids_query *q = l->data;
+ if (q->result.results[0]) count++;
+ }
+ return count;
+}
+
+static gchar *split_loc_default(const char *line) {
+ return g_utf8_strchr(line, -1, ' ');
+}
+
+GSList *ids_file_all_get_all(const gchar *file, split_loc_function split_loc_func) {
+ GSList *ret = NULL;
+ gchar buff[IDS_LOOKUP_BUFF_SIZE] = "";
+ gchar *p = NULL, *name = NULL;
+
+ FILE *fd;
+ int tabs = 0, tabs_last = 0;
+ long fpos, line = -1;
+
+ fd = fopen(file, "r");
+ if (!fd) {
+ ids_msg("file could not be read: %s", file);
+ return ret;
+ }
+
+ ids_query_result *working = g_new0(ids_query_result, 1);
+ gchar **qparts = g_new0(gchar*, IDS_LOOKUP_MAX_DEPTH + 1);
+ for(tabs = IDS_LOOKUP_MAX_DEPTH-1; tabs>=0; tabs--)
+ qparts[tabs] = g_malloc0(IDS_LOOKUP_BUFF_SIZE);
+ tabs = 0;
+
+ if (!split_loc_func) split_loc_func = split_loc_default;
+
+ for (fpos = ftell(fd); fgets(buff, IDS_LOOKUP_BUFF_SIZE, fd); fpos = ftell(fd)) {
+ p = strchr(buff, '\n');
+ if (!p)
+ ids_msg("line longer than IDS_LOOKUP_BUFF_SIZE (%d), file: %s, offset: %ld", IDS_LOOKUP_BUFF_SIZE, file, fpos);
+ line++;
+
+ /* line ends at comment */
+ p = strchr(buff, '#');
+ if (p) *p = 0;
+ /* trim trailing white space */
+ if (!p) p = buff + strlen(buff);
+ p--;
+ while(p > buff && isspace((unsigned char)*p)) p--;
+ *(p+1) = 0;
+ p = buff;
+
+ if (buff[0] == 0) continue; /* empty line */
+ if (buff[0] == '\n') continue; /* empty line */
+
+ /* scan for fields */
+ tabs = 0;
+ while(*p == '\t') { tabs++; p++; }
+
+ if (tabs >= IDS_LOOKUP_MAX_DEPTH) continue; /* too deep */
+ if (tabs > tabs_last + 1) {
+ /* jump too big, there's a qpath part that is "" */
+ ids_msg("jump too big from depth %d to %d, file: %s, offset: %ld", tabs_last, tabs, file, fpos);
+ continue;
+ }
+
+ name = split_loc_func(p);
+ if (!name) {
+ ids_msg("expected name/value split not found, file: %s, offset: %ld", file, fpos);
+ continue;
+ }
+ *name = 0; name++; /* split ptr is the first char of split string */
+ g_strstrip(p);
+ g_strstrip(name);
+
+ // now p = id, name = name
+ // ids_msg("p: %s -- name: %s", p, name);
+
+ strncpy(qparts[tabs], p, IDS_LOOKUP_BUFF_SIZE-1);
+ ids_query_result_set_str(working, tabs, name);
+ if (tabs < tabs_last)
+ for(;tabs_last > tabs; tabs_last--) {
+ qparts[tabs_last][0] = 0;
+ working->results[tabs_last] = NULL;
+ }
+
+ ids_query *found = ids_query_new(NULL);
+ ids_query_result_cpy(&found->result, working);
+ found->qpath = g_strjoinv("/", qparts);
+ p = found->qpath + strlen(found->qpath) - 1;
+ while(*p == '/') { *p = 0; p--; }
+ ret = g_slist_append(ret, found);
+
+ tabs_last = tabs;
+ } /* for each line */
+
+ fclose(fd);
+ g_strfreev(qparts);
+ ids_query_result_free(working);
+
+ return ret;
+}
diff --git a/deps/sysobj_early/src/util_sysobj.c b/deps/sysobj_early/src/util_sysobj.c
new file mode 100644
index 00000000..94f71944
--- /dev/null
+++ b/deps/sysobj_early/src/util_sysobj.c
@@ -0,0 +1,303 @@
+/*
+ * sysobj - https://github.com/bp0/verbose-spork
+ * Copyright (C) 2018 Burt P. <pburt0@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h> /* for realpath() */
+#include <unistd.h> /* for getuid() */
+#include <ctype.h> /* for isxdigit(), etc. */
+
+#include "util_sysobj.h"
+
+gchar *util_build_fn(const gchar *base, const gchar *name) {
+ gchar *ret = NULL;
+ gboolean slash = TRUE;
+
+ if (!base) return NULL;
+ if (base[strlen(base)-1] == '/')
+ slash = FALSE;
+
+ if (name) {
+ if (*name == '/') slash = FALSE;
+ ret = g_strdup_printf("%s%s%s", base, slash ? "/" : "", name);
+ } else
+ ret = g_strdup(base);
+
+ util_null_trailing_slash(ret);
+ return ret;
+}
+
+gboolean util_have_root() {
+ return (getuid() == 0) ? TRUE : FALSE;
+}
+
+void util_compress_space(gchar *str) {
+ gchar *p = str, *t = str;
+ if (str && *str) {
+ int n = 0;
+ while(*p) {
+ if (isspace(*p) ) {
+ if (!n)
+ *t++ = ' ';
+ n++;
+ } else {
+ n = 0;
+ if (t != p)
+ *t = *p;
+ t++;
+ }
+ p++;
+ }
+ }
+ if (t != p)
+ *t = 0;
+}
+
+void util_null_trailing_slash(gchar *str) {
+ if (str && *str) {
+ if (str[strlen(str)-1] == '/' )
+ str[strlen(str)-1] = 0;
+ }
+}
+
+gsize util_count_lines(const gchar *str) {
+ gchar **lines = NULL;
+ gsize count = 0;
+
+ if (str) {
+ lines = g_strsplit(str, "\n", 0);
+ count = g_strv_length(lines);
+ if (count && *lines[count-1] == 0) {
+ /* if the last line is empty, don't count it */
+ count--;
+ }
+ g_strfreev(lines);
+ }
+
+ return count;
+}
+
+int util_get_did(gchar *str, const gchar *lbl) {
+ int id = -2;
+ gchar tmpfmt[128] = "";
+ gchar tmpchk[128] = "";
+ sprintf(tmpfmt, "%s%s", lbl, "%d");
+ if ( sscanf(str, tmpfmt, &id)==1 ) {
+ sprintf(tmpchk, tmpfmt, id);
+ if ( SEQ(str, tmpchk) )
+ return id;
+ }
+ return -1;
+}
+
+gchar *util_escape_markup(gchar *v, gboolean replacing) {
+ gchar *clean, *tmp;
+ gchar **vl;
+ if (v == NULL) return NULL;
+
+ vl = g_strsplit(v, "&", -1);
+ if (g_strv_length(vl) > 1)
+ clean = g_strjoinv("&amp;", vl);
+ else
+ clean = g_strdup(v);
+ g_strfreev(vl);
+
+ vl = g_strsplit(clean, "<", -1);
+ if (g_strv_length(vl) > 1) {
+ tmp = g_strjoinv("&lt;", vl);
+ g_free(clean);
+ clean = tmp;
+ }
+ g_strfreev(vl);
+
+ vl = g_strsplit(clean, ">", -1);
+ if (g_strv_length(vl) > 1) {
+ tmp = g_strjoinv("&gt;", vl);
+ g_free(clean);
+ clean = tmp;
+ }
+ g_strfreev(vl);
+
+ if (replacing)
+ g_free((gpointer)v);
+ return clean;
+}
+
+void util_strstrip_double_quotes_dumb(gchar *str) {
+ if (!str) return;
+ g_strstrip(str);
+ gchar *front = str, *back = str + strlen(str) - 1;
+ if (back <= front) return;
+ while(*front == '"') { *front = 'X'; front++; }
+ while(*back == '"') { *back = 0; back--; }
+ int nl = strlen(front);
+ memmove(str, front, nl);
+ str[nl] = 0;
+}
+
+/* "194.110 MHz" -> "194.11 MHz"
+ * "5,0 golden rings" -> "5 golden rings" */
+gchar *util_strchomp_float(gchar* str_float) {
+ if (!str_float) return NULL;
+ char *dot = strchr(str_float, '.');
+ char *comma = strchr(str_float, ',');
+ char *p = NULL, *dec = NULL, *src = NULL, *target = NULL;
+ if (!dot && !comma) return str_float;
+ if (dot > comma)
+ dec = dot;
+ else
+ dec = comma;
+ p = dec + 1;
+ while(isdigit(*p)) p++;
+ target = src = p;
+ p--;
+ while(*p == '0') { target = p; p--; };
+ if (target == dec + 1)
+ target = dec;
+ if (target != src)
+ memmove(target, src, strlen(src)+1);
+ return str_float;
+}
+
+/* resolve . and .., but not symlinks */
+gchar *util_normalize_path(const gchar *path, const gchar *relto) {
+ gchar *resolved = NULL;
+#if GLIB_CHECK_VERSION(2, 58, 0)
+ resolved = g_canonicalize_filename(path, relto);
+#else
+ /* burt's hack version */
+ gchar *frt = relto ? g_strdup(relto) : NULL;
+ util_null_trailing_slash(frt);
+ gchar *fpath = frt
+ ? g_strdup_printf("%s%s%s", frt, (*path == '/') ? "" : "/", path)
+ : g_strdup(path);
+ g_free(frt);
+
+ /* note: **parts will own all the part strings throughout */
+ gchar **parts = g_strsplit(fpath, "/", -1);
+ gsize i, pn = g_strv_length(parts);
+ GList *lparts = NULL, *l = NULL, *n = NULL, *p = NULL;
+ for (i = 0; i < pn; i++)
+ lparts = g_list_append(lparts, parts[i]);
+
+ i = 0;
+ gchar *part = NULL;
+ l = lparts;
+ while(l) {
+ n = l->next; p = l->prev;
+ part = l->data;
+
+ if (SEQ(part, ".") )
+ lparts = g_list_delete_link(lparts, l);
+
+ if (SEQ(part, "..") ) {
+ if (p)
+ lparts = g_list_delete_link(lparts, p);
+ lparts = g_list_delete_link(lparts, l);
+ }
+
+ l = n;
+ }
+
+ resolved = g_strdup("");
+ l = lparts;
+ while(l) {
+ resolved = g_strdup_printf("%s%s/", resolved, (gchar*)l->data );
+ l = l->next;
+ }
+ g_list_free(lparts);
+ util_null_trailing_slash(resolved);
+ g_free(fpath);
+
+ g_strfreev(parts);
+#endif
+
+ return resolved;
+}
+
+/* resolve . and .. and symlinks */
+gchar *util_canonicalize_path(const gchar *path) {
+ char *resolved = realpath(path, NULL);
+ gchar *ret = g_strdup(resolved); /* free with g_free() instead of free() */
+ free(resolved);
+ return ret;
+}
+
+int util_maybe_num(gchar *str) {
+ int r = 10, i = 0, l = (str) ? strlen(str) : 0;
+ if (!l || l > 32) return 0;
+ gchar *chk = g_strdup(str);
+ g_strstrip(chk);
+ l = strlen(chk);
+ if (l > 2 && !strncmp(chk, "0x", 2)) {
+ i = 2; r = 16;
+ }
+ for (; i < l; i++) {
+ if (isxdigit(chk[i])) {
+ if (!isdigit(chk[i]))
+ r = 16;
+ } else {
+ r = 0;
+ break;
+ }
+ }
+ g_free(chk);
+ return r;
+}
+
+gchar *util_safe_name(const gchar *name, gboolean lower_case) {
+ if (!name) return NULL;
+ const gchar *p = name;
+ gchar *buff = g_new0(gchar, strlen(name) + 1);
+ gchar *t = buff;
+ while(*p) {
+ gboolean ok = g_ascii_isalnum(*p);
+ if (*p == '.' || *p == '-') ok = TRUE;
+ if (*p == '/') ok = FALSE;
+ *t = ok ? *p : '_';
+ t++;
+ p = g_utf8_next_char(p);
+ }
+ gchar *ret = lower_case ? g_ascii_strdown(buff, -1) : g_strdup(buff);
+ g_free(buff);
+ return ret;
+}
+
+gchar *util_find_line_value(gchar *data, gchar *key, gchar delim) {
+ gchar *ret = NULL;
+ gchar **lines = g_strsplit(data, "\n", -1);
+ gsize line_count = g_strv_length(lines);
+ gsize i = 0;
+ for (i = 0; i < line_count; i++) {
+ gchar *line = lines[i];
+ gchar *value = g_utf8_strchr(line, -1, delim);
+ if (!value) continue;
+ *value = 0;
+ value = g_strstrip(value+1);
+ gchar *lkey = g_strstrip(line);
+
+ if (SEQ(lkey, key) ) {
+ ret = g_strdup(value);
+ }
+ }
+ g_strfreev(lines);
+ return ret;
+}