#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "procman.h" #include "load-graph.h" #include "util.h" #include "gsm_color_button.h" void LoadGraph::clear_background() { if (background) { cairo_pattern_destroy (background); this->background = NULL; } } unsigned LoadGraph::num_bars() const { unsigned n; // keep 100 % num_bars == 0 switch (static_cast(this->draw_height / (fontsize + 14))) { case 0: case 1: n = 1; break; case 2: case 3: n = 2; break; case 4: n = 4; break; default: n = 5; } return n; } #define FRAME_WIDTH 4 static void draw_background(LoadGraph *graph) { GtkAllocation allocation; cairo_t *cr; guint i; unsigned num_bars; g_autofree gchar *caption; PangoLayout* layout; PangoFontDescription* font_desc; PangoRectangle extents; cairo_surface_t *surface; GdkRGBA fg, bg; num_bars = graph->num_bars(); graph->graph_dely = (graph->draw_height - 15) / num_bars; /* round to int to avoid AA blur */ graph->real_draw_height = graph->graph_dely * num_bars; graph->graph_delx = (graph->draw_width - 2.0 - graph->rmargin - graph->indent) / (LoadGraph::NUM_POINTS - 3); graph->graph_buffer_offset = (int) (1.5 * graph->graph_delx) + FRAME_WIDTH ; gtk_widget_get_allocation (graph->disp, &allocation); surface = gdk_window_create_similar_surface (gtk_widget_get_window (graph->disp), CAIRO_CONTENT_COLOR_ALPHA, allocation.width, allocation.height); cr = cairo_create (surface); GtkStyleContext *context = gtk_widget_get_style_context (ProcData::get_instance()->notebook); gtk_style_context_save (context); gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL); gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), &bg); gtk_style_context_get_color (context, gtk_style_context_get_state (context), &fg); gtk_style_context_restore (context); // set the background color gdk_cairo_set_source_rgba (cr, &bg); cairo_paint (cr); layout = pango_cairo_create_layout (cr); gtk_style_context_save (context); gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL); gtk_style_context_get (context, gtk_style_context_get_state (context), GTK_STYLE_PROPERTY_FONT, &font_desc, NULL); gtk_style_context_restore (context); pango_font_description_set_size (font_desc, 0.8 * graph->fontsize * PANGO_SCALE); pango_layout_set_font_description (layout, font_desc); pango_font_description_free (font_desc); /* draw frame */ cairo_translate (cr, FRAME_WIDTH, FRAME_WIDTH); /* Draw background rectangle */ cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); cairo_rectangle (cr, graph->rmargin + graph->indent, 0, graph->draw_width - graph->rmargin - graph->indent, graph->real_draw_height); cairo_fill(cr); cairo_set_line_width (cr, 1.0); cairo_set_source_rgba (cr, 0.89, 0.89, 0.89, 1.0); for (i = 0; i <= num_bars; ++i) { double y; if (i == 0) y = 0.5 + graph->fontsize / 2.0; else if (i == num_bars) y = i * graph->graph_dely + 0.5; else y = i * graph->graph_dely + graph->fontsize / 2.0; gdk_cairo_set_source_rgba (cr, &fg); if (graph->type == LOAD_GRAPH_NET) { // operation orders matters so it's 0 if i == num_bars guint64 rate = graph->net.max - (i * graph->net.max / num_bars); caption = g_format_size_full (rate, ProcData::get_instance()->config.network_in_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_DEFAULT); pango_layout_set_text (layout, caption, -1); pango_layout_get_extents (layout, NULL, &extents); cairo_move_to (cr, graph->indent - 1.0 * extents.width / PANGO_SCALE + 20, y - 1.0 * extents.height / PANGO_SCALE / 2); pango_cairo_show_layout (cr, layout); } else { // operation orders matters so it's 0 if i == num_bars caption = g_strdup_printf("%d%%", 100 - i * (100 / num_bars)); pango_layout_set_text (layout, caption, -1); pango_layout_get_extents (layout, NULL, &extents); cairo_move_to (cr, graph->indent - 1.0 * extents.width / PANGO_SCALE + 20, y - 1.0 * extents.height / PANGO_SCALE / 2); pango_cairo_show_layout (cr, layout); } if (i==0 || i==num_bars) cairo_set_source_rgba (cr, 0.70, 0.71, 0.70, 1.0); else cairo_set_source_rgba (cr, 0.89, 0.89, 0.89, 1.0); cairo_move_to (cr, graph->rmargin + graph->indent - 3, i * graph->graph_dely + 0.5); cairo_line_to (cr, graph->draw_width - 0.5, i * graph->graph_dely + 0.5); cairo_stroke (cr); } const unsigned total_seconds = graph->speed * (LoadGraph::NUM_POINTS - 2) / 1000; for (unsigned int i = 0; i < 7; i++) { double x = (i) * (graph->draw_width - graph->rmargin - graph->indent) / 6; if (i==0 || i==6) cairo_set_source_rgba (cr, 0.70, 0.71, 0.70, 1.0); else cairo_set_source_rgba (cr, 0.89, 0.89, 0.89, 1.0); cairo_move_to (cr, (ceil(x) + 0.5) + graph->rmargin + graph->indent, 0.5); cairo_line_to (cr, (ceil(x) + 0.5) + graph->rmargin + graph->indent, graph->real_draw_height + 4.5); cairo_stroke(cr); unsigned seconds = total_seconds - i * total_seconds / 6; const char* format; if (i == 0) format = dngettext(GETTEXT_PACKAGE, "%u second", "%u seconds", seconds); else format = "%u"; caption = g_strdup_printf(format, seconds); pango_layout_set_text (layout, caption, -1); pango_layout_get_extents (layout, NULL, &extents); cairo_move_to (cr, ((ceil(x) + 0.5) + graph->rmargin + graph->indent) - (1.0 * extents.width / PANGO_SCALE/2), graph->draw_height - 1.0 * extents.height / PANGO_SCALE); gdk_cairo_set_source_rgba (cr, &fg); pango_cairo_show_layout (cr, layout); } g_object_unref(layout); cairo_stroke (cr); cairo_destroy (cr); graph->background = cairo_pattern_create_for_surface (surface); cairo_surface_destroy (surface); } /* Redraws the backing buffer for the load graph and updates the window */ void load_graph_queue_draw (LoadGraph *graph) { /* repaint */ gtk_widget_queue_draw (graph->disp); } static int load_graph_update (gpointer user_data); // predeclare load_graph_update so we can compile ;) static gboolean load_graph_configure (GtkWidget *widget, GdkEventConfigure *event, gpointer data_ptr) { GtkAllocation allocation; LoadGraph * const graph = static_cast(data_ptr); gtk_widget_get_allocation (widget, &allocation); graph->draw_width = allocation.width - 2 * FRAME_WIDTH; graph->draw_height = allocation.height - 2 * FRAME_WIDTH; graph->clear_background(); load_graph_queue_draw (graph); return TRUE; } static gboolean load_graph_draw (GtkWidget *widget, cairo_t *context, gpointer data_ptr) { LoadGraph * const graph = static_cast(data_ptr); GdkWindow *window; guint i, j; gdouble sample_width, x_offset; window = gtk_widget_get_window (graph->disp); /* Number of pixels wide for one graph point */ sample_width = (float)(graph->draw_width - graph->rmargin - graph->indent) / (float)LoadGraph::NUM_POINTS; /* General offset */ x_offset = graph->draw_width - graph->rmargin + (sample_width*2); /* Subframe offset */ x_offset += graph->rmargin - ((sample_width / graph->frames_per_unit) * graph->render_counter); /* draw the graph */ cairo_t* cr; cr = gdk_cairo_create (window); if (graph->background == NULL) { draw_background(graph); } cairo_set_source (cr, graph->background); cairo_paint (cr); cairo_set_line_width (cr, 1); cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); cairo_rectangle (cr, graph->rmargin + graph->indent + FRAME_WIDTH + 1, FRAME_WIDTH - 1, graph->draw_width - graph->rmargin - graph->indent - 1, graph->real_draw_height + FRAME_WIDTH - 1); cairo_clip(cr); for (j = 0; j < graph->n; ++j) { cairo_move_to (cr, x_offset, (1.0f - graph->data[0][j]) * graph->real_draw_height); gdk_cairo_set_source_rgba (cr, &(graph->colors [j])); for (i = 1; i < LoadGraph::NUM_POINTS; ++i) { if (graph->data[i][j] == -1.0f) continue; cairo_curve_to (cr, x_offset - ((i - 0.5f) * graph->graph_delx), (1.0f - graph->data[i-1][j]) * graph->real_draw_height + 3.5f, x_offset - ((i - 0.5f) * graph->graph_delx), (1.0f - graph->data[i][j]) * graph->real_draw_height + 3.5f, x_offset - (i * graph->graph_delx), (1.0f - graph->data[i][j]) * graph->real_draw_height + 3.5f); } cairo_stroke (cr); } cairo_destroy (cr); return TRUE; } static void get_load (LoadGraph *graph) { guint i; glibtop_cpu cpu; glibtop_get_cpu (&cpu); #undef NOW #undef LAST #define NOW (graph->cpu.times[graph->cpu.now]) #define LAST (graph->cpu.times[graph->cpu.now ^ 1]) if (graph->n == 1) { NOW[0][CPU_TOTAL] = cpu.total; NOW[0][CPU_USED] = cpu.user + cpu.nice + cpu.sys; } else { for (i = 0; i < graph->n; i++) { NOW[i][CPU_TOTAL] = cpu.xcpu_total[i]; NOW[i][CPU_USED] = cpu.xcpu_user[i] + cpu.xcpu_nice[i] + cpu.xcpu_sys[i]; } } // on the first call, LAST is 0 // which means data is set to the average load since boot // that value has no meaning, we just want all the // graphs to be aligned, so the CPU graph needs to start // immediately for (i = 0; i < graph->n; i++) { float load; float total, used; gchar *text; total = NOW[i][CPU_TOTAL] - LAST[i][CPU_TOTAL]; used = NOW[i][CPU_USED] - LAST[i][CPU_USED]; load = used / MAX(total, 1.0f); graph->data[0][i] = load; /* Update label */ text = g_strdup_printf("%.1f%%", load * 100.0f); gtk_label_set_text(GTK_LABEL(graph->labels.cpu[i]), text); g_free(text); } graph->cpu.now ^= 1; #undef NOW #undef LAST } namespace { void set_memory_label_and_picker(GtkLabel* label, GSMColorButton* picker, guint64 used, guint64 total, double percent) { char* used_text; char* total_text; char* text; used_text = g_format_size_full(used, G_FORMAT_SIZE_IEC_UNITS); total_text = g_format_size_full(total, G_FORMAT_SIZE_IEC_UNITS); if (total == 0) { text = g_strdup(_("not available")); } else { // xgettext: 540MiB (53 %) of 1.0 GiB text = g_strdup_printf(_("%s (%.1f%%) of %s"), used_text, 100.0 * percent, total_text); } gtk_label_set_text(label, text); g_free(used_text); g_free(total_text); g_free(text); if (picker) gsm_color_button_set_fraction(picker, percent); } } static void get_memory (LoadGraph *graph) { float mempercent, swappercent; glibtop_mem mem; glibtop_swap swap; glibtop_get_mem (&mem); glibtop_get_swap (&swap); /* There's no swap on LiveCD : 0.0f is better than NaN :) */ swappercent = (swap.total ? (float)swap.used / (float)swap.total : 0.0f); mempercent = (float)mem.user / (float)mem.total; set_memory_label_and_picker(GTK_LABEL(graph->labels.memory), GSM_COLOR_BUTTON(graph->mem_color_picker), mem.user, mem.total, mempercent); set_memory_label_and_picker(GTK_LABEL(graph->labels.swap), GSM_COLOR_BUTTON(graph->swap_color_picker), swap.used, swap.total, swappercent); graph->data[0][0] = mempercent; graph->data[0][1] = swappercent; } /* Nice Numbers for Graph Labels after Paul Heckbert nicenum: find a "nice" number approximately equal to x. Round the number if round=1, take ceiling if round=0 */ static double nicenum (double x, int round) { int expv; /* exponent of x */ double f; /* fractional part of x */ double nf; /* nice, rounded fraction */ expv = floor( log10(x) ); f = x/pow( 10.0, expv ); /* between 1 and 10 */ if (round) { if ( f < 1.5 ) nf = 1.0; else if ( f < 3.0 ) nf = 2.0; else if ( f < 7.0 ) nf = 5.0; else nf = 10.0; } else { if ( f <= 1.0 ) nf = 1.0; else if ( f <= 2.0 ) nf = 2.0; else if ( f <= 5.0 ) nf = 5.0; else nf = 10.0; } return nf * pow(10.0, expv); } static void net_scale (LoadGraph *graph, guint64 din, guint64 dout) { graph->data[0][0] = 1.0f * din / graph->net.max; graph->data[0][1] = 1.0f * dout / graph->net.max; guint64 dmax = std::max(din, dout); graph->net.values[graph->net.cur] = dmax; graph->net.cur = (graph->net.cur + 1) % LoadGraph::NUM_POINTS; guint64 new_max; // both way, new_max is the greatest value if (dmax >= graph->net.max) new_max = dmax; else new_max = *std::max_element(&graph->net.values[0], &graph->net.values[LoadGraph::NUM_POINTS]); // // Round network maximum // const guint64 bak_max(new_max); if (ProcData::get_instance()->config.network_in_bits) { // nice number is for the ticks unsigned ticks = graph->num_bars(); // gets messy at low values due to division by 8 guint64 bit_max = std::max( new_max*8, G_GUINT64_CONSTANT(10000) ); // our tick size leads to max double d = nicenum(bit_max/ticks, 0); bit_max = ticks * d; new_max = bit_max / 8; procman_debug("bak*8 %" G_GUINT64_FORMAT ", ticks %d, d %f" ", bit_max %" G_GUINT64_FORMAT ", new_max %" G_GUINT64_FORMAT, bak_max*8, ticks, d, bit_max, new_max ); } else { // round up to get some extra space // yes, it can overflow new_max = 1.1 * new_max; // make sure max is not 0 to avoid / 0 // default to 1 KiB new_max = std::max(new_max, G_GUINT64_CONSTANT(1024)); // decompose new_max = coef10 * 2**(base10 * 10) // where coef10 and base10 are integers and coef10 < 2**10 // // e.g: ceil(100.5 KiB) = 101 KiB = 101 * 2**(1 * 10) // where base10 = 1, coef10 = 101, pow2 = 16 guint64 pow2 = std::floor(log2(new_max)); guint64 base10 = pow2 / 10.0; guint64 coef10 = std::ceil(new_max / double(G_GUINT64_CONSTANT(1) << (base10 * 10))); g_assert(new_max <= (coef10 * (G_GUINT64_CONSTANT(1) << (base10 * 10)))); // then decompose coef10 = x * 10**factor10 // where factor10 is integer and x < 10 // so we new_max has only 1 significant digit guint64 factor10 = std::pow(10.0, std::floor(std::log10(coef10))); coef10 = std::ceil(coef10 / double(factor10)) * factor10; // then make coef10 divisible by num_bars if (coef10 % graph->num_bars() != 0) coef10 = coef10 + (graph->num_bars() - coef10 % graph->num_bars()); g_assert(coef10 % graph->num_bars() == 0); new_max = coef10 * (G_GUINT64_CONSTANT(1) << guint64(base10 * 10)); procman_debug("bak %" G_GUINT64_FORMAT " new_max %" G_GUINT64_FORMAT "pow2 %" G_GUINT64_FORMAT " coef10 %" G_GUINT64_FORMAT, bak_max, new_max, pow2, coef10); } if (bak_max > new_max) { procman_debug("overflow detected: bak=%" G_GUINT64_FORMAT " new=%" G_GUINT64_FORMAT, bak_max, new_max); new_max = bak_max; } // if max is the same or has decreased but not so much, don't // do anything to avoid rescaling if ((0.8 * graph->net.max) < new_max && new_max <= graph->net.max) return; const double scale = 1.0f * graph->net.max / new_max; for (size_t i = 0; i < LoadGraph::NUM_POINTS; i++) { if (graph->data[i][0] >= 0.0f) { graph->data[i][0] *= scale; graph->data[i][1] *= scale; } } procman_debug("rescale dmax = %" G_GUINT64_FORMAT " max = %" G_GUINT64_FORMAT " new_max = %" G_GUINT64_FORMAT, dmax, graph->net.max, new_max); graph->net.max = new_max; // force the graph background to be redrawn now that scale has changed graph->clear_background(); } static void get_net (LoadGraph *graph) { glibtop_netlist netlist; char **ifnames; guint32 i; guint64 in = 0, out = 0; GTimeVal time; guint64 din, dout; ifnames = glibtop_get_netlist(&netlist); for (i = 0; i < netlist.number; ++i) { glibtop_netload netload; glibtop_get_netload (&netload, ifnames[i]); if (netload.if_flags & (1 << GLIBTOP_IF_FLAGS_LOOPBACK)) continue; /* Skip interfaces without any IPv4/IPv6 address (or those with only a LINK ipv6 addr) However we need to be able to exclude these while still keeping the value so when they get online (with NetworkManager for example) we don't get a suddent peak. Once we're able to get this, ignoring down interfaces will be possible too. */ if (not (netload.flags & (1 << GLIBTOP_NETLOAD_ADDRESS6) and netload.scope6 != GLIBTOP_IF_IN6_SCOPE_LINK) and not (netload.flags & (1 << GLIBTOP_NETLOAD_ADDRESS))) continue; /* Don't skip interfaces that are down (GLIBTOP_IF_FLAGS_UP) to avoid spikes when they are brought up */ in += netload.bytes_in; out += netload.bytes_out; } g_strfreev(ifnames); g_get_current_time (&time); if (in >= graph->net.last_in && out >= graph->net.last_out && graph->net.time.tv_sec != 0) { float dtime; dtime = time.tv_sec - graph->net.time.tv_sec + (double) (time.tv_usec - graph->net.time.tv_usec) / G_USEC_PER_SEC; din = static_cast((in - graph->net.last_in) / dtime); dout = static_cast((out - graph->net.last_out) / dtime); } else { /* Don't calc anything if new data is less than old (interface removed, counters reset, ...) or if it is the first time */ din = 0; dout = 0; } graph->net.last_in = in; graph->net.last_out = out; graph->net.time = time; net_scale(graph, din, dout); bool network_in_bits = ProcData::get_instance()->config.network_in_bits; g_autofree gchar *str=NULL, *formatted_str=NULL; str = g_format_size_full (din, network_in_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_DEFAULT); formatted_str = g_strdup_printf(_("%s/s"), str); gtk_label_set_text (GTK_LABEL (graph->labels.net_in), formatted_str); str = g_format_size_full (in, network_in_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_DEFAULT); formatted_str = g_strdup_printf(_("%s/s"), str); gtk_label_set_text (GTK_LABEL (graph->labels.net_in_total), formatted_str); str = g_format_size_full (dout, network_in_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_DEFAULT); formatted_str = g_strdup_printf(_("%s/s"), str); gtk_label_set_text (GTK_LABEL (graph->labels.net_out), formatted_str); str = g_format_size_full (out, network_in_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_DEFAULT); formatted_str = g_strdup_printf(_("%s/s"), str); gtk_label_set_text (GTK_LABEL (graph->labels.net_out_total), formatted_str); } /* Updates the load graph when the timeout expires */ static gboolean load_graph_update (gpointer user_data) { LoadGraph * const graph = static_cast(user_data); if (graph->render_counter == graph->frames_per_unit - 1) { std::rotate(&graph->data[0], &graph->data[LoadGraph::NUM_POINTS - 1], &graph->data[LoadGraph::NUM_POINTS]); switch (graph->type) { case LOAD_GRAPH_CPU: get_load(graph); break; case LOAD_GRAPH_MEM: get_memory(graph); break; case LOAD_GRAPH_NET: get_net(graph); break; default: g_assert_not_reached(); } } if (graph->draw) load_graph_queue_draw (graph); graph->render_counter++; if (graph->render_counter >= graph->frames_per_unit) graph->render_counter = 0; return TRUE; } LoadGraph::~LoadGraph() { load_graph_stop(this); if (timer_index) g_source_remove(timer_index); clear_background(); } static gboolean load_graph_destroy (GtkWidget *widget, gpointer data_ptr) { LoadGraph * const graph = static_cast(data_ptr); delete graph; return FALSE; } LoadGraph::LoadGraph(guint type) : fontsize(8.0), rmargin(3.5 * fontsize), indent(24.0), n(0), type(type), speed(0), draw_width(0), draw_height(0), render_counter(0), frames_per_unit(10), // this will be changed but needs initialising graph_dely(0), real_draw_height(0), graph_delx(0.0), graph_buffer_offset(0), main_widget(NULL), disp(NULL), background(NULL), timer_index(0), draw(FALSE), mem_color_picker(NULL), swap_color_picker(NULL) { LoadGraph * const graph = this; // FIXME: // on configure, graph->frames_per_unit = graph->draw_width/(LoadGraph::NUM_POINTS); // knock FRAMES down to 5 until cairo gets faster switch (type) { case LOAD_GRAPH_CPU: memset(&cpu, 0, sizeof cpu); n = ProcData::get_instance()->config.num_cpus; for(guint i = 0; i < G_N_ELEMENTS(labels.cpu); ++i) labels.cpu[i] = gtk_label_new(NULL); break; case LOAD_GRAPH_MEM: n = 2; labels.memory = gtk_label_new(NULL); labels.swap = gtk_label_new(NULL); break; case LOAD_GRAPH_NET: memset(&net, 0, sizeof net); n = 2; net.max = 1; labels.net_in = gtk_label_new(NULL); labels.net_in_total = gtk_label_new(NULL); labels.net_out = gtk_label_new(NULL); labels.net_out_total = gtk_label_new(NULL); break; } speed = ProcData::get_instance()->config.graph_update_interval; colors.resize(n); switch (type) { case LOAD_GRAPH_CPU: memcpy(&colors[0], ProcData::get_instance()->config.cpu_color, n * sizeof colors[0]); break; case LOAD_GRAPH_MEM: colors[0] = ProcData::get_instance()->config.mem_color; colors[1] = ProcData::get_instance()->config.swap_color; mem_color_picker = gsm_color_button_new (&colors[0], GSMCP_TYPE_PIE); swap_color_picker = gsm_color_button_new (&colors[1], GSMCP_TYPE_PIE); break; case LOAD_GRAPH_NET: colors[0] = ProcData::get_instance()->config.net_in_color; colors[1] = ProcData::get_instance()->config.net_out_color; break; } timer_index = 0; render_counter = (frames_per_unit - 1); draw = FALSE; main_widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_widget_set_size_request(main_widget, -1, LoadGraph::GRAPH_MIN_HEIGHT); gtk_widget_show (main_widget); disp = gtk_drawing_area_new (); gtk_widget_show (disp); g_signal_connect (G_OBJECT (disp), "draw", G_CALLBACK (load_graph_draw), graph); g_signal_connect (G_OBJECT(disp), "configure_event", G_CALLBACK (load_graph_configure), graph); g_signal_connect (G_OBJECT(disp), "destroy", G_CALLBACK (load_graph_destroy), graph); gtk_widget_set_events (disp, GDK_EXPOSURE_MASK); gtk_box_pack_start (GTK_BOX (main_widget), disp, TRUE, TRUE, 0); /* Allocate data in a contiguous block */ data_block = std::vector(n * LoadGraph::NUM_POINTS, -1.0f); for (guint i = 0; i < LoadGraph::NUM_POINTS; ++i) data[i] = &data_block[0] + i * n; gtk_widget_show_all (main_widget); } void load_graph_start (LoadGraph *graph) { if(!graph->timer_index) { load_graph_update(graph); graph->timer_index = g_timeout_add (graph->speed / graph->frames_per_unit, load_graph_update, graph); } graph->draw = TRUE; } void load_graph_stop (LoadGraph *graph) { /* don't draw anymore, but continue to poll */ graph->draw = FALSE; } void load_graph_change_speed (LoadGraph *graph, guint new_speed) { if (graph->speed == new_speed) return; graph->speed = new_speed; if(graph->timer_index) { g_source_remove (graph->timer_index); graph->timer_index = g_timeout_add (graph->speed / graph->frames_per_unit, load_graph_update, graph); } graph->clear_background(); } LoadGraphLabels* load_graph_get_labels (LoadGraph *graph) { return &graph->labels; } GtkWidget* load_graph_get_widget (LoadGraph *graph) { return graph->main_widget; } GtkWidget* load_graph_get_mem_color_picker(LoadGraph *graph) { return graph->mem_color_picker; } GtkWidget* load_graph_get_swap_color_picker(LoadGraph *graph) { return graph->swap_color_picker; }