#include <sys/time.h>
#include <pthread.h>
#include <string.h>
#include <gtk/gtk.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#include <X11/Xlib.h>
#endif
#include <gst/app/gstappsrc.h>
#if GST_CHECK_VERSION(1,0,0)
#include <gst/video/videooverlay.h>
#define VIDEOCONVERT "videoconvert"
#else
#include <gst/interfaces/xoverlay.h>
#define VIDEOCONVERT "ffmpegcolorspace"
#endif
#include <m3api/xiApi.h>

BOOLEAN acquire, quitting, render = TRUE;
int maxcx, maxcy, roix0, roiy0, roicx, roicy;
pthread_t videoThread;
HANDLE handle = INVALID_HANDLE_VALUE;
guintptr window_handle;

GstBusSyncReply bus_sync_handler(GstBus* bus, GstMessage* message, gpointer) {
#if GST_CHECK_VERSION(1,0,0)
	if(!gst_is_video_overlay_prepare_window_handle_message(message))
#else
	if(GST_MESSAGE_TYPE(message) != GST_MESSAGE_ELEMENT || !gst_structure_has_name(message->structure, "prepare-xwindow-id"))
#endif
		return GST_BUS_PASS;
#if GST_CHECK_VERSION(1,0,0)
	gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(GST_MESSAGE_SRC(message)), window_handle);
#elif GST_CHECK_VERSION(0,10,31)
	gst_x_overlay_set_window_handle(GST_X_OVERLAY(GST_MESSAGE_SRC(message)), window_handle);
#else
	gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(GST_MESSAGE_SRC(message)), window_handle);
#endif
	gst_message_unref(message);
	return GST_BUS_DROP;
}

#if defined(GDK_WINDOWING_X11)
#define VIDEOSINK "xvimagesink"
void video_widget_realize_cb(GtkWidget* widget, gpointer) {
	GdkWindow *window = gtk_widget_get_window(widget);
	if (!gdk_window_ensure_native(window))
		g_error ("Couldn't create native window needed for GstXOverlay!");
	window_handle = GDK_WINDOW_XID(window);
}
#elif defined(GDK_WINDOWING_QUARTZ)
#define VIDEOSINK "osxvideosink"
void video_widget_realize_cb(GtkWidget*, gpointer);
#else
#error Unsupported GDK backend
#endif

inline unsigned long getcurus() {
	struct timeval now;
	gettimeofday(&now, NULL);
	return now.tv_sec * 1000000 + now.tv_usec;
}

gboolean close_cb(GtkWidget*, GdkEvent*, gpointer quit) {
	quitting = quit ? TRUE : FALSE;
	if(videoThread)
		acquire = FALSE;
	else
		gtk_main_quit();
	return TRUE;
}

void* videoDisplay(void*) {
	GtkWidget *videoWindow;
	GdkScreen *screen;
	GstElement *pipeline, *appsrc, *scale;
	GstFlowReturn ret;
	GstBuffer *buffer;
	GstBus *bus;
	GstCaps *caps = 0;
	GstCaps *size_caps = 0;
	int max_width, max_height;
	int prev_width = -1;
	int prev_height = -1;
	unsigned long frames = 0;
	unsigned long prevframes = 0;
	unsigned long lostframes = 0;
	unsigned long curtime, prevtime;
	long lastframe = -1;
	gchar title[256];
	XI_IMG_FORMAT prev_format = XI_RAW8;
	XI_IMG image;
	image.size = sizeof(XI_IMG);
	image.bp = NULL;
	image.bp_size = 0;
	gdk_threads_enter();
	videoWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_window_set_title(GTK_WINDOW(videoWindow), "streamViewer");
	gtk_widget_set_double_buffered(videoWindow, FALSE);
	g_signal_connect(videoWindow, "realize", G_CALLBACK(video_widget_realize_cb), NULL);
	g_signal_connect(videoWindow, "delete-event", G_CALLBACK(close_cb), NULL);
	gtk_widget_show_all(videoWindow);
	gtk_widget_realize(videoWindow);
	screen = gdk_screen_get_default();
	max_width = 0.8*gdk_screen_get_width(screen);
	max_height = 0.8*gdk_screen_get_width(screen);
	gdk_threads_leave();
	if(xiStartAcquisition(handle) != XI_OK)
		goto exit;
	pipeline = gst_parse_launch("appsrc is-live=TRUE name=streamViewer ! queue max-size-buffers=2 leaky=2 ! videoscale add-borders=TRUE ! capsfilter name=scale ! " VIDEOCONVERT " ! " VIDEOSINK, NULL);
	if(!pipeline)
		goto exit;
	bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
	gst_bus_set_sync_handler(bus, (GstBusSyncHandler)bus_sync_handler,
#if GST_CHECK_VERSION(1,0,0)
			pipeline,
#endif
			NULL);
	gst_object_unref(bus);
	appsrc = gst_bin_get_by_name(GST_BIN(pipeline), "streamViewer");
	scale = gst_bin_get_by_name(GST_BIN(pipeline), "scale");
	prevtime = getcurus();
	while(acquire) {
		if(xiGetImage(handle, 5000, &image) != XI_OK)
			break;
		if(render) {
			unsigned long buffer_size = image.width*image.height*(image.frm == XI_RAW8 ? 1 : 4);
			if(!image.padding_x) {
				buffer = gst_buffer_new();
#if GST_CHECK_VERSION(1,0,0)
				gst_buffer_insert_memory(buffer, -1, gst_memory_new_wrapped(GST_MEMORY_FLAG_READONLY, (guint8*)image.bp, buffer_size, 0, buffer_size, 0, 0));
#else
				gst_buffer_set_data(buffer, (guint8*)image.bp, buffer_size);
#endif
			} else {
#if GST_CHECK_VERSION(1,0,0)
				buffer = gst_buffer_new_allocate(0, buffer_size, 0);
				if(!buffer)
					break;
#else
				buffer = gst_buffer_new_and_alloc(buffer_size);
#endif
				for(int i = 0; i < image.height; i++)
#if GST_CHECK_VERSION(1,0,0)
					gst_buffer_fill(buffer,
#else
					memcpy(GST_BUFFER_DATA(buffer)+
#endif
							i*image.width*(image.frm == XI_RAW8 ? 1 : 4),
							(guint8*)image.bp+i*(image.width*(image.frm == XI_RAW8 ? 1 : 4)+image.padding_x),
							image.width*(image.frm == XI_RAW8 ? 1 : 4));
			}
			GST_BUFFER_TIMESTAMP(buffer) = GST_CLOCK_TIME_NONE;
			if(prev_width != image.width || prev_height != image.height || prev_format != image.frm) {
				if(caps)
					gst_caps_unref(caps);
				if(image.frm == XI_RAW8)
					caps = gst_caps_new_simple(
#if GST_CHECK_VERSION(1,0,0)
							"video/x-raw",
							"format", G_TYPE_STRING, "GRAY8",
#else
							"video/x-raw-gray",
#endif
							"bpp", G_TYPE_INT, 8,
							"depth", G_TYPE_INT, 8,
							"framerate", GST_TYPE_FRACTION, 0, 1,
							"width", G_TYPE_INT, image.width,
							"height", G_TYPE_INT, image.height,
							NULL);
				else if(image.frm == XI_RGB32)
					caps = gst_caps_new_simple(
#if GST_CHECK_VERSION(1,0,0)
							"video/x-raw",
							"format", G_TYPE_STRING, "BGRx",
#else
							"video/x-raw-rgb",
#endif
							"bpp", G_TYPE_INT, 32,
							"depth", G_TYPE_INT, 24,
							"endianness", G_TYPE_INT, G_BIG_ENDIAN,
							"red_mask",   G_TYPE_INT, 0x0000ff00,
							"green_mask", G_TYPE_INT, 0x00ff0000,
							"blue_mask",  G_TYPE_INT, 0xff000000,
							"framerate", GST_TYPE_FRACTION, 0, 1,
							"width", G_TYPE_INT, image.width,
							"height", G_TYPE_INT, image.height,
							NULL);
				else
					break;
				gst_element_set_state(pipeline, GST_STATE_PAUSED);
				gst_app_src_set_caps(GST_APP_SRC(appsrc), caps);
				gst_element_set_state(pipeline, GST_STATE_PLAYING);
				prev_width = image.width;
				prev_height = image.height;
				prev_format = image.frm;
				int width = image.width;
				int height = image.height;
				while(width > max_width || height > max_height) {
					width /= 2;
					height /=2;
				}
				if(size_caps)
					gst_caps_unref(size_caps);
				size_caps = gst_caps_new_simple(
#if GST_CHECK_VERSION(1,0,0)
						"video/x-raw",
#else
						image.frm == XI_RAW8 ? "video/x-raw-gray" : "video/x-raw-rgb",
#endif
						"width", G_TYPE_INT, width,
						"height", G_TYPE_INT, height,
						NULL);
				g_object_set(G_OBJECT(scale), "caps", size_caps, NULL);
				gdk_threads_enter();
				gtk_window_resize(GTK_WINDOW(videoWindow), width, height);
				gdk_threads_leave();
			}
			ret = gst_app_src_push_buffer(GST_APP_SRC(appsrc), buffer);
			if(ret != GST_FLOW_OK)
				break;
		}
		frames++;
		if(image.nframe > lastframe)
			lostframes += image.nframe - (lastframe + 1);
		lastframe = image.nframe;
		curtime = getcurus();
		if(curtime - prevtime > 1000000) {
			snprintf(title, 256, "Acquisition [ captured: %lu, skipped: %lu, fps: %.2f ]", frames, lostframes, 1000000.0 * (frames - prevframes) / (curtime - prevtime));
			gtk_window_set_title(GTK_WINDOW(videoWindow), title);
			prevframes = frames;
			prevtime = curtime;
		}
	}
	if(caps)
		gst_caps_unref(caps);
	if(size_caps)
		gst_caps_unref(size_caps);
	gst_app_src_end_of_stream(GST_APP_SRC(appsrc));
	gst_element_set_state(pipeline, GST_STATE_NULL);
	gst_object_unref(GST_OBJECT(pipeline));
exit:
	gdk_threads_enter();
	gtk_widget_destroy(videoWindow);
	if(quitting)
		gtk_main_quit();
	xiStopAcquisition(handle);
	acquire = FALSE;
	xiCloseDevice(handle);
	handle = INVALID_HANDLE_VALUE;
	videoThread = 0;
	gdk_threads_leave();
	return 0;
}

struct ctrl_window {
    GtkWidget *window, *boxmain, *boxgpi, *boxx, *boxy, *gpi1, *gpi2, *gpi3, *gpi4, *labelexp, *exp, *labelgain, *gain, *labelx0, *x0, *labely0, *y0, *labelcx, *cx, *labelcy, *cy, *raw, *show, *run;
};

gboolean time_handler(ctrl_window *ctrl) {
	int level = 0;
	if(acquire && handle != INVALID_HANDLE_VALUE) {
		xiSetParamInt(handle, XI_PRM_GPI_SELECTOR, 1);
		xiGetParamInt(handle, XI_PRM_GPI_LEVEL, &level);
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ctrl->gpi1), level);
		xiSetParamInt(handle, XI_PRM_GPI_SELECTOR, 2);
		xiGetParamInt(handle, XI_PRM_GPI_LEVEL, &level);
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ctrl->gpi2), level);
		xiSetParamInt(handle, XI_PRM_GPI_SELECTOR, 3);
		xiGetParamInt(handle, XI_PRM_GPI_LEVEL, &level);
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ctrl->gpi3), level);
		xiSetParamInt(handle, XI_PRM_GPI_SELECTOR, 4);
		xiGetParamInt(handle, XI_PRM_GPI_LEVEL, &level);
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ctrl->gpi4), level);
	}
	gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(ctrl->run), gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ctrl->run)) != acquire);
	gtk_widget_set_sensitive(ctrl->boxx, acquire);
	gtk_widget_set_sensitive(ctrl->boxy, acquire);
	gtk_widget_set_sensitive(ctrl->exp, acquire);
	gtk_widget_set_sensitive(ctrl->gain, acquire);
	return TRUE;
}

gboolean update_x0(GtkAdjustment *adj, gpointer) {
	roix0 = gtk_adjustment_get_value(adj);
	if(roicx + roix0 > maxcx){
		roix0 = maxcx - roicx;
		gtk_adjustment_set_value(adj, roix0);
	}
	xiSetParamInt(handle, XI_PRM_OFFSET_X, roix0);
	return TRUE;
}

gboolean update_y0(GtkAdjustment *adj, gpointer) {
	roiy0 = gtk_adjustment_get_value(adj);
	if(roicy + roiy0 > maxcy){
		roiy0 = maxcy - roicy;
		gtk_adjustment_set_value(adj, roiy0);
	}
	xiSetParamInt(handle, XI_PRM_OFFSET_Y, roiy0);
	return TRUE;
}

gboolean update_cx(GtkAdjustment *adj, gpointer) {
	roicx = gtk_adjustment_get_value(adj);
	if(roix0 + roicx > maxcx){
		roicx = maxcx - roix0;
		gtk_adjustment_set_value(adj, roicx);
	}
	xiSetParamInt(handle, XI_PRM_WIDTH, roicx);
	return TRUE;
}

gboolean update_cy(GtkAdjustment *adj, gpointer) {
	roicy = gtk_adjustment_get_value(adj);
	if(roiy0 + roicy > maxcy) {
		roicy = maxcy - roiy0;
		gtk_adjustment_set_value(adj, roicy);
	}
	xiSetParamInt(handle, XI_PRM_HEIGHT, roicy);
	return TRUE;
}

gboolean update_exposure(GtkAdjustment *adj, gpointer) {
	xiSetParamInt(handle, XI_PRM_EXPOSURE, 1000*gtk_adjustment_get_value(adj));		
	return TRUE;
}

gboolean update_gain(GtkAdjustment *adj, gpointer) {
	xiSetParamFloat(handle, XI_PRM_GAIN, gtk_adjustment_get_value(adj));
	return TRUE;
}

gboolean update_raw(GtkToggleButton *raw, ctrl_window *ctrl) {
	if(handle != INVALID_HANDLE_VALUE) {
		float mingain, maxgain;
		xiSetParamInt(handle, XI_PRM_IMAGE_DATA_FORMAT, gtk_toggle_button_get_active(raw) ? XI_RAW8 : XI_RGB32);
		xiGetParamFloat(handle, XI_PRM_GAIN XI_PRM_INFO_MIN, &mingain);
		xiGetParamFloat(handle, XI_PRM_GAIN XI_PRM_INFO_MAX, &maxgain);
		xiGetParamInt(handle, XI_PRM_WIDTH XI_PRM_INFO_MAX, &maxcx);
		xiGetParamInt(handle, XI_PRM_HEIGHT XI_PRM_INFO_MAX, &maxcy);
		roicx = maxcx;
		roicy = maxcy;
		roix0 = 0;
		roiy0 = 0;
		gtk_adjustment_configure(gtk_range_get_adjustment(GTK_RANGE(ctrl->gain)), mingain, mingain, maxgain, 0.1, 1, 0);
		gtk_adjustment_configure(gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(ctrl->x0)), roix0, 0, maxcx-4, 2, 20, 0);
		gtk_adjustment_configure(gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(ctrl->y0)), roiy0, 0, maxcy-2, 2, 20, 0);
		gtk_adjustment_configure(gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(ctrl->cx)), roicx, 4, maxcx, 4, 20, 0);
		gtk_adjustment_configure(gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(ctrl->cy)), roicy, 2, maxcy, 2, 20, 0);
		xiSetParamFloat(handle, XI_PRM_GAIN, mingain);
		xiSetParamInt(handle, XI_PRM_OFFSET_X, roix0);
		xiSetParamInt(handle, XI_PRM_OFFSET_Y, roiy0);
		xiSetParamInt(handle, XI_PRM_WIDTH, roicx);
		xiSetParamInt(handle, XI_PRM_HEIGHT, roicy);
		//exposure doesn't seem to be affected by format change
	}
	return TRUE;
}

gboolean update_show(GtkToggleButton *show, gpointer) {
	render = gtk_toggle_button_get_active(show);
	return TRUE;
}

gboolean update_run(GtkToggleButton *run, ctrl_window *ctrl) {
	gtk_toggle_button_set_inconsistent(run, false);
	acquire = gtk_toggle_button_get_active(run);
	if(acquire && handle == INVALID_HANDLE_VALUE) {
		DWORD nIndex = 0;
		char* env = getenv("CAM_INDEX");
		if(env) {
			nIndex = atoi(env);
		}
		DWORD tmp;
		xiGetNumberDevices(&tmp); //rescan available devices
		if(xiOpenDevice(nIndex, &handle) != XI_OK) {
			printf("Couldn't setup camera!\n");
			acquire = FALSE;
			return TRUE;
		}
		update_raw(GTK_TOGGLE_BUTTON(ctrl->raw), ctrl);
		int isColor = 0;
		xiGetParamInt(handle, XI_PRM_IMAGE_IS_COLOR, &isColor);
		if(isColor)
			xiSetParamInt(handle, XI_PRM_AUTO_WB, 1);
		xiSetParamInt(handle, XI_PRM_EXPOSURE, 10000);
		gtk_adjustment_set_value(gtk_range_get_adjustment(GTK_RANGE(ctrl->exp)), 10);
		if(pthread_create(&videoThread, NULL, videoDisplay, NULL))
			exit(1);
	}
	gtk_widget_set_sensitive(ctrl->boxx, acquire);
	gtk_widget_set_sensitive(ctrl->boxy, acquire);
	gtk_widget_set_sensitive(ctrl->exp, acquire);
	gtk_widget_set_sensitive(ctrl->gain, acquire);
	return TRUE;
}

gboolean start_cb(ctrl_window* ctrl) {
	//start acquisition
	update_run(GTK_TOGGLE_BUTTON(ctrl->run), ctrl);
	return FALSE;
}

int main(int argc, char **argv)
{
	ctrl_window ctrl;
#ifdef GDK_WINDOWING_X11
	XInitThreads();
#endif
#if !GLIB_CHECK_VERSION(2, 31, 0)
	g_thread_init(NULL);
#endif
	gdk_threads_init();
	gdk_threads_enter();
	gst_init(&argc, &argv);
	gtk_init(&argc, &argv);
	//create widgets
	ctrl.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	ctrl.boxmain = gtk_vbox_new(FALSE, 0);
	ctrl.boxgpi = gtk_hbox_new(TRUE, 0);
	ctrl.boxx = gtk_hbox_new(FALSE, 0);
	ctrl.boxy = gtk_hbox_new(FALSE, 0);
	ctrl.labelexp = gtk_label_new("Exposure (ms)");
	ctrl.labelgain = gtk_label_new("Gain (dB)");
	ctrl.labelx0 = gtk_label_new("x0");
	ctrl.labelcx = gtk_label_new("cx");
	ctrl.labely0 = gtk_label_new("y0");
	ctrl.labelcy = gtk_label_new("cy");
	ctrl.gpi1 = gtk_check_button_new_with_label("GPI1");
	ctrl.gpi2 = gtk_check_button_new_with_label("GPI2");
	ctrl.gpi3 = gtk_check_button_new_with_label("GPI3");
	ctrl.gpi4 = gtk_check_button_new_with_label("GPI4");
	ctrl.exp = gtk_hscale_new_with_range(1, 1000, 1);
	ctrl.gain = gtk_hscale_new_with_range(0, 1, 0.1); //use dummy limits
	ctrl.x0 = gtk_spin_button_new_with_range(0, 128, 2); //use dummy max limit
	ctrl.y0 = gtk_spin_button_new_with_range(0, 128, 2); //use dummy max limit
	ctrl.cx = gtk_spin_button_new_with_range(4, 128, 4); //use dummy max limit
	ctrl.cy = gtk_spin_button_new_with_range(2, 128, 2); //use dummy max limit
	ctrl.raw = gtk_toggle_button_new_with_label("Display RAW data");
	ctrl.show = gtk_toggle_button_new_with_label("Live view");
	ctrl.run = gtk_toggle_button_new_with_label("Acquisition");
	//tune them
	gtk_window_set_title(GTK_WINDOW(ctrl.window), "streamViewer control");
	gtk_window_set_keep_above(GTK_WINDOW(ctrl.window), TRUE);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ctrl.raw), TRUE);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ctrl.show), TRUE);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ctrl.run), TRUE); //actual start is delayed by 100ms, see below
	gtk_widget_set_sensitive(ctrl.boxgpi, FALSE);
	gtk_scale_set_digits(GTK_SCALE(ctrl.exp), 0);
	gtk_range_set_update_policy(GTK_RANGE(ctrl.exp), GTK_UPDATE_DISCONTINUOUS);
	gtk_range_set_update_policy(GTK_RANGE(ctrl.gain), GTK_UPDATE_DISCONTINUOUS);
	gtk_adjustment_set_value(gtk_range_get_adjustment(GTK_RANGE(ctrl.exp)), 10);
	gtk_scale_set_value_pos(GTK_SCALE(ctrl.exp), GTK_POS_RIGHT);
	gtk_scale_set_value_pos(GTK_SCALE(ctrl.gain), GTK_POS_RIGHT);
	gtk_widget_set_sensitive(ctrl.boxx, FALSE);
	gtk_widget_set_sensitive(ctrl.boxy, FALSE);
	gtk_widget_set_sensitive(ctrl.exp, FALSE);
	gtk_widget_set_sensitive(ctrl.gain, FALSE);
	//pack everything into window
	gtk_container_add(GTK_CONTAINER(ctrl.boxgpi), ctrl.gpi1);
	gtk_container_add(GTK_CONTAINER(ctrl.boxgpi), ctrl.gpi2);
	gtk_container_add(GTK_CONTAINER(ctrl.boxgpi), ctrl.gpi3);
	gtk_container_add(GTK_CONTAINER(ctrl.boxgpi), ctrl.gpi4);
	gtk_container_add(GTK_CONTAINER(ctrl.boxmain), ctrl.boxgpi);
	gtk_container_add(GTK_CONTAINER(ctrl.boxx), ctrl.labelx0);
	gtk_container_add(GTK_CONTAINER(ctrl.boxx), ctrl.x0);
	gtk_container_add(GTK_CONTAINER(ctrl.boxy), ctrl.labely0);
	gtk_container_add(GTK_CONTAINER(ctrl.boxy), ctrl.y0);
	gtk_container_add(GTK_CONTAINER(ctrl.boxx), ctrl.labelcx);
	gtk_container_add(GTK_CONTAINER(ctrl.boxx), ctrl.cx);
	gtk_container_add(GTK_CONTAINER(ctrl.boxy), ctrl.labelcy);
	gtk_container_add(GTK_CONTAINER(ctrl.boxy), ctrl.cy);
	gtk_container_add(GTK_CONTAINER(ctrl.boxmain), ctrl.boxx);
	gtk_container_add(GTK_CONTAINER(ctrl.boxmain), ctrl.boxy);	
	gtk_container_add(GTK_CONTAINER(ctrl.boxmain), ctrl.labelexp);
	gtk_container_add(GTK_CONTAINER(ctrl.boxmain), ctrl.exp);
	gtk_container_add(GTK_CONTAINER(ctrl.boxmain), ctrl.labelgain);
	gtk_container_add(GTK_CONTAINER(ctrl.boxmain), ctrl.gain);
	gtk_container_add(GTK_CONTAINER(ctrl.boxmain), ctrl.raw);
	gtk_container_add(GTK_CONTAINER(ctrl.boxmain), ctrl.show);
	gtk_container_add(GTK_CONTAINER(ctrl.boxmain), ctrl.run);
	gtk_container_add(GTK_CONTAINER(ctrl.window), ctrl.boxmain);
	//register handlers
	gdk_threads_add_timeout(1000, (GSourceFunc)time_handler, (gpointer)&ctrl);
	gdk_threads_add_timeout(100, (GSourceFunc)start_cb, (gpointer)&ctrl); //only way I found to make sure window is displayed right away
	g_signal_connect(ctrl.window, "delete_event", G_CALLBACK(close_cb), (gpointer)TRUE);
	g_signal_connect(gtk_range_get_adjustment(GTK_RANGE(ctrl.gain)), "value_changed", G_CALLBACK(update_gain), NULL);
	g_signal_connect(gtk_range_get_adjustment(GTK_RANGE(ctrl.exp)), "value_changed", G_CALLBACK(update_exposure), NULL);
	g_signal_connect(gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(ctrl.x0)), "value_changed", G_CALLBACK(update_x0), NULL);
	g_signal_connect(gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(ctrl.y0)), "value_changed", G_CALLBACK(update_y0), NULL);
	g_signal_connect(gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(ctrl.cx)), "value_changed", G_CALLBACK(update_cx), NULL);
	g_signal_connect(gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(ctrl.cy)), "value_changed", G_CALLBACK(update_cy), NULL);
	g_signal_connect(ctrl.raw, "toggled", G_CALLBACK(update_raw), (gpointer)&ctrl);
	g_signal_connect(ctrl.show, "toggled", G_CALLBACK(update_show), NULL);
	g_signal_connect(ctrl.run, "toggled", G_CALLBACK(update_run), (gpointer)&ctrl);
	//show window
	gtk_widget_show_all(ctrl.window);
	//start the main loop
	gtk_main();
	//exit
	gdk_threads_leave();
	acquire = FALSE;
	if(videoThread)
		pthread_join(videoThread, NULL);
	return 0;
}
