diff options
Diffstat (limited to 'deps')
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> %s </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("&", vl); + else + clean = g_strdup(v); + g_strfreev(vl); + + vl = g_strsplit(clean, "<", -1); + if (g_strv_length(vl) > 1) { + tmp = g_strjoinv("<", vl); + g_free(clean); + clean = tmp; + } + g_strfreev(vl); + + vl = g_strsplit(clean, ">", -1); + if (g_strv_length(vl) > 1) { + tmp = g_strjoinv(">", 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__ */ |