/*	
 *	clipmou.h
 *
 *	シリアルマウスドライバ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2004 Naoyuki Sawa
 *
 *	* Mon May 31 05:44:00 JST 2004 Naoyuki Sawa
 *	- 作成開始。
 */
#include "clip.h"

/****************************************************************************
 *	
 ****************************************************************************/

typedef struct _MOUSEDRIVER {
	int ready;		/* マウスを認識したら1 */
	unsigned char buf[3];	/* 受信バッファ */
	int len;		/* 受信バッファ内の有効バイト数 */
	//
	int dx;			/* 前回のmouse_get()以降の、水平移動量 */
	int dy;			/* 前回のmouse_get()以降の、垂直移動量 */
	int button;		/* 静的なボタン状態 (MOUSE_BUTTON(*)) */
	int trigger;		/* 前回のmouse_get()以降に発生したトリガ (MOUSE_PRESS(*)、MOUSE_RELEASE(*)) */
} MOUSEDRIVER;
static MOUSEDRIVER mouse;

static void mouse_recv_callback();

/****************************************************************************
 *	
 ****************************************************************************/

int
mouse_open()
{
	int T0;

	/* まず確実に閉じます。 */
	mouse_close();

	/* ドライバ構造体をクリアします。 */
	memset(&mouse, 0, sizeof mouse);

	/* シリアルポートを開きます。 */
	com_open(	/* マイクロソフトプロトコル仕様... */
		1200,	/* 1200 bits/sec   */
		0,	/* パリティ無し    */
		7,	/* 7データビット   */
		1,	/* 1ストップビット */
		0, 0, mouse_recv_callback, NULL);

	/* いったんマウスの電源を落とします。(マウスはRTS信号線から電源を取っています) */
	com_rts(0);

	/* 確実に電源が落ちるよう、0.1秒程度待ちます。(調整可) */
	T0 = pceTimerGetCount();
	while(pceTimerGetCount() - T0 < 100) {
		/** no job **/
	}

	/* 念のため、受信バッファをクリアします。 */
	com_reset();

	/* マウスの電源を入れます。 */
	com_rts(1);

	/* 14ミリ秒以内に'M'が送られて来るはずです。
	 * 'M'を受信したらmouse_recv_callback()がreadyを1にするので、それまで待ちます。
	 * マウスの電源ONに時間がかかるかも知れないので、ちょっと長めに0.5秒まで待つことにします。
	 */
	T0 = pceTimerGetCount();
	while(pceTimerGetCount() - T0 < 500) {
		if(mouse.ready) break;
	}

	/* マウスが認識できなければ、クリーンアップして、失敗を返します。 */
	if(!mouse.ready) {
		mouse_close();
		return -1; /* 失敗 */
	}

	return 0; /* 成功 */
}

void
mouse_close()
{
	/* 確実にシリアルポートを閉じます。 */
	com_close();

	/* 確実にドライバ構造体をクリアします。 */
	memset(&mouse, 0, sizeof mouse);
}

void
mouse_recv_callback()
{
	int retval;
	unsigned char v;
	int dx;
	int dy;
	int lb;
	int rb;

	for(;;) {
		/* 受信バッファから1バイト取り出します。 */
		retval = com_recv(&v, 1);

		/* 受信データがなければ、何もせずに抜けます。 */
		if(!retval) {
			break;
		}

		/* 受信エラーならば、リセットして抜けます。次回以降は通常処理を続けます。 */
		if(retval < 0) {
			com_reset();
			break;
		}

		/* これ以降は、いま取り出した1バイトデータの処理です。 */

		/*** マウス認識まだ ***/
		if(!mouse.ready) {
			/* 電源投入後の'M'を受信したら、マウス認識OKです。 */
			if((char)v == 'M') {
				mouse.ready = 1;
			}

		/*** マウス認識済み ***/
		} else {
			/* マイクロソフトプロトコルのパケット形式は次のとおりです。
			 *
			 *	ビット番号: [6] [5] [4] [3] [2] [1] [0]
			 *	１バイト目:  1   lb  rb dy7 dy6 dx7 dx6
			 *	２バイト目:  0  dx5 dx4 dx3 dx2 dx1 dx0
			 *	３バイト目:  0  dy5 dy4 dy3 dy2 dy1 dy0
			 *
 			 *	lb: 左ボタン
 			 *	rb: 右ボタン
 			 *	dx: 水平移動量
 			 *	dy: 垂直移動量
 			 */
			if(v & 0x40) {
				/* bit6=1のデータを受信したら、強制的に1バイト目に格納します。
				 * 既に何バイトが受信していた場合、それらはゴミとして破棄します。
				 */
				mouse.buf[0] = v;
				mouse.len = 1;
			} else {
				/* bit6=0のデータを受信したら、2or3バイト目として格納します。
				 * まだ1バイト目を受信していなければ、このデータはゴミとして破棄します。
				 */
				if(mouse.len) {
					mouse.buf[mouse.len++] = v;
					if(mouse.len == 3) { /* パケット完成 */
						dx = (char)((mouse.buf[0] & 0x03) << 6 | (mouse.buf[1] & 0x3f));
						dy = (char)((mouse.buf[0] & 0x0c) << 4 | (mouse.buf[2] & 0x3f));
						lb = mouse.buf[0] & 0x20;
						rb = mouse.buf[0] & 0x10;

						if(!dx && !dy &&
						   ((lb && (mouse.button & MOUSE_BUTTON(0))) || (!lb && !(mouse.button & MOUSE_BUTTON(0)))) &&
						   ((rb && (mouse.button & MOUSE_BUTTON(1))) || (!rb && !(mouse.button & MOUSE_BUTTON(1))))) {
							/* 移動量ゼロでボタン状態も変化無しなら、中ボタンのトグル通知です。
							 * ※マウスを移動しながら中ボタンを押下/開放すると、移動量アリになってしまうようです。
							 *   すなわち、中ボタンのトグル通知として判別できるメッセージが失われてしまいます。
							 *   回避方法不明です・・・(Sysgration 「MOUSE 700」 FCC ID: HQXPC93010-12 にて確認)
							 */
							if(mouse.button & MOUSE_BUTTON(2)) {
								mouse.button &= ~MOUSE_BUTTON(2);
								mouse.trigger |= MOUSE_RELEASE(2);
							} else {
								mouse.button |= MOUSE_BUTTON(2);
								mouse.trigger |= MOUSE_PRESS(2);
							}
						} else {
							/* さもなくば、通常のマウス移動または左右ボタン押下・開放通知です。 */
							mouse.dx += dx;
							mouse.dy += dy;
							if(lb) {
								if(!(mouse.button & MOUSE_BUTTON(0))) {
									mouse.button |= MOUSE_BUTTON(0);
									mouse.trigger |= MOUSE_PRESS(0);
								}
							} else {
								if(mouse.button & MOUSE_BUTTON(0)) {
									mouse.button &= ~MOUSE_BUTTON(0);
									mouse.trigger |= MOUSE_RELEASE(0);
								}
							}
							if(rb) {
								if(!(mouse.button & MOUSE_BUTTON(1))) {
									mouse.button |= MOUSE_BUTTON(1);
									mouse.trigger |= MOUSE_PRESS(1);
								}
							} else {
								if(mouse.button & MOUSE_BUTTON(1)) {
									mouse.button &= ~MOUSE_BUTTON(1);
									mouse.trigger |= MOUSE_RELEASE(1);
								}
							}
						}

						mouse.len = 0; /* これ忘れないで!! */
					}
				}
			}
		}
	}
}

void
mouse_get(int* dx, int* dy, int* button)
{
ENTER_CS;
	if(dx) *dx = mouse.dx;
	if(dy) *dy = mouse.dy;
	if(button) *button = mouse.button | mouse.trigger;

	mouse.dx = 0;
	mouse.dy = 0;
	//mouse.buttonはクリアしません!!
	mouse.trigger = 0;
LEAVE_CS;
}
