File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / x68kkbd.cpp
Revision 1.1.1.20 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:00 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

//
// X68k キーボード
//

// 送信タイミングについて
//	キーボードは MFP の USART に繋がっている。
//	MFP USART の RR 線 (Receiver Ready) は受信可能なら 'H'、受信中は 'L' に
//	なっている。キーボードは RR が 'H' の時だけデータを送信する。また、
//	システムポート#4 の KEY CTRL = 0 (キーデータ送信不可) はこの RR を
//	'L' に落とす AND 回路になっている。キーボードからの送信は 2400bps、
//	ストップビット1、パリティなしなので、1バイトの送信に 3.75 msec かかる。
//	実装では、キーボードは MFP の RR が 'H' なら MFP にキーデータを送信。
//	MFP 側で RR を 'L' にして 3.75 msec をイベントで待った後、キーを処理
//	して RR を 'H' に戻すことにする。

// SHIFT キーについては keyboard.cpp のコメントを参照。

#include "x68kkbd.h"
#include "event.h"
#include "mfp.h"
#include "monitor.h"
#include "scc.h"
#include "scheduler.h"
#include "sysport.h"
#include "uimessage.h"

// コンストラクタ
X68030Keyboard::X68030Keyboard()
{
	led.resize(7);

	monitor = gMonitorManager->Regist(ID_MONITOR_KEYBOARD, this);
	monitor->SetCallback(&X68030Keyboard::MonitorScreen);
	monitor->SetSize(47, 5);
}

// デストラクタ
X68030Keyboard::~X68030Keyboard()
{
}

// 初期化
bool
X68030Keyboard::Init()
{
	if (inherited::Init() == false) {
		return false;
	}

	mfp = GetMFPDevice();
	scc = GetSCCDevice();
	sysport = GetSysportDevice();

	auto evman = GetEventManager();
	rept_event = evman->Regist(this,
		ToEventCallback(&X68030Keyboard::ReptCallback),
		"Keyboard Repeat");

	mouse_event = evman->Regist(this,
		ToEventCallback(&X68030Keyboard::MouseCallback),
		"Mouse");

	return true;
}

// キーボード接続時の MD 固有処理
void
X68030Keyboard::MDConnect()
{
	rept_delay_data = 3;
	rept_delay = 500_msec;
	rept_time_data = 4;
	rept_time  = 110_msec;
	rept_key = KC_none;
	scheduler->StopEvent(rept_event);

	led_darkness = 0;

	// XXX マウスの接続取り外しは未定
	scheduler->StopEvent(mouse_event);
	mouse_event->code = 0;
}

// キーボード取り外し時の MD 固有処理
void
X68030Keyboard::MDDisconnect()
{
	scheduler->StopEvent(rept_event);
	scheduler->StopEvent(mouse_event);
	mouse_event->code = 0;
}

// モニター
void
X68030Keyboard::MonitorScreen(Monitor *, TextScreen& screen)
{
	int y;

	screen.Clear();
	y = 0;

	// 0         1         2         3         4
	// 01234567890123456789012345678901234567890123456
	// Key Enable:     %             MSCTRL:         %
	// Ctrl Enable:                  OPT.2 Enable:   %
	// Key Repeat: Delay  $x(????msec)
	//             Repeat $x(????msec)
	// LED: $xx    Zen  Hira INS  CAPS Code Roma Kana
	// LED Darkness: x

	// KEY_EN, CTRL_EN, OPT2_EN, MSCTRL は未実装

	// キーリピート
	screen.Puts(0, y, "Key Repeat:");
	screen.Print(12, y++, "Delay  $%x(%4umsec)",
		rept_delay_data, (uint)tsec_to_msec(rept_delay));
	screen.Print(12, y++, "Repeat $%x(%4umsec)",
		rept_time_data, (uint)tsec_to_msec(rept_time));

	// マウス
	int mx = mouse_x;
	int my = mouse_y;
	if (mx < -999)
		mx = -999;
	if (mx > 999)
		mx = 999;
	if (my < -999)
		my = -999;
	if (my > 999)
		my = 999;

	screen.Puts(0, y, "Mouse:");
	screen.Print(12, y, "x=%-4d y=%-4d", mx, my);
	screen.Print(27, y, TA::OnOff(mouse_l), "L");
	screen.Print(29, y, TA::OnOff(mouse_r), "R");
	y++;

	// LED
	screen.Puts(0, y, "LED:");
	static const char * const ledname[] = {
		"Zen",  "Hira", "INS",  "CAPS", "Code", "Roma", "Kana"
	};
	uint val = 0x80;
	for (int i = 0; i < 7; i++) {
		int n = 6 - i;
		screen.Puts(12 + i * 5, y, TA::OnOff(led[n]), ledname[i]);
		if (led[n] == 0) {
			val |= 1U << n;
		}
	}
	screen.Print(5, y, "$%02x", val);
	y++;

	// LED の明るさ (というより暗さ)
	screen.Print(0, y++, "LED Darkness: %u", led_darkness);
}

// 共通 keystat を X680x0 キーコードに変換して返す。
// 対応する X680x0 のキーがなければ 0 を返す。
uint
X68030Keyboard::KeyToMDKey(uint keystat) const
{
	uint keycode = KC_CODE(keystat);
	if (keycode >= countof(keycode2x68kkey_table)) {
		return 0;
	}

	uint mdkey = keycode2x68kkey_table[keycode];
	if (mdkey && KC_IS_BREAK(keystat)) {
		mdkey |= 0x80;
	}
	return mdkey;
}

// シフト状態なら true を返す
bool
X68030Keyboard::IsShift() const
{
	return pressed[KC_SHIFT];
}

// キー入力を1つ処理する
void
X68030Keyboard::KeyInput(uint keystat)
{
	uint keycode = KC_CODE(keystat);
	bool ismake = KC_IS_MAKE(keystat);

	assert(keycode < KC_max);

	// X68k のキーボードは左右の [SHIFT] の区別がない。
	// KC_SHIFT_L はホストの物理キー左 [SHIFT] に対応、
	// KC_SHIFT_R はホストの物理キー右 [SHIFT] に対応、
	// KC_SHIFT はソフトウェアキーボードからの左右のない [SHIFT] に対応。
	// どのキーコードで問い合わせをされても KC_SHIFT_L と KC_SHIFT_R の OR が
	// [SHIFT] キーの状態である。
	if (keycode == KC_SHIFT_L || keycode == KC_SHIFT_R) {
		// それぞれのシフトキーが押されたことは覚えておく。
		SetPressed(keycode, ismake);
		// 反対側のキーが押されていたら、いま押した・離したほうは無視する
		if (IsPressed(keycode ^ 1)) {
			return;
		}
		keycode = KC_SHIFT;

		// keycode を変更したので keystat も更新
		keystat = (keystat & 0xff00) | keycode;
	}

	// 共通処理へ
	if (KeyInputCommon(keystat) == false) {
		return;
	}

	// LED は MPU 側から点灯/消灯指示が来るのでここでは何もしなくてよい

	// ここでキーリピートのイベント開始処理
	if (ismake) {
		// いま起きているリピートは中止
		rept_key = keycode;
		rept_event->time = rept_delay;
		scheduler->RestartEvent(rept_event);
	} else {
		if (keycode == rept_key) {
			// 同じキーのブレークでのみリピートを終了する
			scheduler->StopEvent(rept_event);
			rept_key = KC_none;
		}
	}
}

void
X68030Keyboard::ReptCallback(Event *ev)
{
	// リピート
	sendqueue.Enqueue(KeyToMDKey(rept_key));
	SendStart();

	// 二回目以降のリピートは rept_time 間隔で発生
	rept_event->time = rept_time;
	scheduler->StartEvent(rept_event);
}

// MFP、システムポートと、キーボードの関係。
//
// キーボードの Ready 信号は MFP の RR とシステムポートの KeyCtrl の
// AND で作られているが、この AND 部分はキーボードが担当することにする。
// そのほうが都合がいいため。
//
//                 <- 本体 || キーボード ->
//   MFP                   ||
// --------+     :         ||  +-----
//         |     :  |&     ||  |
//       RR|o-------|&     ||  |
// --------+     :  |&---------|T0 (Ready)
//            +-----|&     ||  |
//  Sysport   |  :  |&     ||  |
// --------+  |  :         ||  |
//  KeyCtrl|--+  :         ||  +-----
//         |     :         ||
//               :エミュレータでは点線から右をキーボードが担当

// キーデータを送信
void
X68030Keyboard::SendStart()
{
	if (mfp->RxIsReady()) {
		// 本体が Ready ならここで送信
		Ready();
	} else {
		// そうでない場合、ここでは処理は不要。
		// MFP 側が受信可能になると Ready() がコールされ、そっちで処理される
	}
}

// MFP からの Ready 通知
// 実際にはキーボード側が MFP (を含む本体側) が Ready になるのを待っている
// のだが、待っているを作るのは大変なので。
void
X68030Keyboard::Ready()
{
	if (sysport->GetKeyCtrl()) {
		// KEYCTRL が有効なら処理する

		// ここで取り出せるのは MDKey。
		uint x68kkey;
		if (sendqueue.Dequeue(&x68kkey)) {
			// あれば送信

			// 呼ばれたからには送信できるはず
			assert(mfp->RxIsReady() == true);

			assert(x68kkey != 0);

			// 送信
			mfp->Rx(x68kkey);
		} else {
			// なければ何もせず終了
		}
	} else {
		// KEYCTRL が無効なら何もしないだけでなく、キューをクリア。
		// KEYCTRL が無効から有効になった時に、無効だった期間のキーデータを
		// 吐き出すのはおかしいので。
		sendqueue.Clear();
	}
}

// マウス入力
void
X68030Keyboard::MouseInput(int x, int y, bool rb, bool lb, bool mb)
{
	// ホストからマウスイベントのたびに呼ばれるので、ここで積算。
	// 20msec ごとのサンプリングのタイミングで積算結果を処理する。
	// X68k のマウスの Y 方向は下に動く方向がプラス。
	mouse_x += x;
	mouse_y += y;
	mouse_r = rb;
	mouse_l = lb;
}

// マウスデータを VM に送信
void
X68030Keyboard::MouseSendStart()
{
	uint8 status = 0;

	// 最後のバイトを送るまでは、次は受け付けられない。
	// 送信中かどうかは mouse_event->code で判断している。
	if (mouse_event->code != 0) {
		return;
	}

	if (mouse_x > 127) {
		status |= XOVFL;
		mouse_x = 127;
	}
	if (mouse_x < -128) {
		status |= XUNFL;
		mouse_x = -128;
	}
	if (mouse_y > 127) {
		status |= YOVFL;
		mouse_y = 127;
	}
	if (mouse_y < -128) {
		status |= YUNFL;
		mouse_y = -128;
	}
	status |= mouse_l ? SW_L : 0;
	status |= mouse_r ? SW_R : 0;

	// この時点でラッチしてみる(?)
	mouse_data[0] = status;
	mouse_data[1] = mouse_x;
	mouse_data[2] = mouse_y;

	mouse_x = 0;
	mouse_y = 0;

	// 1バイト目は MSCTRL が立ち下がってから 700us 以上後から送信。
	// さらに1バイトの送信に 2.3msec かかる(このすぐ下参照)。
	mouse_event->time = 700_usec + 2300_usec;
	mouse_event->code = 1;
	scheduler->RestartEvent(mouse_event);
}

// マウスデータのイベントコールバック
void
X68030Keyboard::MouseCallback(Event *ev)
{
	// マウスは 4800bps で、ストップビット 2 なので11ビット。
	// 1/4800 * 11 = 2.29166 msec なので、1バイトあたり 2.3 msec にしておく。

	// code 1 なら1バイト目 (mouse_data[0]) の送信完了時刻が来た。
	// code 2 なら2バイト目 (mouse_data[1]) の送信完了時刻が来た。
	// code 3 なら3バイト目 (mouse_data[2]) の送信完了時刻が来た。
	// 3バイト目を送信してイベントループ終了。

	scc->Rx(1, mouse_data[mouse_event->code - 1]);

	if (mouse_event->code < 3) {
		// 次のイベント
		mouse_event->time = 2300_usec;
		mouse_event->code++;
		scheduler->StartEvent(mouse_event);
	} else {
		// 3 バイト目の送信が終わった。
		mouse_event->code = 0;
	}
}

// ホストからの制御
void
X68030Keyboard::Command(uint32 data)
{
	if (data < 0x40) {
		// $00-$3f TV 制御
		// (実装不要)
		putlog(2, "Command TV control $%02x", (data & 0x1f));

	} else if (data < 0x48) {
		// $40-$47 MSCTRL 信号制御
		// こいつは頻度が高いのでログレベルを上げておく
		putlog(3, "Command MSCTRL control %u", (data & 1));

	} else if (data < 0x50) {
		// $48-$4f キーデータ送出許可
		putlog(1, "Command Key send enable %u (NOT IMPLEMENTED)", (data & 1));

	} else if (data < 0x54) {
		// $50-$53 ディスプレイ制御モード選択
		// (実装不要)
		putlog(2, "Command Display control mode %u", (data & 1));

	} else if (data < 0x58) {
		// $54-$57 LED 明るさ選択
		// 0 が明るく 3 が暗いので、明るさというより暗さ。
		led_darkness = (data & 3);
		putlog(1, "Command LED brightness %u", led_darkness);
		uimessage->Post(UIMessage::KEYBOARD);

	} else if (data < 0x5c) {
		// $58-$5b 本体からのディスプレイ制御許可
		// (実装不要)
		putlog(2, "Command Display control enable %u", (data & 1));

	} else if (data < 0x60) {
		// $5c-$5f OPT.2 によるディスプレイ制御許可
		// (実装不要)
		putlog(2, "Command OPT2 control enable %u", (data & 1));

	} else if (data < 0x70) {
		// $60-$6f リピートまでの時間
		rept_delay_data = data & 15;
		rept_delay = 200_msec + rept_delay_data * 100_msec;
		putlog(1, "Command Repeat Delay %u (%u msec)", rept_delay_data,
			(uint)tsec_to_msec(rept_delay));

	} else if (data < 0x80) {
		// $70-$7f リピート間隔
		rept_time_data = data & 15;
		rept_time = 30_msec + rept_time_data * rept_time_data * 5_msec;
		putlog(1, "Command Repeat time  %d (%u msec)", rept_time_data,
			(uint)tsec_to_msec(rept_time));

	} else {
		// $80-$ff LED 制御
		data &= 0x7f;
		putlog(1, "Command LED control $%02x", data);
		uint32 b = 0x01;
		for (int i = 0; i < led.size(); i++) {
			// data は %0 で点灯、led[] は true で点灯
			led[i] = ((data & b) == 0);
			b <<= 1;
		}
		uimessage->Post(UIMessage::KEYBOARD);
	}
}

// キーコード変換表
// 共通キーコードから X680x0 キーコードに変換する。
/*static*/ const uint
X68030Keyboard::keycode2x68kkey_table[KC_max] = {
	NoKey,					// [00]
	X68kKey_BREAK,			// [01]
	NoKey,					// [02]
	X68kKey_romaji,			// [03]
	X68kKey_code,			// [04]
	X68kKey_kigou,			// [05]
	X68kKey_touroku,		// [06]
	X68kKey_HELP,			// [07]
	X68kKey_SHIFT,			// [08]
	X68kKey_TAB,			// [09]
	X68kKey_CTRL,			// [0a]
	X68kKey_kana,			// [0b]
	X68kKey_SHIFT,			// [0c]
	X68kKey_SHIFT,			// [0d]
	X68kKey_CAPS,			// [0e]
	NoKey,					// [0f]

	X68kKey_ESC,			// [10]
	X68kKey_BS,				// [11]
	X68kKey_enter,			// [12]
	NoKey,					// [13]
	X68kKey_space,			// [14]
	X68kKey_DEL,			// [15]
	NoKey,					// [16]
	NoKey,					// [17]
	NoKey,					// [18]
	NoKey,					// [19]
	NoKey,					// [1a]
	NoKey,					// [1b]
	X68kKey_up,				// [1c]
	X68kKey_left,			// [1d]
	X68kKey_right,			// [1e]
	X68kKey_down,			// [1f]

	X68kKey_INS,			// [20]
	X68kKey_COPY,			// [21]
	X68kKey_1,				// [22]
	X68kKey_2,				// [23]
	X68kKey_3,				// [24]
	X68kKey_4,				// [25]
	X68kKey_5,				// [26]
	X68kKey_6,				// [27]
	X68kKey_7,				// [28]
	X68kKey_8,				// [29]
	X68kKey_9,				// [2a]
	X68kKey_0,				// [2b]
	X68kKey_minus,			// [2c]
	X68kKey_circum,			// [2d]
	X68kKey_backslash,		// [2e]
	NoKey,					// [2f]

	NoKey,					// [30]
	NoKey,					// [31]
	X68kKey_Q,				// [32]
	X68kKey_W,				// [33]
	X68kKey_E,				// [34]
	X68kKey_R,				// [35]
	X68kKey_T,				// [36]
	X68kKey_Y,				// [37]
	X68kKey_U,				// [38]
	X68kKey_I,				// [39]
	X68kKey_O,				// [3a]
	X68kKey_P,				// [3b]
	X68kKey_at,				// [3c]
	X68kKey_bracketleft,	// [3d]
	X68kKey_hira,			// [3e]
	X68kKey_zenkaku,		// [3f]

	NoKey,					// [40]
	X68kKey_UNDO,			// [41]
	X68kKey_A,				// [42]
	X68kKey_S,				// [43]
	X68kKey_D,				// [44]
	X68kKey_F,				// [45]
	X68kKey_G,				// [46]
	X68kKey_H,				// [47]
	X68kKey_J,				// [48]
	X68kKey_K,				// [49]
	X68kKey_L,				// [4a]
	X68kKey_semicolon,		// [4b]
	X68kKey_colon,			// [4c]
	X68kKey_bracketright,	// [4d]
	X68kKey_OPT1,			// [4e]
	X68kKey_OPT2,			// [4f]

	X68kKey_XF1,			// [50]
	X68kKey_XF2,			// [51]
	X68kKey_Z,				// [52]
	X68kKey_X,				// [53]
	X68kKey_C,				// [54]
	X68kKey_V,				// [55]
	X68kKey_B,				// [56]
	X68kKey_N,				// [57]
	X68kKey_M,				// [58]
	X68kKey_comma,			// [59]
	X68kKey_period,			// [5a]
	X68kKey_slash,			// [5b]
	X68kKey_underscore,		// [5c]
	X68kKey_XF3,			// [5d]
	X68kKey_XF4,			// [5e]
	X68kKey_XF5,			// [5f]

	X68kKey_HOME,			// [60]
	X68kKey_PAD_plus,		// [61]
	X68kKey_PAD_minus,		// [62]
	X68kKey_PAD_7,			// [63]
	X68kKey_PAD_8,			// [64]
	X68kKey_PAD_9,			// [65]
	X68kKey_PAD_4,			// [66]
	X68kKey_PAD_5,			// [67]
	X68kKey_PAD_6,			// [68]
	X68kKey_PAD_1,			// [69]
	X68kKey_PAD_2,			// [6a]
	X68kKey_PAD_3,			// [6b]
	X68kKey_PAD_0,			// [6c]
	X68kKey_PAD_decimal,	// [6d]
	X68kKey_PAD_enter,		// [6e]
	X68kKey_PAD_CLR,		// [6f]

	X68kKey_ROLLUP,			// [70]
	X68kKey_ROLLDOWN,		// [71]
	X68kKey_F1,				// [72]
	X68kKey_F2,				// [73]
	X68kKey_F3,				// [74]
	X68kKey_F4,				// [75]
	X68kKey_F5,				// [76]
	X68kKey_F6,				// [77]
	X68kKey_F7,				// [78]
	X68kKey_F8,				// [79]
	X68kKey_F9,				// [7a]
	X68kKey_F10,			// [7b]
	X68kKey_PAD_multiply,	// [7c]
	X68kKey_PAD_divide,		// [7d]
	X68kKey_PAD_equal,		// [7e]
	X68kKey_PAD_comma,		// [7f]
};

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.