/*
    lsadl is a frontend/wrapper to the adl_sdk using gtk
    Copyright (C) 2011  Sterling Pickens

    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 "ls_gtk.h"
#define GRAPH_W 680
#define GRAPH_H 480
int PerfLevels;
int Not_Started = 1;
int unlocked = 0;
struct GTK_Widgets gtk;
struct PERF_Cur *PerfCur;
struct PERF_Ranges PerfRanges;
ADLODParameters PerfParams;
ADLODPerformanceLevels *PLevels;
GdkColor color;
struct Graph_Data{
	int perf[60];
	int usage[60];
	int fan[60];
	double temp[60];
}graphdata;
int graph_idx = 0;
const int total_charts = 4;
struct Chart_Vectors{
	double black_top; //start of black
	double black_bottom; //end of black
	double data_top;
	double data_bottom;
}cv[4];

gboolean on_toggle_unlock(GtkWidget *widget, GdkEventExpose *event, gpointer data);

void Adjust_spinner(GtkAdjustment *adjustment, GtkWidget *spiner, gdouble value){
	gtk_adjustment_set_value(adjustment, value);
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(spiner), value);
	gtk_widget_show_all(spiner);
}

char Perf_Alloc(void){
	int num1 = 0;
	int num2 = 0;
	int ret_val = 0;
	ADLBiosInfo BiosInfo;
	ADLFanSpeedInfo FanSpeedInfo;
    ADLFanSpeedValue FanSpeedValue;

	(void)memset(&graphdata, '\0', sizeof(struct Graph_Data));

	ret_val = LSADL_Adapter_VideoBiosInfo_Get(iAdapterIndex, &BiosInfo);
	if(ret_val != ADL_OK){
		printf("Error: ADL_Adapter_VideoBiosInfo_Get(\n");
		Print_Code(ret_val);
		printf("Exiting!\n");
		return FALSE;
	}
	printf("%s\n", BiosInfo.strPartNumber);
	printf("%s\n", BiosInfo.strVersion);
	printf("%s\n", BiosInfo.strDate);

	ret_val = LSADL_OD5_FanSpeedInfo_Get(iAdapterIndex, 0, &FanSpeedInfo);
	if(ret_val != ADL_OK){
		printf("Cannot get FanSpeedInfo !\n");
		Print_Code(ret_val);
		printf("Exiting!\n");
		return FALSE;
	}
	ret_val = LSADL_OD5_FanSpeed_Get(iAdapterIndex, 0, &FanSpeedValue);
	if(ret_val != ADL_OK){
		printf("Cannot get FanSpeedValue !\n");
		Print_Code(ret_val);
		printf("Exiting!\n");
		return FALSE;
	}

	ret_val = LSADL_Adapter_ASICFamilyType_Get(iAdapterIndex, &num1, &num2);
	if(ret_val != ADL_OK){
		printf("Error: ADL_Adapter_ASICFamilyType_Get(\n");
		Print_Code(ret_val);
		printf("Exiting!\n");
		return FALSE;
	}
	printf("Asic: %d %d\n", num1, num2);

	ret_val = LSADL_OD5_ODParameters_Get(iAdapterIndex, &PerfParams);
	if(ret_val != ADL_OK){
		printf("LSADL_OD5_ODParameters_Get(\n");
		Print_Code(ret_val);
		printf("Exiting!\n");
		return FALSE;
	}

	ret_val = LSADL_Adapter_Speed_Caps(iAdapterIndex, &num1, &num2);
	if(ret_val != ADL_OK){
		printf("ADL_Adapter_Speed_Caps(\n");
		Print_Code(ret_val);
		printf("Exiting!\n");
		return FALSE;
	}
	ret_val = LSADL_Adapter_Speed_Get(iAdapterIndex, &PerfRanges.cur_force3d, &PerfRanges.def_force3d);
	if(ret_val != ADL_OK){
		printf("ADL_Adapter_Speed_Get(\n");
		Print_Code(ret_val);
		printf("Exiting!\n");
		return FALSE;
	}
	if(num2 == ADL_ADAPTER_SPEEDCAPS_SUPPORTED){
		printf("ADL_ADAPTER_SPEEDCAPS_SUPPORTED\n");
		if( PerfRanges.cur_force3d == ADL_CONTEXT_SPEED_UNFORCED)
			printf("ADL_CONTEXT_SPEED_UNFORCED is current force3d\n");
		if( PerfRanges.cur_force3d == ADL_CONTEXT_SPEED_FORCEHIGH)
			printf("ADL_CONTEXT_SPEED_FORCEHIGH is current force3d\n");
		if( PerfRanges.cur_force3d == ADL_CONTEXT_SPEED_FORCELOW)
			printf("ADL_CONTEXT_SPEED_FORCELOW is current force3d\n");
	}else{
		printf("no speedcaps support\n");
	}

	PerfLevels            = PerfParams.iNumberOfPerformanceLevels;
	PerfRanges.gpu_min    = PerfParams.sEngineClock.iMin;
	PerfRanges.gpu_max    = PerfParams.sEngineClock.iMax;
	PerfRanges.gpu_step   = 1;
	//PerfRanges.gpu_step   = PerfParams.sEngineClock.iStep;

	PerfRanges.mem_min    = PerfParams.sMemoryClock.iMin;
	PerfRanges.mem_max    = PerfParams.sMemoryClock.iMax;
	PerfRanges.mem_step   = 1;
	//PerfRanges.mem_step   = PerfParams.sMemoryClock.iStep;

	PerfRanges.vddc_min   = PerfParams.sVddc.iMin;
	//PerfRanges.vddc_min   = 700;
	PerfRanges.vddc_max   = PerfParams.sVddc.iMax;
	PerfRanges.vddc_step  = PerfParams.sVddc.iStep;
	
	PerfRanges.gpu_min    /= 100;
	PerfRanges.gpu_max    /= 100;
	//PerfRanges.gpu_step   /= 100;
	PerfRanges.mem_min    /= 100;
	PerfRanges.mem_max    /= 100;
	//PerfRanges.mem_step   /= 100;
	PerfRanges.vddc_min   /= 1000;
	PerfRanges.vddc_max   /= 1000;
	PerfRanges.vddc_step  /= 1000;

	PerfRanges.fan_min = FanSpeedInfo.iMinRPM;
	PerfRanges.fan_max = FanSpeedInfo.iMaxRPM;
	PerfRanges.fan_step = 1;
	PerfRanges.fan_cur = FanSpeedValue.iFanSpeed;

	num1 = sizeof(ADLODPerformanceLevels)+sizeof(ADLODPerformanceLevel)*(PerfLevels-1);
	PLevels = (ADLODPerformanceLevels *)malloc(num1);
	if(PLevels == NULL){
		return FALSE;
	}
	PLevels->iSize = num1;
	ret_val = LSADL_OD5_ODPerformanceLevels_Get(iAdapterIndex, 1, PLevels);
	if(ret_val != ADL_OK){
		printf("LSADL_OD5_ODPerformanceLevels_Get(\n");
		Print_Code(ret_val);
		printf("Exiting!\n");
		return FALSE;
	}

	//allocate memory
	PerfCur           = (struct PERF_Cur *)malloc(sizeof(struct PERF_Cur)*PerfLevels);
	if(PerfCur == NULL){
		free(PLevels);
		return FALSE;
	}

	for(num1=0; num1<PerfLevels; num1++){
		PerfCur[num1].id   = num1;
		PerfCur[num1].gpu  = PLevels->aLevels[num1].iEngineClock;
		PerfCur[num1].mem  = PLevels->aLevels[num1].iMemoryClock;
		PerfCur[num1].vddc = PLevels->aLevels[num1].iVddc;
		PerfCur[num1].gpu /= 100;
		PerfCur[num1].mem /= 100;
		PerfCur[num1].vddc /= 1000;
	}
	return TRUE;
}

char Perf_Dealloc(void){
    free(PerfCur);
	free(PLevels);

	return TRUE;
}

static void on_destroy(GtkWidget *widget, gpointer mw){
	gtk_widget_destroy(mw);
}

gboolean on_file_load(GtkWidget *widget, GdkEventButton *event, gpointer data){
	GtkWidget *dialog;
	dialog = gtk_file_chooser_dialog_new("Open File", NULL, GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
		GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
	if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT){
		gchar *filename;
		char buff[8];
		int perf_id = 0;
		int line_num = 0;
		int remainder = 0;
		int gpu = 0;
		int mem = 0;
		int vddc = 0;
		(void)memset((void*)buff, '\0', 8);
		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
		printf("filename: %s\n", filename);
		FILE* fp;
		if( ( fp = fopen(filename, "r" ) ) == NULL ) {
			printf("Error opening file: %s\n", filename);
			g_free(filename);
			gtk_widget_destroy(dialog);
			return FALSE;
		}
		while( fgets(buff, 8, fp) != NULL){
			line_num++;
		}
		if(line_num != PerfLevels*3){
			printf("Error file contains wrong # of lines\n");
			g_free(filename);
			gtk_widget_destroy(dialog);
			fclose(fp);
			return FALSE;
		}
		line_num = 0;
		rewind(fp);
		(void)memset((void*)buff, '\0', 8);

		while( fgets(buff, 8, fp) != NULL){
			buff[strlen(buff)-1] = '\0';
			printf("%s\n", buff);

			if(line_num == 0){
				line_num++;
				gpu = atoi(buff);
				PLevels->aLevels[perf_id].iEngineClock = atoi(buff);
				PerfCur[perf_id].gpu  = PLevels->aLevels[perf_id].iEngineClock;
				PerfCur[perf_id].gpu /= 100;
				Adjust_spinner(PerfCur[perf_id].gpu_adj, PerfCur[perf_id].gpu_b, PerfCur[perf_id].gpu);
				(void)memset((void*)buff, '\0', 8);
				continue;
			}
			if(line_num == 1){
				line_num++;
				mem = atoi(buff);
				PLevels->aLevels[perf_id].iMemoryClock = mem;
				PerfCur[perf_id].mem = PLevels->aLevels[perf_id].iMemoryClock;
				PerfCur[perf_id].mem /= 100;
				Adjust_spinner(PerfCur[perf_id].mem_adj, PerfCur[perf_id].mem_b, PerfCur[perf_id].mem);

				(void)memset((void*)buff, '\0', 8);
				continue;
			}
			if(line_num == 2){
				line_num++;
				vddc = atoi(buff);
				PLevels->aLevels[perf_id].iVddc = vddc;
				PerfCur[perf_id].vddc = PLevels->aLevels[perf_id].iVddc;
				PerfCur[perf_id].vddc /= 1000;
				Adjust_spinner(PerfCur[perf_id].vddc_adj, PerfCur[perf_id].vddc_b, PerfCur[perf_id].vddc);
				(void)memset((void*)buff, '\0', 8);
				continue;
			}

			remainder = line_num % 3;
			if(remainder == 0){
				line_num++;
				perf_id++;
				gpu = atoi(buff);
				PLevels->aLevels[perf_id].iEngineClock = gpu;
				PerfCur[perf_id].gpu  = PLevels->aLevels[perf_id].iEngineClock;
				PerfCur[perf_id].gpu /= 100;
				//adjust spin button
				Adjust_spinner(PerfCur[perf_id].gpu_adj, PerfCur[perf_id].gpu_b, PerfCur[perf_id].gpu);
				(void)memset((void*)buff, '\0', 8);
				continue;
			}
			if(remainder == 1){
				line_num++;
				mem = atoi(buff);
				PLevels->aLevels[perf_id].iMemoryClock = mem;
				PerfCur[perf_id].mem = PLevels->aLevels[perf_id].iMemoryClock;
				PerfCur[perf_id].mem /= 100;

				Adjust_spinner(PerfCur[perf_id].mem_adj, PerfCur[perf_id].mem_b, PerfCur[perf_id].mem);
				(void)memset((void*)buff, '\0', 8);
				continue;
			}
			if(remainder == 2){
				line_num++;
				vddc = atoi(buff);
				PLevels->aLevels[perf_id].iVddc = vddc;
				PerfCur[perf_id].vddc = PLevels->aLevels[perf_id].iVddc;
				PerfCur[perf_id].vddc /= 1000;

				Adjust_spinner(PerfCur[perf_id].vddc_adj, PerfCur[perf_id].vddc_b, PerfCur[perf_id].vddc);
				(void)memset((void*)buff, '\0', 8);
				continue;
			}
			printf("Error #55!\n");
		}
		g_free(filename);
		fclose(fp);
	}
	gtk_widget_destroy (dialog);
	gtk_widget_show_all(GTK_WIDGET( widget ));
	return TRUE;
}

gboolean on_file_save(GtkWidget *widget, GdkEventButton *event, gpointer data){
	GtkWidget *dialog;
	dialog = gtk_file_chooser_dialog_new("Save File", NULL, GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
		GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL);
	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT){
		gchar *filename;
		FILE* fp;
		int perf_id = 0;
		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (dialog));
		printf("filename: %s\n", filename);

		//Need an ABI check and line  or md5sum check (gcrypt)
		if( (fp = fopen(filename, "w")) ){
			for(perf_id=0; perf_id<PerfLevels; perf_id++){
				fprintf(fp, "%d\n", PLevels->aLevels[perf_id].iEngineClock);
				fprintf(fp, "%d\n", PLevels->aLevels[perf_id].iMemoryClock);
				fprintf(fp, "%d\n", PLevels->aLevels[perf_id].iVddc);
			}
		}else{
			printf("Error opening file!\n");
		}
		g_free(filename);
		fclose(fp);
	}
	gtk_widget_destroy(dialog);
	gtk_widget_show_all(GTK_WIDGET(widget));
	return TRUE;
}

gboolean on_toggle_force3d(GtkWidget *widget, gpointer mw){
	int ret_val = 0;
	printf("Here to force3d\n");
	if(gtk_combo_box_get_active((GtkComboBox *)(gtk.combo_box)) == ADL_CONTEXT_SPEED_UNFORCED){
		PerfRanges.cur_force3d = ADL_CONTEXT_SPEED_UNFORCED;
		printf("\tunforced\n");
	}
	if(gtk_combo_box_get_active((GtkComboBox *)(gtk.combo_box)) == ADL_CONTEXT_SPEED_FORCELOW){
		PerfRanges.cur_force3d = ADL_CONTEXT_SPEED_FORCELOW;
		printf("\tlow\n");
	}
	if(gtk_combo_box_get_active((GtkComboBox *)(gtk.combo_box)) == ADL_CONTEXT_SPEED_FORCEHIGH){
		PerfRanges.cur_force3d = ADL_CONTEXT_SPEED_FORCEHIGH;
		printf("\thigh\n");
	}
	ret_val = LSADL_Adapter_Speed_Set(iAdapterIndex, PerfRanges.cur_force3d);
	if(ret_val != ADL_OK){
		printf("Error: ADL_Adapter_Speed_Set\n");
		Print_Code(ret_val);
		return FALSE;
	}
	return TRUE;
}


void *Refresh_Func(void *empty){
	char value[16];
	int ret_val = 0;
	int num1 = 0;
	int seconds = 0;
	int cur_perf = 0;
	gdouble dtmp = 0;
	ADLPMActivity current;
	ADLFanSpeedInfo FanSpeedInfo;
	ADLFanSpeedValue FanSpeedValue;
	gpointer data = NULL;
	GdkEventExpose *event = NULL;


	while(Not_Started)
		sleep(1);

	while(No_Exit == 1){
		sleep(2);
		ret_val = LSADL_OD5_CurrentActivity_Get(iAdapterIndex, &current);
		if(ret_val != ADL_OK){
			printf("Cannot get the number of od5 power state!\n");
			Print_Code(ret_val);
			printf("Exiting!\n");
			No_Exit = 0;
			break;
		}

		ret_val = current.iCurrentPerformanceLevel;
		gdk_threads_enter();
		// CARD BIOS/DRIVER BUG ?
		//0 and 2 reported, instead of 0 and 1
		//2 other cards report right
		if(ret_val == PerfLevels)
			ret_val--;
		cur_perf = ret_val;
		for(num1=0; num1<PerfLevels; num1++){
			if(num1 == cur_perf){
				(void)sprintf(value, "-->%d", num1);
			}else{
				(void)sprintf(value, "%d", num1);
			}
			gtk_label_set_text((GtkLabel *)PerfCur[num1].table_labels, (const gchar *)value);
			gtk_widget_show(PerfCur[num1].table_labels);
		}
		(void)sprintf(value, "%d", ret_val);
		gtk_label_set_text((GtkLabel *)gtk.ca_labels[0], (const gchar *)value);
		gtk_widget_show(((struct GTK_Widgets) gtk).ca_labels[0]);

		dtmp = (double)current.iEngineClock/100;
		(void)sprintf(value, "%.02lfMHz", dtmp);
		gtk_label_set_text((GtkLabel *)gtk.ca_labels[1], (const gchar *)value);
		gtk_widget_show(((struct GTK_Widgets) gtk).ca_labels[1]);
		dtmp = (double)current.iMemoryClock/100;
		(void)sprintf(value, "%.02lfMHz", dtmp);
		gtk_label_set_text((GtkLabel *)gtk.ca_labels[2], (const gchar *)value);
		gtk_widget_show(((struct GTK_Widgets) gtk).ca_labels[2]);
		dtmp = (double)current.iVddc/1000;
		(void)sprintf(value, "%.02lfV", dtmp);
		gtk_label_set_text((GtkLabel *)gtk.ca_labels[3], (const gchar *)value);
		gtk_widget_show(((struct GTK_Widgets) gtk).ca_labels[3]);
		(void)sprintf(value, "%d%%", current.iActivityPercent);
		gtk_label_set_text((GtkLabel *)gtk.ca_labels[4], (const gchar *)value);
		gtk_widget_show(((struct GTK_Widgets) gtk).ca_labels[4]);
		dtmp = (double)current.iCurrentBusSpeed/1000;
		(void)sprintf(value, "%.02lfGT/s", dtmp);
		gtk_label_set_text((GtkLabel *)gtk.ca_labels[5], (const gchar *)value);
		gtk_widget_show(((struct GTK_Widgets) gtk).ca_labels[5]);
		(void)sprintf(value, "%dX", current.iMaximumBusLanes);
		gtk_label_set_text((GtkLabel *)gtk.ca_labels[6], (const gchar *)value);
		gtk_widget_show(((struct GTK_Widgets) gtk).ca_labels[6]);
		(void)sprintf(value, "%dX", current.iCurrentBusLanes);
		gtk_label_set_text((GtkLabel *)gtk.ca_labels[7], (const gchar *)value);
		gtk_widget_show(((struct GTK_Widgets) gtk).ca_labels[7]);

		ret_val = LSADL_OD5_FanSpeedInfo_Get(iAdapterIndex, 0, &FanSpeedInfo);
		if(ret_val != ADL_OK){
			printf("Cannot get FanSpeedInfo !\n");
			Print_Code(ret_val);
			printf("Exiting!\n");
			No_Exit = 0;
			break;
		}

		//#define ADL_DL_FANCTRL_SUPPORTS_PERCENT_READ   1
		//#define ADL_DL_FANCTRL_SUPPORTS_PERCENT_WRITE   2
		//#define ADL_DL_FANCTRL_SUPPORTS_RPM_READ   4
		//#define ADL_DL_FANCTRL_SUPPORTS_RPM_WRITE   8

/*
		if(FanSpeedInfo.iFlags & ADL_DL_FANCTRL_SUPPORTS_PERCENT_READ)
			printf("ADL_DL_FANCTRL_SUPPORTS_PERCENT_READ\n");
		if(FanSpeedInfo.iFlags & ADL_DL_FANCTRL_SUPPORTS_PERCENT_WRITE)
			printf("ADL_DL_FANCTRL_SUPPORTS_PERCENT_WRITE\n");
		if(FanSpeedInfo.iFlags & ADL_DL_FANCTRL_SUPPORTS_RPM_READ)
			printf("ADL_DL_FANCTRL_SUPPORTS_RPM_READ\n");
		if(FanSpeedInfo.iFlags & ADL_DL_FANCTRL_SUPPORTS_RPM_WRITE)
			printf("ADL_DL_FANCTRL_SUPPORTS_RPM_WRITE\n");
		printf("FanSpeedInfo\n");
		//iFlags
		printf("\t%d\n", FanSpeedInfo.iFlags);
		//iMinPercent
		printf("\t%d\n", FanSpeedInfo.iMinPercent);
		//iMaxPercent
		printf("\t%d\n", FanSpeedInfo.iMaxPercent);
		//iMinRPM
		printf("\t%d\n", FanSpeedInfo.iMinRPM);
		//iMaxRPM
		printf("\t%d\n", FanSpeedInfo.iMaxRPM);

*/
		ret_val = LSADL_OD5_FanSpeed_Get(iAdapterIndex, 0, &FanSpeedValue);
		if(ret_val != ADL_OK){
			printf("Cannot get FanSpeedValue !\n");
			Print_Code(ret_val);
			printf("Exiting!\n");
			No_Exit = 0;
			break;
		}
		(void)sprintf(value, "%d rpm", FanSpeedValue.iFanSpeed);
		gtk_label_set_text((GtkLabel *)gtk.ca_labels[8], (const gchar *)value);

		ret_val = LSADL_OD5_Temperature_Get(iAdapterIndex, 0, &iTemperature);
		if(ret_val != ADL_OK){
			printf("Cannot get the number of od5 temperature!\n");
			Print_Code(ret_val);
			printf("Exiting!\n");
			No_Exit = 0;
			break;
		}
		dtmp = (double)iTemperature.iTemperature/1000;
		(void)sprintf(value, "%.02lfC", dtmp);
		gtk_label_set_text((GtkLabel *)gtk.ca_labels[9], (const gchar *)value);
		//update labels
		//refresh the labels
		gtk_widget_show(((struct GTK_Widgets) gtk).ca_labels[9]);
		seconds += 2;
		if(seconds % 60 == 0){
			graphdata.perf[graph_idx] = cur_perf;
			graphdata.usage[graph_idx] = current.iActivityPercent;
			graphdata.fan[graph_idx] = FanSpeedValue.iFanSpeed;
			graphdata.temp[graph_idx] = dtmp;
			if(graph_idx == 59)
				graph_idx = 0;
			else
				graph_idx++;
			seconds = 0;

			(void)g_signal_emit_by_name(G_OBJECT(gtk.graph),  "expose-event", G_CALLBACK(on_expose_cairo), &data);
		}
		gdk_threads_leave();
	}
	pthread_exit(NULL);
}

void *GTK_Func(void *empty){
	int ret_val = 0;
	int num1 = 0;
	char tmp[10];
	gdouble temp_num = 0;
	gtk.main_win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_container_set_border_width(GTK_CONTAINER(gtk.main_win), 2);
	gtk_window_set_default_size(GTK_WINDOW(gtk.main_win), 680, 360);
	gtk_window_set_title(GTK_WINDOW(gtk.main_win), PACKAGE_STRING);
	gtk.main_vbox = gtk_vbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(gtk.main_win), gtk.main_vbox);

	gtk.main_menubar = gtk_menu_bar_new();
	gtk.main_filemenu = gtk_menu_new();
	gtk.main_file = gtk_menu_item_new_with_label("File");
	gtk.main_results = gtk_menu_item_new_with_label("API Results");
	gtk.main_quit = gtk_menu_item_new_with_label("Quit");

	gtk_menu_item_set_submenu(GTK_MENU_ITEM(gtk.main_file), gtk.main_filemenu);
	gtk_menu_shell_append(GTK_MENU_SHELL(gtk.main_filemenu), gtk.main_results);
	gtk_menu_shell_append(GTK_MENU_SHELL(gtk.main_filemenu), gtk.main_quit);
	gtk_menu_shell_append(GTK_MENU_SHELL(gtk.main_menubar), gtk.main_file);

	gtk.table = gtk_table_new(PerfLevels+7, 5, TRUE); //h, w

	gtk.table_labels[0] = gtk_label_new("PerfLevel");
	gtk.table_labels[1] = gtk_label_new("GPU");
	gtk.table_labels[2] = gtk_label_new("MEM");
	gtk.table_labels[3] = gtk_label_new("Vddc");
	gtk.table_labels[4] = gtk_label_new("empty");

	gtk.table_labels[5] = gtk_label_new("Current:");
	gtk.table_labels[6] = gtk_label_new("PerfLevel");
	gtk.table_labels[7] = gtk_label_new("GPU");
	gtk.table_labels[8] = gtk_label_new("MEM");
	gtk.table_labels[9] = gtk_label_new("Vddc");
	gtk.table_labels[10] = gtk_label_new("GPU usage");
	gtk.table_labels[11] = gtk_label_new("pcie speed");
	gtk.table_labels[12] = gtk_label_new("max lanes");
	gtk.table_labels[13] = gtk_label_new("cur lanes");
	gtk.table_labels[14] = gtk_label_new("Fan");
	gtk.table_labels[15] = gtk_label_new("Temp");
	gtk.table_labels[16] = gtk_label_new("");
	gtk.table_labels[17] = gtk_label_new("Fan:");

	gtk_table_attach(GTK_TABLE(gtk.table), gtk.table_labels[0],        0, 1, 0, 1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), gtk.table_labels[1],        1, 2, 0, 1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), gtk.table_labels[2],        2, 3, 0, 1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), gtk.table_labels[3],        3, 4, 0, 1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	for(num1=0; num1<PerfLevels; num1++){
		(void)sprintf(tmp, "%d", num1);
		PerfCur[num1].table_labels = gtk_label_new((const gchar *)tmp);
		temp_num = PerfCur[num1].gpu;
		PerfCur[num1].gpu_adj  = (GtkAdjustment *)gtk_adjustment_new(temp_num,   PerfRanges.gpu_min,  PerfRanges.gpu_max,  PerfRanges.gpu_step,  PerfRanges.gpu_step, 0.0);
		temp_num = PerfCur[num1].mem;
		PerfCur[num1].mem_adj  = (GtkAdjustment *)gtk_adjustment_new(temp_num,   PerfRanges.mem_min,  PerfRanges.mem_max,  PerfRanges.mem_step,  PerfRanges.mem_step, 0.0);
		temp_num = PerfCur[num1].vddc;
		PerfCur[num1].vddc_adj = (GtkAdjustment *)gtk_adjustment_new(temp_num,  PerfRanges.vddc_min, PerfRanges.vddc_max, PerfRanges.vddc_step, PerfRanges.vddc_step, 0.0);

		PerfCur[num1].gpu_b    = gtk_spin_button_new(PerfCur[num1].gpu_adj,  1, 3);
		PerfCur[num1].mem_b    = gtk_spin_button_new(PerfCur[num1].mem_adj,  1, 3);
		PerfCur[num1].vddc_b   = gtk_spin_button_new(PerfCur[num1].vddc_adj, 1, 3);
		PerfCur[num1].apply_b  = gtk_button_new_with_label("Apply");
		gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(PerfCur[num1].table_labels),  0, 1, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
		gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(PerfCur[num1].gpu_b),         1, 2, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
		gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(PerfCur[num1].mem_b),         2, 3, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
		gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(PerfCur[num1].vddc_b),        3, 4, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
		gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(PerfCur[num1].apply_b),       4, 5, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
		g_signal_connect(G_OBJECT(PerfCur[num1].apply_b), "clicked", G_CALLBACK(apply_cb), (gpointer)&(PerfCur[num1]));
	}

	PerfCur[0].fan_adj  = (GtkAdjustment *)gtk_adjustment_new(PerfRanges.fan_cur,   PerfRanges.fan_min,  PerfRanges.fan_max,  PerfRanges.fan_step,  PerfRanges.fan_step, 0.0);
	PerfCur[0].fan_b    = gtk_spin_button_new(PerfCur[0].fan_adj,  1, 3);
	PerfCur[0].fan_set_b  = gtk_button_new_with_label("Apply");
	PerfCur[0].fan_reset_b  = gtk_button_new_with_label("Reset");
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).table_labels[17]),  1, 2, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(PerfCur[0].fan_b),       2, 3, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(PerfCur[0].fan_set_b),   3, 4, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(PerfCur[0].fan_reset_b), 4, 5, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	g_signal_connect(G_OBJECT(PerfCur[0].fan_set_b), "clicked", G_CALLBACK(set_fanspeed_cb), (gpointer)&(PerfCur[0]));
	g_signal_connect(G_OBJECT(PerfCur[0].fan_reset_b), "clicked", G_CALLBACK(reset_fanspeed_cb), (gpointer)&(PerfCur[0]));

	num1++;
	//Current:
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).table_labels[5]),  0, 1, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);

	gtk.combo_box = gtk_combo_box_text_new();

	gtk_combo_box_prepend_text((GtkComboBox *)gtk.combo_box, "ForceLow");
	gtk_combo_box_prepend_text((GtkComboBox *)gtk.combo_box, "ForceHigh");
	gtk_combo_box_prepend_text((GtkComboBox *)gtk.combo_box, "Unforced");
	gtk_combo_box_set_active((GtkComboBox *)gtk.combo_box, (gint)PerfRanges.cur_force3d);


	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).combo_box),        1, 2, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	g_signal_connect(G_OBJECT(gtk.combo_box), "changed", G_CALLBACK(on_toggle_force3d), (gpointer)&gtk.combo_box);

	gtk.unlock_box = gtk_check_button_new_with_label("unlock limts");
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).unlock_box),        2, 3, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	g_signal_connect(G_OBJECT(gtk.unlock_box), "toggled", G_CALLBACK(on_toggle_unlock), (gpointer)&gtk.unlock_box);


	gtk.load_b  = gtk_button_new_with_label("Load");
	gtk.save_b  = gtk_button_new_with_label("Save");
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).load_b),           3, 4, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).save_b),           4, 5, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	g_signal_connect(G_OBJECT(gtk.load_b), "clicked", G_CALLBACK(on_file_load), (gpointer)&(gtk.main_win) );
	g_signal_connect(G_OBJECT(gtk.save_b), "clicked", G_CALLBACK(on_file_save), (gpointer)&(gtk.main_win) );

	num1++;
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).table_labels[6]),  0, 1, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).table_labels[7]),  1, 2, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).table_labels[8]),  2, 3, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).table_labels[9]),  3, 4, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).table_labels[10]), 4, 5, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);


	gtk.ca_labels[0] = gtk_label_new("0");
	gtk.ca_labels[1] = gtk_label_new("0");
	gtk.ca_labels[2] = gtk_label_new("0");
	gtk.ca_labels[3] = gtk_label_new("0");
	gtk.ca_labels[4] = gtk_label_new("0");
	gtk.ca_labels[5] = gtk_label_new("0");
	gtk.ca_labels[6] = gtk_label_new("0");
	gtk.ca_labels[7] = gtk_label_new("0");
	gtk.ca_labels[8] = gtk_label_new("0"); //fan speed
	gtk.ca_labels[9] = gtk_label_new("0"); //temp
	gtk.ca_labels[10] = gtk_label_new("0");

	num1++;
	//ca_labels
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).ca_labels[0]),  0, 1, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).ca_labels[1]),  1, 2, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).ca_labels[2]),  2, 3, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).ca_labels[3]),  3, 4, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).ca_labels[4]),  4, 5, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);

	num1++;

	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).table_labels[11]),  0, 1, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).table_labels[12]),  1, 2, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).table_labels[13]),  2, 3, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).table_labels[14]),  3, 4, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).table_labels[15]),  4, 5, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);

	num1++;

	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).ca_labels[5]),  0, 1, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).ca_labels[6]),  1, 2, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).ca_labels[7]),  2, 3, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).ca_labels[8]),  3, 4, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(gtk.table), GTK_WIDGET(((struct GTK_Widgets) gtk).ca_labels[9]),  4, 5, 1+num1, 2+num1, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);

	gtk.scrolled_win = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gtk.scrolled_win), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(gtk.scrolled_win), gtk.table);

    gtk.notebook_label1 = gtk_label_new("settings");
    gtk.notebook_label2 = gtk_label_new("graphs");

	gtk.graph = gtk_drawing_area_new();
	gtk_widget_set_size_request(gtk.graph, GRAPH_W, GRAPH_H);

	gtk.graph_scroll = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gtk.graph_scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(gtk.graph_scroll), gtk.graph);

	gtk.notebook = gtk_notebook_new();
    ret_val = gtk_notebook_append_page(GTK_NOTEBOOK(gtk.notebook), GTK_WIDGET(gtk.scrolled_win), GTK_WIDGET(gtk.notebook_label1));
    if(ret_val == -1){
        //error
        printf("Error creating notebook!\n");
        pthread_exit(NULL);
    }
    ret_val = gtk_notebook_append_page(GTK_NOTEBOOK(gtk.notebook), GTK_WIDGET(gtk.graph_scroll), GTK_WIDGET(gtk.notebook_label2));
    if(ret_val == -1){
        //error
        printf("Error creating notebook!\n");
        pthread_exit(NULL);
    }

	gtk_box_pack_start(GTK_BOX(gtk.main_vbox), gtk.main_menubar, FALSE, FALSE, 3);
	gtk_box_pack_end(GTK_BOX(gtk.main_vbox), gtk.notebook, TRUE, TRUE, 3);

	g_signal_connect(gtk.main_win, "destroy", G_CALLBACK(exit_cb), NULL);
	g_signal_connect(G_OBJECT(gtk.main_quit), "activate", G_CALLBACK(exit_cb), NULL);
	g_signal_connect(G_OBJECT(gtk.main_results), "activate", G_CALLBACK(API_Funcs_Found_Popup_cb), NULL);
	//Note that the ::expose-event signal has been replaced by a ::draw signal in GTK+ 3. The GTK+ 3 migration guide for hints on how to port from ::expose-event to ::draw.
#ifndef USING_GTK_3
	g_signal_connect(G_OBJECT(gtk.graph),  "expose-event", G_CALLBACK(on_expose_cairo), NULL);
#else
	g_signal_connect(G_OBJECT(gtk.graph), "draw", G_CALLBACK(on_expose_cairo), NULL);
#endif
	gtk_widget_show_all(gtk.main_win);
	gtk_widget_realize(gtk.graph);
	Not_Started = 0;
	gtk_main();
	pthread_exit(NULL);
}

gboolean exit_cb(GtkWidget *widget, gpointer data){
	gtk_main_quit();
	No_Exit = 0;
	return TRUE;
}

gboolean set_fanspeed_cb(GtkWidget *widget, gpointer data){
	int ret_val = 0;
	int num1 = 0;
    //ADLPMActivity current;
    //ADLFanSpeedInfo FanSpeedInfo;
	ADLFanSpeedValue FanSpeedValue;
	gdouble fan_rpm = gtk_spin_button_get_value( (GtkSpinButton *)(((struct PERF_Cur *)data)->fan_b) );
	printf("requested: %.03lf rpm\n", fan_rpm);

	/*
        ret_val = LSADL_OD5_FanSpeedInfo_Get(iAdapterIndex, 0, &FanSpeedInfo);
        if(ret_val != ADL_OK){
            printf("Cannot get FanSpeedInfo !\n");
            Print_Code(ret_val);
            printf("Exiting!\n");
            No_Exit = 0;
            break;
        }
	*/
	ret_val = LSADL_OD5_FanSpeed_Get(iAdapterIndex, 0, &FanSpeedValue);
	if(ret_val != ADL_OK){
		printf("Cannot get FanSpeedValue !\n");
		Print_Code(ret_val);
		printf("\n");
		return FALSE;
	}
	FanSpeedValue.iSize = sizeof(ADLFanSpeedValue);
	FanSpeedValue.iFanSpeed = (int)floor(fan_rpm);
	FanSpeedValue.iSpeedType = ADL_DL_FANCTRL_SPEED_TYPE_RPM;
	//FanSpeedValue.iSpeedType = ADL_DL_FANCTRL_SPEED_TYPE_PERCENT;
	FanSpeedValue.iFlags = ADL_DL_FANCTRL_FLAG_USER_DEFINED_SPEED;
	ret_val = LSADL_OD5_FanSpeed_Set(iAdapterIndex, 0, &FanSpeedValue);
	if(ret_val != ADL_OK){
		printf("Cannot set FanSpeedValue !\n");
		Print_Code(ret_val);
		printf("\n");
		return FALSE;
	}
	printf("Success!\n");
	return TRUE;
}

gboolean reset_fanspeed_cb(GtkWidget *widget, gpointer data){
	int ret_val = 0;
	ADLFanSpeedValue FanSpeedValue;
	ret_val = LSADL_OD5_FanSpeedToDefault_Set(iAdapterIndex, 0);
	if(ret_val != ADL_OK){
		printf("Failed to Set fanspeed to default !\n");
		return FALSE;
	}

    ret_val = LSADL_OD5_FanSpeed_Get(iAdapterIndex, 0, &FanSpeedValue);
    if(ret_val != ADL_OK){
        printf("Cannot get FanSpeedValue !\n");
        Print_Code(ret_val);
        printf("Exiting!\n");
        return FALSE;
    }

	Adjust_spinner(((struct PERF_Cur *)data)->fan_adj, ((struct PERF_Cur *)data)->fan_b, (gdouble)FanSpeedValue.iFanSpeed);
	gtk_widget_show_all( ((struct GTK_Widgets)gtk).main_win );
	printf("Success: fanspeeds reset to default\n");
	return TRUE;
}

gboolean apply_cb(GtkWidget *widget, gpointer data){
	gdouble gpu  = gtk_spin_button_get_value( (GtkSpinButton *)(((struct PERF_Cur *)data)->gpu_b) );
	gdouble mem  = gtk_spin_button_get_value( (GtkSpinButton *)(((struct PERF_Cur *)data)->mem_b) );
	gdouble vddc = gtk_spin_button_get_value( (GtkSpinButton *)(((struct PERF_Cur *)data)->vddc_b) );
	printf("requested: %.03lf %.03lf %.03lf\n", (double)gpu, (double)mem, (double)vddc);
	int ret_val = 0;
	int perf_id = (int)(((struct PERF_Cur *)data)->id);
	int gpu_bak  = PLevels->aLevels[perf_id].iEngineClock;
	int mem_bak  = PLevels->aLevels[perf_id].iMemoryClock;
	int vddc_bak = PLevels->aLevels[perf_id].iVddc;

	PLevels->aLevels[perf_id].iEngineClock = floor(gpu*100);
	PLevels->aLevels[perf_id].iMemoryClock = floor(mem*100);
	PLevels->aLevels[perf_id].iVddc        = floor(vddc*1000);

	printf("Attempting: %d %d %d\n", PLevels->aLevels[perf_id].iEngineClock, PLevels->aLevels[perf_id].iMemoryClock, PLevels->aLevels[perf_id].iVddc);

    ret_val = LSADL_OD5_ODPerformanceLevels_Set(iAdapterIndex, PLevels);
    if(ret_val != ADL_OK){
        printf("LSADL_OD5_ODPerformanceLevels_Set(\n");
        Print_Code(ret_val);
        printf("Failed!\n");
		//should just set defaults instead !! FIXME:
		PLevels->aLevels[perf_id].iEngineClock = gpu_bak;
		PLevels->aLevels[perf_id].iMemoryClock = mem_bak;
		PLevels->aLevels[perf_id].iVddc        = vddc_bak;
        return FALSE;
    }
	PerfCur[perf_id].gpu  = PLevels->aLevels[perf_id].iEngineClock;
	PerfCur[perf_id].mem  = PLevels->aLevels[perf_id].iMemoryClock;
	PerfCur[perf_id].vddc = PLevels->aLevels[perf_id].iVddc;
	PerfCur[perf_id].gpu /= 100;
	PerfCur[perf_id].mem /= 100;
	PerfCur[perf_id].vddc /= 1000;
	printf("Success!\n");
	gtk_widget_show_all( ((struct GTK_Widgets)gtk).main_win );
	return TRUE;
}

gboolean API_Funcs_Found_Popup_cb(GtkWidget *widget, gpointer data){
	int num1 = 0;
    GtkWidget *win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_container_set_border_width(GTK_CONTAINER(win), 2);
    gtk_window_set_default_size(GTK_WINDOW(win), 600, 400);
    gtk_window_set_title(GTK_WINDOW(win), "API Status");
    GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
    gtk_container_add(GTK_CONTAINER(win), vbox);
    GtkWidget *top_table = gtk_table_new(LSADL_FUNCS, 3, TRUE);
	GtkWidget *top_table_labels[3];
    top_table_labels[0] = gtk_label_new("Function:");
    top_table_labels[1] = gtk_label_new("Status:");
    top_table_labels[2] = gtk_label_new("Note:");
	GtkWidget *all_labels[LSADL_FUNCS*3];

    gtk_table_attach(GTK_TABLE(top_table), top_table_labels[0],  0, 1, 0, 1, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 5, 5);
    gtk_table_attach(GTK_TABLE(top_table), top_table_labels[1],  1, 2, 0, 1, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 5, 5);
    gtk_table_attach(GTK_TABLE(top_table), top_table_labels[2],  2, 3, 0, 1, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 5, 5);

	for(num1=0; num1<LSADL_FUNCS; num1+=3){
		all_labels[num1] = gtk_label_new((const gchar *)ADL_Func_Names[num1]);
		if(ADL_Func_Enabled[num1] == 1)
			all_labels[num1+1] = gtk_label_new((const gchar *)"Enabled");
		else
			all_labels[num1+1] = gtk_label_new((const gchar *)"Disabled");
		if(ADL_Func_Notes[num1] == FUNC_FOUND_IN_API)
			all_labels[num1+2] = gtk_label_new((const gchar *)"Found in API");
		else if(ADL_Func_Notes[num1] == FUNC_MISSING_FROM_API)
			all_labels[num1+2] = gtk_label_new((const gchar *)"Missing From API");
		else
			all_labels[num1+2] = gtk_label_new((const gchar *)"Disabled by LSADL");
		gtk_table_attach(GTK_TABLE(top_table), all_labels[num1],  0, 1, num1+1, num1+2, GTK_SHRINK, GTK_SHRINK, 5, 5);
		gtk_table_attach(GTK_TABLE(top_table), all_labels[num1+1],  1, 2, num1+1, num1+2, GTK_SHRINK, GTK_SHRINK, 5, 5);
		gtk_table_attach(GTK_TABLE(top_table), all_labels[num1+2],  2, 3, num1+1, num1+2, GTK_SHRINK, GTK_SHRINK, 5, 5);
	}
	GtkWidget *close_button = gtk_button_new_with_label("Close");
    GtkWidget *scrolled_win = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_win), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_win), top_table);
    gtk_box_pack_start(GTK_BOX(vbox), scrolled_win, TRUE, TRUE, 3);
    gtk_box_pack_end(GTK_BOX(vbox), close_button, FALSE, FALSE, 3);
	g_signal_connect(win, "destroy", G_CALLBACK(on_destroy), (gpointer)(win));
	g_signal_connect(G_OBJECT(close_button), "clicked", G_CALLBACK(on_destroy), (gpointer)(win));
	gtk_widget_show_all(win);

	return TRUE;
}

#ifndef USING_GTK_3
void on_expose_cairo(GtkWidget *widget, GdkEventExpose *event, gpointer data){
#else
void on_expose_cairo(GtkWidget *widget, CairoContext *cr, gpointer data){
//Signal handlers connected to this signal can modify the cairo context passed as cr in any way they
//like and don't need to restore it. The signal emission takes care of calling cairo_save() before and cairo_restore() after invoking the handler.
#endif
	cairo_format_t format;
	cairo_surface_t *target;
	cairo_t *context;
	cairo_status_t status;
	double red, green, blue, alpha;
	double x1, y1, x2, y2;
	double last_x[4] = {0, 0, 0, 0};
	double last_y[4] = {0, 0, 0, 0};
	double tmp1 = 0;
	double tmp2 = 0;
	double highest_temp = 0;
	double highest_fan = 0;
	double highest_usage = 0;
	double width = (double)widget->allocation.width;
	double height = (double)widget->allocation.height;
	double white_height = height * 0.03;
	double black_height = (height - white_height*5) / total_charts;
	double data_w = width * 0.95;
	double data_h = black_height * 0.86;
	double data_s = data_w/60;
	int stride;
	int num1 = 0;
	int local_idx = graph_idx;
	unsigned char *img_data;
	char buffer[16];

	for(num1=0; num1<total_charts; num1++){
		cv[num1].black_top = (num1+1)*white_height + black_height*num1;
		cv[num1].black_bottom = cv[num1].black_top + black_height;
		cv[num1].data_top = cv[num1].black_top + black_height * 0.07;
		cv[num1].data_bottom = cv[num1].black_top + black_height - black_height * 0.07;
	}

	format = CAIRO_FORMAT_ARGB32;
	stride = cairo_format_stride_for_width(format, width);
	img_data = malloc(stride * height);
	target = cairo_image_surface_create_for_data(img_data, format, width, height, stride);
	context = gdk_cairo_create(GDK_DRAWABLE((gtk.graph)->window));

	status = cairo_status(context);
	if(status != 0){
		printf("Error: %s\n", cairo_status_to_string(status) );
		free(img_data);
		return;
	}

	red = 0;
	green = 0;
	blue = 0;
	alpha = 1.0;
	cairo_set_source_rgba(context, red, green, blue, alpha);
	cairo_rectangle(context, 0, 0, width, height);
	cairo_fill(context);
	red = 1.0;
	green = 1.0;
	blue = 1.0;
	alpha = 1.0;
	x1 = 0;
	y1 = 0;
	for(num1=0; num1<5; num1++){
		cairo_set_source_rgba(context, red, green, blue, alpha);
		cairo_rectangle(context, x1, y1, width, white_height);
		cairo_fill(context);
		y1 += white_height + black_height;
	}

	x1 = 0;
	y1 = 0;
	x2 = width;
	y2 = height;

	red = 0;
	green = 1;
	blue = 0;
	alpha = 1.0;
	cairo_set_dash(context, 0, 0, 0);
	cairo_set_source_rgba(context, red, green, blue, alpha);
	cairo_set_line_width(context, 1);
	cairo_select_font_face(context, "serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
	cairo_set_font_size(context, 10.0);
	for(num1=0; num1<60; num1++){
		if(graphdata.fan[num1] > highest_fan)
			highest_fan = graphdata.fan[num1];
		if(graphdata.temp[num1] > highest_temp)
			highest_temp = graphdata.temp[num1];
		if(graphdata.usage[num1] > highest_usage)
			highest_usage = graphdata.usage[num1];
	}
	//cairo labels
	cairo_move_to(context, width/2, cv[0].data_top);
	cairo_show_text(context, "Perf Level");
	cairo_move_to(context, width/2, cv[1].data_top);
	cairo_show_text(context, "GPU Usage");
	cairo_move_to(context, width/2, cv[2].data_top);
	cairo_show_text(context, "Fan Speed");
	cairo_move_to(context, width/2, cv[3].data_top);
	cairo_show_text(context, "GPU Temp");

	//High/low legend
	cairo_move_to(context, data_w, cv[0].data_top);
	(void)memset(buffer, '\0', 16);
	(void)sprintf(buffer, "%d", (int)PerfLevels-1);
	cairo_show_text(context, buffer);
	cairo_move_to(context, data_w, cv[0].data_bottom);
	cairo_show_text(context, "0");

	cairo_move_to(context, data_w, cv[1].data_top);
	cairo_show_text(context, "100");
	cairo_move_to(context, data_w, cv[1].data_bottom);
	cairo_show_text(context, "0");

	cairo_move_to(context, data_w, cv[2].data_top);
	(void)memset(buffer, '\0', 16);
	(void)sprintf(buffer, "%u", (unsigned int)highest_fan);
	cairo_show_text(context, buffer);
	cairo_move_to(context, data_w, cv[2].data_bottom);
	cairo_show_text(context, "0");

	cairo_move_to(context, data_w, cv[3].data_top);
	(void)memset(buffer, '\0', 16);
	(void)sprintf(buffer, "%.01lf", highest_temp);
	cairo_show_text(context, buffer);
	cairo_move_to(context, data_w, cv[3].data_bottom);
	cairo_show_text(context, "0");

	for(num1=0; num1<60; num1++){
		tmp1 = cv[0].data_bottom;
		tmp2 = (double)graphdata.perf[local_idx]/(PerfLevels-1);
		tmp2 *= data_h;
		tmp1 -= tmp2;
		cairo_move_to(context, last_x[0], last_y[0]);
		cairo_line_to(context, (double)num1*data_s, tmp1);
		cairo_stroke(context);
		last_x[0] = num1*data_s;
		last_y[0] = tmp1;

		tmp1 = cv[1].data_bottom;
		tmp2 = (double)graphdata.usage[local_idx]/100;
		tmp2 *= data_h;
		tmp1 -= tmp2;
		cairo_move_to(context, last_x[1], last_y[1]);
		cairo_line_to(context, (double)num1*data_s, tmp1);
		cairo_stroke(context);
		last_x[1] = num1*data_s;
		last_y[1] = tmp1;

		tmp1 = cv[2].data_bottom;
		tmp2 = (double)graphdata.fan[local_idx]/highest_fan;
		tmp2 *= data_h;
		tmp1 -= tmp2;
		cairo_move_to(context, last_x[2], last_y[2]);
		cairo_line_to(context, (double)num1*data_s, tmp1);
		cairo_stroke(context);
		last_x[2] = num1*data_s;
		last_y[2] = tmp1;

		tmp1 = cv[3].data_bottom;
		tmp2 = (double)graphdata.temp[local_idx]/highest_temp;
		tmp2 *= data_h;
		tmp1 -= tmp2;
		cairo_move_to(context, last_x[3], last_y[3]);
		cairo_line_to(context, (double)num1*data_s, tmp1);
		cairo_stroke(context);
		last_x[3] = num1*data_s;
		last_y[3] = tmp1;

		if(local_idx == 59)
			local_idx = 0;
		else
			local_idx++;
	}

	cairo_destroy(context);
	cairo_surface_destroy(target);

	free(img_data);
}

gboolean on_toggle_unlock(GtkWidget *widget, GdkEventExpose *event, gpointer data){
	int num1 = 0;
	unlocked ^= 1;
	while(num1 < PerfLevels){
		if(unlocked){
			PerfCur[num1].gpu_adj->lower = PerfRanges.gpu_step;
			PerfCur[num1].gpu_adj->upper = 10000;
			PerfCur[num1].mem_adj->lower = PerfRanges.mem_step;
			PerfCur[num1].mem_adj->upper = 10000;
			PerfCur[num1].vddc_adj->lower = PerfRanges.vddc_step;
			PerfCur[num1].vddc_adj->upper = 2.0;
		}else{
			PerfCur[num1].gpu_adj->lower = PerfRanges.gpu_min;
			PerfCur[num1].gpu_adj->upper = PerfRanges.gpu_max;
			PerfCur[num1].mem_adj->lower = PerfRanges.mem_min;
			PerfCur[num1].mem_adj->upper = PerfRanges.mem_max;
			PerfCur[num1].vddc_adj->lower = PerfRanges.vddc_min;
			PerfCur[num1].vddc_adj->upper = PerfRanges.vddc_max;
		}
		num1++;
	}
	//fan
	if(unlocked){
		PerfCur[0].fan_adj->lower = PerfRanges.fan_step;
		PerfCur[0].fan_adj->upper = 10000;
	}else{
		PerfCur[0].fan_adj->lower = PerfRanges.fan_min;
		PerfCur[0].fan_adj->upper = PerfRanges.fan_max;
	}
	gtk_widget_show_all(((struct GTK_Widgets)gtk).main_win);
	return TRUE;
}