diff options
Diffstat (limited to 'modules/devices/monitors.c')
-rw-r--r-- | modules/devices/monitors.c | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/modules/devices/monitors.c b/modules/devices/monitors.c new file mode 100644 index 00000000..02fb1d67 --- /dev/null +++ b/modules/devices/monitors.c @@ -0,0 +1,515 @@ +/* + * HardInfo - Displays System Information + * Copyright (C) 2003-2019 L. A. F. Pereira <l@tia.mat.br> + * Copyright (C) 2019 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, version 2 or later. + * + * 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; +gchar *ieee_oui_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(), "hardinfo2", "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); +} + +void find_ieee_oui_ids_file() { + if (ieee_oui_ids_file) return; + char *file_search_order[] = { + g_build_filename(g_get_user_config_dir(), "hardinfo2", "ieee_oui.ids", NULL), + g_build_filename(params.path_data, "ieee_oui.ids", NULL), + NULL + }; + int n; + for(n = 0; file_search_order[n]; n++) { + if (!ieee_oui_ids_file && !access(file_search_order[n], R_OK)) + ieee_oui_ids_file = file_search_order[n]; + else + g_free(file_search_order[n]); + } + auto_free(ieee_oui_ids_file); +} + +typedef struct { + gchar *drm_path; + gchar *drm_connection; + gchar *drm_status; + gchar *drm_enabled; + 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; + if (!sysfs_edid_file || !*sysfs_edid_file) return NULL; + monitor *m = monitor_new(); + m->drm_path = g_path_get_dirname(sysfs_edid_file); + m->drm_connection = g_path_get_basename(m->drm_path); + gchar *drm_enabled_file = g_strdup_printf("%s/%s", m->drm_path, "enabled"); + gchar *drm_status_file = g_strdup_printf("%s/%s", m->drm_path, "status"); + g_file_get_contents(drm_enabled_file, &m->drm_enabled, NULL, NULL); + if (m->drm_enabled) g_strstrip(m->drm_enabled); + g_file_get_contents(drm_status_file, &m->drm_status, NULL, NULL); + if (m->drm_status) g_strstrip(m->drm_status); + g_file_get_contents(sysfs_edid_file, &edid_bin, &edid_len, NULL); + if (edid_len) + m->e = edid_new(edid_bin, edid_len); + g_free(drm_enabled_file); + g_free(drm_status_file); + 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); + } +} + +gchar *monitor_vendor_str(monitor *m, gboolean include_value, gboolean link_name) { + if (!m || !m->e) return NULL; + edid_ven ven = m->e->ven; + gchar v[20] = "", t[4] = ""; + ids_query_result result;// = {}; + + memset(&result,0,sizeof(ids_query_result)); + if (ven.type == VEN_TYPE_PNP) { + strcpy(v, ven.pnp); + strcpy(t, "PNP"); + } else if (ven.type == VEN_TYPE_OUI) { + strcpy(v, ven.oui_str); + strcpy(t, "OUI"); + } + + if (!m->_vstr) { + if (ven.type == VEN_TYPE_PNP) { + if (!edid_ids_file) + find_edid_ids_file(); + scan_ids_file(edid_ids_file, v, &result, -1); + if (result.results[0]) + m->_vstr = g_strdup(result.results[0]); + } else if (ven.type == VEN_TYPE_OUI) { + if (!ieee_oui_ids_file) + find_ieee_oui_ids_file(); + scan_ids_file(ieee_oui_ids_file, v, &result, -1); + if (result.results[0]) + m->_vstr = g_strdup(result.results[0]); + } + } + + gchar *ret = NULL; + if (include_value) + ret = g_strdup_printf("[%s:%s]", t, v); + if (m->_vstr) { + if (link_name) { + gchar *lv = vendor_get_link(m->_vstr); + ret = appfsp(ret, "%s", lv); + g_free(lv); + } else + ret = appfsp(ret, "%s", m->_vstr); + } else if (!include_value && ven.type == VEN_TYPE_PNP) { + ret = appfsp(ret, "%s", ven.pnp); + } else + ret = appfsp(ret, "%s", _("(Unknown)")); + return ret; +} + +gchar *monitor_name(monitor *m, gboolean include_vendor) { + if (!m) return NULL; + gchar *desc = NULL; + edid *e = m->e; + if (!e) + return g_strdup(_("(Unknown)")); + + if (include_vendor) { + if (e->ven.type != VEN_TYPE_INVALID) { + gchar *vstr = monitor_vendor_str(m, FALSE, FALSE); + gchar *vtag = vendor_match_tag(vstr, params.fmt_opts); + desc = appfsp(desc, "%s", vtag ? vtag : vstr); + g_free(vstr); + g_free(vtag); + } 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) { + gchar *vstr = monitor_vendor_str(m, TRUE, FALSE); + + gchar *dom = NULL; + if (!e->dom.is_model_year && e->dom.week && e->dom.year) + dom = g_strdup_printf(_("Week %d of %d"), e->dom.week, e->dom.year); + else if (e->dom.year) + dom = g_strdup_printf("%d", e->dom.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"); + + gchar *iface; + if (e->interface && e->di.exists) { + gchar *tmp = g_strdup_printf("[%x] %s\n[DI-EXT:%x] %s", + e->interface, _(edid_interface(e->interface)), + e->di.interface, _(edid_di_interface(e->di.interface)) ); + iface = gg_key_file_parse_string_as_value(tmp, '|'); + g_free(tmp); + } else if (e->di.exists) { + iface = g_strdup_printf("[DI-EXT:%x] %s", + e->di.interface, _(edid_di_interface(e->di.interface)) ); + } else { + iface = g_strdup_printf("[%x] %s", + e->interface, + e->interface ? _(edid_interface(e->interface)) : _("(Unspecified)") ); + } + + gchar *d_list, *ext_list, *dtd_list, *cea_list, + *etb_list, *std_list, *svd_list, *sad_list, + *didt_list, *did_string_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++) { + char *desc = edid_base_descriptor_describe(&e->d[i]); + d_list = appfnl(d_list, "descriptor%d=%s", i, desc); + g_free(desc); + } + 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)")); + + svd_list = NULL; + for(i = 0; i < e->svd_count; i++) { + char *desc = edid_output_describe(&e->svds[i].out); + svd_list = appfnl(svd_list, "svd%d=%s", i, desc); + g_free(desc); + } + if (!svd_list) svd_list = g_strdup_printf("%s=\n", _("(Empty List)")); + + sad_list = NULL; + for(i = 0; i < e->sad_count; i++) { + char *desc = edid_cea_audio_describe(&e->sads[i]); + sad_list = appfnl(sad_list, "sad%d=%s", i, desc); + g_free(desc); + } + if (!sad_list) sad_list = g_strdup_printf("%s=\n", _("(Empty List)")); + + didt_list = NULL; + for(i = 0; i < e->didt_count; i++) { + char *desc = edid_output_describe(&e->didts[i]); + didt_list = appfnl(didt_list, "didt%d=%s", i, desc); + g_free(desc); + } + if (!didt_list) didt_list = g_strdup_printf("%s=\n", _("(Empty List)")); + + did_string_list = NULL; + for(i = 0; i < e->did_string_count; i++) { + did_string_list = appfnl(did_string_list, "did_string%d=%s", i, e->did_strings[i].str); + } + if (!did_string_list) did_string_list = g_strdup_printf("%s=\n", _("(Empty List)")); + + gchar *speakers = NULL; + if (e->speaker_alloc_bits) { + gchar *spk_tmp = edid_cea_speaker_allocation_describe(e->speaker_alloc_bits, 0); + speakers = gg_key_file_parse_string_as_value(spk_tmp, '|'); + g_free(spk_tmp); + } else + speakers = g_strdup(_("(Unspecified)")); + + 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("<tt>%s</tt>", 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=%s\n" /* interface */ + "%s=%s\n" /* bpcc */ + "%s=%s\n" /* speakers */ + "[%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\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"), iface, + _("Bits per Color Channel"), UNSPECIFNULL2(bpcc), + _("Speaker Allocation"), speakers, + _("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, + _("EIA/CEA-861 Short Audio Descriptors"), sad_list, + _("EIA/CEA-861 Short Video Descriptors"), svd_list, + _("DisplayID Timings"), didt_list, + _("DisplayID Strings"), did_string_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(sad_list); + g_free(svd_list); + g_free(didt_list); + g_free(did_string_list); + g_free(iface); + g_free(vstr); + 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/edid2/ -name edid.*"); + int i, found = 0; + for(i = 0; edid_files[i]; i++) { + monitor *m = monitor_new_from_sysfs(edid_files[i]); + //if (m && m->e->std < STD_DISPLAYID) continue; + //if (m && !m->e->interface) continue; + //if (m && m->e->interface != 1) continue; + if (m && !SEQ(m->drm_status, "disconnected")) { + 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 = NULL; + edid *e = m->e; + if (e && e->checksum_ok) + edid_section = make_edid_section(m); + + gchar *details = g_strdup_printf("[%s]\n" + "%s=%s\n" + "%s=%s %s\n" + "%s\n", + _("Connection"), + _("DRM"), m->drm_connection, + _("Status"), m->drm_status, m->drm_enabled, + edid_section ? edid_section : "" + ); + moreinfo_add_with_prefix(tag_prefix, tag, details); /* moreinfo now owns *details */ + ret = h_strdup_cprintf("$!%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); + found++; + } + monitor_free(m); + } + g_strfreev(edid_files); + + 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; +} |