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__ */ | 
