/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
 *																			*
 * This library is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU Lesser General Public License		*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU Lesser General Public License for more details: lgpl.txt or	*
 * http://www.fsf.org/copyleft/lesser.html									*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include <math.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <string.h>

#include <genwrap.h>
#include <semwrap.h>
#include <threadwrap.h>
#include <link_list.h>

#include "ciolib.h"

#ifdef _MSC_VER
#pragma warning(disable : 4244 4267 4018)
#endif

#define MSEC_CLOCK()	xp_timer64()

enum {
	 MOUSE_NOSTATE
	,MOUSE_SINGLEPRESSED
	,MOUSE_CLICKED
	,MOUSE_DOUBLEPRESSED
	,MOUSE_DOUBLECLICKED
	,MOUSE_TRIPLEPRESSED
	,MOUSE_TRIPLECLICKED
	,MOUSE_QUADPRESSED
	,MOUSE_QUADCLICKED
	,MOUSE_DRAGSTARTED
};

struct in_mouse_event {
	uint64_t epoch;
	int	event;
	int	x;
	int	y;
	int	x_res;
	int	y_res;
	int	kbmodifiers;
	uint16_t hyperlink_id;
	long long ts;
	void	*nextevent;
};

struct out_mouse_event {
	uint64_t epoch;
	int event;
	int bstate;
	int kbsm;		/* Known button state mask */
	int kbmodifiers;
	uint16_t hyperlink_id;
	int startx;
	int starty;
	int endx;
	int endy;
	int startx_res;
	int starty_res;
	int endx_res;
	int endy_res;
	void *nextevent;
};

struct mouse_state {
	uint64_t epoch;
	int	buttonstate;			/* Current state of all buttons - bitmap */
	int	knownbuttonstatemask;	/* Mask of buttons that have done something since
								 * We started watching... the rest are actually in
								 * an unknown state */
	int	button_state[5];		/* Expanded state of each button */
	int	button_x[5];			/* Start X/Y position of the current state */
	int	button_y[5];
	int	button_x_res[5];			/* Start X/Y position of the current state */
	int	button_y_res[5];
	int	button_kbmodifiers[5];	/* Keyboard modifiers at press time */
	uint16_t button_hyperlink_id[5]; /* Hyperlink ID at press time */
	long long timeout[5];	/* Button event timeouts (timespecs ie: time of expiry) */
	int	curx;					/* Current X position */
	int	cury;					/* Current Y position */
	int	curx_res;					/* Current X position */
	int	cury_res;					/* Current Y position */
	int	cur_kbmodifiers;		/* Current keyboard modifiers */
	uint16_t cur_hyperlink_id;	/* Current hyperlink ID */
	int	events;					/* Currently enabled events */
	int	click_timeout;			/* Timeout between press and release events for a click (ms) */
	int	multi_timeout;			/* Timeout after a click for detection of multi clicks (ms) */
	int	click_drift;			/* Allowed "drift" during a click event */
	link_list_t	input;
	link_list_t	output;
};

struct mouse_state state;
uint64_t mouse_events=0;
pthread_once_t ciolib_mouse_initialized = PTHREAD_ONCE_INIT;
static int ungot=0;
pthread_mutex_t unget_mutex;
static atomic_uint_fast64_t input_epoch = 1;

void init_mouse(void)
{
	memset(&state,0,sizeof(state));
	state.epoch=atomic_load(&input_epoch);
	state.click_timeout=0;
	state.multi_timeout=300;
	listInit(&state.input,LINK_LIST_SEMAPHORE|LINK_LIST_MUTEX);
	listInit(&state.output,LINK_LIST_SEMAPHORE|LINK_LIST_MUTEX);
	assert_pthread_mutex_init(&unget_mutex, NULL);
}

void mousestate(int *x, int *y, uint8_t *buttons)
{
	pthread_once(&ciolib_mouse_initialized, init_mouse);
	if (x)
		*x = state.curx;
	if (y)
		*y = state.cury;
	if (buttons)
		*buttons = (state.buttonstate & 0xff);
	return;
}

void mousestate_res(int *x, int *y, uint8_t *buttons)
{
	pthread_once(&ciolib_mouse_initialized, init_mouse);
	if (x)
		*x = state.curx_res;
	if (y)
		*y = state.cury_res;
	if (buttons)
		*buttons = (state.buttonstate & 0xff);
	return;
}

uint64_t ciomouse_getevents(void)
{
	return mouse_events;
}

uint64_t ciomouse_setevents(uint64_t events)
{
	mouse_events=events;
	return mouse_events;
}

uint64_t ciomouse_addevents(uint64_t events)
{
	mouse_events |= events;
	return mouse_events;
}

uint64_t ciomouse_delevents(uint64_t events)
{
	mouse_events &= ~events;
	return mouse_events;
}

uint64_t ciomouse_addevent(uint64_t event)
{
	mouse_events |= (UINT64_C(1)<<event);
	return mouse_events;
}

uint64_t ciomouse_delevent(uint64_t event)
{
	mouse_events &= ~(UINT64_C(1)<<event);
	return mouse_events;
}

void ciomouse_gotevent(int event, int x, int y, int x_res, int y_res, int kbmodifiers)
{
	struct in_mouse_event *ime;

	pthread_once(&ciolib_mouse_initialized, init_mouse);
	ime=(struct in_mouse_event *)malloc(sizeof(struct in_mouse_event));
	if(ime) {
		ime->epoch=atomic_load(&input_epoch);
		ime->ts=MSEC_CLOCK();
		ime->event=event;
		ime->x=x;
		ime->y=y;
		ime->x_res=x_res;
		ime->y_res=y_res;
		ime->kbmodifiers=kbmodifiers;
		ime->hyperlink_id=0;
		if (x >= 1 && y >= 1) {
			struct vmem_cell cell;
			if (ciolib_vmem_gettext(x, y, x, y, &cell))
				ime->hyperlink_id = cell.hyperlink_id;
		}
		ime->nextevent=NULL;

		listPushNode(&state.input,ime);
	}
}

void ciomouse_reset_input(void)
{
	pthread_once(&ciolib_mouse_initialized, init_mouse);
	atomic_fetch_add(&input_epoch, 1);
}

static void reset_mouse_state(uint64_t epoch)
{
	state.buttonstate=0;
	state.knownbuttonstatemask=0;
	memset(state.button_state,0,sizeof(state.button_state));
	memset(state.button_x,0,sizeof(state.button_x));
	memset(state.button_y,0,sizeof(state.button_y));
	memset(state.button_x_res,0,sizeof(state.button_x_res));
	memset(state.button_y_res,0,sizeof(state.button_y_res));
	memset(state.button_kbmodifiers,0,sizeof(state.button_kbmodifiers));
	memset(state.button_hyperlink_id,0,sizeof(state.button_hyperlink_id));
	memset(state.timeout,0,sizeof(state.timeout));
	state.cur_kbmodifiers=0;
	state.cur_hyperlink_id=0;
	state.epoch=epoch;
}

void add_outevent(int event, int x, int y, int xres, int yres)
{
	struct out_mouse_event *ome;
	int	but;

	if(!(mouse_events & UINT64_C(1)<<event))
		return;
	ome=(struct out_mouse_event *)malloc(sizeof(struct out_mouse_event));

	if(ome) {
		ome->epoch=state.epoch;
		but=CIOLIB_BUTTON_NUMBER(event);
		ome->event=event;
		ome->bstate=state.buttonstate;
		ome->kbsm=state.knownbuttonstatemask;
		ome->kbmodifiers=but ? state.button_kbmodifiers[but-1] : state.cur_kbmodifiers;
		ome->hyperlink_id=but ? state.button_hyperlink_id[but-1] : state.cur_hyperlink_id;
		ome->startx=but?state.button_x[but-1]:state.curx;
		ome->starty=but?state.button_y[but-1]:state.cury;
		ome->endx=x;
		ome->endy=y;
		ome->startx_res=but ? state.button_x_res[but-1] : state.curx_res;
		ome->starty_res=but ? state.button_y_res[but-1] : state.cury_res;
		ome->endx_res=xres;
		ome->endy_res=yres;

		ome->nextevent=(struct out_mouse_event *)NULL;
		if (ciolib_swap_mouse_butt45) {
			int orig_bstate = ome->bstate;
			int orig_kbsm = ome->kbsm;
			int orig_event = ome->event;

			switch (CIOLIB_BUTTON_NUMBER(orig_event)) {
				case 4:
					ome->event += 9;
					break;
				case 5:
					ome->event -= 9;
					break;
			}
			if (orig_bstate & CIOLIB_BUTTON(4))
				ome->bstate &= ~(CIOLIB_BUTTON(4));
			if (orig_bstate & CIOLIB_BUTTON(5))
				ome->bstate &= ~(CIOLIB_BUTTON(5));
			if (orig_bstate & CIOLIB_BUTTON(4))
				ome->bstate |= CIOLIB_BUTTON(5);
			if (orig_bstate & CIOLIB_BUTTON(5))
				ome->bstate |= CIOLIB_BUTTON(4);

			if (orig_kbsm & CIOLIB_BUTTON(4))
				ome->kbsm &= ~(CIOLIB_BUTTON(4));
			if (orig_kbsm & CIOLIB_BUTTON(5))
				ome->kbsm &= ~(CIOLIB_BUTTON(5));
			if (orig_kbsm & CIOLIB_BUTTON(4))
				ome->kbsm |= CIOLIB_BUTTON(5);
			if (orig_kbsm & CIOLIB_BUTTON(5))
				ome->kbsm |= CIOLIB_BUTTON(4);
		}

		listPushNode(&state.output,ome);
	}
}

int more_multies(int button, int clicks)
{
	switch(clicks) {
		case 0:
			if(mouse_events & (UINT64_C(1)<<CIOLIB_BUTTON_CLICK(button)))
				return(1);
			/* Fall-through */
		case 1:
			if(mouse_events & (UINT64_C(1)<<CIOLIB_BUTTON_DBL_CLICK(button)))
				return(1);
			/* Fall-through */
		case 2:
			if(mouse_events & (UINT64_C(1)<<CIOLIB_BUTTON_TRPL_CLICK(button)))
				return(1);
			/* Fall-through */
		case 3:
			if(mouse_events & (UINT64_C(1)<<CIOLIB_BUTTON_QUAD_CLICK(button)))
				return(1);
			/* Fall-through */
	}
	return(0);
}

void ciolib_mouse_thread(void *data)
{
	int	timedout;
	int timeout_button=0;
	int but;
	int delay;
	long long ttime=0;

	SetThreadName("Mouse");
	pthread_once(&ciolib_mouse_initialized, init_mouse);
	while(1) {
		uint64_t epoch=atomic_load(&input_epoch);
		if(state.epoch!=epoch) {
			reset_mouse_state(epoch);
			timeout_button=0;
		}
		timedout=0;
		if(timeout_button) {
			delay=state.timeout[timeout_button-1]-MSEC_CLOCK();
			if(delay<=0) {
				timedout=1;
			}
			else {
				timedout=!listSemTryWaitBlock(&state.input,delay);
			}
		}
		else {
			listSemWait(&state.input);
		}
		if(timedout) {
			state.timeout[timeout_button-1]=0;
			switch(state.button_state[timeout_button-1]) {
				case MOUSE_SINGLEPRESSED:
					/* Press event */
					add_outevent(CIOLIB_BUTTON_PRESS(timeout_button),state.button_x[timeout_button-1],state.button_y[timeout_button-1],state.button_x_res[timeout_button-1],state.button_y_res[timeout_button-1]);
					break;
				case MOUSE_CLICKED:
					/* Click Event */
					add_outevent(CIOLIB_BUTTON_CLICK(timeout_button),state.button_x[timeout_button-1],state.button_y[timeout_button-1],state.button_x_res[timeout_button-1],state.button_y_res[timeout_button-1]);
					break;
				case MOUSE_DOUBLEPRESSED:
					/* Click event, then press event */
					add_outevent(CIOLIB_BUTTON_CLICK(timeout_button),state.button_x[timeout_button-1],state.button_y[timeout_button-1],state.button_x_res[timeout_button-1],state.button_y_res[timeout_button-1]);
					add_outevent(CIOLIB_BUTTON_PRESS(timeout_button),state.button_x[timeout_button-1],state.button_y[timeout_button-1],state.button_x_res[timeout_button-1],state.button_y_res[timeout_button-1]);
					break;
				case MOUSE_DOUBLECLICKED:
					/* Double-click event */
					add_outevent(CIOLIB_BUTTON_DBL_CLICK(timeout_button),state.button_x[timeout_button-1],state.button_y[timeout_button-1],state.button_x_res[timeout_button-1],state.button_y_res[timeout_button-1]);
					break;
				case MOUSE_TRIPLEPRESSED:
					/* Double-click event, then press event */
					add_outevent(CIOLIB_BUTTON_DBL_CLICK(timeout_button),state.button_x[timeout_button-1],state.button_y[timeout_button-1],state.button_x_res[timeout_button-1],state.button_y_res[timeout_button-1]);
					add_outevent(CIOLIB_BUTTON_PRESS(timeout_button),state.button_x[timeout_button-1],state.button_y[timeout_button-1],state.button_x_res[timeout_button-1],state.button_y_res[timeout_button-1]);
					break;
				case MOUSE_TRIPLECLICKED:
					/* Triple-click event */
					add_outevent(CIOLIB_BUTTON_TRPL_CLICK(timeout_button),state.button_x[timeout_button-1],state.button_y[timeout_button-1],state.button_x_res[timeout_button-1],state.button_y_res[timeout_button-1]);
					break;
				case MOUSE_QUADPRESSED:
					/* Triple-click evetn then press event */
					add_outevent(CIOLIB_BUTTON_TRPL_CLICK(timeout_button),state.button_x[timeout_button-1],state.button_y[timeout_button-1],state.button_x_res[timeout_button-1],state.button_y_res[timeout_button-1]);
					add_outevent(CIOLIB_BUTTON_PRESS(timeout_button),state.button_x[timeout_button-1],state.button_y[timeout_button-1],state.button_x_res[timeout_button-1],state.button_y_res[timeout_button-1]);
					break;
				case MOUSE_QUADCLICKED:
					add_outevent(CIOLIB_BUTTON_QUAD_CLICK(timeout_button),state.button_x[timeout_button-1],state.button_y[timeout_button-1],state.button_x_res[timeout_button-1],state.button_y_res[timeout_button-1]);
					/* Quad click event (This doesn't need a timeout does it? */
					break;
			}
			state.button_state[timeout_button-1]=MOUSE_NOSTATE;
		}
		else {
			struct in_mouse_event *in;

			in=listShiftNode(&state.input);
			if(in==NULL) {
				YIELD();
				continue;
			}
			if(in->epoch!=atomic_load(&input_epoch)) {
				free(in);
				continue;
			}
			if(state.epoch!=in->epoch)
				reset_mouse_state(in->epoch);
			but=CIOLIB_BUTTON_NUMBER(in->event);
			if (in->x < 0)
				in->x = state.curx;
			if (in->y < 0)
				in->y = state.cury;
			if (in->x_res < 0)
				in->x_res = state.curx_res;
			if (in->y_res < 0)
				in->y_res = state.cury_res;

			switch(CIOLIB_BUTTON_BASE(in->event)) {
				case CIOLIB_MOUSE_MOVE:
					if(in->x==state.curx && in->y==state.cury &&
					    in->x_res==state.curx_res && in->y_res==state.cury_res)
						break;
					add_outevent(CIOLIB_MOUSE_MOVE,in->x,in->y,in->x_res, in->y_res);
					for(but=1;but<=5;but++) {
						switch(state.button_state[but-1]) {
							case MOUSE_NOSTATE:
								if(state.buttonstate & CIOLIB_BUTTON(but)) {
									if(!(mouse_events & UINT64_C(1)<<CIOLIB_BUTTON_DRAG_START(but)))
										break;
									add_outevent(CIOLIB_BUTTON_DRAG_START(but),state.button_x[but-1],state.button_y[but-1],state.button_x_res[but-1],state.button_y_res[but-1]);
									add_outevent(CIOLIB_BUTTON_DRAG_MOVE(but),in->x,in->y, in->x_res, in->y_res);
									state.button_state[but-1]=MOUSE_DRAGSTARTED;
								}
								break;
							case MOUSE_SINGLEPRESSED:
								if(!(mouse_events & UINT64_C(1)<<CIOLIB_BUTTON_DRAG_START(but)))
									break;
								add_outevent(CIOLIB_BUTTON_DRAG_START(but),state.button_x[but-1],state.button_y[but-1],state.button_x_res[but-1],state.button_y_res[but-1]);
								add_outevent(CIOLIB_BUTTON_DRAG_MOVE(but),in->x,in->y, in->x_res, in->y_res);
								state.button_state[but-1]=MOUSE_DRAGSTARTED;
								break;
							case MOUSE_CLICKED:
								add_outevent(CIOLIB_BUTTON_CLICK(but),state.button_x[but-1],state.button_y[but-1],state.button_x_res[but-1],state.button_y_res[but-1]);
								state.button_state[but-1]=MOUSE_NOSTATE;
								break;
							case MOUSE_DOUBLEPRESSED:
								if(!(mouse_events & UINT64_C(1)<<CIOLIB_BUTTON_DRAG_START(but)))
									break;
								add_outevent(CIOLIB_BUTTON_CLICK(but),state.button_x[but-1],state.button_y[but-1],state.button_x_res[but-1],state.button_y_res[but-1]);
								add_outevent(CIOLIB_BUTTON_DRAG_START(but),state.button_x[but-1],state.button_y[but-1],state.button_x_res[but-1],state.button_y_res[but-1]);
								add_outevent(CIOLIB_BUTTON_DRAG_MOVE(but),in->x,in->y, in->x_res, in->y_res);
								state.button_state[but-1]=MOUSE_DRAGSTARTED;
								break;
							case MOUSE_DOUBLECLICKED:
								add_outevent(CIOLIB_BUTTON_DBL_CLICK(but),state.button_x[but-1],state.button_y[but-1],state.button_x_res[but-1],state.button_y_res[but-1]);
								state.button_state[but-1]=MOUSE_NOSTATE;
								break;
							case MOUSE_TRIPLEPRESSED:
								if(!(mouse_events & UINT64_C(1)<<CIOLIB_BUTTON_DRAG_START(but)))
									break;
								add_outevent(CIOLIB_BUTTON_DBL_CLICK(but),state.button_x[but-1],state.button_y[but-1],state.button_x_res[but-1],state.button_y_res[but-1]);
								add_outevent(CIOLIB_BUTTON_DRAG_START(but),state.button_x[but-1],state.button_y[but-1],state.button_x_res[but-1],state.button_y_res[but-1]);
								add_outevent(CIOLIB_BUTTON_DRAG_MOVE(but),in->x,in->y, in->x_res, in->y_res);
								state.button_state[but-1]=MOUSE_DRAGSTARTED;
								break;
							case MOUSE_TRIPLECLICKED:
								add_outevent(CIOLIB_BUTTON_TRPL_CLICK(but),state.button_x[but-1],state.button_y[but-1],state.button_x_res[but-1],state.button_y_res[but-1]);
								state.button_state[but-1]=MOUSE_NOSTATE;
								break;
							case MOUSE_QUADPRESSED:
								if(!(mouse_events & UINT64_C(1)<<CIOLIB_BUTTON_DRAG_START(but)))
									break;
								add_outevent(CIOLIB_BUTTON_TRPL_CLICK(but),state.button_x[but-1],state.button_y[but-1],state.button_x_res[but-1],state.button_y_res[but-1]);
								add_outevent(CIOLIB_BUTTON_DRAG_START(but),state.button_x[but-1],state.button_y[but-1],state.button_x_res[but-1],state.button_y_res[but-1]);
								add_outevent(CIOLIB_BUTTON_DRAG_MOVE(but),in->x,in->y, in->x_res, in->y_res);
								state.button_state[but-1]=MOUSE_DRAGSTARTED;
								break;
							case MOUSE_DRAGSTARTED:
								add_outevent(CIOLIB_BUTTON_DRAG_MOVE(but),in->x,in->y, in->x_res, in->y_res);
								break;
						}
					}
					break;
				case CIOLIB_BUTTON_1_PRESS:
					state.buttonstate|=1<<(but-1);
					state.knownbuttonstatemask|=1<<(but-1);
					switch(state.button_state[but-1]) {
						case MOUSE_NOSTATE:
							state.button_state[but-1]=MOUSE_SINGLEPRESSED;
							state.button_x[but-1]=in->x;
							state.button_y[but-1]=in->y;
							state.button_x_res[but-1]=in->x_res;
							state.button_y_res[but-1]=in->y_res;
							state.button_kbmodifiers[but-1]=in->kbmodifiers;
							state.button_hyperlink_id[but-1]=in->hyperlink_id;
							state.timeout[but-1]=MSEC_CLOCK()+state.click_timeout;
							if(state.timeout[but-1]==0)
								state.timeout[but-1]=1;
							if(state.click_timeout==0)
								state.timeout[but-1]=0;
							if(!more_multies(but,0)) {
								add_outevent(CIOLIB_BUTTON_PRESS(but),state.button_x[but-1],state.button_y[but-1],state.button_x_res[but-1],state.button_y_res[but-1]);
								state.button_state[but-1]=MOUSE_NOSTATE;
								state.timeout[but-1]=0;
							}
							if (but > 3) {
								/* Wheel buttons are momentary; no release follows. */
								state.buttonstate &= ~CIOLIB_BUTTON(but);
								state.button_state[but-1] = MOUSE_NOSTATE;
							}
							break;
						case MOUSE_CLICKED:
							state.button_state[but-1]=MOUSE_DOUBLEPRESSED;
							state.timeout[but-1]=MSEC_CLOCK()+state.click_timeout;
							if(state.timeout[but-1]==0)
								state.timeout[but-1]=1;
							if(state.click_timeout==0)
								state.timeout[but-1]=0;
							break;
						case MOUSE_DOUBLECLICKED:
							state.button_state[but-1]=MOUSE_TRIPLEPRESSED;
							state.timeout[but-1]=MSEC_CLOCK()+state.click_timeout;
							if(state.timeout[but-1]==0)
								state.timeout[but-1]=1;
							if(state.click_timeout==0)
								state.timeout[but-1]=0;
							break;
						case MOUSE_TRIPLECLICKED:
							state.button_state[but-1]=MOUSE_QUADPRESSED;
							state.timeout[but-1]=MSEC_CLOCK()+state.click_timeout;
							if(state.timeout[but-1]==0)
								state.timeout[but-1]=1;
							if(state.click_timeout==0)
								state.timeout[but-1]=0;
							break;
					}
					break;
				case CIOLIB_BUTTON_1_RELEASE:
					state.buttonstate&= ~(1<<(but-1));
					state.knownbuttonstatemask|=1<<(but-1);
					switch(state.button_state[but-1]) {
						case MOUSE_NOSTATE:
							state.button_x[but-1]=in->x;
							state.button_y[but-1]=in->y;
							state.button_x_res[but-1]=in->x_res;
							state.button_y_res[but-1]=in->y_res;
							add_outevent(CIOLIB_BUTTON_RELEASE(but),state.button_x[but-1],state.button_y[but-1],state.button_x_res[but-1],state.button_y_res[but-1]);
							break;
						case MOUSE_SINGLEPRESSED:
							state.button_state[but-1]=MOUSE_CLICKED;
							state.timeout[but-1]=more_multies(but,1)?MSEC_CLOCK()+state.multi_timeout:MSEC_CLOCK();
							if(state.timeout[but-1]==0)
								state.timeout[but-1]=1;
							break;
						case MOUSE_DOUBLEPRESSED:
							state.button_state[but-1]=MOUSE_DOUBLECLICKED;
							state.timeout[but-1]=more_multies(but,2)?MSEC_CLOCK()+state.multi_timeout:MSEC_CLOCK();
							if(state.timeout[but-1]==0)
								state.timeout[but-1]=1;
							break;
						case MOUSE_TRIPLEPRESSED:
							state.button_state[but-1]=MOUSE_TRIPLECLICKED;
							state.timeout[but-1]=more_multies(but,3)?MSEC_CLOCK()+state.multi_timeout:MSEC_CLOCK();
							if(state.timeout[but-1]==0)
								state.timeout[but-1]=1;
							break;
						case MOUSE_QUADPRESSED:
							state.button_state[but-1]=MOUSE_NOSTATE;
							add_outevent(CIOLIB_BUTTON_QUAD_CLICK(but),state.button_x[but-1],state.button_y[but-1],state.button_x_res[but-1],state.button_y_res[but-1]);
							state.timeout[but-1]=1;
							break;
						case MOUSE_DRAGSTARTED:
							add_outevent(CIOLIB_BUTTON_DRAG_END(but),in->x,in->y, in->x_res, in->y_res);
							state.button_state[but-1]=0;
					}
			}
			state.curx=in->x;
			state.cury=in->y;
			state.curx_res=in->x_res;
			state.cury_res=in->y_res;
			state.cur_kbmodifiers=in->kbmodifiers;
			state.cur_hyperlink_id=in->hyperlink_id;

			free(in);
		}

		timeout_button=0;
		for(but=1;but<=5;but++) {
			if(state.button_state[but-1]==MOUSE_DRAGSTARTED &&
			    (mouse_events & ((UINT64_C(1)<<CIOLIB_BUTTON_DRAG_START(but)) | (UINT64_C(1)<<CIOLIB_BUTTON_DRAG_MOVE(but)) | (UINT64_C(1)<<CIOLIB_BUTTON_DRAG_END(but)))) == 0)
				state.button_state[but-1] = MOUSE_NOSTATE;
		}

		for(but=1;but<=5;but++) {
			if(state.button_state[but-1]!=MOUSE_NOSTATE
					&& state.button_state[but-1]!=MOUSE_DRAGSTARTED
					&& state.timeout[but-1]!=0
					&& (timeout_button==0 || state.timeout[but-1]<ttime)) {
				ttime=state.timeout[but-1];
				timeout_button=but;
			}
		}
	}
}

static bool mouse_output_current(void)
{
	struct out_mouse_event *out;
	bool current;

	listLock(&state.output);
	if(state.output.first==NULL) {
		listUnlock(&state.output);
		return false;
	}
	out=state.output.first->data;
	current=out!=NULL && out->epoch==atomic_load(&input_epoch);
	if(!current)
		out=listShiftNode(&state.output);
	listUnlock(&state.output);
	if(!current)
		free(out);
	return current;
}

int mouse_trywait(void)
{
	int	result;

	pthread_once(&ciolib_mouse_initialized, init_mouse);
	while(1) {
		result=listSemTryWait(&state.output);
		if(!result)
			return result;
		assert_pthread_mutex_lock(&unget_mutex);
		if(ungot==0) {
			assert_pthread_mutex_unlock(&unget_mutex);
			if(mouse_output_current())
				return(result);
			continue;
		}
		ungot--;
		assert_pthread_mutex_unlock(&unget_mutex);
	}
}

int mouse_wait(void)
{
	int result;

	pthread_once(&ciolib_mouse_initialized, init_mouse);
	while(1) {
		result=listSemWait(&state.output);
		if(!result)
			return result;
		assert_pthread_mutex_lock(&unget_mutex);
		if(ungot==0) {
			assert_pthread_mutex_unlock(&unget_mutex);
			if(mouse_output_current())
				return(result);
			continue;
		}
		ungot--;
		assert_pthread_mutex_unlock(&unget_mutex);
	}
}

int mouse_pending(void)
{
	int count=0;
	uint64_t epoch;
	list_node_t *node;

	pthread_once(&ciolib_mouse_initialized, init_mouse);
	epoch=atomic_load(&input_epoch);
	listLock(&state.output);
	for(node=state.output.first;node!=NULL;node=node->next) {
		struct out_mouse_event *out=node->data;
		if(out!=NULL && out->epoch==epoch)
			count++;
	}
	listUnlock(&state.output);
	return(count);
}

int ciolib_getmouse(struct mouse_event *mevent)
{
	int retval=0;

	pthread_once(&ciolib_mouse_initialized, init_mouse);
	while(listCountNodes(&state.output)) {
		struct out_mouse_event *out;
		out=listShiftNode(&state.output);
		if(out==NULL)
			return(-1);
		if(out->epoch!=atomic_load(&input_epoch)) {
			free(out);
			continue;
		}
		if(mevent != NULL) {
			mevent->event=out->event;
			mevent->bstate=out->bstate;
			mevent->kbsm=out->kbsm;
			mevent->kbmodifiers=out->kbmodifiers;
			mevent->hyperlink_id=out->hyperlink_id;
			mevent->startx=out->startx;
			mevent->starty=out->starty;
			mevent->endx=out->endx;
			mevent->endy=out->endy;
			mevent->startx_res=out->startx_res;
			mevent->starty_res=out->starty_res;
			mevent->endx_res=out->endx_res;
			mevent->endy_res=out->endy_res;
		}
		free(out);
		return(retval);
	}
	fprintf(stderr,"WARNING: attempt to get a mouse key when none pending!\n");
	if(mevent != NULL)
		memset(mevent,0,sizeof(struct mouse_event));
	retval=-1;
	return(retval);
}

int ciolib_ungetmouse(struct mouse_event *mevent)
{
	struct out_mouse_event *me;

	pthread_once(&ciolib_mouse_initialized, init_mouse);
	if((me=malloc(sizeof(*me)))==NULL)
		return(-1);
	me->epoch=atomic_load(&input_epoch);
	me->event=mevent->event;
	me->bstate=mevent->bstate;
	me->kbsm=mevent->kbsm;
	me->kbmodifiers=mevent->kbmodifiers;
	me->hyperlink_id=mevent->hyperlink_id;
	me->startx=mevent->startx;
	me->starty=mevent->starty;
	me->endx=mevent->endx;
	me->endy=mevent->endy;
	me->startx_res=mevent->startx_res;
	me->starty_res=mevent->starty_res;
	me->endx_res=mevent->endx_res;
	me->endy_res=mevent->endy_res;
	me->nextevent=NULL;
	assert_pthread_mutex_lock(&unget_mutex);
	if(listInsertNode(&state.output,me)==NULL) {
		assert_pthread_mutex_unlock(&unget_mutex);
		return(FALSE);
	}
	ungot++;
	assert_pthread_mutex_unlock(&unget_mutex);
	return(TRUE);
}
