/* * HardInfo - Displays System Information * Copyright (C) 2003-2019 Leandro A. F. Pereira * Copyright (C) 2019 Burt P. * * 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, version 2. * * 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "devices.h" #include "util_sysobj.h" #include "util_edid.h" #include "util_ids.h" static const char monitor_icon[] = "monitor.png"; #define UNKIFNULL2(f) ((f) ? f : _("(Unknown)")) #define UNKIFEMPTY2(f) ((*f) ? f : _("(Unknown)")) #define UNSPECIFNULL2(f) ((f) ? f : _("(Unspecified)")) gboolean no_monitors = FALSE; gchar *edid_ids_file = NULL; void find_edid_ids_file() { if (edid_ids_file) return; char *file_search_order[] = { g_build_filename(g_get_user_config_dir(), "hardinfo", "edid.ids", NULL), g_build_filename(params.path_data, "edid.ids", NULL), NULL }; int n; for(n = 0; file_search_order[n]; n++) { if (!edid_ids_file && !access(file_search_order[n], R_OK)) edid_ids_file = file_search_order[n]; else g_free(file_search_order[n]); } auto_free(edid_ids_file); } typedef struct { gchar *drm_connection; edid *e; gchar *_vstr; /* use monitor_vendor_str() */ } monitor; #define monitor_new() g_new0(monitor, 1) monitor *monitor_new_from_sysfs(const gchar *sysfs_edid_file) { gchar *edid_bin = NULL; gsize edid_len = 0; monitor *m = monitor_new(); g_file_get_contents(sysfs_edid_file, &edid_bin, &edid_len, NULL); if (edid_len) { m->e = edid_new(edid_bin, edid_len); } gchar *pd = g_path_get_dirname(sysfs_edid_file); m->drm_connection = g_path_get_basename(pd); g_free(pd); return m; } void monitor_free(monitor *m) { if (m) { g_free(m->_vstr); g_free(m->drm_connection); edid_free(m->e); g_free(m); } } const gchar *monitor_vendor_str(monitor *m) { if (m->_vstr) return m->_vstr; ids_query_result result = {}; if (!edid_ids_file) find_edid_ids_file(); scan_ids_file(edid_ids_file, m->e->ven, &result, -1); if (result.results[0]) { m->_vstr = g_strdup(result.results[0]); return m->_vstr; } return g_strdup(m->e->ven); } gchar *monitor_name(monitor *m, gboolean include_vendor) { if (!m) return NULL; gchar *desc = NULL; edid *e = m->e; if (include_vendor) { if (*e->ven) desc = appfsp(desc, "%s", vendor_get_shortest_name(monitor_vendor_str(m))); else desc = appfsp(desc, "%s", "Unknown"); } if (e->img_max.diag_in) desc = appfsp(desc, "%s\"", e->img_max.class_inch); if (e->name) desc = appfsp(desc, "%s", e->name); else desc = appfsp(desc, "%s %s", e->a_or_d ? "Digital" : "Analog", "Display"); return desc; } gchar **get_output_lines(const char *cmd_line) { gboolean spawned; gchar *out, *err; gchar **ret = NULL; spawned = g_spawn_command_line_sync(cmd_line, &out, &err, NULL, NULL); if (spawned) { ret = g_strsplit(out, "\n", -1); g_free(out); g_free(err); } return ret; } static gchar *tag_make_safe_inplace(gchar *tag) { if (!tag) return tag; if (!g_utf8_validate(tag, -1, NULL)) return tag; //TODO: reconsider gchar *p = tag, *pd = tag; while(*p) { gchar *np = g_utf8_next_char(p); gunichar c = g_utf8_get_char_validated(p, -1); int l = g_unichar_to_utf8(c, NULL); if (l == 1 && g_unichar_isalnum(c)) { g_unichar_to_utf8(c, pd); } else { *pd = '_'; } p = np; pd++; } return tag; } static gchar *make_edid_section(monitor *m) { int i; edid *e = m->e; if (e->len) { const gchar *vstr = monitor_vendor_str(m); gchar *dom = NULL; if (e->week && e->year) dom = g_strdup_printf(_("Week %d of %d"), e->week, e->year); else if (e->year) dom = g_strdup_printf("%d", e->year); gchar *bpcc = NULL; if (e->bpc) bpcc = g_strdup_printf("%d", e->bpc); int aok = e->checksum_ok; if (e->ext_blocks_fail) aok = 0; gchar *csum = aok ? _("Ok") : _("Fail"); const gchar *iface = e->interface ? _(edid_interface(e->interface)) : _("(Unspecified)"); gchar *d_list, *ext_list, *dtd_list, *cea_list, *etb_list, *std_list; etb_list = NULL; for(i = 0; i < e->etb_count; i++) { char *desc = edid_output_describe(&e->etbs[i]); etb_list = appfnl(etb_list, "etb%d=%s", i, desc); g_free(desc); } if (!etb_list) etb_list = g_strdup_printf("%s=\n", _("(Empty List)")); std_list = NULL; for(i = 0; i < e->std_count; i++) { char *desc = edid_output_describe(&e->stds[i].out); std_list = appfnl(std_list, "std%d=%s", i, desc); g_free(desc); } if (!std_list) std_list = g_strdup_printf("%s=\n", _("(Empty List)")); d_list = NULL; for(i = 0; i < 4; i++) d_list = appfnl(d_list, "descriptor%d = ([%02x] %s): %s", i, e->d_type[i], _(edid_descriptor_type(e->d_type[i])), *e->d_text[i] ? e->d_text[i] : "{...}"); if (!d_list) d_list = g_strdup_printf("%s=\n", _("(Empty List)")); ext_list = NULL; for(i = 0; i < e->ext_blocks; i++) { int type = e->u8[(i+1)*128]; int version = e->u8[(i+1)*128 + 1]; ext_list = appfnl(ext_list, "ext%d = ([%02x:v%02x] %s) %s", i, type, version, _(edid_ext_block_type(type)), e->ext_ok[i] ? "ok" : "fail" ); } if (!ext_list) ext_list = g_strdup_printf("%s=\n", _("(Empty List)")); dtd_list = NULL; for(i = 0; i < e->dtd_count; i++) { char *desc = edid_dtd_describe(&e->dtds[i], 0); dtd_list = appfnl(dtd_list, "dtd%d = %s", i, desc); free(desc); } if (!dtd_list) dtd_list = g_strdup_printf("%s=\n", _("(Empty List)")); cea_list = NULL; for(i = 0; i < e->cea_block_count; i++) { char *desc = edid_cea_block_describe(&e->cea_blocks[i]); cea_list = appfnl(cea_list, "cea_block%d = %s", i, desc); } if (!cea_list) cea_list = g_strdup_printf("%s=\n", _("(Empty List)")); gchar *hex = edid_dump_hex(e, 0, 1); gchar *hex_esc = gg_key_file_parse_string_as_value(hex, '|'); g_free(hex); if (params.markup_ok) hex = g_strdup_printf("%s", hex_esc); else hex = g_strdup(hex_esc); g_free(hex_esc); gchar *ret = g_strdup_printf( /* extending "Connection" section */ "%s=%s\n" /* sig type */ "%s=[%x] %s\n" /* interface */ "%s=%s\n" /* bpcc */ "[%s]\n" "%s=%s\n" /* base out */ "%s=%s\n" /* ext out */ "[%s]\n" "%s=%s\n" /* vendor */ "%s=%s\n" /* name */ "%s=[%04x-%08x] %u-%u\n" /* model, n_serial */ "%s=%s\n" /* serial */ "%s=%s\n" /* dom */ "[%s]\n" "%s=%d %s\n" /* size */ "%s=%d.%d\n" /* version */ "%s=%d\n" /* ext block */ "%s=%s\n" /* ext to */ "%s=%s %s\n" /* checksum */ "[%s]\n%s\n" "[%s]\n%s\n" "[%s]\n%s\n" "[%s]\n%s\n" "[%s]\n%s\n" "[%s]\n%s\n" "[%s]\n%s=%s\n" , _("Signal Type"), e->a_or_d ? _("Digital") : _("Analog"), _("Interface"), e->interface, iface, _("Bits per Color Channel"), UNSPECIFNULL2(bpcc), _("Output (Max)"), edid_output_src(e->img.src), edid_output_describe(&e->img), edid_output_src(e->img_max.src), edid_output_describe(&e->img_max), _("EDID Device"), _("Vendor"), vstr, _("Name"), e->name, _("Model"), e->product, e->n_serial, e->product, e->n_serial, _("Serial"), UNKIFNULL2(e->serial), _("Manufacture Date"), UNKIFNULL2(dom), _("EDID Meta"), _("Data Size"), e->len, _("bytes"), _("Version"), (int)e->ver_major, (int)e->ver_minor, _("Extension Blocks"), e->ext_blocks, _("Extended to"), e->std ? _(edid_standard(e->std)) : _("(None)"), _("Checksum"), csum, aok ? "" : problem_marker(), _("EDID Descriptors"), d_list, _("Detailed Timing Descriptors (DTD)"), dtd_list, _("Established Timings Bitmap (ETB)"), etb_list, _("Standard Timings (STD)"), std_list, _("E-EDID Extension Blocks"), ext_list, _("EIA/CEA-861 Data Blocks"), cea_list, _("Hex Dump"), _("Data"), hex ); g_free(bpcc); g_free(dom); g_free(d_list); g_free(ext_list); g_free(etb_list); g_free(std_list); g_free(dtd_list); g_free(cea_list); g_free(hex); //printf("ret: %s\n", ret); return ret; } else return g_strdup(""); } gchar *monitors_get_info() { gchar *icons = g_strdup(""); gchar *ret = g_strdup_printf("[%s]\n", _("Monitors")); gchar tag_prefix[] = "DEV"; gchar **edid_files = get_output_lines("find /sys/devices -name edid"); //gchar **edid_files = get_output_lines("find /home/pburt/github/verbose-spork/junk/testing/.testing/edid/ -name edid.*"); int i, found = 0; for(i = 0; edid_files[i]; i++) { monitor *m = monitor_new_from_sysfs(edid_files[i]); if (m) { edid *e = m->e; found++; if (e && e->checksum_ok && m->drm_connection) { gchar *tag = g_strdup_printf("%d-%s", found, m->drm_connection); tag_make_safe_inplace(tag); gchar *desc = monitor_name(m, TRUE); gchar *edid_section = make_edid_section(m); gchar *details = g_strdup_printf("[%s]\n" "%s=%s\n" "%s\n", _("Connection"), _("DRM"), m->drm_connection, edid_section ); moreinfo_add_with_prefix(tag_prefix, tag, details); /* moreinfo now owns *details */ ret = h_strdup_cprintf("$!%s$%s=%s|%s\n", ret, tag, m->drm_connection, desc ); icons = h_strdup_cprintf("Icon$%s$=%s\n", icons, tag, monitor_icon); g_free(desc); g_free(edid_section); } monitor_free(m); } } no_monitors = FALSE; if(!found) { no_monitors = TRUE; g_free(ret); ret = g_strdup_printf("[%s]\n%s=%s\n" "[$ShellParam$]\nViewType=0\n", _("Monitors"), _("Result"), _("(Empty)") ); } else { ret = h_strdup_cprintf( "[$ShellParam$]\nViewType=1\n" "ColumnTitle$TextValue=%s\n" /* DRM connection */ "ColumnTitle$Value=%s\n" /* Name */ "ShowColumnHeaders=true\n" "%s", ret, _("Connection"), _("Name"), icons ); } return ret; } gboolean monitors_hinote(const char **msg) { PARAM_NOT_UNUSED(msg); return FALSE; }