/* GKrellM
|  Copyright (C) 1999-2019 Bill Wilson
|
|  Author:  Bill Wilson    billw@gkrellm.net
|  Latest versions might be found at:  http://gkrellm.net
|
|
|  GKrellM 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.
|
|  GKrellM 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/
|
|
|  Additional permission under GNU GPL version 3 section 7
|
|  If you modify this program, or any covered work, by linking or
|  combining it with the OpenSSL project's OpenSSL library (or a
|  modified version of that library), containing parts covered by
|  the terms of the OpenSSL or SSLeay licenses, you are granted
|  additional permission to convey the resulting work.
|  Corresponding Source for a non-source form of such a combination
|  shall include the source code for the parts of OpenSSL used as well
|  as that of the covered work.
*/

#include "gkrellm.h"
#include "gkrellm-private.h"
#include "gkrellm-sysdeps.h"


static void	cb_alert_config(GkrellmAlert *ap, gpointer data);
#if !defined(WIN32)
static void	cb_alert_config_create(GkrellmAlert *ap, GtkWidget *vbox,
					gpointer data);
#endif


#ifdef CLK_TCK
#define	CPU_TICKS_PER_SECOND	CLK_TCK
#else
#define	CPU_TICKS_PER_SECOND	100		/* XXX */
#endif


typedef struct
	{
	gchar			*name;
	gchar			*panel_label;
	GtkWidget		*vbox;
	GkrellmChart	*chart;
	GkrellmChartconfig	*chart_config;
	GkrellmChartdata	*sys_cd,
					*user_cd,
					*nice_cd;
	gint			enabled;
	gint			extra_info;
	gint			is_composite;
	gulong			previous_total;
	gint			instance;

	gpointer		sensor_temp,
					sensor_fan;
	GkrellmDecal	*sensor_decal,		/* temperature and possibly fan */
					*fan_decal;			/* fan if separate draw mode	*/
	gboolean		show_temperature,
					show_fan,
					new_text_format;
	gint			save_label_position;  /* When toggling sensors on/off */

	GkrellmAlert	*alert;
	gulong			previous_alert_value,
					previous_alert_total;

	GkrellmLauncher	launch;
	GtkWidget		*launch_entry,
					*tooltip_entry;

	gulong			user,
					nice,
					sys,
					idle;
	}
	CpuMon;


static GkrellmMonitor
				*mon_cpu;

GList			*cpu_mon_list,
				*instance_list;

void			(*read_cpu_data)();
static CpuMon	*composite_cpu;

static GkrellmAlert	*cpu_alert;		/* One alert duped for each CPU */
static gboolean	alert_includes_nice;

static gint		old_smp_mode;		/* Remove these in 2.3.7 release */
static gint		old_cpu_enabled;


static gboolean	show_panel_labels = TRUE;

static gboolean	any_cpu_enabled,
				any_real_cpu_enabled;

static gboolean	omit_nice_mode,
				config_tracking,
				sys_handles_composite_data,
				nice_time_unsupported;

static gint		style_id;
static gint		sensor_separate_mode;

static gchar    *text_format,
				*text_format_locale;

static gint		n_cpus;
static gint		n_smp_cpus;


static gboolean
setup_cpu_interface(void)
	{
	if (!read_cpu_data && !_GK.client_mode && gkrellm_sys_cpu_init())
		{
		read_cpu_data = gkrellm_sys_cpu_read_data;
		}
	return read_cpu_data ? TRUE : FALSE;
	}

void
gkrellm_cpu_client_divert(void (*read_func)())
	{
	read_cpu_data = read_func;
	}

  /* Must be called from gkrellm_sys_cpu_init()
  */
void
gkrellm_cpu_set_number_of_cpus(gint n)
	{
	CpuMon	*cpu;
	GList	*list;
	gint	i;

	n_cpus = n;
	if (instance_list && g_list_length(instance_list) != n)
		{
		instance_list = NULL;
		fprintf(stderr, "Bad sysdep cpu instance list length.\n");
		}
	if (!instance_list)
		for (i = 0; i < n; ++i)
			instance_list = g_list_append(instance_list, GINT_TO_POINTER(i));
	if (n > 1)
		{
		++n_cpus;	/* Include the composite cpu */
		n_smp_cpus = n;
		}
	for (i = 0; i < n_cpus; ++i)
		{
		cpu = g_new0(CpuMon, 1);
		if (i == 0)
			{
			cpu->name = g_strdup("cpu");
			if (n_smp_cpus > 0)
				{
				/* For SMP, default is composite not enabled.
				*/
				cpu->is_composite = TRUE;
				cpu->instance = -1;
				composite_cpu = cpu;
				}
			else
				{
				cpu->instance = GPOINTER_TO_INT(instance_list->data);
				cpu->enabled = TRUE;
				}
			}
		else
			{
			list = g_list_nth(instance_list, i - 1);
			cpu->instance = GPOINTER_TO_INT(list->data);
			cpu->name = g_strdup_printf("cpu%d", cpu->instance);
			cpu->enabled = TRUE;
			}
		cpu->panel_label = g_strdup_printf(_("CPU%s"), &(cpu->name)[3]);
		cpu_mon_list = g_list_append(cpu_mon_list, cpu);
		}
	}

  /* If cpu numbering is not sequential starting from zero, then sysdep code
  |  should call this function for each cpu to make a list of cpu numbers.
  */
void
gkrellm_cpu_add_instance(gint instance)
	{
	instance_list = g_list_append(instance_list, GINT_TO_POINTER(instance));
	}

void
gkrellm_cpu_nice_time_unsupported(void)
	{
	nice_time_unsupported = TRUE;
	}

void
gkrellm_cpu_assign_composite_data(gulong user, gulong nice,
			gulong sys, gulong idle)
	{
	if (!composite_cpu)
		return;
	sys_handles_composite_data = TRUE;
	composite_cpu->user = user;
	composite_cpu->nice = nice;
	composite_cpu->sys = sys;
	composite_cpu->idle = idle;
	}

void
gkrellm_cpu_assign_data(gint n, gulong user, gulong nice,
			gulong sys, gulong idle)
	{
	CpuMon	*cpu = NULL;
	GList	*list;

	for (list = cpu_mon_list; list; list = list->next)
		{
		cpu = (CpuMon *) list->data;
		if (cpu->instance == n)
			break;
		}
	if (list)
		{
		cpu->user = user;
		cpu->nice = nice;
		cpu->sys = sys;
		cpu->idle = idle;
		if (composite_cpu && !sys_handles_composite_data)
			{
			composite_cpu->user += user;
			composite_cpu->nice += nice;
			composite_cpu->sys += sys;
			composite_cpu->idle += idle;
			}
		}
	}

/* ======================================================================== */
/* Exporting CPU data for plugins */

gint
gkrellm_smp_cpus(void)
	{
	return n_smp_cpus;
	}

gboolean
gkrellm_cpu_stats(gint n, gulong *user, gulong *nice,
			gulong *sys, gulong *idle)
	{
	GList	*list;
	CpuMon	*cpu;

	list = g_list_nth(cpu_mon_list, n);
	if (!list)
		return FALSE;
	cpu = (CpuMon *) list->data;
	if (user)
		*user = cpu->user;
	if (nice)
		*nice = cpu->nice;
	if (sys)
		*sys = cpu->sys;
	if (idle)
		*idle = cpu->idle;
	return TRUE;
	}

/* ======================================================================== */

static void
format_cpu_data(CpuMon *cpu, gchar *src_string, gchar *buf, gint size)
	{
	gchar			c, *s;
	gint			len, sys, user, nice = 0, total, t;

	if (!buf || size < 1)
		return;
	--size;
	*buf = '\0';
	if (!src_string)
		return;
	sys = gkrellm_get_current_chartdata(cpu->sys_cd);
	user = gkrellm_get_current_chartdata(cpu->user_cd);
	total = sys + user;
	if (!nice_time_unsupported)
		{
		nice = gkrellm_get_current_chartdata(cpu->nice_cd);
		if (!omit_nice_mode && !gkrellm_get_chartdata_hide(cpu->nice_cd))
			total += nice;
		}
	for (s = src_string; *s != '\0' && size > 0; ++s)
		{
		len = 1;
		if (*s == '$' && *(s + 1) != '\0')
			{
			t = -1;
			if ((c = *(s + 1)) == 'T')
				t = total;
			else if (c == 's')
				t = sys;
			else if (c == 'u')
				t = user;
			else if (c == 'n')
				t = nice;
			else if (c == 'L')
				len = snprintf(buf, size, "%s", cpu->panel_label);
			else if (c == 'N')
				len = snprintf(buf, size, "%s", &(cpu->name)[3]);
			else if (c == 'H')
				len = snprintf(buf, size, "%s", gkrellm_sys_get_host_name());
			else
				{
				*buf = *s;
				if (size > 1)
					{
					*(buf + 1) = *(s + 1);
					++len;
					}
				}
			if (t >= 0)
				{
				/* ChartData values have been scaled to the chart max scale
				|  of CPU_TICKS_PER_SECOND.
				*/
				t = ((200 * t / CPU_TICKS_PER_SECOND) + 1) / 2;
				if (t > 100)
					t = 100;
				len = snprintf(buf, size, "%d%%", t);
				}
			++s;
			}
		else
			*buf = *s;
		size -= len;
		buf += len;
		}
	*buf = '\0';	
	}

static void
draw_cpu_extra(CpuMon *cpu)
	{
	GkrellmChart	*cp = cpu->chart;
	gchar			buf[128];

	if (!cp)
		return;
	format_cpu_data(cpu, text_format_locale, buf, sizeof(buf));
	if (!cpu->new_text_format)
		gkrellm_chart_reuse_text_format(cp);
	cpu->new_text_format = FALSE;
	gkrellm_draw_chart_text(cp, style_id, buf);
	}

static void
cb_command_process(GkrellmAlert *alert, gchar *src, gchar *dst, gint len,
				CpuMon *cpu)
    {
    format_cpu_data(cpu, src, dst, len);
	}

static void
refresh_cpu_chart(CpuMon *cpu)
	{
	GkrellmChart	*cp	= cpu->chart;

	gkrellm_draw_chartdata(cp);
	if (cpu->extra_info)
		draw_cpu_extra(cpu);
	gkrellm_draw_chart_to_screen(cp);
	}

static void
draw_sensor_decals(CpuMon *cpu)
	{
	GkrellmPanel	*p = cpu->chart->panel;
	gchar			units;
	gfloat			t, f;
	gint			toggle;

	if (sensor_separate_mode && cpu->show_temperature && cpu->show_fan)
		{
		gkrellm_sensor_read_temperature(cpu->sensor_temp, &t, &units);
		gkrellm_sensor_draw_temperature_decal(p, cpu->sensor_decal, t, units);
		gkrellm_sensor_read_fan(cpu->sensor_fan, &f);
		gkrellm_sensor_draw_fan_decal(p, cpu->fan_decal, f);
		}
	else
		{
		toggle = _GK.time_now & 2;
		if (cpu->show_fan && (toggle || !cpu->show_temperature) )
			{
			gkrellm_sensor_read_fan(cpu->sensor_fan, &f);
			gkrellm_sensor_draw_fan_decal(p, cpu->sensor_decal, f);
			}
		else if (cpu->show_temperature && (!toggle || !cpu->show_fan) )
			{
			gkrellm_sensor_read_temperature(cpu->sensor_temp, &t, &units);
			gkrellm_sensor_draw_temperature_decal(p, cpu->sensor_decal, t,
							units);
			}
		}
	}

void
gkrellm_cpu_draw_sensors(gpointer sr)
	{
	GList	*list;
	CpuMon	*cpu;

	for (list = cpu_mon_list; list; list = list->next)
		{
		cpu = (CpuMon *) list->data;
		if (sr && sr != cpu->sensor_temp && sr != cpu->sensor_fan)
			continue;
		if (cpu->enabled)
			draw_sensor_decals(cpu);
		}
	}

static void
update_cpu(void)
	{
	GList			*list;
	CpuMon			*cpu;
	GkrellmChart	*cp;
	GkrellmPanel	*p;
	GkrellmKrell	*krell;
	gulong			total, krell_value, full_scale;
	gulong			alert_value, alert_total_diff;

	_GK.cpu_sys_activity = 0;
	if (composite_cpu)
		{
		composite_cpu->user = 0;
		composite_cpu->nice = 0;
		composite_cpu->sys = 0;
		composite_cpu->idle = 0;
		}
	(*read_cpu_data)();

	for (list = cpu_mon_list; list; list = list->next)
		{
		cpu = (CpuMon *) list->data;

		/* Track cpu activity data to provide a helper for the mem monitor.
		*/
		if (list == cpu_mon_list)	/* Use composite cpu values */
			_GK.cpu_sys_activity = (int)(cpu->sys - cpu->sys_cd->previous);

		if (!cpu->enabled)
			continue;

		cp = cpu->chart;
		p = cp->panel;

		total = cpu->user + cpu->nice + cpu->sys + cpu->idle;
		if (GK.second_tick)
			{
			gkrellm_store_chartdata(cp, total, cpu->sys, cpu->user, cpu->nice);
			refresh_cpu_chart(cpu);

			alert_value = cpu->sys + cpu->user;
			if (alert_includes_nice)
				alert_value += cpu->nice;
			alert_total_diff = total - cpu->previous_alert_total;
			if (   alert_total_diff > 0
				&& (   ( cpu->is_composite && !any_real_cpu_enabled)
					|| (!cpu->is_composite &&  any_real_cpu_enabled)
				   )
			   )
				gkrellm_check_alert(cpu->alert,
					100.0 * (gfloat) (alert_value - cpu->previous_alert_value)
					/ (gfloat) alert_total_diff);
			cpu->previous_alert_value = alert_value;
			cpu->previous_alert_total = total;
			}
		if (   (GK.two_second_tick && !sensor_separate_mode)
			|| (GK.five_second_tick && sensor_separate_mode)
		   )
			draw_sensor_decals(cpu);

		krell = KRELL(cp->panel);
		full_scale = total - cpu->previous_total;
		if (full_scale > 0)		/* Can be 0 for data from gkrellmd */
			gkrellm_set_krell_full_scale(krell, (gint) full_scale, 10);

		krell_value = cpu->sys + cpu->user;

		if (cpu->previous_total > 0)
			{
			if (!omit_nice_mode && !gkrellm_get_chartdata_hide(cpu->nice_cd))
				krell_value += cpu->nice;
			gkrellm_update_krell(p, krell, krell_value);
			gkrellm_panel_label_on_top_of_decals(p,
						gkrellm_alert_decal_visible(cpu->alert));
			gkrellm_draw_panel_layers(p);
			}
		cpu->previous_total = total;
		}
	}


static gint
cpu_expose_event(GtkWidget *widget, GdkEventExpose *ev)
	{
	GList			*list;
	GkrellmChart	*cp;
	GdkPixmap		*pixmap	= NULL;

	for (list = cpu_mon_list; list; list = list->next)
		{
		cp = ((CpuMon *) list->data)->chart;
		if (widget == cp->drawing_area)
			pixmap = cp->pixmap;
		else if (widget == cp->panel->drawing_area)
			pixmap = cp->panel->pixmap;
		if (pixmap)
			{
			gdk_draw_drawable(widget->window, gkrellm_draw_GC(1), pixmap,
					ev->area.x, ev->area.y, ev->area.x, ev->area.y,
					ev->area.width, ev->area.height);
			break;
			}
		}
	return FALSE;
	}


static gint
cb_cpu_extra(GtkWidget *widget, GdkEventButton *ev)
	{
	GList			*list;
	CpuMon			*cpu;

	for (list = cpu_mon_list; list; list = list->next)
		{
		cpu = (CpuMon *) list->data;
		if (widget != cpu->chart->drawing_area)
			continue;
		if (ev->button == 1 && ev->type == GDK_BUTTON_PRESS)
			{
			cpu->extra_info = !cpu->extra_info;
			refresh_cpu_chart(cpu);
			gkrellm_config_modified();
			}
		else if (   ev->button == 3
				 || (ev->button == 1 && ev->type == GDK_2BUTTON_PRESS)
				)
			gkrellm_chartconfig_window_create(cpu->chart);
		break;
		}
	return FALSE;
	}

static void
setup_cpu_scaling(GkrellmChartconfig *cf)
	{
	gint	grids;

	grids = gkrellm_get_chartconfig_fixed_grids(cf);
	if (!grids)
		grids = FULL_SCALE_GRIDS;

	gkrellm_set_chartconfig_grid_resolution(cf,
				CPU_TICKS_PER_SECOND / grids);
	}

static void
cb_cpu_temp_alert_trigger(GkrellmAlert *alert, CpuMon *cpu)
	{
	GkrellmAlertdecal	*ad;
	GkrellmDecal		*d;

	if (alert && cpu && cpu->chart)
		{
		ad = &alert->ad;
		d = cpu->sensor_decal;
		if (d)
			{
			ad->x = d->x - 1;
			ad->y = d->y - 1;
			ad->w = d->w + 2;
			ad->h = d->h + 2;
			gkrellm_render_default_alert_decal(alert);
			}
		alert->panel = cpu->chart->panel;
		}
	}

static void
cb_cpu_fan_alert_trigger(GkrellmAlert *alert, CpuMon *cpu)
	{
	GkrellmAlertdecal	*ad;
	GkrellmDecal		*d;

	if (alert && cpu && cpu->chart)
		{
		ad = &alert->ad;
		if (sensor_separate_mode)
			d = cpu->fan_decal;
		else
			d = cpu->sensor_decal;
		if (d)
			{
			ad->x = d->x - 1;
			ad->y = d->y - 1;
			ad->w = d->w + 2;
			ad->h = d->h + 2;
			gkrellm_render_default_alert_decal(alert);
			}
		alert->panel = cpu->chart->panel;
		}
	}

  /* How to decide when to make the sensor_decal and fan_decal visible.
  |  The sensor_decal can show temp values, fan values, or alternating
  |  temp/fan values.  The fan_decal only shows fan values when non
  |  alternating mode is selected. The sensors are mapped in sensors.c
  |
  |   Sensor and fan decal display truth table:
  |   |-----decals visible----||--sensors mapped--|   separate  |
  |	  |sensor_decal  fan_decal||   temp     fan   |    mode     |
  |   |-----------------------||--------------------------------|
  |   |     0           0     ||     0       0          0       |
  |   |     1           0     ||     1       0          0       |
  |   |     1           0     ||     0       1          0       |
  |   |     1           0     ||     1       1          0       |
  |   |     0           0     ||     0       0          1       |
  |   |     1           0     ||     1       0          1       |
  |   |     1           0     ||     0       1          1       |
  |   |     1           1     ||     1       1          1       |
  |   |----------------------------------------------------------
  */
static gboolean
adjust_sensors_display(CpuMon *cpu, gint force)
	{
	GkrellmPanel	*p;
	GkrellmDecal	*ds, *df;
	GkrellmAlert	*alert;
	gint			position = 0;

	ds = cpu->sensor_decal;
	df = cpu->fan_decal;
	if (!ds || !df)
		return FALSE;
	/* The test for state change is decal state vs success at reading
	|  a temperature.
	*/
	p = cpu->chart->panel;
	cpu->show_temperature = cpu->show_fan = FALSE;
	if (!_GK.demo)
		{
		gkrellm_sensor_alert_connect(cpu->sensor_temp,
					cb_cpu_temp_alert_trigger, cpu);
		gkrellm_sensor_alert_connect(cpu->sensor_fan,
					cb_cpu_fan_alert_trigger, cpu);
		}

	/* If a fan alert is triggered, turn it off in case fan decal being used
	|  is changed.  The alert will just retrigger at next fan update.
	*/
	alert = gkrellm_sensor_alert(cpu->sensor_fan);
	gkrellm_reset_alert_soft(alert);

	if (cpu->sensor_temp || _GK.demo)
		cpu->show_temperature = TRUE;
	if (cpu->sensor_fan || _GK.demo)
		cpu->show_fan = TRUE;

	if (cpu->show_temperature || cpu->show_fan)
		{
		if (! gkrellm_is_decal_visible(ds) || force)
			gkrellm_make_decal_visible(p, ds);
		position = 0;
		}
	else
		{
		if (gkrellm_is_decal_visible(ds) || force)
			gkrellm_make_decal_invisible(p, ds);
		position = cpu->save_label_position;
		}
	if (cpu->show_fan && cpu->show_temperature && sensor_separate_mode)
		{
		if (! gkrellm_is_decal_visible(df) || force)
			gkrellm_make_decal_visible(p, df);
		position = -1;
		}
	else
		{
		if (gkrellm_is_decal_visible(df) || force)
			gkrellm_make_decal_invisible(p, df);
		}
	if (position != p->label->position || force)
		{
		if (cpu->save_label_position >= 0)	/* Reassign position only if the */
			p->label->position = position;	/* original label was visible.   */
		gkrellm_draw_panel_label(p);
		draw_sensor_decals(cpu);
		gkrellm_draw_panel_layers(p);
		}
	return TRUE;
	}

static gint
cb_panel_press(GtkWidget *widget, GdkEventButton *ev)
	{
	if (ev->button == 3)
		gkrellm_open_config_window(mon_cpu);
	return FALSE;
	}

static GkrellmPiximage	*nice_data_piximage,
						*nice_data_grid_piximage;
static GdkPixmap		*nice_data_pixmap,
						*nice_data_grid_pixmap;
static GdkColor			nice_color,
						nice_grid_color;
static gchar			*nice_color_string,
						*nice_grid_color_string;

static void
cb_height(GkrellmChartconfig *cf, CpuMon *cpu)
	{
	GList			*list;
	GkrellmChart	*cp;
	gint			h, h_max = 0;

	h = gkrellm_get_chartconfig_height(cf);
	for (list = cpu_mon_list; list; list = list->next)
		{
		cp = ((CpuMon *) list->data)->chart;
		if (cp->h != h && config_tracking)
			{
			gkrellm_chartconfig_callback_block(cp->config, TRUE);
			gkrellm_set_chart_height(cp, h);
			gkrellm_chartconfig_callback_block(cp->config, FALSE);
			}
		if (cp->h > h_max)
			h_max = cp->h;
		}
	gkrellm_render_data_pixmap(nice_data_piximage,
				&nice_data_pixmap, &nice_color, h_max);
	}

static void
load_nice_data_piximages(void)
	{
	if (nice_time_unsupported)
		return;
	g_free(nice_color_string);
	g_free(nice_grid_color_string);
	nice_color_string = gkrellm_get_gkrellmrc_string("cpu_nice_color");
	nice_grid_color_string =
				gkrellm_get_gkrellmrc_string("cpu_nice_grid_color");
	gkrellm_map_color_string(nice_color_string, &nice_color);
	gkrellm_map_color_string(nice_grid_color_string, &nice_grid_color);
		
	if (nice_data_piximage)
		gkrellm_destroy_piximage(nice_data_piximage);
	if (nice_data_grid_piximage)
		gkrellm_destroy_piximage(nice_data_grid_piximage);
	nice_data_piximage = nice_data_grid_piximage = NULL;

	gkrellm_free_pixmap(&nice_data_pixmap);
	gkrellm_free_pixmap(&nice_data_grid_pixmap);

	gkrellm_load_piximage("nice", NULL, &nice_data_piximage, CPU_STYLE_NAME);
	gkrellm_load_piximage("nice_grid", NULL, &nice_data_grid_piximage,
					CPU_STYLE_NAME);
	}

static void
render_nice_data_pixmaps(void)
	{
	GList		*list;
	CpuMon		*cpu;
	gint		h_max;

	gkrellm_render_data_grid_pixmap(nice_data_grid_piximage,
				&nice_data_grid_pixmap, &nice_grid_color);

	h_max = 2;
	for (list = cpu_mon_list; list; list = list->next)
		{
		cpu = (CpuMon *) list->data;
		if (cpu->chart && (cpu->chart->h > h_max))
			h_max = cpu->chart->h;
		}
	gkrellm_render_data_pixmap(nice_data_piximage,
				&nice_data_pixmap, &nice_color, h_max);
	}

static void
create_cpu(GtkWidget *vbox, gint first_create)
	{
	CpuMon			*cpu;
	GkrellmChart	*cp;
	GkrellmPanel	*p;
	GkrellmStyle	*style;
	GList			*list;

	load_nice_data_piximages();
	any_cpu_enabled = any_real_cpu_enabled = FALSE;
	for (list = cpu_mon_list; list; list = list->next)
		{
		cpu = (CpuMon *) list->data;

		if (first_create)
			{
			/* don't really need the cpu->vbox unless I start destroying...
			*/
			cpu->vbox = gtk_vbox_new(FALSE, 0);
			gtk_container_add(GTK_CONTAINER(vbox), cpu->vbox);
			gtk_widget_show(cpu->vbox);
			cpu->chart = gkrellm_chart_new0();
			cpu->chart->panel = gkrellm_panel_new0();
			if (cpu->enabled)
				{
				any_cpu_enabled = TRUE;
				if (!cpu->is_composite)
					any_real_cpu_enabled = TRUE;
				}
			}
		cp = cpu->chart;
		p = cp->panel;

		style = gkrellm_panel_style(style_id);
		gkrellm_create_krell(p, gkrellm_krell_panel_piximage(style_id), style);

		gkrellm_chart_create(cpu->vbox, mon_cpu, cp, &cpu->chart_config);
		gkrellm_set_draw_chart_function(cp, refresh_cpu_chart, cpu);
		cpu->sys_cd = gkrellm_add_default_chartdata(cp, _("sys time"));
		cpu->user_cd = gkrellm_add_default_chartdata(cp, _("user time"));
		if (!nice_time_unsupported)
			{
			if (   (nice_data_piximage && nice_data_grid_piximage)
				|| (nice_color_string && nice_grid_color_string)
			   )
				{
				render_nice_data_pixmaps();
				cpu->nice_cd = gkrellm_add_chartdata(cp, &nice_data_pixmap,
							nice_data_grid_pixmap, _("nice time"));
				}
			else
				cpu->nice_cd = gkrellm_add_default_chartdata(cp,
							_("nice time"));
			}
		gkrellm_set_chartdata_flags(cpu->nice_cd, CHARTDATA_ALLOW_HIDE);

		/* Since there is a constant Max value of the chart (CPU_TICKS/SEC)
		|  I control the grid resolution when fixed grids change.
		|  So don't call gkrellm_grid_resolution_adjustment() so there won't
		|  be a grid resolution part in the chart config.  Also, make sure
		|  auto grid res is off.
		*/
		gkrellm_set_chartconfig_auto_grid_resolution(cp->config, FALSE);
		gkrellm_chartconfig_fixed_grids_connect(cp->config,
					setup_cpu_scaling, NULL);
		gkrellm_chartconfig_height_connect(cp->config, cb_height, cpu);
		setup_cpu_scaling(cp->config);

		cpu->sensor_decal = cpu->fan_decal = NULL;
		if (show_panel_labels)
			gkrellm_sensors_create_decals(p, style_id,
						&cpu->sensor_decal, &cpu->fan_decal);

		gkrellm_panel_configure(p,
					show_panel_labels ? cpu->panel_label : NULL, style);

		gkrellm_panel_create(cpu->vbox, mon_cpu, p);

		cpu->save_label_position = p->label->position;
		adjust_sensors_display(cpu, TRUE);

		gkrellm_alloc_chartdata(cp);
		cpu->new_text_format = TRUE;

		if (!cpu->enabled)
			gkrellm_chart_hide(cp, TRUE);

		if (first_create)
			{
			g_signal_connect(G_OBJECT (cp->drawing_area), "expose_event",
					G_CALLBACK(cpu_expose_event), NULL);
			g_signal_connect(G_OBJECT (p->drawing_area), "expose_event",
					G_CALLBACK(cpu_expose_event), NULL);

			g_signal_connect(G_OBJECT(cp->drawing_area), "button_press_event",
					G_CALLBACK(cb_cpu_extra), NULL);
			g_signal_connect(G_OBJECT(p->drawing_area), "button_press_event",
					G_CALLBACK(cb_panel_press), NULL);
			}
		else
			refresh_cpu_chart(cpu);
		gkrellm_setup_launcher(p, &cpu->launch, CHART_PANEL_TYPE, 4);
		}
	if (any_cpu_enabled)
		gkrellm_spacers_show(mon_cpu);
	else
		gkrellm_spacers_hide(mon_cpu);
	}


/* ------------------------------------------------------------------ */
#define	CPU_CONFIG_KEYWORD	"cpu"

static GtkWidget	*text_format_combo_box;

#if !defined(WIN32)
static GtkWidget	*alert_config_nice_button;
#endif

static void
cb_alert_trigger(GkrellmAlert *alert, CpuMon *cpu)
	{
	GkrellmAlertdecal	*ad;
	GkrellmDecal		*ds, *df;

	alert->panel = cpu->chart->panel;
	ds = cpu->sensor_decal;
	df = cpu->fan_decal;
	if (    gkrellm_is_decal_visible(ds)
	    && !gkrellm_is_decal_visible(df)
	   )
		{
		ad = &alert->ad;
		ad->x = 0;
		ad->y = ds->y - 1;
		ad->w = ds->x - 1;
		ad->h = ds->h + 2;
		gkrellm_render_default_alert_decal(alert);
		}
	}

static void
dup_cpu_alert(void)
	{
	GList	*list;
	CpuMon	*cpu;

	for (list = cpu_mon_list; list; list = list->next)
		{
		cpu = (CpuMon *) list->data;
		gkrellm_alert_dup(&cpu->alert, cpu_alert);
		gkrellm_alert_trigger_connect(cpu->alert, cb_alert_trigger, cpu);
		gkrellm_alert_command_process_connect(cpu->alert,
					cb_command_process, cpu);
		}
	}

static void
create_alert(void)
	{
	GList		*list;

	list = g_list_nth(cpu_mon_list, 0);
	if (!list)
		return;
	cpu_alert = gkrellm_alert_create(NULL, _("CPU"),
					_("Percent Usage"),
					TRUE, FALSE, TRUE,
					100, 10, 1, 10, 0);
	gkrellm_alert_delay_config(cpu_alert, 1, 60 * 60, 2);
	gkrellm_alert_config_connect(cpu_alert, cb_alert_config, NULL);
#if !defined(WIN32)
	gkrellm_alert_config_create_connect(cpu_alert,
							cb_alert_config_create, NULL);
#endif
	/* This alert is a master to be dupped and is itself never checked */
	}

static void
save_cpu_config(FILE *f)
	{
	GList	*list;
	CpuMon	*cpu;

	/* FIXME XXX: temporary maintenance of old config for transition
	|  from 2.3.5 to 2.3.6.  Probably remove in 2.3.7.
	*/
	fprintf(f, "%s enable %d\n", CPU_CONFIG_KEYWORD, old_cpu_enabled);
	fprintf(f, "%s smp_mode %d\n", CPU_CONFIG_KEYWORD, old_smp_mode);

	for (list = cpu_mon_list; list; list = list->next)
		{
		cpu = (CpuMon *) list->data;
		fprintf(f, "%s enabled %s %d\n", CPU_CONFIG_KEYWORD,
					cpu->name, cpu->enabled);

		if (*(cpu->launch.command) != '\0')
			fprintf(f, "%s launch %s %s\n", CPU_CONFIG_KEYWORD,
						cpu->name, cpu->launch.command);
		if (*(cpu->launch.tooltip_comment) != '\0')
			fprintf(f, "%s tooltip_comment %s %s\n", CPU_CONFIG_KEYWORD,
						cpu->name, cpu->launch.tooltip_comment);
		fprintf(f, "%s extra_info %s %d\n", CPU_CONFIG_KEYWORD,
					cpu->name, cpu->extra_info);
		gkrellm_save_chartconfig(f, cpu->chart_config,
					CPU_CONFIG_KEYWORD, cpu->name);
		}
	fprintf(f, "%s show_panel_labels %d\n", CPU_CONFIG_KEYWORD,
				show_panel_labels);
	fprintf(f, "%s omit_nice_mode %d\n", CPU_CONFIG_KEYWORD, omit_nice_mode);
	fprintf(f, "%s config_tracking %d\n", CPU_CONFIG_KEYWORD, config_tracking);
	fprintf(f, "%s sensor_mode %d\n", CPU_CONFIG_KEYWORD,
			sensor_separate_mode);
	fprintf(f, "%s text_format %s\n", CPU_CONFIG_KEYWORD, text_format);
	if (cpu_alert)
		{
		gkrellm_save_alertconfig(f, cpu_alert, CPU_CONFIG_KEYWORD, NULL);
		fprintf(f, "%s alert_includes_nice %d\n", CPU_CONFIG_KEYWORD,
					alert_includes_nice);
		}
	}

static void
load_cpu_config(gchar *arg)
	{
	GList	*list;
	CpuMon	*cpu;
	gchar	config[32], item[CFG_BUFSIZE],
			cpu_name[32], command[CFG_BUFSIZE];
	gint	n;

	n = sscanf(arg, "%31s %[^\n]", config, item);
	if (n == 2)
		{
		if (!strcmp(config, "enable"))				/* XXX remove in 2.3.7 */
			sscanf(item, "%d", &old_cpu_enabled);
		else if (!strcmp(config, "smp_mode"))		/* XXX remove in 2.3.7 */
			sscanf(item, "%d\n", &old_smp_mode);
		else if (!strcmp(config, "show_panel_labels"))
			sscanf(item, "%d\n", &show_panel_labels);
		else if (!strcmp(config, "omit_nice_mode"))
			sscanf(item, "%d\n", &omit_nice_mode);
		else if (!strcmp(config, "config_tracking"))
			sscanf(item, "%d\n", &config_tracking);
		else if (!strcmp(config, "sensor_mode"))
			sscanf(item, "%d\n", &sensor_separate_mode);
		else if (!strcmp(config, "text_format"))
			gkrellm_locale_dup_string(&text_format, item, &text_format_locale);
		else if (!strcmp(config, "alert_includes_nice"))
			sscanf(item, "%d\n", &alert_includes_nice);
		else if (!strcmp(config, GKRELLM_CHARTCONFIG_KEYWORD))
			{
			sscanf(item, "%31s %[^\n]", cpu_name, command);
			for (list = cpu_mon_list; list; list = list->next)
				{
				cpu = (CpuMon *) list->data;
				if (strcmp(cpu->name, cpu_name) == 0)
					gkrellm_load_chartconfig(&cpu->chart_config, command,
							nice_time_unsupported ? 2 : 3);
				}
			}
		else if (!strcmp(config, "enabled"))
			{
			sscanf(item, "%31s %[^\n]", cpu_name, command);
			for (list = cpu_mon_list; list; list = list->next)
				{
				cpu = (CpuMon *) list->data;
				if (strcmp(cpu->name, cpu_name) == 0)
					sscanf(command, "%d\n", &cpu->enabled);
				}
			}
		else if (!strcmp(config, GKRELLM_ALERTCONFIG_KEYWORD))
			{
			if (!cpu_alert)
				create_alert();
			gkrellm_load_alertconfig(&cpu_alert, item);
			dup_cpu_alert();
			}
		else if (!strcmp(config, "extra_info"))
			{
			sscanf(item, "%31s %[^\n]", cpu_name, command);
			for (list = cpu_mon_list; list; list = list->next)
				{
				cpu = (CpuMon *) list->data;
				if (strcmp(cpu->name, cpu_name) == 0)
					sscanf(command, "%d\n", &cpu->extra_info);
				}
			}
		else if (!strcmp(config, "launch"))
			{
			sscanf(item, "%31s %[^\n]", cpu_name, command);
			for (list = cpu_mon_list; list; list = list->next)
				{
				cpu = (CpuMon *) list->data;
				if (strcmp(cpu->name, cpu_name) == 0)
					cpu->launch.command = g_strdup(command);
				}
			}
		else if (!strcmp(config, "tooltip_comment"))
			{
			sscanf(item, "%31s %[^\n]", cpu_name, command);
			for (list = cpu_mon_list; list; list = list->next)
				{
				cpu = (CpuMon *) list->data;
				if (strcmp(cpu->name, cpu_name) == 0)
					cpu->launch.tooltip_comment = g_strdup(command);
				}
			}
		}
	}

static void
cb_alert_config(GkrellmAlert *ap, gpointer data)
	{
#if !defined(WIN32)
	alert_includes_nice = GTK_TOGGLE_BUTTON(alert_config_nice_button)->active;
#endif
	dup_cpu_alert();
	}

#if !defined(WIN32)
static void
cb_alert_config_create(GkrellmAlert *ap, GtkWidget *vbox, gpointer data)
	{
	gkrellm_gtk_check_button(vbox, &alert_config_nice_button,
			alert_includes_nice, FALSE, 2,
			_("nice time"));
	}
#endif

static void
cb_set_alert(GtkWidget *button, gpointer data)
	{
	if (!cpu_alert)
		create_alert();
	gkrellm_alert_config_window(&cpu_alert);
	}

static gboolean
fix_panel(CpuMon *cpu)
	{
	gboolean	result;

	if ((result = adjust_sensors_display(cpu, FALSE)) && cpu->launch.button)
		{
		gkrellm_destroy_button(cpu->launch.button);
		cpu->launch.button = 
			gkrellm_put_label_in_panel_button(cpu->chart->panel,
				gkrellm_launch_button_cb, &cpu->launch, cpu->launch.pad);
		}
	return result;
	}

  /* Called from sensor monitor when user wants to relocate a temp or fan
  |  sensor to a CPU panel.  Return TRUE if this is acceptable.
  */
gboolean
gkrellm_cpu_set_sensor(gpointer sr, gint type, gint n)
	{
	CpuMon	*cpu;

	if (   !show_panel_labels
		|| (cpu = (CpuMon *) g_list_nth_data(cpu_mon_list, n)) == NULL
		|| !cpu->enabled
	   )
		return FALSE;

	if (type == SENSOR_TEMPERATURE)
		cpu->sensor_temp = sr;
	else if (type == SENSOR_FAN)
		cpu->sensor_fan = sr;
	else
		return FALSE;
	return fix_panel(cpu);
	}

static void
cb_sensor_separate(GtkWidget *button, gpointer data)
    {
	GList	*list;

	sensor_separate_mode = GTK_TOGGLE_BUTTON(button)->active;
	for (list = cpu_mon_list; list; list = list->next)
		fix_panel((CpuMon *) list->data);
	}

static void
cb_show_panel_labels(GtkWidget *button, gpointer data)
    {
	CpuMon			*cpu;
	GkrellmPanel	*p;
	GkrellmStyle	*style;
	GList			*list;
	gboolean		rebuild_temps = FALSE, rebuild_fans = FALSE;

	show_panel_labels = GTK_TOGGLE_BUTTON(button)->active;
	style = gkrellm_panel_style(style_id);

	for (list = cpu_mon_list; list; list = list->next)
		{
		cpu = (CpuMon *) list->data;
		p = cpu->chart->panel;

		gkrellm_reset_alert_soft(cpu->alert);

		if (!show_panel_labels)
			{
			if (gkrellm_sensor_reset_location(cpu->sensor_temp))
				rebuild_temps = TRUE;
			if (gkrellm_sensor_reset_location(cpu->sensor_fan))
				rebuild_fans = TRUE;
			cpu->sensor_temp = NULL;
			cpu->sensor_fan = NULL;
			fix_panel(cpu);

			gkrellm_destroy_decal_list(p);
			gkrellm_panel_configure(p, NULL, style);
			}
		else
			{
			fix_panel(cpu);
			gkrellm_destroy_decal_list(p);
			gkrellm_sensors_create_decals(p, style_id,
						&cpu->sensor_decal, &cpu->fan_decal);
			gkrellm_panel_configure(p, cpu->panel_label, style);
			}
		gkrellm_panel_create(cpu->vbox, mon_cpu, p);
		cpu->save_label_position = p->label->position;
		gkrellm_setup_launcher(p, &cpu->launch, CHART_PANEL_TYPE, 4);
		}

	if (rebuild_temps || rebuild_fans)
		gkrellm_sensors_rebuild(rebuild_temps, rebuild_fans, FALSE);
	}

static void
cb_omit_nice(GtkWidget *button, gpointer data)
    {
	omit_nice_mode = GTK_TOGGLE_BUTTON(button)->active;
	}

static void
cb_text_format(GtkWidget *widget, gpointer data)
	{
	GList	*list;
	CpuMon	*cpu;
	gchar	*s;
	GtkWidget *entry;

	entry = gtk_bin_get_child(GTK_BIN(text_format_combo_box));
	s = gkrellm_gtk_entry_get_text(&entry);
	gkrellm_locale_dup_string(&text_format, s, &text_format_locale);
	for (list = cpu_mon_list; list; list = list->next)
		{
		cpu = (CpuMon *) list->data;
		cpu->new_text_format = TRUE;
		refresh_cpu_chart(cpu);
		}
	}


static void
cb_enable(GtkWidget *button, gpointer data)
    {
	GList		*list;
	CpuMon		*cpu;
	gint		i;
	gboolean	enabled, rebuild_temps = FALSE, rebuild_fans = FALSE;

	enabled = GTK_TOGGLE_BUTTON(button)->active;
	any_cpu_enabled = any_real_cpu_enabled = FALSE;
	for (i = 0, list = cpu_mon_list; list; ++i, list = list->next)
		{
		cpu = (CpuMon *) list->data;
		if (   i == GPOINTER_TO_INT(data)
		    && gkrellm_chart_enable_visibility(cpu->chart,
						enabled, &cpu->enabled)
		   )
			{
			gkrellm_reset_and_draw_chart(cpu->chart);
			gkrellm_apply_launcher(&cpu->launch_entry, &cpu->tooltip_entry,
					cpu->chart->panel, &cpu->launch, gkrellm_launch_button_cb);
			gkrellm_reset_alert_soft(cpu->alert);
			if (!cpu->enabled)
				{
			    if (gkrellm_sensor_reset_location(cpu->sensor_temp))
					rebuild_temps = TRUE;
				if (gkrellm_sensor_reset_location(cpu->sensor_fan))
					rebuild_fans = TRUE;
				cpu->sensor_temp = NULL;
				cpu->sensor_fan = NULL;
				fix_panel(cpu);
				}
			}
		if (cpu->enabled)
			{
			any_cpu_enabled = TRUE;
			if (!cpu->is_composite)
				any_real_cpu_enabled = TRUE;
			}
		}
	if (rebuild_temps || rebuild_fans)
		gkrellm_sensors_rebuild(rebuild_temps, rebuild_fans, FALSE);
	if (any_cpu_enabled)
		gkrellm_spacers_show(mon_cpu);
	else
		gkrellm_spacers_hide(mon_cpu);
	}


static void
cb_config_tracking(GtkWidget *button, gpointer data)
	{
	config_tracking = GTK_TOGGLE_BUTTON(button)->active;
	}

static void
cb_launch_entry(GtkWidget *widget, CpuMon *cpu)
	{
	gkrellm_apply_launcher(&cpu->launch_entry, &cpu->tooltip_entry,
				cpu->chart->panel, &cpu->launch, gkrellm_launch_button_cb);
	}

#define	DEFAULT_TEXT_FORMAT	"$T"

static gchar	*cpu_info_text[] =
{
N_("<h>Chart Labels\n"),
N_("Substitution variables for the format string for chart labels:\n"),
N_("\t$L    the CPU label\n"),
N_("\t$N    the CPU number\n"),
N_("\t$T    total CPU time percent usage\n"),
N_("\t$s    sys time percent usage\n"),
N_("\t$u    user time percent usage\n"),
N_("\t$n    nice time percent usage\n"),
"\n",
N_("Substitution variables may be used in alert commands.\n")
};

static void
create_cpu_tab(GtkWidget *tab_vbox)
	{
	GtkWidget	*tabs;
	GtkWidget	*button;
	GtkWidget	*hbox, *vbox, *vbox1, *vbox2;
	GtkWidget	*text;
	GtkWidget	*table;
	GList		*list;
	CpuMon		*cpu;
	gchar		buf[128];
	gint		i;


	tabs = gtk_notebook_new();
	gtk_notebook_set_tab_pos(GTK_NOTEBOOK(tabs), GTK_POS_TOP);
	gtk_box_pack_start(GTK_BOX(tab_vbox), tabs, TRUE, TRUE, 0);

/* -- Options tab */
	vbox = gkrellm_gtk_framed_notebook_page(tabs, _("Options"));

	if (n_smp_cpus == 0)
		{
		gkrellm_gtk_check_button_connected(vbox, NULL, any_cpu_enabled,
				FALSE, FALSE, 10,
				cb_enable, GINT_TO_POINTER(0),
				_("Enable CPU"));
		show_panel_labels = TRUE;
		}

	if (!nice_time_unsupported)
		gkrellm_gtk_check_button_connected(vbox, NULL,
				omit_nice_mode, FALSE, FALSE, 0,
				cb_omit_nice, NULL,
		_("Exclude nice CPU time from krell even if nice is shown on chart"));
	if (gkrellm_sensors_available())
		gkrellm_gtk_check_button_connected(vbox, NULL,
					sensor_separate_mode, FALSE, FALSE, 0,
					cb_sensor_separate, NULL,
		_("Draw fan and temperature values separately (not alternating)."));

	if (n_smp_cpus > 0)
		{
		gkrellm_gtk_check_button_connected(vbox, &button,
			config_tracking, FALSE, FALSE, 0,
			cb_config_tracking, NULL,
			_("Apply any CPU chart config height change to all CPU charts"));

		gkrellm_gtk_check_button_connected(vbox, NULL,
			show_panel_labels, FALSE, FALSE, 0,
			cb_show_panel_labels, NULL,
			_("Show labels in panels (no labels reduces vertical space)"));

		vbox1 = gkrellm_gtk_category_vbox(vbox,
				_("SMP Charts Select"),
				4, 0, TRUE);

		vbox2 = gkrellm_gtk_scrolled_vbox(vbox1, NULL,
					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

		for (i = 0, list = cpu_mon_list; list; list = list->next, ++i)
			{
			cpu = (CpuMon *) list->data;
			if (i == 0)
				snprintf(buf, sizeof(buf), _("Composite CPU."));
			else
				snprintf(buf, sizeof(buf), _("%s"), cpu->name);

			gkrellm_gtk_check_button_connected(vbox2, NULL, cpu->enabled,
					FALSE, FALSE, 0,
					cb_enable, GINT_TO_POINTER(i), buf);
			}
		}

/* -- Setup tab */
	vbox = gkrellm_gtk_framed_notebook_page(tabs, _("Setup"));

	vbox1 = gkrellm_gtk_category_vbox(vbox,
				_("Format String for Chart Labels"),
				4, 0, TRUE);

	hbox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(vbox1), hbox, FALSE, FALSE, 0);

	text_format_combo_box = gtk_combo_box_entry_new_text();
	gtk_box_pack_start(GTK_BOX(hbox), text_format_combo_box, TRUE, TRUE, 0);
	gtk_combo_box_append_text(GTK_COMBO_BOX(text_format_combo_box),
		text_format);
	gtk_combo_box_append_text(GTK_COMBO_BOX(text_format_combo_box),
		DEFAULT_TEXT_FORMAT);
	gtk_combo_box_append_text(GTK_COMBO_BOX(text_format_combo_box),
		_("\\f$L\\n$T"));
	gtk_combo_box_append_text(GTK_COMBO_BOX(text_format_combo_box),
		_("\\fu \\.$u\\n\\fs \\.$s"));
	gtk_combo_box_append_text(GTK_COMBO_BOX(text_format_combo_box),
		_("\\ww\\D2\\f\\au\\.$u\\D1\\f\\as\\.$s"));
	gtk_combo_box_append_text(GTK_COMBO_BOX(text_format_combo_box),
		_("\\ww\\D3\\f\\au\\.$u\\D0\\f\\as\\.$s"));
	gtk_combo_box_append_text(GTK_COMBO_BOX(text_format_combo_box),
		_("\\ww\\D2\\f\\au\\.$u\\D1\\f\\as\\.$s\\D3\\f\\r$L"));
	gtk_combo_box_append_text(GTK_COMBO_BOX(text_format_combo_box),
		_("\\ww\\D2\\f\\au\\.$u\\D1\\f\\as\\.$s\\D3\\f\\r$L"));
	gtk_combo_box_append_text(GTK_COMBO_BOX(text_format_combo_box),
		_("\\ww\\D3\\f$L\\n\\f\\as\\.$s\\n\\f\\au\\.$u"));

	if (!nice_time_unsupported)
		{
		gtk_combo_box_append_text(GTK_COMBO_BOX(text_format_combo_box),
			"\\ww\\C\\f$L\\D5\\f\\an\\.$n\\D2\\f\\au\\.$u\\D1\\f\\as\\.$s");
		}
	gtk_combo_box_set_active(GTK_COMBO_BOX(text_format_combo_box), 0);
	g_signal_connect(G_OBJECT(GTK_COMBO_BOX(text_format_combo_box)), "changed",
			G_CALLBACK(cb_text_format), NULL);

	vbox1 = gkrellm_gtk_category_vbox(vbox,
				_("Launch Commands"),
				4, 0, TRUE);
	vbox1 = gkrellm_gtk_scrolled_vbox(vbox1, NULL,
				GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	table = gkrellm_gtk_launcher_table_new(vbox1, n_cpus);
	for (i = 0, list = cpu_mon_list; list; list = list->next, ++i)
		{
		cpu = (CpuMon *) list->data;
		snprintf(buf, sizeof(buf), _("%s"), cpu->name);
		gkrellm_gtk_config_launcher(table, i,  &cpu->launch_entry,
				&cpu->tooltip_entry, buf, &cpu->launch);
		g_signal_connect(G_OBJECT(cpu->launch_entry), "changed",
				G_CALLBACK(cb_launch_entry), cpu);
		g_signal_connect(G_OBJECT(cpu->tooltip_entry), "changed",
				G_CALLBACK(cb_launch_entry), cpu);
		}

	hbox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
	gkrellm_gtk_alert_button(hbox, NULL, FALSE, FALSE, 4, TRUE,
			cb_set_alert, NULL);

/* --Info tab */
	vbox = gkrellm_gtk_framed_notebook_page(tabs, _("Info"));
	text = gkrellm_gtk_scrolled_text_view(vbox, NULL,
				GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	for (i = 0; i < sizeof(cpu_info_text)/sizeof(gchar *); ++i)
		{
		if (nice_time_unsupported && strstr(cpu_info_text[i], "nice"))
			continue;
		gkrellm_gtk_text_view_append(text, _(cpu_info_text[i]));
		}

	}

gchar *
gkrellm_cpu_get_sensor_panel_label(gint n)
	{
	CpuMon	*cpu;
	gchar	*s = "cpu?";

	cpu = (CpuMon *) g_list_nth_data(cpu_mon_list, n);
	if (cpu)
		s = cpu->panel_label;
	return s;
	}

GkrellmMonitor *
gkrellm_get_cpu_mon(void)
	{
	return mon_cpu;
	}

static GkrellmMonitor	monitor_cpu =
	{
	N_("CPU"),				/* Name, for config tab.	*/
	MON_CPU,			/* Id,  0 if a plugin		*/
	create_cpu,			/* The create function		*/
	update_cpu,			/* The update function		*/
	create_cpu_tab,		/* The config tab create function	*/
	NULL,				/* Apply the config function		*/

	save_cpu_config,	/* Save user conifg			*/
	load_cpu_config,	/* Load user config			*/
	CPU_CONFIG_KEYWORD,	/* config keyword			*/

	NULL,				/* Undef 2	*/
	NULL,				/* Undef 1	*/
	NULL,				/* Undef 0	*/

	0,					/* insert_before_id - place plugin before this mon */

	NULL,				/* Handle if a plugin, filled in by GKrellM		*/
	NULL				/* path if a plugin, filled in by GKrellM		*/
	};

GkrellmMonitor *
gkrellm_init_cpu_monitor(void)
	{
	GList	*list;

	monitor_cpu.name = _(monitor_cpu.name);
	style_id = gkrellm_add_chart_style(&monitor_cpu, CPU_STYLE_NAME);
	gkrellm_locale_dup_string(&text_format, DEFAULT_TEXT_FORMAT,
				&text_format_locale);

	mon_cpu = &monitor_cpu;

	if (setup_cpu_interface())
		{
		for (list = cpu_mon_list; list; list = list->next)
			((CpuMon *)(list->data))->extra_info = TRUE;
		return &monitor_cpu;
		}
	return NULL;
	}

