aboutsummaryrefslogtreecommitdiff
path: root/deps
diff options
context:
space:
mode:
Diffstat (limited to 'deps')
-rw-r--r--deps/sysobj_early/data/sort_vendors.pl43
-rw-r--r--deps/sysobj_early/gui/uri_handler.c53
-rw-r--r--deps/sysobj_early/gui/uri_handler.h32
-rw-r--r--deps/sysobj_early/include/appf.h40
-rw-r--r--deps/sysobj_early/include/auto_free.h67
-rw-r--r--deps/sysobj_early/include/cpubits.h39
-rw-r--r--deps/sysobj_early/include/format_early.h47
-rw-r--r--deps/sysobj_early/include/gg_slist.h30
-rw-r--r--deps/sysobj_early/include/nice_name.h32
-rw-r--r--deps/sysobj_early/include/strstr_word.h35
-rw-r--r--deps/sysobj_early/include/util_edid.h268
-rw-r--r--deps/sysobj_early/include/util_ids.h76
-rw-r--r--deps/sysobj_early/include/util_sysobj.h53
-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
-rw-r--r--deps/uber-graph/README39
-rw-r--r--deps/uber-graph/g-ring.c227
-rw-r--r--deps/uber-graph/g-ring.h84
-rw-r--r--deps/uber-graph/uber-frame-source.c177
-rw-r--r--deps/uber-graph/uber-frame-source.h43
-rw-r--r--deps/uber-graph/uber-graph.c2166
-rw-r--r--deps/uber-graph/uber-graph.h104
-rw-r--r--deps/uber-graph/uber-heat-map.c347
-rw-r--r--deps/uber-graph/uber-heat-map.h66
-rw-r--r--deps/uber-graph/uber-label.c395
-rw-r--r--deps/uber-graph/uber-label.h60
-rw-r--r--deps/uber-graph/uber-line-graph.c1028
-rw-r--r--deps/uber-graph/uber-line-graph.h100
-rw-r--r--deps/uber-graph/uber-range.c64
-rw-r--r--deps/uber-graph/uber-range.h50
-rw-r--r--deps/uber-graph/uber-scale.c54
-rw-r--r--deps/uber-graph/uber-scale.h38
-rw-r--r--deps/uber-graph/uber-scatter.c421
-rw-r--r--deps/uber-graph/uber-scatter.h66
-rw-r--r--deps/uber-graph/uber-timeout-interval.c140
-rw-r--r--deps/uber-graph/uber-timeout-interval.h56
-rw-r--r--deps/uber-graph/uber-window.c330
-rw-r--r--deps/uber-graph/uber-window.h65
-rw-r--r--deps/uber-graph/uber.h30
48 files changed, 10124 insertions, 0 deletions
diff --git a/deps/sysobj_early/data/sort_vendors.pl b/deps/sysobj_early/data/sort_vendors.pl
new file mode 100644
index 00000000..6ed69e58
--- /dev/null
+++ b/deps/sysobj_early/data/sort_vendors.pl
@@ -0,0 +1,43 @@
+#!/usr/bin/perl
+
+# Usage:
+# sort_vendors.pl <some_path/vendor.ids>
+# Output goes to stdout.
+
+use utf8;
+use strict;
+#use File::Slurp::Unicode qw(read_file);
+sub read_file {
+ my $file = shift;
+ return do {
+ local $/ = undef;
+ open my $fh, "<", $file || die "Could not open $file: $!";
+ <$fh>;
+ };
+}
+
+my $file = shift;
+die "Invalid file: $file" unless (-f $file && -r $file);
+my $raw = read_file($file);
+$raw =~ s/^\n*|\n*$//g;
+
+my $c = 0;
+my @coms = ();
+my %vendors = ();
+SEC: foreach my $s (split(/\n{2,}/, $raw)) {
+ my @lines = split(/\n/, $s);
+ foreach my $l (@lines) {
+ if ($l =~ /^name\s+(.*)/) {
+ $vendors{"$1_$c"} = $s;
+ $c++;
+ next SEC;
+ }
+ }
+ push @coms, $s;
+}
+foreach (@coms) {
+ print "$_\n\n";
+}
+foreach (sort { lc $a cmp lc $b } keys %vendors) {
+ print "$vendors{$_}\n\n";
+}
diff --git a/deps/sysobj_early/gui/uri_handler.c b/deps/sysobj_early/gui/uri_handler.c
new file mode 100644
index 00000000..de9f64a7
--- /dev/null
+++ b/deps/sysobj_early/gui/uri_handler.c
@@ -0,0 +1,53 @@
+/*
+ * 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 <stdio.h>
+#include "uri_handler.h"
+
+//compatibility
+#ifndef G_SPAWN_DEFAULT
+#define G_SPAWN_DEFAULT 0
+#endif
+
+static uri_handler uri_func = NULL;
+
+void uri_set_function(uri_handler f) {
+ uri_func = f;
+}
+
+gboolean uri_open(const gchar *uri) {
+ gboolean ret = FALSE;
+ if (uri_func)
+ ret = uri_func(uri);
+ if (ret) return TRUE;
+
+ return uri_open_default(uri);
+}
+
+gboolean uri_open_default(const gchar *uri) {
+ gchar *argv[] = { "/usr/bin/xdg-open", (gchar*)uri, NULL };
+ GError *err = NULL;
+ g_spawn_async(NULL, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, &err );
+ if (err) {
+ fprintf(stderr, "Error opening URI %s: %s\n", uri, err->message);
+ g_error_free(err);
+ }
+ return TRUE;
+}
diff --git a/deps/sysobj_early/gui/uri_handler.h b/deps/sysobj_early/gui/uri_handler.h
new file mode 100644
index 00000000..8d7892c5
--- /dev/null
+++ b/deps/sysobj_early/gui/uri_handler.h
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef URI_HANDLER_H
+#define URI_HANDLER_H
+
+#include <glib.h>
+
+typedef gboolean (*uri_handler)(const gchar *uri);
+
+void uri_set_function(uri_handler f);
+gboolean uri_open(const gchar *uri);
+gboolean uri_open_default(const gchar *uri); /* uses xdg-open */
+
+#endif
diff --git a/deps/sysobj_early/include/appf.h b/deps/sysobj_early/include/appf.h
new file mode 100644
index 00000000..b99f9373
--- /dev/null
+++ b/deps/sysobj_early/include/appf.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef _APPF_H_
+#define _APPF_H_
+
+/* Appends a formatted element to a string, adding an optional
+ * separator string if the string is not empty.
+ * The string is created if str is null.
+ * ex: ret = appf(ret, "; ", "%s = %d", name, value); */
+char *appf(char *str, const char *sep, const char *fmt, ...)
+ __attribute__ ((format (printf, 3, 4)));
+
+/* Same as above except that str is untouched.
+ * ex: ret = appf(keeper, "; ", "%s = %d", name, value); */
+char *appfdup(const char *str, const char *sep, const char *fmt, ...)
+ __attribute__ ((format (printf, 3, 4)));
+
+/* for convenience */
+#define appfsp(str, fmt, ...) appf(str, " ", fmt, __VA_ARGS__)
+#define appfnl(str, fmt, ...) appf(str, "\n", fmt, __VA_ARGS__)
+
+#endif
diff --git a/deps/sysobj_early/include/auto_free.h b/deps/sysobj_early/include/auto_free.h
new file mode 100644
index 00000000..bddaa321
--- /dev/null
+++ b/deps/sysobj_early/include/auto_free.h
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef _AUTO_FREE_H_
+#define _AUTO_FREE_H_
+
+#include <glib.h>
+
+/* DEBUG_AUTO_FREE messages level:
+ * 0 - none
+ * 1 - some
+ * 2 - much
+ */
+#ifndef DEBUG_AUTO_FREE
+#define DEBUG_AUTO_FREE 0
+#endif
+/* the period between free_auto_free()s in the main loop */
+#define AF_SECONDS 11
+/* the minimum time between auto_free(p) and free(p) */
+#define AF_DELAY_SECONDS 10
+
+#define AF_USE_SYSOBJ 0
+
+#if (DEBUG_AUTO_FREE > 0)
+#define auto_free(p) auto_free_ex_(p, (GDestroyNotify)g_free, __FILE__, __LINE__, __FUNCTION__)
+#define auto_free_ex(p, f) auto_free_ex_(p, f, __FILE__, __LINE__, __FUNCTION__)
+#define auto_free_on_exit(p) auto_free_on_exit_ex_(p, (GDestroyNotify)g_free, __FILE__, __LINE__, __FUNCTION__)
+#define auto_free_on_exit_ex(p, f) auto_free_on_exit_ex_(p, f, __FILE__, __LINE__, __FUNCTION__)
+#else
+#define auto_free(p) auto_free_ex_(p, (GDestroyNotify)g_free, NULL, 0, NULL)
+#define auto_free_ex(p, f) auto_free_ex_(p, f, NULL, 0, NULL)
+#define auto_free_on_exit(p) auto_free_on_exit_ex_(p, (GDestroyNotify)g_free, NULL, 0, NULL)
+#define auto_free_on_exit_ex(p, f) auto_free_on_exit_ex_(p, f, NULL, 0, NULL)
+#endif
+gpointer auto_free_ex_(gpointer p, GDestroyNotify f, const char *file, int line, const char *func);
+gpointer auto_free_on_exit_ex_(gpointer p, GDestroyNotify f, const char *file, int line, const char *func);
+
+/* free all the auto_free marked items in the
+ * current thread with age > AF_DELAY_SECONDS */
+void free_auto_free();
+
+/* call at thread termination:
+ * free all the auto_free marked items in the
+ * current thread regardless of age */
+void free_auto_free_thread_final();
+
+/* call at program termination */
+void free_auto_free_final();
+
+#endif
diff --git a/deps/sysobj_early/include/cpubits.h b/deps/sysobj_early/include/cpubits.h
new file mode 100644
index 00000000..2d8fe8a6
--- /dev/null
+++ b/deps/sysobj_early/include/cpubits.h
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef _CPUBITS_H_
+#define _CPUBITS_H_
+
+#include <stdint.h>
+
+typedef uint32_t cpubits;
+uint32_t cpubits_count(cpubits *b);
+int cpubits_min(cpubits *b);
+int cpubits_max(cpubits *b);
+int cpubits_next(cpubits *b, int start, int end);
+cpubits *cpubits_from_str(char *str);
+char *cpubits_to_str(cpubits *bits, char *str, int max_len);
+
+#define CPUBITS_SIZE 4096 /* bytes, multiple of sizeof(uint32_t) */
+#define CPUBIT_SET(BITS, BIT) ((BITS)[(BIT)/32] |= (1 << (BIT)%32))
+#define CPUBIT_GET(BITS, BIT) (((BITS)[(BIT)/32] & (1 << (BIT)%32)) >> (BIT)%32)
+#define CPUBITS_CLEAR(BITS) memset((BITS), 0, CPUBITS_SIZE)
+
+#endif
diff --git a/deps/sysobj_early/include/format_early.h b/deps/sysobj_early/include/format_early.h
new file mode 100644
index 00000000..e5524e26
--- /dev/null
+++ b/deps/sysobj_early/include/format_early.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef _FORMAT_EARLY_H_
+#define _FORMAT_EARLY_H_
+
+#include <glib.h>
+#include <strings.h>
+#include "appf.h"
+#include "util_sysobj.h"
+#include "vendor.h"
+
+enum {
+ FMT_OPT_NONE = 0,
+ FMT_OPT_ATERM = 1<<16, /* ANSI color terminal */
+ FMT_OPT_PANGO = 1<<17, /* pango markup for gtk */
+ FMT_OPT_HTML = 1<<18, /* html */
+};
+
+gchar *safe_ansi_color(gchar *ansi_color, gboolean free_in); /* verify the ansi color */
+const gchar *color_lookup(int ansi_color); /* ansi_color to html color */
+/* wrap the input str with color based on fmt_opts (none,term,html,pango) */
+gchar *format_with_ansi_color(const gchar *str, const gchar *ansi_color, int fmt_opts);
+
+void tag_vendor(gchar **str, guint offset, const gchar *vendor_str, const char *ansi_color, int fmt_opts);
+gchar *vendor_match_tag(const gchar *vendor_str, int fmt_opts);
+
+gchar *vendor_list_ribbon(const vendor_list vl_in, int fmt_opts);
+
+#endif
diff --git a/deps/sysobj_early/include/gg_slist.h b/deps/sysobj_early/include/gg_slist.h
new file mode 100644
index 00000000..255465ee
--- /dev/null
+++ b/deps/sysobj_early/include/gg_slist.h
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef _GG_SLIST_H_
+#define _GG_SLIST_H_
+
+#include <glib.h>
+
+GSList *gg_slist_remove_null(GSList *sl);
+GSList *gg_slist_remove_duplicates(GSList *sl); /* by pointer */
+GSList *gg_slist_remove_duplicates_custom(GSList *sl, GCompareFunc func);
+
+#endif
diff --git a/deps/sysobj_early/include/nice_name.h b/deps/sysobj_early/include/nice_name.h
new file mode 100644
index 00000000..80031c91
--- /dev/null
+++ b/deps/sysobj_early/include/nice_name.h
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef _NICE_NAME_H_
+#define _NICE_NAME_H_
+
+/* cleaned in-place */
+void nice_name_x86_cpuid_model_string(char *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"
+ * cleaned in-place */
+void nice_name_intel_gpu_device(char *pci_ids_device_string);
+
+#endif
diff --git a/deps/sysobj_early/include/strstr_word.h b/deps/sysobj_early/include/strstr_word.h
new file mode 100644
index 00000000..f17e78ff
--- /dev/null
+++ b/deps/sysobj_early/include/strstr_word.h
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef __STRSTR_WORD_H__
+#define __STRSTR_WORD_H__
+
+/* versions of strstr() and strcasestr() where the match must be preceded and
+ * succeded by a non-alpha-numeric character. */
+char *strstr_word(const char *haystack, const char *needle);
+char *strcasestr_word(const char *haystack, const char *needle);
+
+/* word boundary at start only (prefix), or end only (suffix) */
+char *strstr_word_prefix(const char *haystack, const char *needle);
+char *strcasestr_word_prefix(const char *haystack, const char *needle);
+char *strstr_word_suffix(const char *haystack, const char *needle);
+char *strcasestr_word_suffix(const char *haystack, const char *needle);
+
+#endif
diff --git a/deps/sysobj_early/include/util_edid.h b/deps/sysobj_early/include/util_edid.h
new file mode 100644
index 00000000..5bb4b932
--- /dev/null
+++ b/deps/sysobj_early/include/util_edid.h
@@ -0,0 +1,268 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef __UTIL_EDID_H__
+#define __UTIL_EDID_H__
+
+#define _GNU_SOURCE
+#include <stdint.h> /* for *int*_t types */
+#include <glib.h>
+
+typedef struct _edid edid;
+
+typedef struct {
+ edid *e;
+ uint32_t offset;
+} edid_addy;
+
+/* OUI is stored in EDID as 24-bit little-endian,
+ * but lookup file from IEEE expects big-endian.
+ * .oui_str is .oui rendered into big-endian for
+ * easy lookup. */
+typedef struct {
+ union {
+ char pnp[7]; /* only needs 3+null */
+ char oui_str[7]; /* needs 6+null */
+ };
+ uint32_t oui;
+ uint8_t type; /* enum VEN_TYPE_* */
+} edid_ven;
+
+typedef struct {
+ char *str;
+ uint16_t len;
+ uint8_t is_product_name;
+ uint8_t is_serial;
+} DisplayIDString;
+
+typedef struct {
+ uint8_t version;
+ uint8_t extension_length;
+ uint8_t primary_use_case;
+ uint8_t extension_count;
+ uint16_t blocks;
+ uint8_t checksum_ok;
+} DisplayIDMeta;
+
+typedef struct {
+ edid_addy addy;
+ union {
+ uint8_t tag;
+ uint8_t type;
+ };
+ uint8_t revision;
+ uint8_t len;
+ uint8_t bounds_ok;
+
+ /* for vendor specific block */
+ edid_ven ven;
+} DisplayIDBlock;
+
+typedef struct {
+ edid_addy addy;
+ uint8_t interface;
+ uint8_t supports_hdcp;
+ uint8_t exists;
+} DIExtData;
+
+/* order by rising priority */
+enum {
+ OUTSRC_INVALID = -1,
+ OUTSRC_EDID = 0,
+ OUTSRC_ETB,
+ OUTSRC_STD,
+ OUTSRC_DTD,
+ OUTSRC_CEA_DTD,
+ OUTSRC_SVD,
+
+ OUTSRC_DID_TYPE_I,
+ OUTSRC_DID_TYPE_VI,
+ OUTSRC_DID_TYPE_VII,
+};
+
+typedef struct {
+ float horiz_cm, vert_cm;
+ float diag_cm, diag_in;
+ int horiz_blanking, vert_blanking;
+ int horiz_pixels, vert_lines, vert_pixels;
+ float vert_freq_hz;
+ uint8_t is_interlaced;
+ uint8_t is_preferred;
+ int stereo_mode;
+ uint64_t pixel_clock_khz;
+ int src; /* enum OUTSRC_* */
+ uint64_t pixels; /* h*v: easier to compare */
+ char class_inch[12];
+} edid_output;
+
+struct edid_std {
+ uint8_t *ptr;
+ edid_output out;
+};
+
+struct edid_dtd {
+ edid_addy addy;
+ uint8_t cea_ext; /* in a CEA block vs one of the regular EDID descriptors */
+ edid_output out;
+ uint8_t bounds_ok;
+};
+
+struct edid_svd {
+ uint8_t v;
+ uint8_t is_native;
+ edid_output out;
+};
+
+struct edid_sad {
+ uint8_t v[3];
+ uint8_t format, channels, freq_bits;
+ int depth_bits; /* format 1 */
+ int max_kbps; /* formats 2-8 */
+};
+
+struct edid_cea_block {
+ edid_addy addy;
+ int type, len;
+ uint8_t bounds_ok;
+
+ /* for vendor specific block */
+ edid_ven ven;
+};
+
+struct edid_descriptor {
+ edid_addy addy;
+ uint8_t type;
+ char text[14];
+};
+
+struct edid_manf_date {
+ uint8_t week;
+ uint8_t is_model_year; /* ignore week */
+ uint16_t year;
+ int std; /* enum STD_* */
+};
+
+enum {
+ VEN_TYPE_INVALID = 0,
+ VEN_TYPE_PNP,
+ VEN_TYPE_OUI,
+};
+
+enum {
+ STD_EDID = 0,
+ STD_EEDID = 1,
+ STD_EIACEA861 = 2,
+ STD_DISPLAYID = 3,
+ STD_DISPLAYID20 = 4,
+};
+
+typedef struct _edid {
+ union {
+ void* data;
+ uint8_t* u8;
+ uint16_t* u16;
+ };
+ unsigned int len;
+
+ /* enum STD_* */
+ int std;
+
+ uint8_t ver_major, ver_minor;
+ uint8_t checksum_ok; /* first 128-byte block only */
+ uint8_t ext_blocks, ext_blocks_ok, ext_blocks_fail;
+ uint8_t *ext_ok;
+
+ int etb_count;
+ edid_output etbs[24];
+
+ int std_count;
+ struct edid_std stds[8];
+
+ int dtd_count;
+ struct edid_dtd *dtds;
+
+ int cea_block_count;
+ struct edid_cea_block *cea_blocks;
+
+ int svd_count;
+ struct edid_svd *svds;
+
+ int sad_count;
+ struct edid_sad *sads;
+
+ edid_ven ven;
+ struct edid_descriptor d[4];
+ /* point into d[].text */
+ char *name;
+ char *serial;
+ char *ut1;
+ char *ut2;
+
+ uint8_t a_or_d; /* 0 = analog, 1 = digital */
+ uint8_t interface; /* digital interface */
+ uint8_t bpc; /* digital bpc */
+ uint16_t product;
+ uint32_t n_serial;
+ struct edid_manf_date dom;
+ edid_output img;
+ edid_output img_svd;
+ edid_output img_max;
+ uint32_t speaker_alloc_bits;
+
+ DIExtData di;
+
+ DisplayIDMeta did;
+ int did_block_count;
+ DisplayIDBlock *did_blocks;
+ int did_string_count;
+ DisplayIDString *did_strings;
+
+ int didt_count;
+ edid_output *didts;
+
+ GString *msg_log;
+} edid;
+edid *edid_new(const char *data, unsigned int len);
+edid *edid_new_from_hex(const char *hex_string);
+edid *edid_new_from_file(const char *path);
+void edid_free(edid *e);
+char *edid_dump_hex(edid *e, int tabs, int breaks);
+
+const char *edid_standard(int std);
+const char *edid_output_src(int src);
+const char *edid_interface(int type);
+const char *edid_di_interface(int type);
+const char *edid_descriptor_type(int type);
+const char *edid_ext_block_type(int type);
+const char *edid_cea_block_type(int type);
+const char *edid_cea_audio_type(int type);
+
+char *edid_output_describe(edid_output *out);
+char *edid_base_descriptor_describe(struct edid_descriptor *d);
+char *edid_dtd_describe(struct edid_dtd *dtd, int dump_bytes);
+char *edid_cea_block_describe(struct edid_cea_block *blk);
+char *edid_cea_audio_describe(struct edid_sad *sad);
+char *edid_cea_speaker_allocation_describe(int bitfield, int short_version);
+const char *edid_did_block_type(int type);
+char *edid_did_block_describe(DisplayIDBlock *blk);
+
+char *edid_dump2(edid *e);
+
+#endif
diff --git a/deps/sysobj_early/include/util_ids.h b/deps/sysobj_early/include/util_ids.h
new file mode 100644
index 00000000..c5dccfe7
--- /dev/null
+++ b/deps/sysobj_early/include/util_ids.h
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef _UTIL_IDS_H_
+#define _UTIL_IDS_H_
+
+#include <glib.h>
+
+#define IDS_LOOKUP_BUFF_SIZE 220
+#define IDS_LOOKUP_MAX_DEPTH 4
+
+/* may be static, all results[] are NULL or point into _strs */
+typedef struct {
+ gchar *results[IDS_LOOKUP_MAX_DEPTH+1]; /* last always NULL */
+ gchar _strs[IDS_LOOKUP_BUFF_SIZE*IDS_LOOKUP_MAX_DEPTH];
+} ids_query_result;
+#define ids_query_result_new() g_new0(ids_query_result, 1)
+#define ids_query_result_free(s) g_free(s);
+void ids_query_result_cpy(ids_query_result *dest, ids_query_result *src);
+
+/* 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>"
+ * ... need to query "<subvendor>" separately
+ * - arm.ids "<implementer>/<part>"
+ * - sdio.ids "<vendor>/<device>", "C <class>"
+ * - sdcard.ids "OEMID <code>", "MANFID <code>"
+ * - 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);
+
+typedef struct {
+ gchar *qpath;
+ ids_query_result result;
+} ids_query;
+
+ids_query *ids_query_new(const gchar *qpath);
+void ids_query_free(ids_query *s);
+typedef GSList* ids_query_list;
+
+/* query_list is a GSList of ids_query* */
+long scan_ids_file_list(const gchar *file, ids_query_list query_list, long start_offset);
+/* after scan_ids_file_list(), count hits */
+int query_list_count_found(ids_query_list query_list);
+
+/* returns GSList of ids_query* */
+typedef gchar* (*split_loc_function)(const char *line);
+ids_query_list ids_file_all_get_all(const gchar *file, split_loc_function split_loc_func);
+
+/* debugging */
+void ids_trace_start();
+void ids_trace_stop();
+
+#endif
diff --git a/deps/sysobj_early/include/util_sysobj.h b/deps/sysobj_early/include/util_sysobj.h
new file mode 100644
index 00000000..1bff3d5a
--- /dev/null
+++ b/deps/sysobj_early/include/util_sysobj.h
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef _UTIL_SYSOBJ_H_
+#define _UTIL_SYSOBJ_H_
+
+#include <glib.h>
+#include "appf.h"
+
+/* string eq */
+#define SEQ(s1, s2) (g_strcmp0((s1), (s2)) == 0)
+
+/* handy for static halp */
+#define BULLET "\u2022"
+#define REFLINK(URI) "<a href=\"" URI "\">" URI "</a>"
+#define REFLINKT(TEXT, URI) "<a href=\"" URI "\">" TEXT "</a>"
+
+gboolean util_have_root();
+void util_null_trailing_slash(gchar *str); /* in-place */
+void util_compress_space(gchar *str); /* in-place, multiple whitespace replaced by one space */
+void util_strstrip_double_quotes_dumb(gchar *str); /* in-place, strips any double-quotes from the start and end of str */
+gchar *util_build_fn(const gchar *base, const gchar *name); /* returns "<base>[/<name>]" */
+gchar *util_canonicalize_path(const gchar *path); /* resolve . and .., but not symlinks */
+gchar *util_normalize_path(const gchar *path, const gchar *relto); /* resolve . and .., and symlinks */
+gsize util_count_lines(const gchar *str); /* doesn't count empty last line */
+gchar *util_escape_markup(gchar *v, gboolean replacing);
+int util_get_did(gchar *str, const gchar *lbl); /* ("cpu6", "cpu") -> 6, returns -1 if error */
+int util_maybe_num(gchar *str); /* returns the guessed base, 0 for not num */
+gchar *util_find_line_value(gchar *data, gchar *key, gchar delim);
+gchar *util_strchomp_float(gchar* str_float); /* in-place, must use , or . for decimal sep */
+gchar *util_safe_name(const gchar *name, gboolean lower_case); /* make a string into a name nice and safe for file name */
+
+/* to quiet -Wunused-parameter nagging. */
+#define PARAM_NOT_UNUSED(p) (void)p
+
+#endif
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;
+}
diff --git a/deps/uber-graph/README b/deps/uber-graph/README
new file mode 100644
index 00000000..e9a95ab1
--- /dev/null
+++ b/deps/uber-graph/README
@@ -0,0 +1,39 @@
+This is Christian Hergert's "Uber-Graph"
+after commit d31c8014d8cc9f293dfecfcb4bd6a7bf4d61c0be.
+https://gitlab.gnome.org/chergert/uber-graph
+File headers give LGPL2 as the license.
+
+Original README follows:
+
+Some of this code is good. Some of this code is very bad. It is a prototype,
+nothing more, nothing less.
+
+Particularly bad/nasty/unreadable code is in main.c. It is also Linux specific.
+
+UberGraph - A realtime graph similar to that found in Gnome System Monitor.
+ However, it is much faster and smoother. It runs at a higher
+ framerate with less X bandwidth.
+
+ It uses multiple pixmaps on the server-side and shifts data between
+ them to lower the X bandwidth. New data is rendered and clipped so
+ that the transfer is small. If frame movement is < 1 pixel, the
+ framerate is dynamically reduced.
+
+UberHeatMap - This is going to eventually be similar to UberGraph but as a
+ heat map. It's not very far yet, however.
+
+GRing - A small circular buffer meant for values in the realtime graphs. This
+ is most definitely not meant to be used as a byte buffer, so don't use
+ it as such.
+
+ Example:
+
+ GRing *ring = g_ring_sized_new(sizeof(gdouble), 60, NULL);
+
+ or
+
+ static void free_array_func (gpointer data) {
+ GArray **ar = data;
+ g_array_unref(*ar);
+ }
+ GRing *ring = g_ring_sized_new(sizeof(GArray*), 60, free_array_func);
diff --git a/deps/uber-graph/g-ring.c b/deps/uber-graph/g-ring.c
new file mode 100644
index 00000000..b014bf74
--- /dev/null
+++ b/deps/uber-graph/g-ring.c
@@ -0,0 +1,227 @@
+/* g-ring.c
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.com>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include "g-ring.h"
+
+#ifndef g_malloc0_n
+#define g_malloc0_n(x,y) g_malloc0((gsize)x * y)
+#endif
+
+#define get_element(r,i) ((r)->data + ((r)->elt_size * i))
+
+typedef struct _GRingImpl GRingImpl;
+
+struct _GRingImpl
+{
+ guint8 *data; /* Pointer to real data. */
+ guint len; /* Length of data allocation. */
+ guint pos; /* Position in ring. */
+ guint elt_size; /* Size of each element. */
+ gboolean looped; /* Have we wrapped around at least once. */
+ GDestroyNotify destroy; /* Destroy element callback. */
+ volatile gint ref_count; /* Reference count. */
+};
+
+/**
+ * g_ring_sized_new:
+ * @element_size: (in): The size per element.
+ * @reserved_size: (in): The number of elements to allocate.
+ * @element_destroy: (in): Notification called when removing an element.
+ *
+ * Creates a new instance of #GRing with the given number of elements.
+ *
+ * Returns: A new #GRing.
+ * Side effects: None.
+ */
+GRing*
+g_ring_sized_new (guint element_size,
+ guint reserved_size,
+ GDestroyNotify element_destroy)
+{
+ GRingImpl *ring_impl;
+
+ ring_impl = g_slice_new0(GRingImpl);
+ ring_impl->elt_size = element_size;
+ ring_impl->len = reserved_size;
+ ring_impl->data = g_malloc0_n(reserved_size, element_size);
+ ring_impl->destroy = element_destroy;
+ ring_impl->ref_count = 1;
+
+ return (GRing *)ring_impl;
+}
+
+/**
+ * g_ring_append_vals:
+ * @ring: (in): A #GRing.
+ * @data: (in): A pointer to the array of values.
+ * @len: (in): The number of values.
+ *
+ * Appends @len values located at @data.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+g_ring_append_vals (GRing *ring,
+ gconstpointer data,
+ guint len)
+{
+ GRingImpl *ring_impl = (GRingImpl *)ring;
+ gpointer idx;
+ gint x;
+ guint i;
+
+ g_return_if_fail(ring_impl != NULL);
+
+ for (i = 0; i < len; i++) {
+ x = (int)ring->pos - i;
+ if (x < 0) x += ring->len;
+ idx = ring->data + (ring_impl->elt_size * x);
+ if (ring_impl->destroy && ring_impl->looped == TRUE) {
+ ring_impl->destroy(idx);
+ }
+ memcpy(idx, data, ring_impl->elt_size);
+ ring->pos++;
+ if (ring->pos >= ring->len) {
+ ring_impl->looped = TRUE;
+ }
+ ring->pos %= ring->len;
+ data += ring_impl->elt_size;
+ }
+}
+
+/**
+ * g_ring_foreach:
+ * @ring: (in): A #GRing.
+ * @func: (in): A #GFunc to call for each element.
+ * @user_data: (in): user data for @func.
+ *
+ * Calls @func for every item in the #GRing starting from the most recently
+ * inserted element to the least recently inserted.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+g_ring_foreach (GRing *ring,
+ GFunc func,
+ gpointer user_data)
+{
+ GRingImpl *ring_impl = (GRingImpl *)ring;
+ gint i;
+
+ g_return_if_fail(ring_impl != NULL);
+ g_return_if_fail(func != NULL);
+
+ for (i = ring_impl->pos - 1; i >= 0; i--) {
+ func(get_element(ring_impl, i), user_data);
+ }
+ for (i = ring->len - 1; i >= (int)ring_impl->pos; i--) {
+ func(get_element(ring_impl, i), user_data);
+ }
+}
+
+/**
+ * g_ring_destroy:
+ * @ring: (in): A #GRing.
+ *
+ * Cleans up after a #GRing that is no longer in use.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+g_ring_destroy (GRing *ring)
+{
+ GRingImpl *ring_impl = (GRingImpl *)ring;
+
+ g_return_if_fail(ring != NULL);
+ g_return_if_fail(ring_impl->ref_count == 0);
+
+ g_free(ring_impl->data);
+ g_slice_free(GRingImpl, ring_impl);
+}
+
+/**
+ * g_ring_ref:
+ * @ring: (in): A #GRing.
+ *
+ * Atomically increments the reference count of @ring by one.
+ *
+ * Returns: The @ring pointer.
+ * Side effects: None.
+ */
+GRing*
+g_ring_ref (GRing *ring)
+{
+ GRingImpl *ring_impl = (GRingImpl *)ring;
+
+ g_return_val_if_fail(ring != NULL, NULL);
+ g_return_val_if_fail(ring_impl->ref_count > 0, NULL);
+
+ g_atomic_int_inc(&ring_impl->ref_count);
+ return ring;
+}
+
+/**
+ * g_ring_unref:
+ * @ring: (in): A #GRing.
+ *
+ * Atomically decrements the reference count of @ring by one. When the
+ * reference count reaches zero, the structure is freed.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+g_ring_unref (GRing *ring)
+{
+ GRingImpl *ring_impl = (GRingImpl *)ring;
+
+ g_return_if_fail(ring != NULL);
+ g_return_if_fail(ring_impl->ref_count > 0);
+
+ if (g_atomic_int_dec_and_test(&ring_impl->ref_count)) {
+ g_ring_destroy(ring);
+ }
+}
+
+/**
+ * g_ring_get_type:
+ *
+ * Retrieves the #GType identifier for #GRing.
+ *
+ * Returns: The #GType for #GRing.
+ * Side effects: The type is registered on first call.
+ */
+GType
+g_ring_get_type (void)
+{
+ static gsize initialized = FALSE;
+ static GType type_id = 0;
+
+ if (g_once_init_enter(&initialized)) {
+ type_id = g_boxed_type_register_static("GRing",
+ (GBoxedCopyFunc)g_ring_ref,
+ (GBoxedFreeFunc)g_ring_unref);
+ g_once_init_leave(&initialized, TRUE);
+ }
+ return type_id;
+}
diff --git a/deps/uber-graph/g-ring.h b/deps/uber-graph/g-ring.h
new file mode 100644
index 00000000..a4943878
--- /dev/null
+++ b/deps/uber-graph/g-ring.h
@@ -0,0 +1,84 @@
+/* g-ring.h
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.com>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __G_RING_H__
+#define __G_RING_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/**
+ * g_ring_append_val:
+ * @ring: A #GRing.
+ * @val: A value to append to the #GRing.
+ *
+ * Appends a value to the ring buffer. @val must be a variable as it is
+ * referenced to.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+#define g_ring_append_val(ring, val) g_ring_append_vals(ring, &(val), 1)
+
+/**
+ * g_ring_get_index:
+ * @ring: A #GRing.
+ * @type: The type to extract.
+ * @i: The index within the #GRing relative to the current position.
+ *
+ * Retrieves the value at the given index from the #GRing. The value
+ * is cast to @type. You may retrieve a pointer to the value within the
+ * array by using &.
+ *
+ * [[
+ * gdouble *v = &g_ring_get_index(ring, gdouble, 0);
+ * gdouble v = g_ring_get_index(ring, gdouble, 0);
+ * ]]
+ *
+ * Returns: The value at the given index.
+ * Side effects: None.
+ */
+#define g_ring_get_index(ring, type, i) \
+ (((type*)(ring)->data)[(((gint)(ring)->pos - 1 - (i)) >= 0) ? \
+ ((ring)->pos - 1 - (i)) : \
+ ((ring)->len + ((ring)->pos - 1 - (i)))])
+
+typedef struct
+{
+ guint8 *data;
+ guint len;
+ guint pos;
+} GRing;
+
+GType g_ring_get_type (void) G_GNUC_CONST;
+GRing* g_ring_sized_new (guint element_size,
+ guint reserved_size,
+ GDestroyNotify element_destroy);
+void g_ring_append_vals (GRing *ring,
+ gconstpointer data,
+ guint len);
+void g_ring_foreach (GRing *ring,
+ GFunc func,
+ gpointer user_data);
+GRing* g_ring_ref (GRing *ring);
+void g_ring_unref (GRing *ring);
+
+G_END_DECLS
+
+#endif /* __G_RING_H__ */
diff --git a/deps/uber-graph/uber-frame-source.c b/deps/uber-graph/uber-frame-source.c
new file mode 100644
index 00000000..3de4d537
--- /dev/null
+++ b/deps/uber-graph/uber-frame-source.c
@@ -0,0 +1,177 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Authored By Neil Roberts <neil@linux.intel.com>
+ *
+ * Copyright (C) 2008 OpenedHand
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ */
+
+/* Modified by Christian Hergert for use in Uber Graph. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "uber-frame-source.h"
+#include "uber-timeout-interval.h"
+
+typedef struct _UberFrameSource UberFrameSource;
+
+struct _UberFrameSource
+{
+ GSource source;
+
+ UberTimeoutInterval timeout;
+};
+
+static gboolean uber_frame_source_prepare (GSource *source,
+ gint *timeout);
+static gboolean uber_frame_source_check (GSource *source);
+static gboolean uber_frame_source_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data);
+
+static GSourceFuncs uber_frame_source_funcs =
+{
+ uber_frame_source_prepare,
+ uber_frame_source_check,
+ uber_frame_source_dispatch,
+ NULL
+};
+
+/**
+ * uber_frame_source_add_full:
+ * @priority: the priority of the frame source. Typically this will be in the
+ * range between %G_PRIORITY_DEFAULT and %G_PRIORITY_HIGH.
+ * @fps: the number of times per second to call the function
+ * @func: function to call
+ * @data: data to pass to the function
+ * @notify: function to call when the timeout source is removed
+ *
+ * Sets a function to be called at regular intervals with the given
+ * priority. The function is called repeatedly until it returns
+ * %FALSE, at which point the timeout is automatically destroyed and
+ * the function will not be called again. The @notify function is
+ * called when the timeout is destroyed. The first call to the
+ * function will be at the end of the first @interval.
+ *
+ * This function is similar to g_timeout_add_full() except that it
+ * will try to compensate for delays. For example, if @func takes half
+ * the interval time to execute then the function will be called again
+ * half the interval time after it finished. In contrast
+ * g_timeout_add_full() would not fire until a full interval after the
+ * function completes so the delay between calls would be 1.0 / @fps *
+ * 1.5. This function does not however try to invoke the function
+ * multiple times to catch up missing frames if @func takes more than
+ * @interval ms to execute.
+ *
+ * Return value: the ID (greater than 0) of the event source.
+ *
+ * Since: 0.8
+ */
+guint
+uber_frame_source_add_full (gint priority,
+ guint fps,
+ GSourceFunc func,
+ gpointer data,
+ GDestroyNotify notify)
+{
+ guint ret;
+ GSource *source = g_source_new (&uber_frame_source_funcs,
+ sizeof (UberFrameSource));
+ UberFrameSource *frame_source = (UberFrameSource *) source;
+
+ _uber_timeout_interval_init (&frame_source->timeout, fps);
+
+ if (priority != G_PRIORITY_DEFAULT)
+ g_source_set_priority (source, priority);
+
+#if GLIB_CHECK_VERSION (2, 25, 8)
+ g_source_set_name (source, "Uber frame timeout");
+#endif
+
+ g_source_set_callback (source, func, data, notify);
+
+ ret = g_source_attach (source, NULL);
+
+ g_source_unref (source);
+
+ return ret;
+}
+
+/**
+ * uber_frame_source_add:
+ * @fps: the number of times per second to call the function
+ * @func: function to call
+ * @data: data to pass to the function
+ *
+ * Simple wrapper around uber_frame_source_add_full().
+ *
+ * Return value: the ID (greater than 0) of the event source.
+ *
+ * Since: 0.8
+ */
+guint
+uber_frame_source_add (guint fps,
+ GSourceFunc func,
+ gpointer data)
+{
+ return uber_frame_source_add_full (G_PRIORITY_DEFAULT,
+ fps, func, data, NULL);
+}
+
+static gboolean
+uber_frame_source_prepare (GSource *source,
+ gint *delay)
+{
+ UberFrameSource *frame_source = (UberFrameSource *) source;
+ gint64 current_time;
+
+#if GLIB_CHECK_VERSION (2, 27, 3)
+ current_time = g_source_get_time (source) / 1000;
+#else
+ {
+ GTimeVal source_time;
+ g_source_get_current_time (source, &source_time);
+ current_time = source_time.tv_sec * 1000 + source_time.tv_usec / 1000;
+ }
+#endif
+
+ return _uber_timeout_interval_prepare (current_time,
+ &frame_source->timeout,
+ delay);
+}
+
+static gboolean
+uber_frame_source_check (GSource *source)
+{
+ return uber_frame_source_prepare (source, NULL);
+}
+
+static gboolean
+uber_frame_source_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ UberFrameSource *frame_source = (UberFrameSource *) source;
+
+ return _uber_timeout_interval_dispatch (&frame_source->timeout,
+ callback, user_data);
+}
diff --git a/deps/uber-graph/uber-frame-source.h b/deps/uber-graph/uber-frame-source.h
new file mode 100644
index 00000000..8976beaa
--- /dev/null
+++ b/deps/uber-graph/uber-frame-source.h
@@ -0,0 +1,43 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Authored By Matthew Allum <mallum@openedhand.com>
+ *
+ * Copyright (C) 2008 OpenedHand
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UBER_FRAME_SOURCE_H__
+#define __UBER_FRAME_SOURCE_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+guint uber_frame_source_add (guint fps,
+ GSourceFunc func,
+ gpointer data);
+
+guint uber_frame_source_add_full (gint priority,
+ guint fps,
+ GSourceFunc func,
+ gpointer data,
+ GDestroyNotify notify);
+
+G_END_DECLS
+
+#endif /* __UBER_FRAME_SOURCE_H__ */
diff --git a/deps/uber-graph/uber-graph.c b/deps/uber-graph/uber-graph.c
new file mode 100644
index 00000000..9118f263
--- /dev/null
+++ b/deps/uber-graph/uber-graph.c
@@ -0,0 +1,2166 @@
+/* uber-graph.c
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <math.h>
+#include <string.h>
+
+#include "uber-graph.h"
+#include "uber-scale.h"
+#include "uber-frame-source.h"
+
+#define WIDGET_CLASS (GTK_WIDGET_CLASS(uber_graph_parent_class))
+#define RECT_RIGHT(r) ((r).x + (r).width)
+#define RECT_BOTTOM(r) ((r).y + (r).height)
+#define UNSET_SURFACE(p) \
+ G_STMT_START { \
+ if (p) { \
+ cairo_surface_destroy (p); \
+ p = NULL; \
+ } \
+ } G_STMT_END
+#define CLEAR_CAIRO(c, a) \
+ G_STMT_START { \
+ cairo_save(c); \
+ cairo_rectangle(c, 0, 0, a.width, a.height); \
+ cairo_set_operator(c, CAIRO_OPERATOR_CLEAR); \
+ cairo_fill(c); \
+ cairo_restore(c); \
+ } G_STMT_END
+
+/**
+ * SECTION:uber-graph.h
+ * @title: UberGraph
+ * @short_description: Graphing of realtime data.
+ *
+ * #UberGraph is an abstract base class for building realtime graphs. It
+ * handles scrolling the graph based on the required time intervals and tries
+ * to render as little data as possible.
+ *
+ * Subclasses are responsible for acquiring data when #UberGraph notifies them
+ * of their next data sample. Additionally, there are two rendering methods.
+ * UberGraph::render is a render of the full scene. UberGraph::render_fast is
+ * a rendering of just a new data sample. Ideally, UberGraph::render_fast is
+ * going to be called.
+ *
+ * #UberGraph uses a #cairo_surface_t as a ring buffer to store the contents of the
+ * graph. Upon destructive changes to the widget such as allocation changed
+ * or a new #GtkStyle set, a full rendering of the graph will be required.
+ */
+
+
+struct _UberGraphPrivate
+{
+ cairo_surface_t *fg_surface;
+ cairo_surface_t *bg_surface;
+
+ GdkRectangle content_rect; /* Content area rectangle. */
+ GdkRectangle nonvis_rect; /* Non-visible drawing area larger than
+ * content rect. Used to draw over larger
+ * area so we can scroll and not fall out
+ * of view.
+ */
+ UberGraphFormat format; /* Data type format. */
+ gboolean paused; /* Is the graph paused. */
+ gboolean have_rgba; /* Do we support 32-bit RGBA colormaps. */
+ gint x_slots; /* Number of data points on x axis. */
+ gint fps; /* Desired frames per second. */
+ gint fps_real; /* Milleseconds between FPS callbacks. */
+ gfloat fps_each; /* How far to move in each FPS tick. */
+ guint fps_handler; /* Timeout for moving the content. */
+ gfloat dps; /* Desired data points per second. */
+ gint dps_slot; /* Which slot in the surface buffer. */
+ gfloat dps_each; /* How many pixels between data points. */
+ GTimeVal dps_tv; /* Timeval of last data point. */
+ guint dps_handler; /* Timeout for getting new data. */
+ guint dps_downscale; /* Count since last downscale. */
+ gboolean fg_dirty; /* Does the foreground need to be redrawn. */
+ gboolean bg_dirty; /* Does the background need to be redrawn. */
+ guint tick_len; /* How long should axis-ticks be. */
+ gboolean show_xlines; /* Show X axis lines. */
+ gboolean show_xlabels; /* Show X axis labels. */
+ gboolean show_ylines; /* Show Y axis lines. */
+ gboolean full_draw; /* Do we need to redraw all foreground content.
+ * If false, draws will try to only add new
+ * content to the back buffer.
+ */
+ GtkWidget *labels; /* Container for graph labels. */
+ GtkWidget *align; /* Alignment for labels. */
+ gint fps_count; /* Track actual FPS. */
+};
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(UberGraph, uber_graph, GTK_TYPE_DRAWING_AREA)
+
+static gboolean show_fps = FALSE;
+
+enum
+{
+ PROP_0,
+ PROP_FORMAT,
+};
+
+/**
+ * uber_graph_new:
+ *
+ * Creates a new instance of #UberGraph.
+ *
+ * Returns: the newly created instance of #UberGraph.
+ * Side effects: None.
+ */
+GtkWidget*
+uber_graph_new (void)
+{
+ UberGraph *graph;
+
+ graph = g_object_new(UBER_TYPE_GRAPH, NULL);
+ return GTK_WIDGET(graph);
+}
+
+/**
+ * uber_graph_fps_timeout:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: %TRUE always.
+ * Side effects: None.
+ */
+static gboolean
+uber_graph_fps_timeout (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), FALSE);
+
+ priv = graph->priv;
+ gtk_widget_queue_draw_area(GTK_WIDGET(graph),
+ priv->content_rect.x,
+ priv->content_rect.y,
+ priv->content_rect.width,
+ priv->content_rect.height);
+ return TRUE;
+}
+
+/**
+ * uber_graph_get_content_area:
+ * @graph: A #UberGraph.
+ *
+ * Retrieves the content area of the graph.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_get_content_area (UberGraph *graph, /* IN */
+ GdkRectangle *rect) /* OUT */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+ g_return_if_fail(rect != NULL);
+
+ priv = graph->priv;
+ *rect = priv->content_rect;
+}
+
+/**
+ * uber_graph_get_show_xlines:
+ * @graph: A #UberGraph.
+ *
+ * Retrieves if the X grid lines should be shown.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+gboolean
+uber_graph_get_show_xlines (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), FALSE);
+
+ priv = graph->priv;
+ return priv->show_xlines;
+}
+
+/**
+ * uber_graph_set_show_xlines:
+ * @graph: A #UberGraph.
+ * @show_xlines: Show x lines.
+ *
+ * Sets if the x lines should be shown.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_set_show_xlines (UberGraph *graph, /* IN */
+ gboolean show_xlines) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->show_xlines = show_xlines;
+ priv->bg_dirty = TRUE;
+ gtk_widget_queue_draw(GTK_WIDGET(graph));
+}
+
+/**
+ * uber_graph_get_show_ylines:
+ * @graph: A #UberGraph.
+ *
+ * Retrieves if the X grid lines should be shown.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+gboolean
+uber_graph_get_show_ylines (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), FALSE);
+
+ priv = graph->priv;
+ return priv->show_ylines;
+}
+
+/**
+ * uber_graph_set_show_ylines:
+ * @graph: A #UberGraph.
+ * @show_ylines: Show x lines.
+ *
+ * Sets if the x lines should be shown.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_set_show_ylines (UberGraph *graph, /* IN */
+ gboolean show_ylines) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->show_ylines = show_ylines;
+ priv->bg_dirty = TRUE;
+ gtk_widget_queue_draw(GTK_WIDGET(graph));
+}
+
+/**
+ * uber_graph_get_labels:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+GtkWidget*
+uber_graph_get_labels (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), NULL);
+
+ priv = graph->priv;
+ return priv->align;
+}
+
+/**
+ * uber_graph_scale_changed:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_scale_changed (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+ GdkRectangle rect;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ if (!priv->paused) {
+ priv->fg_dirty = TRUE;
+ priv->bg_dirty = TRUE;
+ priv->full_draw = TRUE;
+ gtk_widget_get_allocation(GTK_WIDGET(graph), &alloc);
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = alloc.width;
+ rect.height = alloc.height;
+ gdk_window_invalidate_rect(gtk_widget_get_window(GTK_WIDGET(graph)),
+ &rect, TRUE);
+ }
+}
+
+/**
+ * uber_graph_get_next_data:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static inline gboolean
+uber_graph_get_next_data (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ gboolean ret = TRUE;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), FALSE);
+
+ /*
+ * Get the current time for this data point. This is used to calculate
+ * the proper offset in the FPS callback.
+ */
+ priv = graph->priv;
+ g_get_current_time(&priv->dps_tv);
+ /*
+ * Notify the subclass to retrieve the data point.
+ */
+ if (UBER_GRAPH_GET_CLASS(graph)->get_next_data) {
+ ret = UBER_GRAPH_GET_CLASS(graph)->get_next_data(graph);
+ }
+ return ret;
+}
+
+/**
+ * uber_graph_init_texture:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_init_texture (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+ GdkWindow *window;
+ cairo_t *cr;
+ gint width;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ gtk_widget_get_allocation(GTK_WIDGET(graph), &alloc);
+ window = gtk_widget_get_window(GTK_WIDGET(graph));
+ /*
+ * Get drawable to base surface upon.
+ */
+ if (!window) {
+ g_critical("%s() called before GdkWindow is allocated.", G_STRFUNC);
+ return;
+ }
+ /*
+ * Initialize foreground and background surface.
+ */
+ width = MAX(priv->nonvis_rect.x + priv->nonvis_rect.width, alloc.width);
+ priv->fg_surface = gdk_window_create_similar_surface(window, CAIRO_CONTENT_COLOR_ALPHA, width, alloc.height);
+ /*
+ * Clear foreground contents.
+ */
+ cr = cairo_create(priv->fg_surface);
+ CLEAR_CAIRO(cr, alloc);
+ cairo_destroy(cr);
+}
+
+/**
+ * uber_graph_init_bg:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_init_bg (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+ GdkWindow *window;
+ cairo_t *cr;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ gtk_widget_get_allocation(GTK_WIDGET(graph), &alloc);
+ window = gtk_widget_get_window(GTK_WIDGET(graph));
+ /*
+ * Get drawable for surface.
+ */
+ if (!window) {
+ g_critical("%s() called before GdkWindow is allocated.", G_STRFUNC);
+ return;
+ }
+ /*
+ * Create the server-side surface.
+ */
+ priv->bg_surface = gdk_window_create_similar_surface(window, CAIRO_CONTENT_COLOR_ALPHA, alloc.width, alloc.height);
+ /*
+ * Clear background contents.
+ */
+ cr = cairo_create(priv->bg_surface);
+ CLEAR_CAIRO(cr, alloc);
+ cairo_destroy(cr);
+}
+
+/**
+ * uber_graph_calculate_rects:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_calculate_rects (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+ PangoLayout *layout;
+ PangoFontDescription *font_desc;
+ GdkWindow *window;
+ gint pango_width;
+ gint pango_height;
+ cairo_t *cr;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ gtk_widget_get_allocation(GTK_WIDGET(graph), &alloc);
+ window = gtk_widget_get_window(GTK_WIDGET(graph));
+ /*
+ * We can't calculate rectangles before we have a GdkWindow.
+ */
+ if (!window) {
+ return;
+ }
+ /*
+ * Determine the pixels required for labels.
+ */
+ cr = gdk_cairo_create(window);
+ layout = pango_cairo_create_layout(cr);
+ font_desc = pango_font_description_new();
+ pango_font_description_set_family_static(font_desc, "Monospace");
+ pango_font_description_set_size(font_desc, 6 * PANGO_SCALE);
+ pango_layout_set_font_description(layout, font_desc);
+ pango_layout_set_text(layout, "XXXXXXXXXX", -1);
+ pango_layout_get_pixel_size(layout, &pango_width, &pango_height);
+ pango_font_description_free(font_desc);
+ g_object_unref(layout);
+ cairo_destroy(cr);
+ /*
+ * Calculate content area rectangle.
+ */
+ priv->content_rect.x = priv->tick_len + pango_width + 1.5;
+ priv->content_rect.y = (pango_height / 2.) + 1.5;
+ priv->content_rect.width = alloc.width - priv->content_rect.x - 3.0;
+ priv->content_rect.height = alloc.height - priv->tick_len - pango_height
+ - (pango_height / 2.) - 3.0;
+ if (!priv->show_xlabels) {
+ priv->content_rect.height += pango_height;
+ }
+ /*
+ * Adjust label offset.
+ */
+ /*
+ * Calculate FPS/DPS adjustments.
+ */
+ priv->dps_each = ceil((gfloat)priv->content_rect.width
+ / (gfloat)(priv->x_slots - 1));
+ priv->fps_each = priv->dps_each
+ / ((gfloat)priv->fps / (gfloat)priv->dps);
+ /*
+ * XXX: Small hack to make things a bit smoother at small scales.
+ */
+ if (priv->fps_each < .5) {
+ priv->fps_each = 1;
+ priv->fps_real = (1000. / priv->dps_each) / 2.;
+ } else {
+ priv->fps_real = 1000. / priv->fps;
+ }
+ /*
+ * Update FPS callback.
+ */
+ if (priv->fps_handler) {
+ g_source_remove(priv->fps_handler);
+ priv->fps_handler = uber_frame_source_add(priv->fps,
+ (GSourceFunc)uber_graph_fps_timeout,
+ graph);
+ }
+ /*
+ * Calculate the non-visible area that drawing should happen within.
+ */
+ priv->nonvis_rect = priv->content_rect;
+ priv->nonvis_rect.width = priv->dps_each * priv->x_slots;
+ /*
+ * Update positioning for label alignment.
+ */
+ gtk_widget_set_margin_top(GTK_WIDGET(priv->align), 6);
+ gtk_widget_set_margin_bottom(GTK_WIDGET(priv->align), 6);
+ gtk_widget_set_margin_start(GTK_WIDGET(priv->align), priv->content_rect.x);
+ gtk_widget_set_margin_end(GTK_WIDGET(priv->align), 0);
+}
+
+/**
+ * uber_graph_get_show_xlabels:
+ * @graph: A #UberGraph.
+ *
+ * Retrieves if the X grid labels should be shown.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+gboolean
+uber_graph_get_show_xlabels (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), FALSE);
+
+ priv = graph->priv;
+ return priv->show_xlabels;
+}
+
+/**
+ * uber_graph_set_show_xlabels:
+ * @graph: A #UberGraph.
+ * @show_xlabels: Show x labels.
+ *
+ * Sets if the x labels should be shown.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_set_show_xlabels (UberGraph *graph, /* IN */
+ gboolean show_xlabels) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->show_xlabels = show_xlabels;
+ priv->bg_dirty = TRUE;
+ uber_graph_calculate_rects(graph);
+ gtk_widget_queue_draw(GTK_WIDGET(graph));
+}
+
+/**
+ * uber_graph_dps_timeout:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: %TRUE always.
+ * Side effects: None.
+ */
+static gboolean
+uber_graph_dps_timeout (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), FALSE);
+
+ priv = graph->priv;
+ if (!uber_graph_get_next_data(graph)) {
+ /*
+ * XXX: How should we handle failed data retrieval.
+ */
+ }
+ if (G_UNLIKELY(show_fps)) {
+ g_print("UberGraph[%p] %02d FPS\n", graph, priv->fps_count);
+ priv->fps_count = 0;
+ }
+ /*
+ * Make sure the content is re-rendered.
+ */
+ if (!priv->paused) {
+ priv->fg_dirty = TRUE;
+ /*
+ * We do not queue a draw here since the next FPS callback will happen
+ * when it is the right time to show the frame.
+ */
+ }
+ /*
+ * Try to downscale the graph. We do this whether or not we are paused
+ * as redrawing is deferred if we are in a paused state.
+ */
+ priv->dps_downscale++;
+ if (priv->dps_downscale >= 5) {
+ if (UBER_GRAPH_GET_CLASS(graph)->downscale) {
+ if (UBER_GRAPH_GET_CLASS(graph)->downscale(graph)) {
+ if (!priv->paused) {
+ uber_graph_redraw(graph);
+ }
+ }
+ }
+ priv->dps_downscale = 0;
+ }
+ return TRUE;
+}
+
+/**
+ * uber_graph_register_dps_handler:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_register_dps_handler (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ guint dps_freq;
+ gboolean do_now = TRUE;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ if (priv->dps_handler) {
+ g_source_remove(priv->dps_handler);
+ do_now = FALSE;
+ }
+ /*
+ * Calculate the update frequency.
+ */
+ dps_freq = 1000 / priv->dps;
+ /*
+ * Install the data handler.
+ */
+ priv->dps_handler = g_timeout_add(dps_freq,
+ (GSourceFunc)uber_graph_dps_timeout,
+ graph);
+ /*
+ * Call immediately.
+ */
+ if (do_now) {
+ uber_graph_dps_timeout(graph);
+ }
+}
+
+/**
+ * uber_graph_register_fps_handler:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_register_fps_handler (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ /*
+ * Remove any existing FPS handler.
+ */
+ if (priv->fps_handler) {
+ g_source_remove(priv->fps_handler);
+ }
+ /*
+ * Install the FPS timeout.
+ */
+ priv->fps_handler = uber_frame_source_add(priv->fps,
+ (GSourceFunc)uber_graph_fps_timeout,
+ graph);
+}
+
+/**
+ * uber_graph_set_dps:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_set_dps (UberGraph *graph, /* IN */
+ gfloat dps) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->dps = dps;
+ /*
+ * TODO: Does this belong somewhere else?
+ */
+ if (UBER_GRAPH_GET_CLASS(graph)->set_stride) {
+ UBER_GRAPH_GET_CLASS(graph)->set_stride(graph, priv->x_slots);
+ }
+ /*
+ * Recalculate frame rates and timeouts.
+ */
+ uber_graph_calculate_rects(graph);
+ uber_graph_register_dps_handler(graph);
+ uber_graph_register_fps_handler(graph);
+}
+
+/**
+ * uber_graph_set_fps:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_set_fps (UberGraph *graph, /* IN */
+ guint fps) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->fps = fps;
+ uber_graph_register_fps_handler(graph);
+}
+
+/**
+ * uber_graph_realize:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_realize (GtkWidget *widget) /* IN */
+{
+ UberGraph *graph;
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(widget));
+
+ graph = UBER_GRAPH(widget);
+ priv = graph->priv;
+ WIDGET_CLASS->realize(widget);
+ /*
+ * Calculate new layout based on allocation.
+ */
+ uber_graph_calculate_rects(graph);
+ /*
+ * Re-initialize textures for updated sizes.
+ */
+ UNSET_SURFACE(priv->bg_surface);
+ UNSET_SURFACE(priv->fg_surface);
+ uber_graph_init_bg(graph);
+ uber_graph_init_texture(graph);
+ /*
+ * Notify subclass of current data stride (points per graph).
+ */
+ if (UBER_GRAPH_GET_CLASS(widget)->set_stride) {
+ UBER_GRAPH_GET_CLASS(widget)->set_stride(UBER_GRAPH(widget),
+ priv->x_slots);
+ }
+ /*
+ * Install the data collector.
+ */
+ uber_graph_register_dps_handler(graph);
+}
+
+/**
+ * uber_graph_unrealize:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_unrealize (GtkWidget *widget) /* IN */
+{
+ UberGraph *graph;
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(widget));
+
+ graph = UBER_GRAPH(widget);
+ priv = graph->priv;
+ /*
+ * Unregister any data acquisition handlers.
+ */
+ if (priv->dps_handler) {
+ g_source_remove(priv->dps_handler);
+ priv->dps_handler = 0;
+ }
+ /*
+ * Destroy textures.
+ */
+ UNSET_SURFACE(priv->bg_surface);
+ UNSET_SURFACE(priv->fg_surface);
+}
+
+/**
+ * uber_graph_screen_changed:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_screen_changed (GtkWidget *widget, /* IN */
+ GdkScreen *old_screen) /* IN */
+{
+ UberGraphPrivate *priv;
+ GdkScreen *screen;
+ GdkVisual *visual;
+
+ g_return_if_fail(UBER_IS_GRAPH(widget));
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (widget));
+ visual = gdk_screen_get_rgba_visual (screen);
+
+ priv = UBER_GRAPH(widget)->priv;
+ /*
+ * Check if we have RGBA colormaps available.
+ */
+ priv->have_rgba = visual != NULL;
+}
+
+/**
+ * uber_graph_show:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_show (GtkWidget *widget) /* IN */
+{
+ g_return_if_fail(UBER_IS_GRAPH(widget));
+
+ WIDGET_CLASS->show(widget);
+ /*
+ * Only run the FPS timeout when we are visible.
+ */
+ uber_graph_register_fps_handler(UBER_GRAPH(widget));
+}
+
+/**
+ * uber_graph_hide:
+ * @widget: A #GtkWIdget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_hide (GtkWidget *widget) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(widget));
+
+ priv = UBER_GRAPH(widget)->priv;
+ /*
+ * Disable the FPS timeout when we are not visible.
+ */
+ if (priv->fps_handler) {
+ g_source_remove(priv->fps_handler);
+ priv->fps_handler = 0;
+ }
+}
+
+static inline void
+uber_graph_get_pixmap_rect (UberGraph *graph, /* IN */
+ GdkRectangle *rect) /* OUT */
+{
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ gtk_widget_get_allocation(GTK_WIDGET(graph), &alloc);
+ rect->x = 0;
+ rect->y = 0;
+ rect->width = MAX(alloc.width,
+ priv->nonvis_rect.x + priv->nonvis_rect.width);
+ rect->height = alloc.height;
+}
+
+/**
+ * uber_graph_render_fg:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_render_fg (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+ GdkRectangle rect;
+ cairo_t *cr;
+ gfloat each;
+ gfloat x_epoch;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ /*
+ * Acquire resources.
+ */
+ priv = graph->priv;
+ gtk_widget_get_allocation(GTK_WIDGET(graph), &alloc);
+ uber_graph_get_pixmap_rect(graph, &rect);
+ cr = cairo_create(priv->fg_surface);
+ /*
+ * Render to texture if needed.
+ */
+ if (priv->fg_dirty) {
+ /*
+ * Caclulate relative positionings for use in renderers.
+ */
+ each = priv->content_rect.width / (gfloat)(priv->x_slots - 1);
+ x_epoch = RECT_RIGHT(priv->nonvis_rect);
+ /*
+ * If we are in a fast draw, lets copy the content from the other
+ * buffer at the next offset.
+ */
+ if (!priv->full_draw && UBER_GRAPH_GET_CLASS(graph)->render_fast) {
+ /*
+ * Determine next rendering slot.
+ */
+ rect.x = priv->content_rect.x
+ + (priv->dps_each * priv->dps_slot);
+ rect.width = priv->dps_each;
+ rect.y = priv->content_rect.y;
+ rect.height = priv->content_rect.height;
+ priv->dps_slot = (priv->dps_slot + 1) % priv->x_slots;
+ x_epoch = RECT_RIGHT(rect);
+ /*
+ * Clear content area.
+ */
+ cairo_save(cr);
+ cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
+ gdk_cairo_rectangle(cr, &rect);
+ cairo_fill(cr);
+ cairo_restore(cr);
+
+#if 0
+ /*
+ * XXX: Draw line helper for debugging.
+ */
+ cairo_save(cr);
+ cairo_set_source_rgb(cr, .3, .3, .3);
+ cairo_rectangle(cr,
+ rect.x,
+ rect.y + (rect.height / (gfloat)priv->x_slots * priv->dps_slot),
+ rect.width,
+ rect.height / priv->x_slots);
+ cairo_fill(cr);
+ cairo_restore(cr);
+#endif
+
+ /*
+ * Render new content clipped.
+ */
+ cairo_save(cr);
+ cairo_reset_clip(cr);
+ gdk_cairo_rectangle(cr, &rect);
+ cairo_clip(cr);
+ /*
+ * Determine area for this draw.
+ */
+ UBER_GRAPH_GET_CLASS(graph)->render_fast(graph,
+ cr,
+ &rect,
+ x_epoch,
+ each + .5);
+ cairo_restore(cr);
+ } else {
+ /*
+ * Clear content area.
+ */
+ cairo_save(cr);
+ cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
+ gdk_cairo_rectangle(cr, &rect);
+ cairo_fill(cr);
+ cairo_restore(cr);
+ /*
+ * Draw the entire foreground.
+ */
+ if (UBER_GRAPH_GET_CLASS(graph)->render) {
+ priv->dps_slot = 0;
+ cairo_save(cr);
+ gdk_cairo_rectangle(cr, &priv->nonvis_rect);
+ cairo_clip(cr);
+ UBER_GRAPH_GET_CLASS(graph)->render(graph,
+ cr,
+ &priv->nonvis_rect,
+ x_epoch,
+ each);
+ cairo_restore(cr);
+ }
+ }
+ }
+ /*
+ * Foreground is no longer dirty.
+ */
+ priv->fg_dirty = FALSE;
+ priv->full_draw = FALSE;
+ /*
+ * Cleanup.
+ */
+ cairo_destroy(cr);
+}
+
+/**
+ * uber_graph_redraw:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_redraw (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->fg_dirty = TRUE;
+ priv->bg_dirty = TRUE;
+ priv->full_draw = TRUE;
+ gtk_widget_queue_draw(GTK_WIDGET(graph));
+}
+
+/**
+ * uber_graph_get_yrange:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static inline void
+uber_graph_get_yrange (UberGraph *graph, /* IN */
+ UberRange *range) /* OUT */
+{
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+ g_return_if_fail(range != NULL);
+
+ memset(range, 0, sizeof(*range));
+ if (UBER_GRAPH_GET_CLASS(graph)->get_yrange) {
+ UBER_GRAPH_GET_CLASS(graph)->get_yrange(graph, range);
+ }
+}
+
+/**
+ * uber_graph_set_format:
+ * @graph: A UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_set_format (UberGraph *graph, /* IN */
+ UberGraphFormat format) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->format = format;
+ priv->bg_dirty = TRUE;
+ gtk_widget_queue_draw(GTK_WIDGET(graph));
+}
+
+static void
+uber_graph_render_x_axis (UberGraph *graph, /* IN */
+ cairo_t *cr) /* IN */
+{
+ UberGraphPrivate *priv;
+ const gdouble dashes[] = { 1.0, 2.0 };
+ PangoFontDescription *fd;
+ PangoLayout *pl;
+ GtkStyleContext *style;
+ GdkRGBA fg_color;
+ gfloat each;
+ gfloat x;
+ gfloat y;
+ gfloat h;
+ gchar text[16] = { 0 };
+ gint count;
+ gint wi;
+ gint hi;
+ gint i;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ style = gtk_widget_get_style_context(GTK_WIDGET(graph));
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &fg_color);
+
+ count = priv->x_slots / 10;
+ each = priv->content_rect.width / (gfloat)count;
+ /*
+ * Draw ticks.
+ */
+ cairo_save(cr);
+ pl = pango_cairo_create_layout(cr);
+ fd = pango_font_description_new();
+ pango_font_description_set_family_static(fd, "Monospace");
+ pango_font_description_set_size(fd, 6 * PANGO_SCALE);
+ pango_layout_set_font_description(pl, fd);
+ gdk_cairo_set_source_rgba(cr, &fg_color);
+ cairo_set_line_width(cr, 1.0);
+ cairo_set_dash(cr, dashes, G_N_ELEMENTS(dashes), 0);
+ for (i = 0; i <= count; i++) {
+ x = RECT_RIGHT(priv->content_rect) - (gint)(i * each) + .5;
+ if (priv->show_xlines && (i != 0 && i != count)) {
+ y = priv->content_rect.y;
+ h = priv->content_rect.height + priv->tick_len;
+ } else {
+ y = priv->content_rect.y + priv->content_rect.height;
+ h = priv->tick_len;
+ }
+ if (i != 0 && i != count) {
+ cairo_move_to(cr, x, y);
+ cairo_line_to(cr, x, y + h);
+ cairo_stroke(cr);
+ }
+ /*
+ * Render the label.
+ */
+ if (priv->show_xlabels) {
+ g_snprintf(text, sizeof(text), "%d", i * 10);
+ pango_layout_set_text(pl, text, -1);
+ pango_layout_get_pixel_size(pl, &wi, &hi);
+ if (i != 0 && i != count) {
+ cairo_move_to(cr, x - (wi / 2), y + h);
+ } else if (i == 0) {
+ cairo_move_to(cr,
+ RECT_RIGHT(priv->content_rect) - (wi / 2),
+ RECT_BOTTOM(priv->content_rect) + priv->tick_len);
+ } else if (i == count) {
+ cairo_move_to(cr,
+ priv->content_rect.x - (wi / 2),
+ RECT_BOTTOM(priv->content_rect) + priv->tick_len);
+ }
+ pango_cairo_show_layout(cr, pl);
+ }
+ }
+ g_object_unref(pl);
+ pango_font_description_free(fd);
+ cairo_restore(cr);
+}
+
+static void G_GNUC_PRINTF(6, 7)
+uber_graph_render_y_line (UberGraph *graph, /* IN */
+ cairo_t *cr, /* IN */
+ gint y, /* IN */
+ gboolean tick_only, /* IN */
+ gboolean no_label, /* IN */
+ const gchar *format, /* IN */
+ ...) /* IN */
+{
+ UberGraphPrivate *priv;
+ const gdouble dashes[] = { 1.0, 2.0 };
+ PangoFontDescription *fd;
+ PangoLayout *pl;
+ GtkStyleContext *style;
+ GdkRGBA fg_color;
+ va_list args;
+ gchar *text;
+ gint width;
+ gint height;
+ gfloat real_y = y + .5;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+ g_return_if_fail(cr != NULL);
+ g_return_if_fail(format != NULL);
+
+ priv = graph->priv;
+ style = gtk_widget_get_style_context(GTK_WIDGET(graph));
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &fg_color);
+ /*
+ * Draw grid line.
+ */
+ cairo_save(cr);
+ cairo_set_dash(cr, dashes, G_N_ELEMENTS(dashes), 0);
+ cairo_set_line_width(cr, 1.0);
+ gdk_cairo_set_source_rgba(cr, &fg_color);
+ cairo_move_to(cr, priv->content_rect.x - priv->tick_len, real_y);
+ if (tick_only) {
+ cairo_line_to(cr, priv->content_rect.x, real_y);
+ } else {
+ cairo_line_to(cr, RECT_RIGHT(priv->content_rect), real_y);
+ }
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ /*
+ * Show label.
+ */
+ if (!no_label) {
+ cairo_save(cr);
+ gdk_cairo_set_source_rgba(cr, &fg_color);
+ /*
+ * Format text.
+ */
+ va_start(args, format);
+ text = g_strdup_vprintf(format, args);
+ va_end(args);
+ /*
+ * Render pango layout.
+ */
+ pl = pango_cairo_create_layout(cr);
+ fd = pango_font_description_new();
+ pango_font_description_set_family_static(fd, "Monospace");
+ pango_font_description_set_size(fd, 6 * PANGO_SCALE);
+ pango_layout_set_font_description(pl, fd);
+ pango_layout_set_text(pl, text, -1);
+ pango_layout_get_pixel_size(pl, &width, &height);
+ cairo_move_to(cr, priv->content_rect.x - priv->tick_len - width - 3,
+ real_y - height / 2);
+ pango_cairo_show_layout(cr, pl);
+ /*
+ * Cleanup resources.
+ */
+ g_free(text);
+ pango_font_description_free(fd);
+ g_object_unref(pl);
+ cairo_restore(cr);
+ }
+}
+
+static void
+uber_graph_render_y_axis_percent (UberGraph *graph, /* IN */
+ cairo_t *cr, /* IN */
+ UberRange *range, /* IN */
+ UberRange *pixel_range, /* IN */
+ gint n_lines) /* IN */
+{
+ static const gchar format[] = "%0.0f %%";
+ UberGraphPrivate *priv;
+ gdouble value;
+ gdouble y;
+ gint i;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+ g_return_if_fail(range != NULL);
+ g_return_if_fail(pixel_range != NULL);
+ g_return_if_fail(n_lines >= 0);
+ g_return_if_fail(n_lines < 6);
+
+ priv = graph->priv;
+ /*
+ * Render top and bottom lines.
+ */
+ uber_graph_render_y_line(graph, cr,
+ priv->content_rect.y - 1,
+ TRUE, FALSE, format, 100.);
+ uber_graph_render_y_line(graph, cr,
+ RECT_BOTTOM(priv->content_rect),
+ TRUE, FALSE, format, 0.);
+ /*
+ * Render lines between the edges.
+ */
+ for (i = 1; i < n_lines; i++) {
+ value = (n_lines - i) / (gfloat)n_lines;
+ y = pixel_range->end - (pixel_range->range * value);
+ value *= 100.;
+ uber_graph_render_y_line(graph, cr, y,
+ !priv->show_ylines, FALSE,
+ format, value);
+ }
+}
+
+/**
+ * uber_graph_render_y_axis_direct:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_render_y_axis_direct (UberGraph *graph, /* IN */
+ cairo_t *cr, /* IN */
+ UberRange *range, /* IN */
+ UberRange *pixel_range, /* IN */
+ gint n_lines, /* IN */
+ gboolean kibi) /* IN */
+{
+ static const gchar format[] = "%0.1f%s";
+ const gchar *modifier = "";
+ UberGraphPrivate *priv;
+ gdouble value;
+ gdouble y;
+ gint i;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+#define CONDENSE(v) \
+G_STMT_START { \
+ if (kibi) { \
+ if ((v) >= 1073741824.) { \
+ (v) /= 1073741824.; \
+ modifier = " Gi"; \
+ } else if ((v) >= 1048576.) { \
+ (v) /= 1048576.; \
+ modifier = " Mi"; \
+ } else if ((v) >= 1024.) { \
+ (v) /= 1024.; \
+ modifier = " Ki"; \
+ } else { \
+ modifier = ""; \
+ } \
+ } else { \
+ if ((v) >= 1000000000.) { \
+ (v) /= 1000000000.; \
+ modifier = " G"; \
+ } else if ((v) >= 1000000.) { \
+ (v) /= 1000000.; \
+ modifier = " M"; \
+ } else if ((v) >= 1000.) { \
+ (v) /= 1000.; \
+ modifier = " K"; \
+ } else { \
+ modifier = ""; \
+ } \
+ } \
+} G_STMT_END
+
+ priv = graph->priv;
+ /*
+ * Render top and bottom lines.
+ */
+ value = range->end;
+ CONDENSE(value);
+ uber_graph_render_y_line(graph, cr,
+ priv->content_rect.y - 1,
+ TRUE, FALSE, format, value, modifier);
+ value = range->begin;
+ CONDENSE(value);
+ uber_graph_render_y_line(graph, cr,
+ RECT_BOTTOM(priv->content_rect),
+ TRUE, FALSE, format, value, modifier);
+ /*
+ * Render lines between the edges.
+ */
+ for (i = 1; i < n_lines; i++) {
+ y = value = range->range / (gfloat)(n_lines) * (gfloat)i;
+ /*
+ * TODO: Use proper scale.
+ */
+ uber_scale_linear(range, pixel_range, &y, NULL);
+ if (y == 0 || y == pixel_range->begin || y == pixel_range->end) {
+ continue;
+ }
+ y = pixel_range->end - y;
+ CONDENSE(value);
+ uber_graph_render_y_line(graph, cr, y,
+ !priv->show_ylines,
+ (range->begin == range->end),
+ format, value, modifier);
+ }
+}
+
+/**
+ * uber_graph_render_y_axis:
+ * @graph: A #UberGraph.
+ *
+ * Render the Y axis grid lines and labels.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_render_y_axis (UberGraph *graph, /* IN */
+ cairo_t *cr) /* IN */
+{
+ UberGraphPrivate *priv;
+ UberRange pixel_range;
+ UberRange range;
+ gint n_lines;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ /*
+ * Calculate ranges.
+ */
+ uber_graph_get_yrange(graph, &range);
+ pixel_range.begin = priv->content_rect.y;
+ pixel_range.end = priv->content_rect.y + priv->content_rect.height;
+ pixel_range.range = pixel_range.end - pixel_range.begin;
+ /*
+ * Render grid lines for given format.
+ */
+ n_lines = MIN(priv->content_rect.height / 25, 5);
+ switch (priv->format) {
+ case UBER_GRAPH_FORMAT_PERCENT:
+ uber_graph_render_y_axis_percent(graph, cr, &range, &pixel_range,
+ n_lines);
+ break;
+ case UBER_GRAPH_FORMAT_DIRECT:
+ uber_graph_render_y_axis_direct(graph, cr, &range, &pixel_range,
+ n_lines, FALSE);
+ break;
+ case UBER_GRAPH_FORMAT_DIRECT1024:
+ uber_graph_render_y_axis_direct(graph, cr, &range, &pixel_range,
+ n_lines, TRUE);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+/**
+ * uber_graph_render_bg:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_render_bg (UberGraph *graph) /* IN */
+{
+ const gdouble dashes[] = { 1.0, 2.0 };
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+ GtkStyleContext *style;
+ GdkRGBA fg_color;
+ GdkRGBA light_color;
+ cairo_t *cr;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ /*
+ * Acquire resources.
+ */
+ priv = graph->priv;
+ gtk_widget_get_allocation(GTK_WIDGET(graph), &alloc);
+ style = gtk_widget_get_style_context(GTK_WIDGET(graph));
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &fg_color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_SELECTED, &light_color);
+ cr = cairo_create(priv->bg_surface);
+ /*
+ * Ensure valid resources.
+ */
+ g_assert(style);
+ g_assert(priv->bg_surface);
+ /*
+ * Clear entire background. Hopefully this looks okay for RGBA themes
+ * that are translucent.
+ */
+ cairo_save(cr);
+ cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
+ cairo_rectangle(cr, 0, 0, alloc.width, alloc.height);
+ cairo_fill(cr);
+ cairo_restore(cr);
+ /*
+ * Paint the content area background.
+ */
+ cairo_save(cr);
+ gdk_cairo_set_source_rgba(cr, &light_color);
+ gdk_cairo_rectangle(cr, &priv->content_rect);
+ cairo_fill(cr);
+ cairo_restore(cr);
+ /*
+ * Stroke the border around the content area.
+ */
+ cairo_save(cr);
+ gdk_cairo_set_source_rgba(cr, &fg_color);
+ cairo_set_line_width(cr, 1.0);
+ cairo_set_dash(cr, dashes, G_N_ELEMENTS(dashes), 0);
+ cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
+ cairo_rectangle(cr,
+ priv->content_rect.x - .5,
+ priv->content_rect.y - .5,
+ priv->content_rect.width + 1.0,
+ priv->content_rect.height + 1.0);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+ /*
+ * Render the axis ticks.
+ */
+ uber_graph_render_y_axis(graph, cr);
+ uber_graph_render_x_axis(graph, cr);
+ /*
+ * Background is no longer dirty.
+ */
+ priv->bg_dirty = FALSE;
+ /*
+ * Cleanup.
+ */
+ cairo_destroy(cr);
+}
+
+static inline void
+g_time_val_subtract (GTimeVal *a, /* IN */
+ GTimeVal *b, /* IN */
+ GTimeVal *c) /* OUT */
+{
+ g_return_if_fail(a != NULL);
+ g_return_if_fail(b != NULL);
+ g_return_if_fail(c != NULL);
+
+ c->tv_sec = a->tv_sec - b->tv_sec;
+ c->tv_usec = a->tv_usec - b->tv_usec;
+ if (c->tv_usec < 0) {
+ c->tv_usec += G_USEC_PER_SEC;
+ c->tv_sec -= 1;
+ }
+}
+
+/**
+ * uber_graph_get_fps_offset:
+ * @graph: A #UberGraph.
+ *
+ * Calculates the number of pixels that the foreground should be rendered
+ * from the origin.
+ *
+ * Returns: The pixel offset to render the foreground.
+ * Side effects: None.
+ */
+static gfloat
+uber_graph_get_fps_offset (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+ GTimeVal rel = { 0 };
+ GTimeVal tv;
+ gfloat f;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(graph), 0.);
+
+ priv = graph->priv;
+ g_get_current_time(&tv);
+ g_time_val_subtract(&tv, &priv->dps_tv, &rel);
+ f = ((rel.tv_sec * 1000) + (rel.tv_usec / 1000))
+ / (1000. / priv->dps) /* MSec Per Data Point */
+ * priv->dps_each; /* Pixels Per Data Point */
+ return MIN(f, (priv->dps_each - priv->fps_each));
+}
+
+/**
+ * uber_graph_draw:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: %FALSE always.
+ * Side effects: None.
+ */
+static gboolean
+uber_graph_draw (GtkWidget *widget, /* IN */
+ cairo_t *cr) /* IN */
+{
+ UberGraphPrivate *priv;
+ GtkAllocation alloc;
+// cairo_t *cr;
+ gfloat offset;
+ gint x;
+
+ g_return_val_if_fail(UBER_IS_GRAPH(widget), FALSE);
+
+ priv = UBER_GRAPH(widget)->priv;
+ gtk_widget_get_allocation(widget, &alloc);
+ priv->fps_count++;
+ /*
+ * Ensure that the texture is initialized.
+ */
+ g_assert(priv->fg_surface);
+ g_assert(priv->bg_surface);
+ /*
+ * Clear window background.
+ */
+#if 0
+ gdk_window_clear_area(expose->window,
+ expose->area.x,
+ expose->area.y,
+ expose->area.width,
+ expose->area.height);
+ /*
+ * Allocate resources.
+ */
+ cr = gdk_cairo_create(expose->window);
+ /*
+ * Clip to exposure area.
+ */
+ gdk_cairo_rectangle(cr, &expose->area);
+ cairo_clip(cr);
+#endif
+ /*
+ * Render background or foreground if needed.
+ */
+ if (priv->bg_dirty) {
+ uber_graph_render_bg(UBER_GRAPH(widget));
+ }
+ if (priv->fg_dirty) {
+ uber_graph_render_fg(UBER_GRAPH(widget));
+ }
+ /*
+ * Paint the background to the exposure area.
+ */
+ cairo_save(cr);
+ cairo_set_source_surface(cr, priv->bg_surface, 0, 0);
+ cairo_rectangle(cr, 0, 0, alloc.width, alloc.height);
+ cairo_fill(cr);
+ cairo_restore(cr);
+ /*
+ * Draw the foreground.
+ */
+ offset = uber_graph_get_fps_offset(UBER_GRAPH(widget));
+ if (priv->have_rgba) {
+ cairo_save(cr);
+ /*
+ * Clip exposure to the content area.
+ */
+ cairo_reset_clip(cr);
+ gdk_cairo_rectangle(cr, &priv->content_rect);
+ cairo_clip(cr);
+ /*
+ * Data in the fg surface is a ring bufer. Render the first portion
+ * at its given offset.
+ */
+ x = ((priv->x_slots - priv->dps_slot) * priv->dps_each) - offset;
+ cairo_set_source_surface(cr, priv->fg_surface, (gint)x, 0);
+ gdk_cairo_rectangle(cr, &priv->content_rect);
+ cairo_fill(cr);
+ /*
+ * Render the second part of the ring surface buffer.
+ */
+ x = (priv->dps_each * -priv->dps_slot) - offset;
+ cairo_set_source_surface(cr, priv->fg_surface, (gint)x, 0);
+ gdk_cairo_rectangle(cr, &priv->content_rect);
+ cairo_fill(cr);
+ /*
+ * Cleanup.
+ */
+ cairo_restore(cr);
+ } else {
+ /*
+ * TODO: Use XOR command for fallback.
+ */
+ g_warn_if_reached();
+ }
+ /*
+ * Cleanup resources.
+ */
+ //cairo_destroy(cr);
+ return FALSE;
+}
+
+/**
+ * uber_graph_style_set:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_style_set (GtkWidget *widget, /* IN */
+ GtkStyle *old_style) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(widget));
+
+ priv = UBER_GRAPH(widget)->priv;
+ WIDGET_CLASS->style_set(widget, old_style);
+ priv->fg_dirty = TRUE;
+ priv->bg_dirty = TRUE;
+ gtk_widget_queue_draw(widget);
+}
+
+/**
+ * uber_graph_size_allocate:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_size_allocate (GtkWidget *widget, /* IN */
+ GtkAllocation *alloc) /* IN */
+{
+ UberGraph *graph;
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(widget));
+
+ graph = UBER_GRAPH(widget);
+ priv = graph->priv;
+ WIDGET_CLASS->size_allocate(widget, alloc);
+ /*
+ * If there is no window yet, we can defer setup.
+ */
+ if (!gtk_widget_get_window(widget)) {
+ return;
+ }
+ /*
+ * Recalculate rectangles.
+ */
+ uber_graph_calculate_rects(graph);
+ /*
+ * Recreate server side surface.
+ */
+ UNSET_SURFACE(priv->bg_surface);
+ UNSET_SURFACE(priv->fg_surface);
+ uber_graph_init_bg(graph);
+ uber_graph_init_texture(graph);
+ /*
+ * Mark foreground and background as dirty.
+ */
+ priv->fg_dirty = TRUE;
+ priv->bg_dirty = TRUE;
+ priv->full_draw = TRUE;
+ gtk_widget_queue_draw(widget);
+}
+
+static void
+uber_graph_size_request (GtkWidget *widget, /* IN */
+ GtkRequisition *req) /* OUT */
+{
+ g_return_if_fail(req != NULL);
+
+ req->width = 150;
+ req->height = 50;
+}
+
+static void
+uber_graph_get_preferred_width (GtkWidget *widget, /* IN */
+ gint *minimal_width,
+ gint *natural_width)
+{
+ GtkRequisition requisition;
+ uber_graph_size_request(widget, &requisition);
+ *minimal_width = * natural_width = requisition.width;
+}
+
+static void
+uber_graph_get_preferred_height (GtkWidget *widget, /* IN */
+ gint *minimal_height,
+ gint *natural_height)
+{
+ GtkRequisition requisition;
+ uber_graph_size_request(widget, &requisition);
+ *minimal_height = * natural_height = requisition.height;
+}
+
+/**
+ * uber_graph_add_label:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_graph_add_label (UberGraph *graph, /* IN */
+ UberLabel *label) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+ g_return_if_fail(UBER_IS_LABEL(label));
+
+ priv = graph->priv;
+ gtk_box_pack_start(GTK_BOX(priv->labels), GTK_WIDGET(label),
+ TRUE, TRUE, 0);
+ gtk_widget_show(GTK_WIDGET(label));
+}
+
+/**
+ * uber_graph_take_screenshot:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_take_screenshot (UberGraph *graph) /* IN */
+{
+ GtkWidget *widget;
+ GtkWidget *dialog;
+ GtkAllocation alloc;
+ const gchar *filename;
+ cairo_status_t status;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ widget = GTK_WIDGET(graph);
+ gtk_widget_get_allocation(widget, &alloc);
+
+ /*
+ * Create save dialog and ask user for filename.
+ */
+ dialog = gtk_file_chooser_dialog_new(_("Save As"),
+ GTK_WINDOW(gtk_widget_get_toplevel(widget)),
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Save"), GTK_RESPONSE_ACCEPT,
+ NULL);
+ if (GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog))) {
+ /*
+ * Create surface and cairo context.
+ */
+ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ alloc.width, alloc.height);
+ cr = cairo_create(surface);
+ cairo_rectangle(cr, 0, 0, alloc.width, alloc.height);
+ cairo_clip(cr);
+
+ /* Paint to the image surface instead of the screen */
+ uber_graph_draw(widget, cr);
+
+ /*
+ * Save surface to png.
+ */
+ filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+ status = cairo_surface_write_to_png(surface, filename);
+ if (status != CAIRO_STATUS_SUCCESS) {
+ g_critical("Failed to save pixmap to file.");
+ goto cleanup;
+ }
+ /*
+ * Cleanup resources.
+ */
+ cleanup:
+ cairo_destroy(cr);
+ cairo_surface_destroy(surface);
+ }
+ gtk_widget_destroy(dialog);
+}
+
+/**
+ * uber_graph_toggle_paused:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_toggle_paused (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->paused = !priv->paused;
+ if (priv->fps_handler) {
+ g_source_remove(priv->fps_handler);
+ priv->fps_handler = 0;
+ } else {
+ if (!priv->paused) {
+ uber_graph_redraw(graph);
+ }
+ uber_graph_register_fps_handler(graph);
+ }
+}
+
+/**
+ * uber_graph_button_press:
+ * @widget: A #GtkWidget.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static gboolean
+uber_graph_button_press_event (GtkWidget *widget, /* IN */
+ GdkEventButton *button) /* IN */
+{
+ g_return_val_if_fail(UBER_IS_GRAPH(widget), FALSE);
+
+ switch (button->button) {
+ case 2: /* Middle Click */
+ if (button->state & GDK_CONTROL_MASK) {
+ uber_graph_take_screenshot(UBER_GRAPH(widget));
+ } else {
+ uber_graph_toggle_paused(UBER_GRAPH(widget));
+ }
+ break;
+ default:
+ break;
+ }
+ return FALSE;
+}
+
+/**
+ * uber_graph_finalize:
+ * @object: A #UberGraph.
+ *
+ * Finalizer for a #UberGraph instance. Frees any resources held by
+ * the instance.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_finalize (GObject *object) /* IN */
+{
+ G_OBJECT_CLASS(uber_graph_parent_class)->finalize(object);
+}
+
+/**
+ * uber_graph_dispose:
+ * @object: A #GObject.
+ *
+ * Dispose callback for @object. This method releases references held
+ * by the #GObject instance.
+ *
+ * Returns: None.
+ * Side effects: Plenty.
+ */
+static void
+uber_graph_dispose (GObject *object) /* IN */
+{
+ UberGraph *graph;
+ UberGraphPrivate *priv;
+
+ graph = UBER_GRAPH(object);
+ priv = graph->priv;
+ /*
+ * Stop any timeout handlers.
+ */
+ if (priv->fps_handler) {
+ g_source_remove(priv->fps_handler);
+ }
+ if (priv->dps_handler) {
+ g_source_remove(priv->dps_handler);
+ }
+ /*
+ * Destroy textures.
+ */
+ UNSET_SURFACE(priv->bg_surface);
+ UNSET_SURFACE(priv->fg_surface);
+ /*
+ * Call base class.
+ */
+ G_OBJECT_CLASS(uber_graph_parent_class)->dispose(object);
+}
+
+/**
+ * uber_graph_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (out): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Get a given #GObject property.
+ */
+static void
+uber_graph_get_property (GObject *object, /* IN */
+ guint prop_id, /* IN */
+ GValue *value, /* OUT */
+ GParamSpec *pspec) /* IN */
+{
+ UberGraph *graph = UBER_GRAPH(object);
+
+ switch (prop_id) {
+ case PROP_FORMAT:
+ g_value_set_uint(value, graph->priv->format);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+/**
+ * uber_graph_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (in): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Set a given #GObject property.
+ */
+static void
+uber_graph_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ UberGraph *graph = UBER_GRAPH(object);
+
+ switch (prop_id) {
+ case PROP_FORMAT:
+ uber_graph_set_format(graph, g_value_get_uint(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+/**
+ * uber_graph_class_init:
+ * @klass: A #UberGraphClass.
+ *
+ * Initializes the #UberGraphClass and prepares the vtable.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_class_init (UberGraphClass *klass) /* IN */
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ object_class = G_OBJECT_CLASS(klass);
+ object_class->dispose = uber_graph_dispose;
+ object_class->finalize = uber_graph_finalize;
+ object_class->get_property = uber_graph_get_property;
+ object_class->set_property = uber_graph_set_property;
+
+ widget_class = GTK_WIDGET_CLASS(klass);
+ widget_class->draw = uber_graph_draw;
+ widget_class->hide = uber_graph_hide;
+ widget_class->realize = uber_graph_realize;
+ widget_class->screen_changed = uber_graph_screen_changed;
+ widget_class->show = uber_graph_show;
+ widget_class->size_allocate = uber_graph_size_allocate;
+ widget_class->style_set = uber_graph_style_set;
+ widget_class->unrealize = uber_graph_unrealize;
+ widget_class->get_preferred_width = uber_graph_get_preferred_width;
+ widget_class->get_preferred_height = uber_graph_get_preferred_height;
+ widget_class->button_press_event = uber_graph_button_press_event;
+
+ show_fps = !!g_getenv("UBER_SHOW_FPS");
+
+ /*
+ * FIXME: Use enum.
+ */
+ g_object_class_install_property(object_class,
+ PROP_FORMAT,
+ g_param_spec_uint("format",
+ "format",
+ "format",
+ 0,
+ UBER_GRAPH_FORMAT_PERCENT,
+ 0,
+ G_PARAM_READWRITE));
+}
+
+/**
+ * uber_graph_init:
+ * @graph: A #UberGraph.
+ *
+ * Initializes the newly created #UberGraph instance.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_graph_init (UberGraph *graph) /* IN */
+{
+ UberGraphPrivate *priv;
+
+ /*
+ * Store pointer to private data allocation.
+ */
+ graph->priv = uber_graph_get_instance_private(graph);
+
+ priv = graph->priv;
+ /*
+ * Enable required events.
+ */
+ gtk_widget_set_events(GTK_WIDGET(graph), GDK_BUTTON_PRESS_MASK);
+ /*
+ * Prepare default values.
+ */
+ priv->tick_len = 10;
+ priv->fps = 20;
+ priv->fps_real = 1000. / priv->fps;
+ priv->dps = 1.;
+ priv->x_slots = 60;
+ priv->fg_dirty = TRUE;
+ priv->bg_dirty = TRUE;
+ priv->full_draw = TRUE;
+ priv->show_xlines = TRUE;
+ priv->show_ylines = TRUE;
+ /*
+ * TODO: Support labels in a grid.
+ */
+ priv->labels = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
+ gtk_box_set_homogeneous (GTK_BOX(priv->labels), TRUE);
+ priv->align = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add(GTK_CONTAINER(priv->align), priv->labels);
+ gtk_widget_show(priv->labels);
+}
diff --git a/deps/uber-graph/uber-graph.h b/deps/uber-graph/uber-graph.h
new file mode 100644
index 00000000..2c452d25
--- /dev/null
+++ b/deps/uber-graph/uber-graph.h
@@ -0,0 +1,104 @@
+/* uber-graph.h
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UBER_GRAPH_H__
+#define __UBER_GRAPH_H__
+
+#include <gtk/gtk.h>
+
+#include "uber-range.h"
+#include "uber-label.h"
+
+G_BEGIN_DECLS
+
+#define UBER_TYPE_GRAPH (uber_graph_get_type())
+#define UBER_GRAPH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UBER_TYPE_GRAPH, UberGraph))
+#define UBER_GRAPH_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UBER_TYPE_GRAPH, UberGraph const))
+#define UBER_GRAPH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UBER_TYPE_GRAPH, UberGraphClass))
+#define UBER_IS_GRAPH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UBER_TYPE_GRAPH))
+#define UBER_IS_GRAPH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UBER_TYPE_GRAPH))
+#define UBER_GRAPH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UBER_TYPE_GRAPH, UberGraphClass))
+
+typedef enum
+{
+ UBER_GRAPH_FORMAT_DIRECT,
+ UBER_GRAPH_FORMAT_DIRECT1024,
+ UBER_GRAPH_FORMAT_PERCENT,
+} UberGraphFormat;
+
+typedef struct _UberGraph UberGraph;
+typedef struct _UberGraphClass UberGraphClass;
+typedef struct _UberGraphPrivate UberGraphPrivate;
+
+struct _UberGraph
+{
+ GtkDrawingArea parent;
+
+ /*< private >*/
+ UberGraphPrivate *priv;
+};
+
+struct _UberGraphClass
+{
+ GtkDrawingAreaClass parent_class;
+
+ gboolean (*downscale) (UberGraph *graph);
+ gboolean (*get_next_data) (UberGraph *graph);
+ void (*get_yrange) (UberGraph *graph,
+ UberRange *range);
+ void (*render) (UberGraph *graph,
+ cairo_t *cairo,
+ GdkRectangle *content_area,
+ guint epoch,
+ gfloat each);
+ void (*render_fast) (UberGraph *graph,
+ cairo_t *cairo,
+ GdkRectangle *content_area,
+ guint epoch,
+ gfloat each);
+ void (*set_stride) (UberGraph *graph,
+ guint stride);
+};
+
+GType uber_graph_get_type (void) G_GNUC_CONST;
+void uber_graph_set_dps (UberGraph *graph,
+ gfloat dps);
+void uber_graph_set_fps (UberGraph *graph,
+ guint fps);
+void uber_graph_redraw (UberGraph *graph);
+void uber_graph_set_format (UberGraph *graph,
+ UberGraphFormat format);
+GtkWidget* uber_graph_get_labels (UberGraph *graph);
+void uber_graph_get_content_area (UberGraph *graph,
+ GdkRectangle *rect);
+void uber_graph_add_label (UberGraph *graph,
+ UberLabel *label);
+gboolean uber_graph_get_show_xlines (UberGraph *graph);
+void uber_graph_set_show_xlines (UberGraph *graph,
+ gboolean show_xlines);
+gboolean uber_graph_get_show_xlabels (UberGraph *graph);
+void uber_graph_set_show_xlabels (UberGraph *graph,
+ gboolean show_xlabels);
+gboolean uber_graph_get_show_ylines (UberGraph *graph);
+void uber_graph_set_show_ylines (UberGraph *graph,
+ gboolean show_ylines);
+void uber_graph_scale_changed (UberGraph *graph);
+
+G_END_DECLS
+
+#endif /* __UBER_GRAPH_H__ */
diff --git a/deps/uber-graph/uber-heat-map.c b/deps/uber-graph/uber-heat-map.c
new file mode 100644
index 00000000..6c2caa3b
--- /dev/null
+++ b/deps/uber-graph/uber-heat-map.c
@@ -0,0 +1,347 @@
+/* uber-heat-map.c
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include "uber-heat-map.h"
+#include "g-ring.h"
+
+/**
+ * SECTION:uber-heat-map.h
+ * @title: UberHeatMap
+ * @short_description:
+ *
+ * Section overview.
+ */
+
+struct _UberHeatMapPrivate
+{
+ GRing *raw_data;
+ gboolean fg_color_set;
+ GdkRGBA fg_color;
+ UberHeatMapFunc func;
+ GDestroyNotify func_destroy;
+ gpointer func_user_data;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE(UberHeatMap, uber_heat_map, UBER_TYPE_GRAPH)
+
+
+/**
+ * uber_heat_map_new:
+ *
+ * Creates a new instance of #UberHeatMap.
+ *
+ * Returns: the newly created instance of #UberHeatMap.
+ * Side effects: None.
+ */
+GtkWidget*
+uber_heat_map_new (void)
+{
+ UberHeatMap *map;
+
+ map = g_object_new(UBER_TYPE_HEAT_MAP, NULL);
+ return GTK_WIDGET(map);
+}
+
+/**
+ * uber_heat_map_destroy_array:
+ * @array: A #GArray.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_heat_map_destroy_array (gpointer data) /* IN */
+{
+ GArray **ar = data;
+
+ if (ar) {
+ g_array_unref(*ar);
+ }
+}
+
+/**
+ * uber_heat_map_set_stride:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_heat_map_set_stride (UberGraph *graph, /* IN */
+ guint stride) /* IN */
+{
+ UberHeatMapPrivate *priv;
+
+ g_return_if_fail(UBER_IS_HEAT_MAP(graph));
+
+ priv = UBER_HEAT_MAP(graph)->priv;
+ if (priv->raw_data) {
+ g_ring_unref(priv->raw_data);
+ }
+ priv->raw_data = g_ring_sized_new(sizeof(GArray*), stride,
+ uber_heat_map_destroy_array);
+}
+
+/**
+ * uber_heat_map_set_data_func:
+ * @map: A #UberHeatMap.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_heat_map_set_data_func (UberHeatMap *map, /* IN */
+ UberHeatMapFunc func, /* IN */
+ gpointer user_data, /* IN */
+ GDestroyNotify destroy) /* IN */
+{
+ UberHeatMapPrivate *priv;
+
+ g_return_if_fail(UBER_IS_HEAT_MAP(map));
+
+ priv = map->priv;
+ if (priv->func_destroy) {
+ priv->func_destroy(priv->func_user_data);
+ }
+ priv->func = func;
+ priv->func_destroy = destroy;
+ priv->func_user_data = user_data;
+}
+
+/**
+ * uber_heat_map_render:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_heat_map_render (UberGraph *graph, /* IN */
+ cairo_t *cr, /* IN */
+ GdkRectangle *area, /* IN */
+ guint epoch, /* IN */
+ gfloat each) /* IN */
+{
+#if 0
+ UberGraphPrivate *priv;
+ cairo_pattern_t *cp;
+
+ g_return_if_fail(UBER_IS_HEAT_MAP(graph));
+
+ priv = graph->priv;
+ /*
+ * XXX: Temporarily draw a nice little gradient to test sliding.
+ */
+ cp = cairo_pattern_create_linear(0, 0, area->width, 0);
+ cairo_pattern_add_color_stop_rgb(cp, 0, .1, .1, .1);
+ cairo_pattern_add_color_stop_rgb(cp, .2, .3, .3, .5);
+ cairo_pattern_add_color_stop_rgb(cp, .4, .2, .7, .4);
+ cairo_pattern_add_color_stop_rgb(cp, .7, .6, .2, .1);
+ cairo_pattern_add_color_stop_rgb(cp, .8, .6, .8, .1);
+ cairo_pattern_add_color_stop_rgb(cp, 1., .3, .8, .5);
+ gdk_cairo_rectangle(cr, area);
+ cairo_set_source(cr, cp);
+ cairo_fill(cr);
+ cairo_pattern_destroy(cp);
+#endif
+}
+
+/**
+ * uber_heat_map_render_fast:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_heat_map_render_fast (UberGraph *graph, /* IN */
+ cairo_t *cr, /* IN */
+ GdkRectangle *area, /* IN */
+ guint epoch, /* IN */
+ gfloat each) /* IN */
+{
+ UberHeatMapPrivate *priv;
+ GtkStyleContext *style;
+ GdkRGBA color;
+ gfloat height;
+ gint i;
+
+ g_return_if_fail(UBER_IS_HEAT_MAP(graph));
+
+ priv = UBER_HEAT_MAP(graph)->priv;
+ color = priv->fg_color;
+ if (!priv->fg_color_set) {
+ style = gtk_widget_get_style_context(GTK_WIDGET(graph));
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_SELECTED, &color);
+ }
+ /*
+ * XXX: Temporarily draw nice little squares.
+ */
+ #define COUNT 10
+ height = area->height / (gfloat)COUNT;
+ for (i = 0; i < COUNT; i++) {
+ cairo_rectangle(cr,
+ area->x + area->width - each,
+ area->y + (i * height),
+ each,
+ height);
+ cairo_set_source_rgba(cr,
+ color.red,
+ color.green,
+ color.blue,
+ g_random_double_range(0., 1.));
+ cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
+ cairo_fill(cr);
+ }
+}
+
+/**
+ * uber_heat_map_get_next_data:
+ * @graph: A #UberGraph.
+ *
+ * Retrieve the next data point for the graph.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static gboolean
+uber_heat_map_get_next_data (UberGraph *graph) /* IN */
+{
+ UberHeatMapPrivate *priv;
+ GArray *array = NULL;
+
+ g_return_val_if_fail(UBER_IS_HEAT_MAP(graph), FALSE);
+
+ priv = UBER_HEAT_MAP(graph)->priv;
+ if (!priv->func) {
+ return FALSE;
+ }
+ /*
+ * Retrieve the next data point.
+ */
+ if (!priv->func(UBER_HEAT_MAP(graph), &array, priv->func_user_data)) {
+ return FALSE;
+ }
+ /*
+ * Store data points.
+ */
+ g_ring_append_val(priv->raw_data, array);
+// for (int i = 0; i < array->len; i++) {
+// g_print("==> %f\n", g_array_index(array, gdouble, i));
+// }
+ return TRUE;
+}
+
+/**
+ * uber_heat_map_set_fg_color:
+ * @map: A #UberHeatMap.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_heat_map_set_fg_color (UberHeatMap *map, /* IN */
+ const GdkRGBA *color) /* IN */
+{
+ UberHeatMapPrivate *priv;
+
+ g_return_if_fail(UBER_IS_HEAT_MAP(map));
+
+ priv = map->priv;
+ if (!color) {
+ priv->fg_color_set = FALSE;
+ memset(&priv->fg_color, 0, sizeof(priv->fg_color));
+ } else {
+ priv->fg_color = *color;
+ priv->fg_color_set = TRUE;
+ }
+}
+
+/**
+ * uber_heat_map_finalize:
+ * @object: A #UberHeatMap.
+ *
+ * Finalizer for a #UberHeatMap instance. Frees any resources held by
+ * the instance.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_heat_map_finalize (GObject *object) /* IN */
+{
+ G_OBJECT_CLASS(uber_heat_map_parent_class)->finalize(object);
+}
+
+/**
+ * uber_heat_map_class_init:
+ * @klass: A #UberHeatMapClass.
+ *
+ * Initializes the #UberHeatMapClass and prepares the vtable.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_heat_map_class_init (UberHeatMapClass *klass) /* IN */
+{
+ GObjectClass *object_class;
+ UberGraphClass *graph_class;
+
+ object_class = G_OBJECT_CLASS(klass);
+ object_class->finalize = uber_heat_map_finalize;
+
+ graph_class = UBER_GRAPH_CLASS(klass);
+ graph_class->render = uber_heat_map_render;
+ graph_class->render_fast = uber_heat_map_render_fast;
+ graph_class->set_stride = uber_heat_map_set_stride;
+ graph_class->get_next_data = uber_heat_map_get_next_data;
+}
+
+/**
+ * uber_heat_map_init:
+ * @map: A #UberHeatMap.
+ *
+ * Initializes the newly created #UberHeatMap instance.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_heat_map_init (UberHeatMap *map) /* IN */
+{
+ map->priv = uber_heat_map_get_instance_private(map);
+}
diff --git a/deps/uber-graph/uber-heat-map.h b/deps/uber-graph/uber-heat-map.h
new file mode 100644
index 00000000..fb1bd2bd
--- /dev/null
+++ b/deps/uber-graph/uber-heat-map.h
@@ -0,0 +1,66 @@
+/* uber-heat-map.h
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UBER_HEAT_MAP_H__
+#define __UBER_HEAT_MAP_H__
+
+#include "uber-graph.h"
+
+G_BEGIN_DECLS
+
+#define UBER_TYPE_HEAT_MAP (uber_heat_map_get_type())
+#define UBER_HEAT_MAP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UBER_TYPE_HEAT_MAP, UberHeatMap))
+#define UBER_HEAT_MAP_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UBER_TYPE_HEAT_MAP, UberHeatMap const))
+#define UBER_HEAT_MAP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UBER_TYPE_HEAT_MAP, UberHeatMapClass))
+#define UBER_IS_HEAT_MAP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UBER_TYPE_HEAT_MAP))
+#define UBER_IS_HEAT_MAP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UBER_TYPE_HEAT_MAP))
+#define UBER_HEAT_MAP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UBER_TYPE_HEAT_MAP, UberHeatMapClass))
+
+typedef struct _UberHeatMap UberHeatMap;
+typedef struct _UberHeatMapClass UberHeatMapClass;
+typedef struct _UberHeatMapPrivate UberHeatMapPrivate;
+
+typedef gboolean (*UberHeatMapFunc) (UberHeatMap *map,
+ GArray **values,
+ gpointer user_data);
+
+struct _UberHeatMap
+{
+ UberGraph parent;
+
+ /*< private >*/
+ UberHeatMapPrivate *priv;
+};
+
+struct _UberHeatMapClass
+{
+ UberGraphClass parent_class;
+};
+
+GType uber_heat_map_get_type (void) G_GNUC_CONST;
+GtkWidget* uber_heat_map_new (void);
+void uber_heat_map_set_fg_color (UberHeatMap *map,
+ const GdkRGBA *color);
+void uber_heat_map_set_data_func (UberHeatMap *map,
+ UberHeatMapFunc func,
+ gpointer user_data,
+ GDestroyNotify destroy);
+
+G_END_DECLS
+
+#endif /* __UBER_HEAT_MAP_H__ */
diff --git a/deps/uber-graph/uber-label.c b/deps/uber-graph/uber-label.c
new file mode 100644
index 00000000..abc865fc
--- /dev/null
+++ b/deps/uber-graph/uber-label.c
@@ -0,0 +1,395 @@
+/* uber-label.c
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib/gi18n.h>
+
+#include "uber-label.h"
+
+/**
+ * SECTION:uber-label.h
+ * @title: UberLabel
+ * @short_description:
+ *
+ * Section overview.
+ */
+
+
+struct _UberLabelPrivate
+{
+ GtkWidget *hbox;
+ GtkWidget *block;
+ GtkWidget *label;
+ GdkRGBA color;
+ gboolean in_block;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE(UberLabel, uber_label, GTK_TYPE_WIDGET)
+
+enum
+{
+ COLOR_CHANGED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_COLOR,
+ PROP_TEXT,
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/**
+ * uber_label_new:
+ *
+ * Creates a new instance of #UberLabel.
+ *
+ * Returns: the newly created instance of #UberLabel.
+ * Side effects: None.
+ */
+GtkWidget*
+uber_label_new (void)
+{
+ UberLabel *label;
+
+ label = g_object_new(UBER_TYPE_LABEL, NULL);
+ return GTK_WIDGET(label);
+}
+
+/**
+ * uber_label_set_text:
+ * @label: A #UberLabel.
+ * @text: The label text.
+ *
+ * Sets the text for the label.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_label_set_text (UberLabel *label, /* IN */
+ const gchar *text) /* IN */
+{
+ UberLabelPrivate *priv;
+
+ g_return_if_fail(UBER_IS_LABEL(label));
+
+ priv = label->priv;
+ gtk_label_set_text(GTK_LABEL(priv->label), text);
+}
+
+/**
+ * uber_label_set_color:
+ * @label: A #UberLabel.
+ * @color: A #GdkRGBA.
+ *
+ * Sets the color of the label.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_label_set_color (UberLabel *label, /* IN */
+ const GdkRGBA *color) /* IN */
+{
+ UberLabelPrivate *priv;
+
+ g_return_if_fail(UBER_IS_LABEL(label));
+
+ priv = label->priv;
+ priv->color = *color;
+}
+
+static void
+uber_label_block_draw (GtkWidget *block, /* IN */
+ cairo_t *cr, /* IN */
+ UberLabel *label) /* IN */
+{
+ UberLabelPrivate *priv;
+ GtkAllocation alloc;
+
+ g_return_if_fail(UBER_IS_LABEL(label));
+
+ priv = label->priv;
+ gtk_widget_get_allocation(block, &alloc);
+ /*
+ * Draw background.
+ */
+ gdk_cairo_set_source_rgba(cr, &priv->color);
+ cairo_rectangle(cr, .5, .5, alloc.width - 1., alloc.height - 1.);
+ cairo_fill_preserve(cr);
+ /*
+ * Add highlight if mouse is in the block.
+ */
+ if (priv->in_block) {
+ cairo_set_source_rgba(cr, 1., 1., 1., .3);
+ cairo_fill_preserve(cr);
+ }
+ /*
+ * Stroke the edge of the block.
+ */
+ cairo_set_line_width(cr, 1.0);
+ cairo_set_source_rgba(cr, 0., 0., 0., .5);
+ cairo_stroke(cr);
+ /*
+ * Stroke the highlight of the block.
+ */
+ cairo_rectangle(cr, 1.5, 1.5, alloc.width - 3., alloc.height - 3.);
+ cairo_set_source_rgba(cr, 1., 1., 1., .5);
+ cairo_stroke(cr);
+}
+
+/**
+ * uber_label_block_enter_notify_event:
+ * @label: A #UberLabel.
+ *
+ * Tracks the mouse entering the block widget.
+ *
+ * Returns: %FALSE to allow further callbacks.
+ * Side effects: None.
+ */
+static gboolean
+uber_label_block_enter_notify_event (GtkWidget *widget, /* IN */
+ GdkEventCrossing *event, /* IN */
+ UberLabel *label) /* IN */
+{
+ UberLabelPrivate *priv;
+
+ priv = label->priv;
+ priv->in_block = TRUE;
+ gtk_widget_queue_draw(widget);
+ return FALSE;
+}
+
+/**
+ * uber_label_block_leave_notify_event:
+ * @label: A #UberLabel.
+ *
+ * Tracks the mouse leaving the block widget.
+ *
+ * Returns: %FALSE to allow further callbacks.
+ * Side effects: None.
+ */
+static gboolean
+uber_label_block_leave_notify_event (GtkWidget *widget, /* IN */
+ GdkEventCrossing *event, /* IN */
+ UberLabel *label) /* IN */
+{
+ UberLabelPrivate *priv;
+
+ priv = label->priv;
+ priv->in_block = FALSE;
+ gtk_widget_queue_draw(widget);
+ return FALSE;
+}
+
+/**
+ * uber_label_block_button_press_event:
+ * @widget: A #GtkWidget.
+ * @event: A #GdkEventButton.
+ * @label: An #UberLabel.
+ *
+ * Callback to handle a button press event within the colored block.
+ *
+ * Returns: %FALSE always to allow further signal emission.
+ * Side effects: None.
+ */
+static gboolean
+uber_label_block_button_press_event (GtkWidget *widget, /* IN */
+ GdkEventButton *event, /* IN */
+ UberLabel *label) /* IN */
+{
+ UberLabelPrivate *priv;
+ GtkWidget *dialog;
+
+ g_return_val_if_fail(UBER_IS_LABEL(label), FALSE);
+
+ priv = label->priv;
+ dialog = gtk_color_chooser_dialog_new("", GTK_WINDOW(gtk_widget_get_toplevel(widget)));
+ gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(dialog), TRUE);
+ gtk_color_chooser_set_rgba(
+ GTK_COLOR_CHOOSER(dialog),
+ &priv->color);
+ if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) {
+ gtk_color_chooser_get_rgba(
+ GTK_COLOR_CHOOSER(dialog),
+ &priv->color);
+ gtk_widget_queue_draw(widget);
+ g_signal_emit(label, signals[COLOR_CHANGED],
+ 0, &priv->color);
+ }
+ gtk_widget_destroy(dialog);
+ return FALSE;
+}
+
+/**
+ * uber_label_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (in): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Set a given #GObject property.
+ */
+static void
+uber_label_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ UberLabel *label = UBER_LABEL(object);
+
+ switch (prop_id) {
+ case PROP_COLOR:
+ uber_label_set_color(label, g_value_get_boxed(value));
+ break;
+ case PROP_TEXT:
+ uber_label_set_text(label, g_value_get_string(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+/**
+ * uber_label_finalize:
+ * @object: A #UberLabel.
+ *
+ * Finalizer for a #UberLabel instance. Frees any resources held by
+ * the instance.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_label_finalize (GObject *object) /* IN */
+{
+ G_OBJECT_CLASS(uber_label_parent_class)->finalize(object);
+}
+
+/**
+ * uber_label_class_init:
+ * @klass: A #UberLabelClass.
+ *
+ * Initializes the #UberLabelClass and prepares the vtable.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_label_class_init (UberLabelClass *klass) /* IN */
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS(klass);
+ object_class->finalize = uber_label_finalize;
+ object_class->set_property = uber_label_set_property;
+
+ g_object_class_install_property(object_class,
+ PROP_COLOR,
+ g_param_spec_boxed("color",
+ "color",
+ "color",
+ GDK_TYPE_RGBA,
+ G_PARAM_WRITABLE));
+
+ g_object_class_install_property(object_class,
+ PROP_TEXT,
+ g_param_spec_string("text",
+ "text",
+ "text",
+ NULL,
+ G_PARAM_WRITABLE));
+
+ /**
+ * UberLabel::color-changed:
+ * @label: An #UberLabel.
+ * @color: A #GdkRGBA.
+ *
+ * Signal emitted when the color is changed.
+ */
+ signals[COLOR_CHANGED] = g_signal_new("color-changed",
+ UBER_TYPE_LABEL,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+}
+
+/**
+ * uber_label_init:
+ * @label: A #UberLabel.
+ *
+ * Initializes the newly created #UberLabel instance.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_label_init (UberLabel *label) /* IN */
+{
+ UberLabelPrivate *priv;
+
+ label->priv = uber_label_get_instance_private(label);
+
+ priv = label->priv;
+ priv->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+ priv->block = gtk_drawing_area_new();
+ priv->label = gtk_label_new(NULL);
+ gdk_rgba_parse(&priv->color, "#cc0000");
+ gtk_widget_set_halign(GTK_WIDGET(priv->label), GTK_ALIGN_START);
+ gtk_widget_set_valign(GTK_WIDGET(priv->label), GTK_ALIGN_CENTER);
+ gtk_widget_set_size_request(priv->block, 32, 17);
+ gtk_container_add(GTK_CONTAINER(label), priv->hbox);
+ gtk_box_pack_start(GTK_BOX(priv->hbox), priv->block, FALSE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(priv->hbox), priv->label, TRUE, TRUE, 0);
+ gtk_widget_add_events(priv->block,
+ GDK_ENTER_NOTIFY_MASK |
+ GDK_LEAVE_NOTIFY_MASK |
+ GDK_BUTTON_PRESS_MASK);
+ g_signal_connect(priv->block,
+ "draw",
+ G_CALLBACK(uber_label_block_draw),
+ label);
+ g_signal_connect(priv->block,
+ "enter-notify-event",
+ G_CALLBACK(uber_label_block_enter_notify_event),
+ label);
+ g_signal_connect(priv->block,
+ "leave-notify-event",
+ G_CALLBACK(uber_label_block_leave_notify_event),
+ label);
+ g_signal_connect(priv->block,
+ "button-press-event",
+ G_CALLBACK(uber_label_block_button_press_event),
+ label);
+ gtk_widget_set_tooltip_text(GTK_WIDGET(priv->block),
+ _("Click to select color"));
+ gtk_widget_show(priv->hbox);
+ gtk_widget_show(priv->block);
+ gtk_widget_show(priv->label);
+}
diff --git a/deps/uber-graph/uber-label.h b/deps/uber-graph/uber-label.h
new file mode 100644
index 00000000..8c4c74f5
--- /dev/null
+++ b/deps/uber-graph/uber-label.h
@@ -0,0 +1,60 @@
+/* uber-label.h
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UBER_LABEL_H__
+#define __UBER_LABEL_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define UBER_TYPE_LABEL (uber_label_get_type())
+#define UBER_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UBER_TYPE_LABEL, UberLabel))
+#define UBER_LABEL_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UBER_TYPE_LABEL, UberLabel const))
+#define UBER_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UBER_TYPE_LABEL, UberLabelClass))
+#define UBER_IS_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UBER_TYPE_LABEL))
+#define UBER_IS_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UBER_TYPE_LABEL))
+#define UBER_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UBER_TYPE_LABEL, UberLabelClass))
+
+typedef struct _UberLabel UberLabel;
+typedef struct _UberLabelClass UberLabelClass;
+typedef struct _UberLabelPrivate UberLabelPrivate;
+
+struct _UberLabel
+{
+ GtkAlignment parent;
+
+ /*< private >*/
+ UberLabelPrivate *priv;
+};
+
+struct _UberLabelClass
+{
+ GtkAlignmentClass parent_class;
+};
+
+GType uber_label_get_type (void) G_GNUC_CONST;
+GtkWidget* uber_label_new (void);
+void uber_label_set_color (UberLabel *label,
+ const GdkRGBA *color);
+void uber_label_set_text (UberLabel *label,
+ const gchar *text);
+
+G_END_DECLS
+
+#endif /* __UBER_LABEL_H__ */
diff --git a/deps/uber-graph/uber-line-graph.c b/deps/uber-graph/uber-line-graph.c
new file mode 100644
index 00000000..776f5363
--- /dev/null
+++ b/deps/uber-graph/uber-line-graph.c
@@ -0,0 +1,1028 @@
+/* uber-line-graph.c
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+#include <string.h>
+
+#include "uber-line-graph.h"
+#include "uber-range.h"
+#include "uber-scale.h"
+#include "g-ring.h"
+
+#define RECT_BOTTOM(r) ((r).y + (r).height)
+#define RECT_RIGHT(r) ((r).x + (r).width)
+#define SCALE_FACTOR (0.2)
+
+/**
+ * SECTION:uber-line-graph.h
+ * @title: UberLineGraph
+ * @short_description:
+ *
+ * Section overview.
+ */
+
+
+typedef struct
+{
+ GRing *raw_data;
+ GdkRGBA color;
+ gdouble width;
+ gdouble *dashes;
+ gint num_dashes;
+ gdouble dash_offset;
+ UberLabel *label;
+ guint label_id;
+} LineInfo;
+
+struct _UberLineGraphPrivate
+{
+ GArray *lines;
+ cairo_antialias_t antialias;
+ guint stride;
+ gboolean autoscale;
+ UberRange range;
+ UberScale scale;
+ gpointer scale_data;
+ GDestroyNotify scale_notify;
+ UberLineGraphFunc func;
+ gpointer func_data;
+ GDestroyNotify func_notify;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE(UberLineGraph, uber_line_graph, UBER_TYPE_GRAPH)
+
+
+enum
+{
+ PROP_0,
+ PROP_AUTOSCALE,
+ PROP_RANGE,
+};
+
+/**
+ * uber_line_graph_init_ring:
+ * @ring: A #GRing.
+ *
+ * Initialize the #GRing to default values (UBER_LINE_GRAPH_NO_VALUE).
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static inline void
+uber_line_graph_init_ring (GRing *ring) /* IN */
+{
+ gdouble val = UBER_LINE_GRAPH_NO_VALUE;
+ guint i;
+
+ g_return_if_fail(ring != NULL);
+
+ for (i = 0; i < ring->len; i++) {
+ g_ring_append_val(ring, val);
+ }
+}
+
+/**
+ * uber_line_graph_new:
+ *
+ * Creates a new instance of #UberLineGraph.
+ *
+ * Returns: the newly created instance of #UberLineGraph.
+ * Side effects: None.
+ */
+GtkWidget*
+uber_line_graph_new (void)
+{
+ UberLineGraph *graph;
+
+ graph = g_object_new(UBER_TYPE_LINE_GRAPH, NULL);
+ return GTK_WIDGET(graph);
+}
+
+/**
+ * uber_line_graph_color:
+ * @graph: A #UberLineGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_line_graph_color_changed (UberLabel *label, /* IN */
+ GdkRGBA *color, /* IN */
+ UberLineGraph *graph) /* IN */
+{
+ UberLineGraphPrivate *priv;
+ LineInfo *info;
+ guint i;
+
+ g_return_if_fail(UBER_IS_LINE_GRAPH(graph));
+ g_return_if_fail(color != NULL);
+
+ priv = graph->priv;
+ for (i = 0; i < priv->lines->len; i++) {
+ info = &g_array_index(priv->lines, LineInfo, i);
+ if (info->label == label) {
+ info->color = *color;
+ }
+ }
+ uber_graph_redraw(UBER_GRAPH(graph));
+}
+
+void
+uber_line_graph_bind_label (UberLineGraph *graph, /* IN */
+ guint line, /* IN */
+ UberLabel *label) /* IN */
+{
+ UberLineGraphPrivate *priv;
+ LineInfo *info;
+
+ g_return_if_fail(UBER_IS_LINE_GRAPH(graph));
+ g_return_if_fail(UBER_IS_LABEL(label));
+ g_return_if_fail(line > 0);
+ g_return_if_fail(line <= graph->priv->lines->len);
+
+ priv = graph->priv;
+ info = &g_array_index(priv->lines, LineInfo, line - 1);
+ if (info->label_id) {
+ g_signal_handler_disconnect(info->label, info->label_id);
+ }
+ info->label = label;
+ info->label_id = g_signal_connect(label,
+ "color-changed",
+ G_CALLBACK(uber_line_graph_color_changed),
+ graph);
+}
+
+/**
+ * uber_line_graph_set_autoscale:
+ * @graph: A #UberLineGraph.
+ * @autoscale: Should we autoscale.
+ *
+ * Sets if we should autoscale the range of the graph when a new input
+ * value is outside the visible range.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_line_graph_set_autoscale (UberLineGraph *graph, /* IN */
+ gboolean autoscale) /* IN */
+{
+ UberLineGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_LINE_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->autoscale = autoscale;
+}
+
+/**
+ * uber_line_graph_get_autoscale:
+ * @graph: A #UberLineGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+gboolean
+uber_line_graph_get_autoscale (UberLineGraph *graph) /* IN */
+{
+ g_return_val_if_fail(UBER_IS_LINE_GRAPH(graph), FALSE);
+ return graph->priv->autoscale;
+}
+
+/**
+ * uber_line_graph_add_line:
+ * @graph: A #UberLineGraph.
+ * @color: A #GdkRGBA for the line or %NULL.
+ *
+ * Adds a new line to the graph. If color is %NULL, the next value
+ * in the default color list will be used.
+ *
+ * See uber_line_graph_remove_line().
+ *
+ * Returns: The line identifier.
+ * Side effects: None.
+ */
+gint
+uber_line_graph_add_line (UberLineGraph *graph, /* IN */
+ const GdkRGBA *color, /* IN */
+ UberLabel *label) /* IN */
+{
+ UberLineGraphPrivate *priv;
+ LineInfo info = { 0 };
+
+ g_return_val_if_fail(UBER_IS_LINE_GRAPH(graph), 0);
+
+ priv = graph->priv;
+ info.width = 1.0;
+ /*
+ * Retrieve the lines color.
+ */
+ if (color) {
+ info.color = *color;
+ } else {
+ gdk_rgba_parse(&info.color, "#729fcf");
+ }
+ /*
+ * Allocate buffers for data points.
+ */
+ info.raw_data = g_ring_sized_new(sizeof(gdouble), priv->stride, NULL);
+ uber_line_graph_init_ring(info.raw_data);
+ /*
+ * Store the newly crated line.
+ */
+ g_array_append_val(priv->lines, info);
+ /*
+ * Mark the graph for full redraw.
+ */
+ uber_graph_redraw(UBER_GRAPH(graph));
+ /*
+ * Attach label.
+ */
+ if (label) {
+ uber_line_graph_bind_label(graph, priv->lines->len, label);
+ uber_graph_add_label(UBER_GRAPH(graph), label);
+ uber_label_set_color(label, &info.color);
+ }
+ /*
+ * Line indexes start from 1.
+ */
+ return priv->lines->len;
+}
+
+/**
+ * uber_line_graph_set_antialias:
+ * @graph: A #UberLineGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_line_graph_set_antialias (UberLineGraph *graph, /* IN */
+ cairo_antialias_t antialias) /* IN */
+{
+ UberLineGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_LINE_GRAPH(graph));
+
+ priv = graph->priv;
+ priv->antialias = antialias;
+ uber_graph_redraw(UBER_GRAPH(graph));
+}
+
+/**
+ * uber_line_graph_get_antialias:
+ * @graph: A #UberLineGraph.
+ *
+ * Retrieves the antialias mode for the graph.
+ *
+ * Returns: A cairo_antialias_t.
+ * Side effects: None.
+ */
+cairo_antialias_t
+uber_line_graph_get_antialias (UberLineGraph *graph) /* IN */
+{
+ g_return_val_if_fail(UBER_IS_LINE_GRAPH(graph), 0);
+
+ return graph->priv->antialias;
+}
+
+/**
+ * uber_line_graph_get_next_data:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static gboolean
+uber_line_graph_get_next_data (UberGraph *graph) /* IN */
+{
+ UberLineGraphPrivate *priv;
+ gboolean scale_changed = FALSE;
+ gboolean ret = FALSE;
+ LineInfo *line;
+ gdouble val;
+ guint i;
+
+ g_return_val_if_fail(UBER_IS_LINE_GRAPH(graph), FALSE);
+
+ priv = UBER_LINE_GRAPH(graph)->priv;
+ /*
+ * Retrieve the next data point.
+ */
+ if (priv->func) {
+ for (i = 0; i < priv->lines->len; i++) {
+ line = &g_array_index(priv->lines, LineInfo, i);
+ val = priv->func(UBER_LINE_GRAPH(graph), i + 1, priv->func_data);
+ g_ring_append_val(line->raw_data, val);
+ if (priv->autoscale) {
+ if (val < priv->range.begin) {
+ priv->range.begin = val - (val * SCALE_FACTOR);
+ priv->range.range = priv->range.end - priv->range.begin;
+ scale_changed = TRUE;
+ } else if (val > priv->range.end) {
+ priv->range.end = val + (val * SCALE_FACTOR);
+ priv->range.range = priv->range.end - priv->range.begin;
+ scale_changed = TRUE;
+ }
+ }
+ }
+ }
+ if (scale_changed) {
+ uber_graph_scale_changed(graph);
+ }
+ return ret;
+}
+
+/**
+ * uber_line_graph_set_data_func:
+ * @graph: A #UberLineGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_line_graph_set_data_func (UberLineGraph *graph, /* IN */
+ UberLineGraphFunc func, /* IN */
+ gpointer user_data, /* IN */
+ GDestroyNotify notify) /* IN */
+{
+ UberLineGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_LINE_GRAPH(graph));
+
+ priv = graph->priv;
+ /*
+ * Free existing data func if neccessary.
+ */
+ if (priv->func_notify) {
+ priv->func_notify(priv->func_data);
+ }
+ /*
+ * Store data func.
+ */
+ priv->func = func;
+ priv->func_data = user_data;
+ priv->func_notify = notify;
+}
+
+/**
+ * uber_line_graph_stylize_line:
+ * @graph: A #UberLineGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_line_graph_stylize_line (UberLineGraph *graph, /* IN */
+ LineInfo *info, /* IN */
+ cairo_t *cr) /* IN */
+{
+ UberLineGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_LINE_GRAPH(graph));
+ g_return_if_fail(info != NULL);
+
+ priv = graph->priv;
+ if (info->dashes) {
+ cairo_set_dash(cr, info->dashes, info->num_dashes, info->dash_offset);
+ } else {
+ cairo_set_dash(cr, NULL, 0, 0);
+ }
+ cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
+ cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
+ cairo_set_line_width(cr, info->width);
+ cairo_set_antialias(cr, priv->antialias);
+ cairo_set_source_rgba(cr,
+ info->color.red,
+ info->color.green,
+ info->color.blue,
+ info->color.alpha);
+}
+
+/**
+ * uber_line_graph_render:
+ * @graph: A #UberGraph.
+ * @cr: A #cairo_t context.
+ * @area: Full area to render contents within.
+ * @line: The line to render.
+ *
+ * Render a particular line to the graph.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_line_graph_render_line (UberLineGraph *graph, /* IN */
+ cairo_t *cr, /* IN */
+ GdkRectangle *area, /* IN */
+ LineInfo *line, /* IN */
+ guint epoch, /* IN */
+ gfloat each) /* IN */
+{
+ UberLineGraphPrivate *priv;
+ UberRange pixel_range;
+ GdkRectangle vis;
+ guint x;
+ guint last_x;
+ gdouble y;
+ gdouble last_y;
+ gdouble val;
+ guint i;
+
+ g_return_if_fail(UBER_IS_LINE_GRAPH(graph));
+
+ priv = graph->priv;
+ uber_graph_get_content_area(UBER_GRAPH(graph), &vis);
+ pixel_range.begin = area->y + 1;
+ pixel_range.end = area->y + area->height;
+ pixel_range.range = pixel_range.end - pixel_range.begin;
+ /*
+ * Prepare cairo settings.
+ */
+ uber_line_graph_stylize_line(graph, line, cr);
+ /*
+ * Force a new path.
+ */
+ cairo_new_path(cr);
+ /*
+ * Draw the line contents as bezier curves.
+ */
+ for (i = 0; i < line->raw_data->len; i++) {
+ /*
+ * Retrieve data point.
+ */
+ val = g_ring_get_index(line->raw_data, gdouble, (int)i);
+ /*
+ * Once we get to UBER_LINE_GRAPH_NO_VALUE, we must be at the end of the data
+ * sequence. This may not always be true in the future.
+ */
+ if (val == UBER_LINE_GRAPH_NO_VALUE) {
+ break;
+ }
+ /*
+ * Translate value to coordinate system.
+ */
+ if (!priv->scale(&priv->range, &pixel_range, &val, priv->scale_data)) {
+ break;
+ }
+ /*
+ * Calculate X/Y coordinate.
+ */
+ y = (gint)(RECT_BOTTOM(*area) - val) - .5;
+ x = epoch - (each * i);
+ if (i == 0) {
+ /*
+ * Just move to the right position on first entry.
+ */
+ cairo_move_to(cr, x, y);
+ goto next;
+ } else {
+ /*
+ * Draw curve to data point using the last X/Y positions as
+ * control points.
+ */
+ cairo_curve_to(cr,
+ last_x - (each / 2.),
+ last_y,
+ last_x - (each / 2.),
+ y, x, y);
+ }
+ next:
+ last_y = y;
+ last_x = x;
+ }
+ /*
+ * Stroke the line content.
+ */
+ cairo_stroke(cr);
+}
+
+/**
+ * uber_line_graph_render:
+ * @graph: A #UberGraph.
+ *
+ * Render the entire contents of the graph.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_line_graph_render (UberGraph *graph, /* IN */
+ cairo_t *cr, /* IN */
+ GdkRectangle *rect, /* IN */
+ guint epoch, /* IN */
+ gfloat each) /* IN */
+{
+ UberLineGraphPrivate *priv;
+ LineInfo *line;
+ guint i;
+
+ g_return_if_fail(UBER_IS_LINE_GRAPH(graph));
+
+ priv = UBER_LINE_GRAPH(graph)->priv;
+ /*
+ * Render each line to the graph.
+ */
+ for (i = 0; i < priv->lines->len; i++) {
+ line = &g_array_index(priv->lines, LineInfo, i);
+ uber_line_graph_render_line(UBER_LINE_GRAPH(graph), cr, rect,
+ line, epoch, each);
+ }
+}
+
+/**
+ * uber_line_graph_render_fast:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_line_graph_render_fast (UberGraph *graph, /* IN */
+ cairo_t *cr, /* IN */
+ GdkRectangle *rect, /* IN */
+ guint epoch, /* IN */
+ gfloat each) /* IN */
+{
+ UberLineGraphPrivate *priv;
+ UberRange pixel_range;
+ LineInfo *line;
+ gdouble last_y;
+ gdouble y;
+ guint i;
+
+ g_return_if_fail(UBER_IS_LINE_GRAPH(graph));
+ g_return_if_fail(cr != NULL);
+ g_return_if_fail(rect != NULL);
+
+ priv = UBER_LINE_GRAPH(graph)->priv;
+ pixel_range.begin = rect->y + 1;
+ pixel_range.end = rect->y + rect->height;
+ pixel_range.range = pixel_range.end - pixel_range.begin;
+ /*
+ * Render most recent data point for each line.
+ */
+ for (i = 0; i < priv->lines->len; i++) {
+ line = &g_array_index(priv->lines, LineInfo, i);
+ uber_line_graph_stylize_line(UBER_LINE_GRAPH(graph), line, cr);
+ /*
+ * Calculate positions.
+ */
+ y = g_ring_get_index(line->raw_data, gdouble, 0);
+ last_y = g_ring_get_index(line->raw_data, gdouble, 1);
+ /*
+ * Don't try to draw before we have real values.
+ */
+ if ((isnan(y) || isinf(y)) || (isnan(last_y) || isinf(last_y))) {
+ continue;
+ }
+ /*
+ * Translate to coordinate scale.
+ */
+ if (!priv->scale(&priv->range, &pixel_range, &y, priv->scale_data) ||
+ !priv->scale(&priv->range, &pixel_range, &last_y, priv->scale_data)) {
+ continue;
+ }
+ /*
+ * Translate position from bottom right corner.
+ */
+ y = (gint)(RECT_BOTTOM(*rect) - y) - .5;
+ last_y = (gint)(RECT_BOTTOM(*rect) - last_y) - .5;
+ /*
+ * Convert relative position to fixed from bottom pixel.
+ */
+ cairo_new_path(cr);
+ cairo_move_to(cr, epoch, y);
+ cairo_curve_to(cr,
+ epoch - (each / 2.),
+ y,
+ epoch - (each / 2.),
+ last_y,
+ epoch - each,
+ last_y);
+ cairo_stroke(cr);
+ }
+}
+
+/**
+ * uber_line_graph_set_stride:
+ * @graph: A #UberGraph.
+ * @stride: The number of data points within the graph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_line_graph_set_stride (UberGraph *graph, /* IN */
+ guint stride) /* IN */
+{
+ UberLineGraphPrivate *priv;
+ LineInfo *line;
+ guint i;
+
+ g_return_if_fail(UBER_IS_LINE_GRAPH(graph));
+
+ priv = UBER_LINE_GRAPH(graph)->priv;
+ priv->stride = stride;
+ /*
+ * TODO: Support changing stride after lines have been added.
+ */
+ if (priv->lines->len) {
+ for (i = 0; i < priv->lines->len; i++) {
+ line = &g_array_index(priv->lines, LineInfo, i);
+ g_ring_unref(line->raw_data);
+ line->raw_data = g_ring_sized_new(sizeof(gdouble),
+ priv->stride, NULL);
+ uber_line_graph_init_ring(line->raw_data);
+ }
+ return;
+ }
+}
+
+/**
+ * uber_line_graph_get_range:
+ * @graph: (in): A #UberLineGraph.
+ *
+ * XXX
+ *
+ * Returns: An #UberRange which should not be modified or freed.
+ * Side effects: None.
+ */
+const UberRange*
+uber_line_graph_get_range (UberLineGraph *graph) /* IN */
+{
+ g_return_val_if_fail(UBER_IS_LINE_GRAPH(graph), NULL);
+ return &graph->priv->range;
+}
+
+/**
+ * uber_line_graph_set_range:
+ * @graph: A #UberLineGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_line_graph_set_range (UberLineGraph *graph, /* IN */
+ const UberRange *range) /* IN */
+{
+ UberLineGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_LINE_GRAPH(graph));
+ g_return_if_fail(range != NULL);
+
+ priv = graph->priv;
+ priv->range = *range;
+}
+
+/**
+ * uber_line_graph_get_yrange:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_line_graph_get_yrange (UberGraph *graph, /* IN */
+ UberRange *range) /* OUT */
+{
+ UberLineGraphPrivate *priv;
+
+ g_return_if_fail(UBER_IS_LINE_GRAPH(graph));
+ g_return_if_fail(range != NULL);
+
+ priv = UBER_LINE_GRAPH(graph)->priv;
+ *range = priv->range;
+}
+
+/**
+ * uber_line_graph_set_line_dash:
+ * @graph: A #UberLineGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_line_graph_set_line_dash (UberLineGraph *graph, /* IN */
+ guint line, /* IN */
+ const gdouble *dashes, /* IN */
+ gint num_dashes, /* IN */
+ gdouble offset) /* IN */
+{
+ UberLineGraphPrivate *priv;
+ LineInfo *info;
+
+ g_return_if_fail(UBER_IS_LINE_GRAPH(graph));
+ g_return_if_fail(dashes != NULL);
+ g_return_if_fail(num_dashes > 0);
+ g_return_if_fail(line > 0);
+ g_return_if_fail(line <= graph->priv->lines->len);
+
+ priv = graph->priv;
+ info = &g_array_index(priv->lines, LineInfo, line - 1);
+ if (info->dashes) {
+ g_free(info->dashes);
+ info->dashes = NULL;
+ info->num_dashes = 0;
+ info->dash_offset = 0;
+ }
+ if (dashes) {
+ info->dashes = g_new0(gdouble, num_dashes);
+ memcpy(info->dashes, dashes, sizeof(gdouble) * num_dashes);
+ info->num_dashes = num_dashes;
+ info->dash_offset = offset;
+ }
+}
+
+/**
+ * uber_line_graph_set_line_width:
+ * @graph: A #UberLineGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_line_graph_set_line_width (UberLineGraph *graph, /* IN */
+ gint line, /* IN */
+ gdouble width) /* IN */
+{
+ LineInfo *info;
+
+ g_return_if_fail(UBER_IS_LINE_GRAPH(graph));
+ g_return_if_fail(line > 0);
+ g_return_if_fail(line <= (int)graph->priv->lines->len);
+
+ info = &g_array_index(graph->priv->lines, LineInfo, line - 1);
+ info->width = width;
+ uber_graph_redraw(UBER_GRAPH(graph));
+}
+
+/**
+ * uber_line_graph_downscale:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static gboolean
+uber_line_graph_downscale (UberGraph *graph) /* IN */
+{
+ UberLineGraphPrivate *priv;
+ gboolean ret = FALSE;
+ gdouble val = 0;
+ gdouble cur;
+ LineInfo *line;
+ guint i;
+ guint j;
+
+ g_return_val_if_fail(UBER_IS_LINE_GRAPH(graph), FALSE);
+
+ priv = UBER_LINE_GRAPH(graph)->priv;
+ /*
+ * If we are set to autoscale, ignore request.
+ */
+ if (!priv->autoscale) {
+ return FALSE;
+ }
+ /*
+ * Determine the largest value available.
+ */
+ for (i = 0; i < priv->lines->len; i++) {
+ line = &g_array_index(priv->lines, LineInfo, i);
+ for (j = 0; j < line->raw_data->len; j++) {
+ cur = g_ring_get_index(line->raw_data, gdouble, (int)j);
+ val = (cur > val) ? cur : val;
+ }
+ }
+ /*
+ * Downscale if we can.
+ */
+ if (val != priv->range.begin) {
+ if ((val * (1. + SCALE_FACTOR)) < priv->range.end) {
+ priv->range.end = val * (1. + SCALE_FACTOR);
+ priv->range.range = priv->range.end - priv->range.begin;
+ ret = TRUE;
+ }
+ }
+ return ret;
+}
+
+/**
+ * uber_line_graph_finalize:
+ * @object: A #UberLineGraph.
+ *
+ * Finalizer for a #UberLineGraph instance. Frees any resources held by
+ * the instance.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_line_graph_finalize (GObject *object) /* IN */
+{
+ UberLineGraphPrivate *priv;
+ LineInfo *line;
+ guint i;
+
+ priv = UBER_LINE_GRAPH(object)->priv;
+ /*
+ * Clean up after cached values.
+ */
+ for (i = 0; i < priv->lines->len; i++) {
+ line = &g_array_index(priv->lines, LineInfo, i);
+ g_ring_unref(line->raw_data);
+ g_free(line->dashes);
+ }
+ G_OBJECT_CLASS(uber_line_graph_parent_class)->finalize(object);
+}
+
+/**
+ * uber_line_graph_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (out): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Get a given #GObject property.
+ */
+static void
+uber_line_graph_get_property (GObject *object, /* IN */
+ guint prop_id, /* IN */
+ GValue *value, /* OUT */
+ GParamSpec *pspec) /* IN */
+{
+ UberLineGraph *graph = UBER_LINE_GRAPH(object);
+
+ switch (prop_id) {
+ case PROP_AUTOSCALE:
+ g_value_set_boolean(value, uber_line_graph_get_autoscale(graph));
+ break;
+ case PROP_RANGE:
+ g_value_set_boxed(value, uber_line_graph_get_range(graph));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+/**
+ * uber_line_graph_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (in): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Set a given #GObject property.
+ */
+static void
+uber_line_graph_set_property (GObject *object, /* IN */
+ guint prop_id, /* IN */
+ const GValue *value, /* IN */
+ GParamSpec *pspec) /* IN */
+{
+ UberLineGraph *graph = UBER_LINE_GRAPH(object);
+
+ switch (prop_id) {
+ case PROP_AUTOSCALE:
+ uber_line_graph_set_autoscale(graph, g_value_get_boolean(value));
+ break;
+ case PROP_RANGE:
+ uber_line_graph_set_range(graph, g_value_get_boxed(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+/**
+ * uber_line_graph_class_init:
+ * @klass: A #UberLineGraphClass.
+ *
+ * Initializes the #UberLineGraphClass and prepares the vtable.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_line_graph_class_init (UberLineGraphClass *klass) /* IN */
+{
+ GObjectClass *object_class;
+ UberGraphClass *graph_class;
+
+ object_class = G_OBJECT_CLASS(klass);
+ object_class->finalize = uber_line_graph_finalize;
+ object_class->get_property = uber_line_graph_get_property;
+ object_class->set_property = uber_line_graph_set_property;
+
+ graph_class = UBER_GRAPH_CLASS(klass);
+ graph_class->downscale = uber_line_graph_downscale;
+ graph_class->get_next_data = uber_line_graph_get_next_data;
+ graph_class->get_yrange = uber_line_graph_get_yrange;
+ graph_class->render = uber_line_graph_render;
+ graph_class->render_fast = uber_line_graph_render_fast;
+ graph_class->set_stride = uber_line_graph_set_stride;
+
+ g_object_class_install_property(object_class,
+ PROP_AUTOSCALE,
+ g_param_spec_boolean("autoscale",
+ "autoscale",
+ "autoscale",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(object_class,
+ PROP_RANGE,
+ g_param_spec_boxed("range",
+ "range",
+ "range",
+ UBER_TYPE_RANGE,
+ G_PARAM_READWRITE));
+}
+
+/**
+ * uber_line_graph_init:
+ * @graph: A #UberLineGraph.
+ *
+ * Initializes the newly created #UberLineGraph instance.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_line_graph_init (UberLineGraph *graph) /* IN */
+{
+ UberLineGraphPrivate *priv;
+
+ /*
+ * Keep pointer to private data.
+ */
+ graph->priv = uber_line_graph_get_instance_private(graph);
+
+ priv = graph->priv;
+ /*
+ * Initialize defaults.
+ */
+ priv->stride = 60;
+ priv->antialias = CAIRO_ANTIALIAS_DEFAULT;
+ priv->lines = g_array_sized_new(FALSE, FALSE, sizeof(LineInfo), 2);
+ priv->scale = uber_scale_linear;
+ priv->autoscale = TRUE;
+}
+
+void
+uber_line_graph_clear (UberLineGraph *graph) /* IN */
+{
+ UberLineGraphPrivate *priv = graph->priv;
+ LineInfo *line;
+ guint i;
+
+ for (i = 0; i < priv->lines->len; i++) {
+ line = &g_array_index(priv->lines, LineInfo, i);
+ uber_line_graph_init_ring(line->raw_data);
+ }
+}
diff --git a/deps/uber-graph/uber-line-graph.h b/deps/uber-graph/uber-line-graph.h
new file mode 100644
index 00000000..22a01681
--- /dev/null
+++ b/deps/uber-graph/uber-line-graph.h
@@ -0,0 +1,100 @@
+/* uber-line-graph.h
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UBER_LINE_GRAPH_H__
+#define __UBER_LINE_GRAPH_H__
+
+#include <math.h>
+
+#include "uber-graph.h"
+#include "uber-range.h"
+#include "uber-label.h"
+
+G_BEGIN_DECLS
+
+#define UBER_TYPE_LINE_GRAPH (uber_line_graph_get_type())
+#define UBER_LINE_GRAPH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UBER_TYPE_LINE_GRAPH, UberLineGraph))
+#define UBER_LINE_GRAPH_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UBER_TYPE_LINE_GRAPH, UberLineGraph const))
+#define UBER_LINE_GRAPH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UBER_TYPE_LINE_GRAPH, UberLineGraphClass))
+#define UBER_IS_LINE_GRAPH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UBER_TYPE_LINE_GRAPH))
+#define UBER_IS_LINE_GRAPH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UBER_TYPE_LINE_GRAPH))
+#define UBER_LINE_GRAPH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UBER_TYPE_LINE_GRAPH, UberLineGraphClass))
+
+#define UBER_LINE_GRAPH_NO_VALUE (NAN)
+
+typedef struct _UberLineGraph UberLineGraph;
+typedef struct _UberLineGraphClass UberLineGraphClass;
+typedef struct _UberLineGraphPrivate UberLineGraphPrivate;
+
+/**
+ * UberLineGraphFunc:
+ * @graph: A #UberLineGraph.
+ * @user_data: User data supplied to uber_line_graph_set_data_func().
+ *
+ * Callback prototype for retrieving the next data point in the graph.
+ *
+ * Returns: a value if successful, otherwise %UBER_LINE_GRAPH_NO_VALUE
+ * Side effects: Implementation dependent.
+ */
+typedef gdouble (*UberLineGraphFunc) (UberLineGraph *graph,
+ guint line,
+ gpointer user_data);
+
+struct _UberLineGraph
+{
+ UberGraph parent;
+
+ /*< private >*/
+ UberLineGraphPrivate *priv;
+};
+
+struct _UberLineGraphClass
+{
+ UberGraphClass parent_class;
+};
+
+gint uber_line_graph_add_line (UberLineGraph *graph,
+ const GdkRGBA *color,
+ UberLabel *label);
+cairo_antialias_t uber_line_graph_get_antialias (UberLineGraph *graph);
+GType uber_line_graph_get_type (void) G_GNUC_CONST;
+GtkWidget* uber_line_graph_new (void);
+void uber_line_graph_set_antialias (UberLineGraph *graph,
+ cairo_antialias_t antialias);
+void uber_line_graph_set_data_func (UberLineGraph *graph,
+ UberLineGraphFunc func,
+ gpointer user_data,
+ GDestroyNotify notify);
+gboolean uber_line_graph_get_autoscale (UberLineGraph *graph);
+void uber_line_graph_set_autoscale (UberLineGraph *graph,
+ gboolean autoscale);
+const UberRange* uber_line_graph_get_range (UberLineGraph *graph);
+void uber_line_graph_set_range (UberLineGraph *graph,
+ const UberRange *range);
+void uber_line_graph_set_line_dash (UberLineGraph *graph,
+ guint line,
+ const gdouble *dashes,
+ gint num_dashes,
+ gdouble offset);
+void uber_line_graph_set_line_width (UberLineGraph *graph,
+ gint line,
+ gdouble width);
+void uber_line_graph_clear (UberLineGraph *graph);
+G_END_DECLS
+
+#endif /* __UBER_LINE_GRAPH_H__ */
diff --git a/deps/uber-graph/uber-range.c b/deps/uber-graph/uber-range.c
new file mode 100644
index 00000000..e8afc0de
--- /dev/null
+++ b/deps/uber-graph/uber-range.c
@@ -0,0 +1,64 @@
+/* uber-range.c
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include "uber-range.h"
+
+UberRange*
+uber_range_copy (UberRange *src)
+{
+ UberRange *dst = g_new0(UberRange, 1);
+ memcpy(dst, src, sizeof(UberRange));
+ return dst;
+}
+
+void
+uber_range_free (UberRange *range)
+{
+ g_free(range);
+}
+
+UberRange*
+uber_range_new (gdouble begin,
+ gdouble end)
+{
+ UberRange *range;
+
+ range = g_new0(UberRange, 1);
+ range->begin = begin;
+ range->end = end;
+ range->range = range->end - range->begin;
+ return range;
+}
+
+GType
+uber_range_get_type (void)
+{
+ static gsize initialized = FALSE;
+ GType type_id = 0;
+
+ if (G_UNLIKELY(g_once_init_enter(&initialized))) {
+ type_id = g_boxed_type_register_static("UberRange",
+ (GBoxedCopyFunc)uber_range_copy,
+ (GBoxedFreeFunc)uber_range_free);
+ g_once_init_leave(&initialized, TRUE);
+ }
+
+ return type_id;
+}
diff --git a/deps/uber-graph/uber-range.h b/deps/uber-graph/uber-range.h
new file mode 100644
index 00000000..166a1313
--- /dev/null
+++ b/deps/uber-graph/uber-range.h
@@ -0,0 +1,50 @@
+/* uber-range.h
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UBER_RANGE_H__
+#define __UBER_RANGE_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define UBER_TYPE_RANGE (uber_range_get_type())
+
+/**
+ * UberRange:
+ *
+ * #UberRange is a structure that encapsulates the range of a particular
+ * scale. It contains the beginning value, ending value, and a pre-calculated
+ * range between the values.
+ */
+typedef struct
+{
+ gdouble begin;
+ gdouble end;
+ gdouble range;
+} UberRange;
+
+GType uber_range_get_type (void) G_GNUC_CONST;
+void uber_range_free (UberRange *range);
+UberRange* uber_range_copy (UberRange *range);
+UberRange* uber_range_new (gdouble begin,
+ gdouble end);
+
+G_END_DECLS
+
+#endif /* __UBER_RANGE_H__ */
diff --git a/deps/uber-graph/uber-scale.c b/deps/uber-graph/uber-scale.c
new file mode 100644
index 00000000..ead9f988
--- /dev/null
+++ b/deps/uber-graph/uber-scale.c
@@ -0,0 +1,54 @@
+/* uber-scale.c
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "uber-scale.h"
+
+/**
+ * uber_scale_linear:
+ * @range: An #UberRange.
+ * @pixel_range: An #UberRange.
+ * @value: A pointer to the value to translate.
+ * @user_data: user data for scale.
+ *
+ * An #UberScale function to translate a value to the coordinate system in
+ * a linear fashion.
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE.
+ * Side effects: None.
+ */
+gboolean
+uber_scale_linear (const UberRange *range, /* IN */
+ const UberRange *pixel_range, /* IN */
+ gdouble *value, /* IN/OUT */
+ gpointer user_data) /* IN */
+{
+ #define A (range->range)
+ #define B (pixel_range->range)
+ #define C (*value)
+ if (*value != 0.) {
+ *value = C * B / A;
+ }
+ #undef A
+ #undef B
+ #undef C
+ return TRUE;
+}
diff --git a/deps/uber-graph/uber-scale.h b/deps/uber-graph/uber-scale.h
new file mode 100644
index 00000000..5101275b
--- /dev/null
+++ b/deps/uber-graph/uber-scale.h
@@ -0,0 +1,38 @@
+/* uber-scale.h
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UBER_SCALE_H__
+#define __UBER_SCALE_H__
+
+#include "uber-range.h"
+
+G_BEGIN_DECLS
+
+typedef gboolean (*UberScale) (const UberRange *range,
+ const UberRange *pixel_range,
+ gdouble *value,
+ gpointer user_data);
+
+gboolean uber_scale_linear (const UberRange *range,
+ const UberRange *pixel_range,
+ gdouble *value,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* __UBER_SCALE_H__ */
diff --git a/deps/uber-graph/uber-scatter.c b/deps/uber-graph/uber-scatter.c
new file mode 100644
index 00000000..56c7b00d
--- /dev/null
+++ b/deps/uber-graph/uber-scatter.c
@@ -0,0 +1,421 @@
+/* uber-scatter.c
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+#include <string.h>
+
+#include "uber-scatter.h"
+#include "uber-scale.h"
+#include "uber-range.h"
+#include "g-ring.h"
+
+#define RADIUS 3
+
+/**
+ * SECTION:uber-scatter.h
+ * @title: UberScatter
+ * @short_description:
+ *
+ * Section overview.
+ */
+
+
+struct _UberScatterPrivate
+{
+ GRing *raw_data;
+ UberRange range;
+ guint stride;
+ GdkRGBA fg_color;
+ gboolean fg_color_set;
+ UberScatterFunc func;
+ gpointer func_user_data;
+ GDestroyNotify func_destroy;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE(UberScatter, uber_scatter, UBER_TYPE_GRAPH)
+
+/**
+ * uber_scatter_new:
+ *
+ * Creates a new instance of #UberScatter.
+ *
+ * Returns: the newly created instance of #UberScatter.
+ * Side effects: None.
+ */
+GtkWidget*
+uber_scatter_new (void)
+{
+ UberScatter *scatter;
+
+ scatter = g_object_new(UBER_TYPE_SCATTER, NULL);
+ return GTK_WIDGET(scatter);
+}
+
+/**
+ * uber_scatter_set_data_func:
+ * @scatter: A #UberScatter.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_scatter_set_data_func (UberScatter *scatter, /* IN */
+ UberScatterFunc func, /* IN */
+ gpointer user_data, /* IN */
+ GDestroyNotify destroy) /* IN */
+{
+ UberScatterPrivate *priv;
+
+ g_return_if_fail(UBER_IS_SCATTER(scatter));
+ g_return_if_fail(func != NULL);
+
+ priv = scatter->priv;
+ /*
+ * Cleanup previous data func if necessary.
+ */
+ if (priv->func_destroy) {
+ priv->func_destroy(priv->func_user_data);
+ }
+ priv->func = func;
+ priv->func_destroy = destroy;
+ priv->func_user_data = user_data;
+}
+
+/**
+ * uber_scatter_destroy_array:
+ * @array: A #GArray.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_scatter_destroy_array (gpointer data) /* IN */
+{
+ GArray **ar = data;
+
+ if (ar) {
+ g_array_unref(*ar);
+ }
+}
+
+/**
+ * uber_scatter_set_stride:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_scatter_set_stride (UberGraph *graph, /* IN */
+ guint stride) /* IN */
+{
+ UberScatterPrivate *priv;
+
+ g_return_if_fail(UBER_IS_SCATTER(graph));
+
+ priv = UBER_SCATTER(graph)->priv;
+ if (priv->stride == stride) {
+ return;
+ }
+ priv->stride = stride;
+ if (priv->raw_data) {
+ g_ring_unref(priv->raw_data);
+ }
+ priv->raw_data = g_ring_sized_new(sizeof(GArray*), stride,
+ uber_scatter_destroy_array);
+}
+
+/**
+ * uber_scatter_render:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_scatter_render (UberGraph *graph, /* IN */
+ cairo_t *cr, /* IN */
+ GdkRectangle *area, /* IN */
+ guint epoch, /* IN */
+ gfloat each) /* IN */
+{
+ UberScatterPrivate *priv;
+ UberRange pixel_range;
+ GtkStyleContext *style;
+ GdkRGBA color;
+ GArray *ar;
+ gdouble x;
+ gdouble y;
+ guint i;
+ guint j;
+
+ g_return_if_fail(UBER_IS_SCATTER(graph));
+
+ priv = UBER_SCATTER(graph)->priv;
+ color = priv->fg_color;
+ if (!priv->fg_color_set) {
+ style = gtk_widget_get_style_context(GTK_WIDGET(graph));
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_SELECTED, &color);
+ }
+ /*
+ * Calculate ranges.
+ */
+ pixel_range.begin = area->y + (RADIUS / 2.);
+ pixel_range.end = area->y + area->height - RADIUS;
+ pixel_range.range = pixel_range.end - pixel_range.begin;
+ /*
+ * Retrieve the current data set.
+ */
+ for (i = 0; i < priv->raw_data->len; i++) {
+ if (!(ar = g_ring_get_index(priv->raw_data, GArray*, (int)i))) {
+ continue;
+ }
+ x = epoch - (i * each) - (each / 2.);
+ for (j = 0; j < ar->len; j++) {
+ y = g_array_index(ar, gdouble, j);
+// g_debug("Raw ==> %f", y);
+ uber_scale_linear(&priv->range, &pixel_range, &y, NULL);
+ /*
+ * Shadow.
+ */
+ cairo_arc(cr, x + .5, y + .5, RADIUS, 0, 2 * M_PI);
+ cairo_set_source_rgb(cr, .1, .1, .1);
+ cairo_fill(cr);
+ /*
+ * Foreground.
+ */
+ cairo_arc(cr, x, y, RADIUS, 0, 2 * M_PI);
+ cairo_set_source_rgb(cr,
+ color.red,
+ color.green,
+ color.blue);
+ cairo_fill(cr);
+ }
+ }
+}
+
+/**
+ * uber_scatter_render_fast:
+ * @graph: A #UberGraph.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_scatter_render_fast (UberGraph *graph, /* IN */
+ cairo_t *cr, /* IN */
+ GdkRectangle *area, /* IN */
+ guint epoch, /* IN */
+ gfloat each) /* IN */
+{
+ UberScatterPrivate *priv;
+ UberRange pixel_range;
+ GtkStyleContext *style;
+ GdkRGBA color;
+ GArray *ar;
+ gdouble x;
+ gdouble y;
+ guint i;
+
+ g_return_if_fail(UBER_IS_SCATTER(graph));
+
+ priv = UBER_SCATTER(graph)->priv;
+ color = priv->fg_color;
+ if (!priv->fg_color_set) {
+ style = gtk_widget_get_style_context(GTK_WIDGET(graph));
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_SELECTED, &color);
+ }
+ /*
+ * Calculate ranges.
+ */
+ pixel_range.begin = area->y + (RADIUS / 2.);
+ pixel_range.end = area->y + area->height - RADIUS;
+ pixel_range.range = pixel_range.end - pixel_range.begin;
+ /*
+ * Retrieve the current data set.
+ */
+ ar = g_ring_get_index(priv->raw_data, GArray*, 0);
+ if (!ar) {
+ return;
+ }
+ /*
+ * Calculate X position (Center of this chunk).
+ */
+ x = epoch - (each / 2.);
+ /*
+ * Draw scatter dots.
+ */
+ for (i = 0; i < ar->len; i++) {
+ /*
+ * Scale the value to our graph coordinates.
+ */
+ y = g_array_index(ar, gdouble, i);
+ /*
+ * XXX: Support multiple scales.
+ */
+ uber_scale_linear(&priv->range, &pixel_range, &y, NULL);
+ /*
+ * Shadow.
+ */
+ cairo_arc(cr, x + .5, y + .5, RADIUS, 0, 2 * M_PI);
+ cairo_set_source_rgb(cr, .1, .1, .1);
+ cairo_fill(cr);
+ /*
+ * Foreground.
+ */
+ cairo_arc(cr, x, y, RADIUS, 0, 2 * M_PI);
+ cairo_set_source_rgb(cr,
+ color.red,
+ color.green,
+ color.blue);
+ cairo_fill(cr);
+ }
+}
+
+/**
+ * uber_scatter_get_next_data:
+ * @graph: A #UberGraph.
+ *
+ * Retrieve the next data point for the graph.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static gboolean
+uber_scatter_get_next_data (UberGraph *graph) /* IN */
+{
+ UberScatterPrivate *priv;
+ GArray *array = NULL;
+
+ g_return_val_if_fail(UBER_IS_SCATTER(graph), FALSE);
+
+ priv = UBER_SCATTER(graph)->priv;
+ if (priv->func) {
+ if (!priv->func(UBER_SCATTER(graph), &array, priv->func_user_data)) {
+ array = NULL;
+ }
+ g_ring_append_val(priv->raw_data, array);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * uber_scatter_set_fg_color:
+ * @scatter: A #UberScatter.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_scatter_set_fg_color (UberScatter *scatter, /* IN */
+ const GdkRGBA *color) /* IN */
+{
+ UberScatterPrivate *priv;
+
+ g_return_if_fail(UBER_IS_SCATTER(scatter));
+
+ priv = scatter->priv;
+ if (color) {
+ priv->fg_color = *color;
+ priv->fg_color_set = TRUE;
+ } else {
+ memset(&priv->fg_color, 0, sizeof(priv->fg_color));
+ priv->fg_color_set = FALSE;
+ }
+}
+
+/**
+ * uber_scatter_finalize:
+ * @object: A #UberScatter.
+ *
+ * Finalizer for a #UberScatter instance. Frees any resources held by
+ * the instance.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_scatter_finalize (GObject *object) /* IN */
+{
+ G_OBJECT_CLASS(uber_scatter_parent_class)->finalize(object);
+}
+
+/**
+ * uber_scatter_class_init:
+ * @klass: A #UberScatterClass.
+ *
+ * Initializes the #UberScatterClass and prepares the vtable.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_scatter_class_init (UberScatterClass *klass) /* IN */
+{
+ GObjectClass *object_class;
+ UberGraphClass *graph_class;
+
+ object_class = G_OBJECT_CLASS(klass);
+ object_class->finalize = uber_scatter_finalize;
+
+ graph_class = UBER_GRAPH_CLASS(klass);
+ graph_class->render = uber_scatter_render;
+ graph_class->render_fast = uber_scatter_render_fast;
+ graph_class->set_stride = uber_scatter_set_stride;
+ graph_class->get_next_data = uber_scatter_get_next_data;
+}
+
+/**
+ * uber_scatter_init:
+ * @scatter: A #UberScatter.
+ *
+ * Initializes the newly created #UberScatter instance.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_scatter_init (UberScatter *scatter) /* IN */
+{
+ UberScatterPrivate *priv;
+
+ scatter->priv = uber_scatter_get_instance_private(scatter);
+
+ priv = scatter->priv;
+
+ priv->range.begin = 0.;
+ priv->range.end = 15000.;
+ priv->range.range = priv->range.end - priv->range.begin;
+}
diff --git a/deps/uber-graph/uber-scatter.h b/deps/uber-graph/uber-scatter.h
new file mode 100644
index 00000000..ef111456
--- /dev/null
+++ b/deps/uber-graph/uber-scatter.h
@@ -0,0 +1,66 @@
+/* uber-scatter.h
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UBER_SCATTER_H__
+#define __UBER_SCATTER_H__
+
+#include "uber-graph.h"
+
+G_BEGIN_DECLS
+
+#define UBER_TYPE_SCATTER (uber_scatter_get_type())
+#define UBER_SCATTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UBER_TYPE_SCATTER, UberScatter))
+#define UBER_SCATTER_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UBER_TYPE_SCATTER, UberScatter const))
+#define UBER_SCATTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UBER_TYPE_SCATTER, UberScatterClass))
+#define UBER_IS_SCATTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UBER_TYPE_SCATTER))
+#define UBER_IS_SCATTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UBER_TYPE_SCATTER))
+#define UBER_SCATTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UBER_TYPE_SCATTER, UberScatterClass))
+
+typedef struct _UberScatter UberScatter;
+typedef struct _UberScatterClass UberScatterClass;
+typedef struct _UberScatterPrivate UberScatterPrivate;
+
+typedef gboolean (*UberScatterFunc) (UberScatter *scatter,
+ GArray **values,
+ gpointer user_data);
+
+struct _UberScatter
+{
+ UberGraph parent;
+
+ /*< private >*/
+ UberScatterPrivate *priv;
+};
+
+struct _UberScatterClass
+{
+ UberGraphClass parent_class;
+};
+
+GType uber_scatter_get_type (void) G_GNUC_CONST;
+GtkWidget* uber_scatter_new (void);
+void uber_scatter_set_fg_color (UberScatter *scatter,
+ const GdkRGBA *color);
+void uber_scatter_set_data_func (UberScatter *scatter,
+ UberScatterFunc func,
+ gpointer user_data,
+ GDestroyNotify destroy);
+
+G_END_DECLS
+
+#endif /* __UBER_SCATTER_H__ */
diff --git a/deps/uber-graph/uber-timeout-interval.c b/deps/uber-graph/uber-timeout-interval.c
new file mode 100644
index 00000000..baabbcca
--- /dev/null
+++ b/deps/uber-graph/uber-timeout-interval.c
@@ -0,0 +1,140 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Authored By Neil Roberts <neil@linux.intel.com>
+ *
+ * Copyright (C) 2009 Intel Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* This file contains the common code to check whether an interval has
+ expired used in uber-frame-source and uber-timeout-pool. */
+
+#include "uber-timeout-interval.h"
+
+void
+_uber_timeout_interval_init (UberTimeoutInterval *interval,
+ guint fps)
+{
+#if GLIB_CHECK_VERSION (2, 27, 3)
+ interval->start_time = g_get_monotonic_time () / 1000;
+#else
+ {
+ GTimeVal start_time;
+ g_get_current_time (&start_time);
+ interval->start_time = start_time.tv_sec * 1000
+ + start_time.tv_usec / 1000;
+ }
+#endif
+
+ interval->fps = fps;
+ interval->frame_count = 0;
+}
+
+static gint64
+_uber_timeout_interval_get_ticks (gint64 current_time,
+ UberTimeoutInterval *interval)
+{
+ return MAX (current_time - interval->start_time, 0);
+}
+
+gboolean
+_uber_timeout_interval_prepare (gint64 current_time,
+ UberTimeoutInterval *interval,
+ gint *delay)
+{
+ guint elapsed_time, new_frame_num;
+
+ elapsed_time = _uber_timeout_interval_get_ticks (current_time, interval);
+ new_frame_num = elapsed_time * interval->fps / 1000;
+
+ /* If time has gone backwards or the time since the last frame is
+ greater than the two frames worth then reset the time and do a
+ frame now */
+ if (new_frame_num < interval->frame_count ||
+ new_frame_num - interval->frame_count > 2)
+ {
+ /* Get the frame time rounded up to the nearest ms */
+ guint frame_time = (1000 + interval->fps - 1) / interval->fps;
+
+ /* Reset the start time */
+ interval->start_time = current_time;
+
+ /* Move the start time as if one whole frame has elapsed */
+ interval->start_time -= frame_time;
+
+ interval->frame_count = 0;
+
+ if (delay)
+ *delay = 0;
+
+ return TRUE;
+ }
+ else if (new_frame_num > interval->frame_count)
+ {
+ if (delay)
+ *delay = 0;
+
+ return TRUE;
+ }
+ else
+ {
+ if (delay)
+ *delay = ((interval->frame_count + 1) * 1000 / interval->fps
+ - elapsed_time);
+
+ return FALSE;
+ }
+}
+
+gboolean
+_uber_timeout_interval_dispatch (UberTimeoutInterval *interval,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ if ((* callback) (user_data))
+ {
+ interval->frame_count++;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gint
+_uber_timeout_interval_compare_expiration (const UberTimeoutInterval *a,
+ const UberTimeoutInterval *b)
+{
+ guint a_delay = 1000 / a->fps;
+ guint b_delay = 1000 / b->fps;
+ gint64 b_difference;
+ gint comparison;
+
+ b_difference = a->start_time - b->start_time;
+
+ comparison = ((gint) (((gint64)a->frame_count + 1) * a_delay)
+ - (gint) (((gint64)b->frame_count + 1) * b_delay + b_difference));
+
+ return (comparison < 0 ? -1
+ : comparison > 0 ? 1
+ : 0);
+}
diff --git a/deps/uber-graph/uber-timeout-interval.h b/deps/uber-graph/uber-timeout-interval.h
new file mode 100644
index 00000000..c1b8b892
--- /dev/null
+++ b/deps/uber-graph/uber-timeout-interval.h
@@ -0,0 +1,56 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Authored By Neil Roberts <neil@linux.intel.com>
+ *
+ * Copyright (C) 2009 Intel Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UBER_TIMEOUT_INTERVAL_H__
+#define __UBER_TIMEOUT_INTERVAL_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _UberTimeoutInterval UberTimeoutInterval;
+
+struct _UberTimeoutInterval
+{
+ gint64 start_time;
+ guint frame_count, fps;
+};
+
+void _uber_timeout_interval_init (UberTimeoutInterval *interval,
+ guint fps);
+
+gboolean _uber_timeout_interval_prepare (gint64 current_time,
+ UberTimeoutInterval *interval,
+ gint *delay);
+
+gboolean _uber_timeout_interval_dispatch (UberTimeoutInterval *interval,
+ GSourceFunc callback,
+ gpointer user_data);
+
+gint _uber_timeout_interval_compare_expiration
+ (const UberTimeoutInterval *a,
+ const UberTimeoutInterval *b);
+
+G_END_DECLS
+
+#endif /* __UBER_TIMEOUT_INTERVAL_H__ */
diff --git a/deps/uber-graph/uber-window.c b/deps/uber-graph/uber-window.c
new file mode 100644
index 00000000..989f3374
--- /dev/null
+++ b/deps/uber-graph/uber-window.c
@@ -0,0 +1,330 @@
+/* uber-window.c
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "uber-window.h"
+
+/**
+ * SECTION:uber-window.h
+ * @title: UberWindow
+ * @short_description:
+ *
+ * Section overview.
+ */
+
+
+struct _UberWindowPrivate
+{
+ gint graph_count;
+ GList *graphs;
+ GtkWidget *notebook;
+ GtkWidget *table;
+};
+G_DEFINE_TYPE_WITH_PRIVATE(UberWindow, uber_window, GTK_TYPE_WINDOW)
+
+/**
+ * uber_window_new:
+ *
+ * Creates a new instance of #UberWindow.
+ *
+ * Returns: the newly created instance of #UberWindow.
+ * Side effects: None.
+ */
+GtkWidget*
+uber_window_new (void)
+{
+ UberWindow *window;
+
+ window = g_object_new(UBER_TYPE_WINDOW, NULL);
+ return GTK_WIDGET(window);
+}
+
+/**
+ * uber_window_show_labels:
+ * @window: A #UberWindow.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_window_show_labels (UberWindow *window, /* IN */
+ UberGraph *graph) /* IN */
+{
+ UberWindowPrivate *priv;
+ GtkWidget *labels;
+ GtkWidget *align;
+ GList *list;
+ gboolean show;
+
+ g_return_if_fail(UBER_IS_WINDOW(window));
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = window->priv;
+ /*
+ * Get the widgets labels.
+ */
+ labels = uber_graph_get_labels(graph);
+ /*
+ * Show/hide ticks and labels.
+ */
+ show = !!labels;
+ if (labels) {
+ align = gtk_bin_get_child(GTK_BIN(labels));
+ list = gtk_container_get_children(GTK_CONTAINER(align));
+ if (list) {
+ gtk_widget_show(labels);
+ } else {
+ gtk_widget_hide(labels);
+ show = FALSE;
+ }
+ g_list_free(list);
+ }
+ if (graph == (gpointer)g_list_last(priv->graphs)) {
+ show = TRUE;
+ }
+ uber_graph_set_show_xlabels(graph, show);
+ /*
+ * Hide labels/xlabels for other graphs.
+ */
+ for (list = priv->graphs; list && list->next; list = list->next) {
+ if (list->data != graph) {
+ uber_graph_set_show_xlabels(list->data, FALSE);
+ labels = uber_graph_get_labels(list->data);
+ if (labels) {
+ gtk_widget_hide(labels);
+ }
+ }
+ }
+ /*
+ * Ensure the last graph always has labels.
+ */
+ if (list) {
+ uber_graph_set_show_xlabels(UBER_GRAPH(list->data), TRUE);
+ }
+}
+
+/**
+ * uber_window_hide_labels:
+ * @window: A #UberWindow.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_window_hide_labels (UberWindow *window, /* IN */
+ UberGraph *graph) /* IN */
+{
+ UberWindowPrivate *priv;
+ GtkWidget *labels;
+ gboolean show;
+
+ g_return_if_fail(UBER_IS_WINDOW(window));
+ g_return_if_fail(UBER_IS_GRAPH(graph));
+
+ priv = window->priv;
+ labels = uber_graph_get_labels(graph);
+ if (labels) {
+ gtk_widget_hide(labels);
+ }
+ show = g_list_last(priv->graphs) == (gpointer)graph;
+ uber_graph_set_show_xlabels(graph, show);
+}
+
+static gboolean
+uber_window_graph_button_press_event (GtkWidget *widget, /* IN */
+ GdkEventButton *button, /* IN */
+ UberWindow *window) /* IN */
+{
+ GtkWidget *labels;
+
+ g_return_val_if_fail(UBER_IS_WINDOW(window), FALSE);
+ g_return_val_if_fail(UBER_IS_GRAPH(widget), FALSE);
+
+ switch (button->button) {
+ case 1: /* Left click */
+ labels = uber_graph_get_labels(UBER_GRAPH(widget));
+ if (gtk_widget_get_visible(labels)) {
+ uber_window_hide_labels(window, UBER_GRAPH(widget));
+ } else {
+ uber_window_show_labels(window, UBER_GRAPH(widget));
+ }
+ break;
+ default:
+ break;
+ }
+ return FALSE;
+}
+
+/**
+ * uber_window_add_graph:
+ * @window: A #UberWindow.
+ *
+ * XXX
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+uber_window_add_graph (UberWindow *window, /* IN */
+ UberGraph *graph, /* IN */
+ const gchar *title) /* IN */
+{
+ UberWindowPrivate *priv;
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ GtkWidget *label;
+ GtkWidget *labels;
+ gchar *formatted;
+ gint left_attach;
+ gint top_attach;
+
+ g_return_if_fail(UBER_IS_WINDOW(window));
+
+ priv = window->priv;
+ /*
+ * Format title string.
+ */
+ formatted = g_markup_printf_escaped("<b>%s</b>", title);
+ /*
+ * Create container for graph.
+ */
+ vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
+ label = gtk_label_new(NULL);
+ labels = uber_graph_get_labels(graph);
+ gtk_label_set_markup(GTK_LABEL(label), formatted);
+ gtk_label_set_angle(GTK_LABEL(label), -270.);
+ gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(graph), TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
+ if (labels) {
+ gtk_box_pack_start(GTK_BOX(vbox), labels, FALSE, TRUE, 0);
+ }
+ gtk_widget_show(label);
+ gtk_widget_show(hbox);
+ gtk_widget_show(vbox);
+ /*
+ * Append graph to table.
+ */
+ left_attach = 0;
+ top_attach = priv->graph_count; // % 4;
+ gtk_grid_attach(GTK_GRID(priv->table), hbox,
+ left_attach,
+ top_attach,
+ 1,
+ 1);
+ /*
+ * Attach signal to show ticks when label is shown.
+ */
+ g_signal_connect_after(graph,
+ "button-press-event",
+ G_CALLBACK(uber_window_graph_button_press_event),
+ window);
+ priv->graphs = g_list_append(priv->graphs, graph);
+ /*
+ * Cleanup.
+ */
+ g_free(formatted);
+ priv->graph_count++;
+}
+
+/**
+ * uber_window_finalize:
+ * @object: A #UberWindow.
+ *
+ * Finalizer for a #UberWindow instance. Frees any resources held by
+ * the instance.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_window_finalize (GObject *object) /* IN */
+{
+ UberWindowPrivate *priv;
+
+ priv = UBER_WINDOW(object)->priv;
+ g_list_free(priv->graphs);
+ G_OBJECT_CLASS(uber_window_parent_class)->finalize(object);
+}
+
+/**
+ * uber_window_class_init:
+ * @klass: A #UberWindowClass.
+ *
+ * Initializes the #UberWindowClass and prepares the vtable.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_window_class_init (UberWindowClass *klass) /* IN */
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS(klass);
+ object_class->finalize = uber_window_finalize;
+}
+
+/**
+ * uber_window_init:
+ * @window: A #UberWindow.
+ *
+ * Initializes the newly created #UberWindow instance.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+uber_window_init (UberWindow *window) /* IN */
+{
+ UberWindowPrivate *priv;
+
+ window->priv = uber_window_get_instance_private(window);
+
+ /*
+ * Initialize defaults.
+ */
+ priv = window->priv;
+ gtk_window_set_title(GTK_WINDOW(window), "Uber Graph");
+ gtk_window_set_default_size(GTK_WINDOW(window), 750, 550);
+ gtk_container_set_border_width(GTK_CONTAINER(window), 12);
+ /*
+ * Create notebook container for pages.
+ */
+ priv->notebook = gtk_notebook_new();
+ gtk_notebook_set_show_border(GTK_NOTEBOOK(priv->notebook), FALSE);
+ gtk_notebook_set_show_tabs(GTK_NOTEBOOK(priv->notebook), FALSE);
+ gtk_container_add(GTK_CONTAINER(window), priv->notebook);
+ gtk_widget_show(priv->notebook);
+ /*
+ * Create table for graphs.
+ */
+ priv->table = gtk_grid_new();
+ gtk_grid_set_row_homogeneous(GTK_GRID(priv->table), TRUE);
+ gtk_grid_set_column_homogeneous(GTK_GRID(priv->table), TRUE);
+ gtk_notebook_append_page(GTK_NOTEBOOK(priv->notebook), priv->table, NULL);
+ gtk_widget_show(priv->table);
+}
diff --git a/deps/uber-graph/uber-window.h b/deps/uber-graph/uber-window.h
new file mode 100644
index 00000000..e913e852
--- /dev/null
+++ b/deps/uber-graph/uber-window.h
@@ -0,0 +1,65 @@
+/* uber-window.h
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UBER_WINDOW_H__
+#define __UBER_WINDOW_H__
+
+#include <gtk/gtk.h>
+
+#include "uber-graph.h"
+
+G_BEGIN_DECLS
+
+#define UBER_TYPE_WINDOW (uber_window_get_type())
+#define UBER_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UBER_TYPE_WINDOW, UberWindow))
+#define UBER_WINDOW_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UBER_TYPE_WINDOW, UberWindow const))
+#define UBER_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UBER_TYPE_WINDOW, UberWindowClass))
+#define UBER_IS_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UBER_TYPE_WINDOW))
+#define UBER_IS_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UBER_TYPE_WINDOW))
+#define UBER_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UBER_TYPE_WINDOW, UberWindowClass))
+
+typedef struct _UberWindow UberWindow;
+typedef struct _UberWindowClass UberWindowClass;
+typedef struct _UberWindowPrivate UberWindowPrivate;
+
+struct _UberWindow
+{
+ GtkWindow parent;
+
+ /*< private >*/
+ UberWindowPrivate *priv;
+};
+
+struct _UberWindowClass
+{
+ GtkWindowClass parent_class;
+};
+
+GType uber_window_get_type (void) G_GNUC_CONST;
+GtkWidget* uber_window_new (void);
+void uber_window_add_graph (UberWindow *window,
+ UberGraph *graph,
+ const gchar *title);
+void uber_window_show_labels (UberWindow *window,
+ UberGraph *graph);
+void uber_window_hide_labels (UberWindow *window,
+ UberGraph *graph);
+
+G_END_DECLS
+
+#endif /* __UBER_WINDOW_H__ */
diff --git a/deps/uber-graph/uber.h b/deps/uber-graph/uber.h
new file mode 100644
index 00000000..b101dfd7
--- /dev/null
+++ b/deps/uber-graph/uber.h
@@ -0,0 +1,30 @@
+/* uber.h
+ *
+ * Copyright (C) 2010 Christian Hergert <chris@dronelabs.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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UBER_H__
+#define __UBER_H__
+
+#include "uber-graph.h"
+#include "uber-line-graph.h"
+#include "uber-heat-map.h"
+#include "uber-range.h"
+#include "uber-scatter.h"
+#include "uber-scale.h"
+#include "uber-window.h"
+
+#endif /* __UBER_H__ */