File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / lunakbd.cpp
Revision 1.1.1.18 (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
//

//
// LUNA のキーボードとマウス
//

// キーボード/マウスからシリアルポートへの入力は、1バイトの送信自体に約1msec
// (9600bps、スタートビット1、ストップビット1) で送信後に 1msec 空けるそう
// なので、ここでは単純に 2msec を1スロットとする。
//
// ブザー発声はキー入力とは本来あまり関係ないが、ブザー発声中もキー入力中と
// 同様に高速走行を抑制したいため、キー扱いとしていることに留意。

#include "lunakbd.h"
#include "event.h"
#include "monitor.h"
#include "scheduler.h"
#include "sio.h"
#include "uimessage.h"

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

	monitor = gMonitorManager->Regist(ID_MONITOR_KEYBOARD, this);
	monitor->SetCallback(&LunaKeyboard::MonitorScreen);
	monitor->SetSize(40, 4);
}

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

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

	// LUNA-88K は左 [SHIFT] が 0x0d、右 [SHIFT] が 0x0c。
	if (gMainApp.IsLUNA88K()) {
		keycode2lunakey_table[0x0c] = LunaKey_SHIFT_R;
		keycode2lunakey_table[0x0d] = LunaKey_SHIFT_L;
	}

	sio = GetSIODevice();

	auto evman = GetEventManager();
	event = evman->Regist(this,
		ToEventCallback(&LunaKeyboard::Callback),
		"Keyboard/Mouse");

	return true;
}

// キーボード接続時の MD 固有処理
void
LunaKeyboard::MDConnect()
{
	// XXX リセットはマウスには届かないような気がするので
	// 接続時(電源オン)で初期化。
	mouse_x = 0;
	mouse_y = 0;
	mouse_r = false;
	mouse_l = false;
	mouse_m = false;
	prev_r = false;
	prev_l = false;
	prev_m = false;

	// ブザー停止状態で初期化すると定義されている
	BreakKey(KC_buzzer);

	// デフォルトでマウス有効
	MouseOn();
}

// キーボード取り外し時の MD 固有処理
void
LunaKeyboard::MDDisconnect()
{
	// 動作中のタイマーは停止
	scheduler->StopEvent(event);

	// 電源が切れるのでブザー停止
	BreakKey(KC_buzzer);

	// マウスサンプリングは停止
	MouseOff();
}

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

	screen.Clear();
	y = 0;

	// 0123456789012345678901234567890123456789
	// LED:    CAP Kana
	// Buzzer: 150msec 6000Hz
	// Mouse:  sampling=off x=-999 y=-999 L M R
	x = 8;
	screen.Puts(0,  y, "LED:");
	screen.Puts(x,  y, TA::OnOff(led[1]), "CAP");
	screen.Puts(x + 4, y, TA::OnOff(led[0]), "Kana");
	y++;
	screen.Puts(0,  y, "Buzzer:");
	screen.Print(x,  y, IsPressed(KC_buzzer) ? TA::Normal : TA::Disable,
		"%umsec %uHz", buzzer_msec, buzzer_freq);
	y++;

	// マウスとの間を1行開ける
	y++;

	mx = mouse_x;
	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(x,  y, "sampling=%s", mouse_on ? "on" : "off");
	screen.Print(x + 13, y, (mouse_on ? TA::Normal : TA::Disable),
		"x=%-4d y=%-4d", mx, my);
	if (mouse_on) {
		screen.Puts(x + 27, y, TA::OnOff(mouse_l), "L");
		screen.Puts(x + 29, y, TA::OnOff(mouse_m), "M");
		screen.Puts(x + 31, y, TA::OnOff(mouse_r), "R");
	} else {
		screen.Puts(x + 27, y, TA::Disable, "L M R");
	}
}

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

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

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

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

	assert(keycode < KC_max);

	// LUNA キーボードの LED キーは、
	// LED 消灯中の押下で Make、点灯中の押下で Break を送り、
	// LED キーの開放は VM に対しては何もしない。Issue#83
	//
	// LED	ismake	  keystat 送信コード
	// ------------	  -------------
	// off	MAKE	: MAKE	MAKE	キー押下、Make  を入力 (LED を on)
	// on	MAKE	: MAKE	BREAK	キー押下、Break を入力 (LED を off)
	// off	BREAK	: BREAK	none	キー開放
	// on	BREAK	: BREAK	none	キー開放
	int n = Keycode2LED(keycode);
	if (n >= 0) {
		if (ismake) {
			keystat |= (led[n] ? KC_FLAG_LED_BREAK : KC_FLAG_LED_MAKE);
		} else {
			// Break 時はキー入力はしない (キーの開放処理自体は必要)
			keystat |= KC_FLAG_LED_DROP;
		}
	}

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

	// 処理できたらここで LED の状態を更新
	if (n >= 0 && ismake) {
		led[n] = !led[n];

		// LED 状態が変更になったので全走査指示。
		// KeyInputCommon() 内で一度ポストしてるので無駄気味だが仕方ない…
		uimessage->Post(UIMessage::KEYBOARD);
	}

	// LUNA のキーボードはキーボード側でキーリピートしないので
	// ここでは何もしなくてよい
}

// 共通キーコードから LED 番号を返す。LED キーでなければ -1 を返す。
int
LunaKeyboard::Keycode2LED(uint keycode) const
{
	// 2つしかないのでこのくらいでいいか
	if (keycode == KC_kana)
		return 0;
	if (keycode == KC_CAPS)
		return 1;

	return -1;
}

// LED 番号から共通キーコードを返す。LED 番号が不正なら KC_none を返す。
uint
LunaKeyboard::LED2Keycode(int ledcode) const
{
	// 2つしかないのでこのくらいでいいか
	if (ledcode == 0)
		return KC_kana;
	if (ledcode == 1)
		return KC_CAPS;

	return KC_none;
}

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

// キー送信を開始 (Keyboard から呼ばれる)
void
LunaKeyboard::SendStart()
{
	// ここでは次の 2msec 単位にイベントコールバックを起動するだけ。
	Start(2_msec);
}

// イベントを次の period nsec 単位後に開始。
void
LunaKeyboard::Start(uint64 period)
{
	uint64 now = scheduler->GetVirtTime();
	uint64 target = ((now / period) + 1) * period;
	// XXX メンバの event.vtime 参照してるけどどうするかね
	if (event->IsRunning() == false || target < event->vtime) {
		event->time = target - now;
		scheduler->RestartEvent(event);
	}
}

// イベントコールバック。
// キー入力、マウス入力、ブザー出力のいずれかが発生している間 2msec ごとに
// 呼ばれる。
void
LunaKeyboard::Callback(Event *ev)
{
	uint64 now = scheduler->GetVirtTime();
	uint mdkey;

	// マウスサンプリングオンなら 20msec ごとにサンプリングを行う。
	if (mouse_on && (now / 2_msec) % 10 == 0) {
		MouseSampling();
	}

	// キューから取り出す。ここで取り出せるのは MDKey またはマウスデータ
	if (sendqueue.Dequeue(&mdkey)) {
		// 送信
		sio->Rx(1, mdkey);
	}

	if (IsPressed(KC_buzzer) && now > buzzer_end) {
		// VM スレッド内なので BreakKey() ではなくこっちを直接呼ぶ
		KeyInput(KC_buzzer | KC_FLAG_BREAK);
	}

	if (sendqueue.Empty() == false || IsPressed(KC_buzzer)) {
		// 送信データが残っていれば次は 2msec 後
		Start(2_msec);
	} else if (mouse_on) {
		// そうでなくて、マウスサンプリングがオンなら次の 20msec 単位後
		Start(20_msec);
	}
}

// マウスサンプリング
void
LunaKeyboard::MouseSampling()
{
	// 移動量が±1 を超えるかボタンがどれかでも変化すれば送信。
	if (mouse_x < -1 || mouse_x > 1 ||
	    mouse_y < -1 || mouse_y > 1 ||
	    mouse_r != prev_r ||
	    mouse_l != prev_l ||
	    mouse_m != prev_m)
	{
		putlog(2, "Mouse Sampling x=%d y=%d %c%c%c",
			mouse_x, mouse_y,
			(mouse_l ? 'L' : '-'),
			(mouse_m ? 'M' : '-'),
			(mouse_r ? 'R' : '-'));

		// 1バイト目、ボタン状態。
		// mouse_[lmr] 変数は押し下げが true、LUNA のマウスは押し下げが %0
		uint8 b = 0x80 |
			(mouse_l ? 0 : 0x04) |
			(mouse_m ? 0 : 0x02) |
			(mouse_r ? 0 : 0x01);

		// 2バイト目が X、3バイト目が Y。
		// 移動量は絶対値 0x7f でサチる (signed char ではない)。
		if (mouse_x < -127)
			mouse_x = -127;
		if (mouse_x > 127)
			mouse_x = 127;

		if (mouse_y < -127)
			mouse_y = -127;
		if (mouse_y > 127)
			mouse_y = 127;

		// キューに入れておく
		sendqueue.Enqueue(b);
		sendqueue.Enqueue((uint8)mouse_x);
		sendqueue.Enqueue((uint8)mouse_y);

		// 積算リセット
		mouse_x = 0;
		mouse_y = 0;
		prev_r = mouse_r;
		prev_l = mouse_l;
		prev_m = mouse_m;
	}
}

// マウスサンプリングを ON にする
void
LunaKeyboard::MouseOn()
{
	// 厳密なタイミングは分からないので、
	// 実装都合で次のマウススロットからサンプリングを開始(再開)する。
	mouse_on = true;

	// 次の 20msec 単位後から開始
	Start(20_msec);
}

// マウスサンプリングを OFF にする
void
LunaKeyboard::MouseOff()
{
	mouse_on = false;
	mouse_x = 0;
	mouse_y = 0;
	mouse_r = false;
	mouse_l = false;
	mouse_m = false;
	prev_r = false;
	prev_l = false;
	prev_m = false;
}

// ?
void
LunaKeyboard::MouseSendStart()
{
}

static const uint buzzer_msec_table[] = { 40, 150, 400, 700 };
static const uint buzzer_freq_table[] = {
	6000, 3000, 1500, 1000, 600, 300, 150, 100
};

// ホストからの制御
void
LunaKeyboard::Command(uint32 data)
{
	switch (data & 0xe0) {
	 case 0x00:	// LED 点灯コマンド
	 {
		//   7   6   5   4   3   2   1   0
		// +---+---+---+---+---+---+---+---+
		// | 0   0   0 |LED| x   x   x |COD|
		// +---+---+---+---+---+---+---+---+
		//               |               +--- 0:かな 1:CAP
		//               +------------------- 0:消灯 1:点灯
		uint code = data & 0x01;
		uint on   = data & 0x10;
		led[code] = (bool)on;
		uimessage->Post(UIMessage::KEYBOARD);
		if (loglevel >= 1) {
			const auto name = GetKeyName(LED2Keycode(code));
			putlogn("Command $%02x LED $%x(%s) %s",
				data, code, name.c_str(), (on ? "ON" : "OFF"));
		}
		break;
	 }

	 case 0x40:	// ブザーコマンド
	 {
		//   7   6   5   4   3   2   1   0
		// +---+---+---+---+---+---+---+---+
		// | 0   1   0 |T2  T1 |F3  F2  F1 |
		// +---+---+---+---+---+---+---+---+
		//                 |         +------- 周波数
		//                 +----------------- 時間
		uint t = (data >> 3) & 3;
		uint f = data & 7;
		buzzer_msec = buzzer_msec_table[t];
		buzzer_freq = buzzer_freq_table[f];
		putlog(1, "Command $%02x Buzzer %umsec, %uHz",
			data, buzzer_msec, buzzer_freq);

		uint64 now = scheduler->GetVirtTime();
		buzzer_end = now + msec_to_nsec(buzzer_msec);
		// VM スレッド内なので MakeKey() ではなく KeyInput() を直接呼ぶ
		KeyInput(KC_buzzer | KC_FLAG_MAKE);
		Start(2_msec);
		break;
	 }

	 case 0x20:	// マウス OFF コマンド
		//   7   6   5   4   3   2   1   0
		// +---+---+---+---+---+---+---+---+
		// | 0   0   1 | x   x   x   x   x |
		// +---+---+---+---+---+---+---+---+
		putlog(1, "Command $%02x Mouse Sampling Off", data);
		MouseOff();
		break;

	 case 0x60:	// マウス ON コマンド
		//   7   6   5   4   3   2   1   0
		// +---+---+---+---+---+---+---+---+
		// | 0   1   1 | x   x   x   x   x |
		// +---+---+---+---+---+---+---+---+
		putlog(1, "Command $%02x Mouse Sampling On", data);
		MouseOn();
		break;

	 default:
		putlog(1, "Command $%02x Undefined", data);
		break;
	}
}

// キーコード変換表
// 共通キーコードから LUNA キーコードに変換する。
// ぶっちゃけた話共通キーコードは LUNA キーコードをベースにしているので、
// 穴がある以外は全部透過。
/*static*/ uint
LunaKeyboard::keycode2lunakey_table[KC_max] = {
	NoKey,					// [00]
	NoKey,					// [01]
	NoKey,					// [02]
	NoKey,					// [03]
	NoKey,					// [04]
	NoKey,					// [05]
	NoKey,					// [06]
	NoKey,					// [07]
	NoKey,					// [08]
	LunaKey_TAB,			// [09]
	LunaKey_CTRL,			// [0a]
	LunaKey_kana,			// [0b]
	LunaKey_SHIFT_L,		// [0c]
	LunaKey_SHIFT_R,		// [0d]
	LunaKey_CAPS,			// [0e]
	LunaKey_SF,				// [0f]

	LunaKey_ESC,			// [10]
	LunaKey_BS,				// [11]
	LunaKey_enter,			// [12]
	NoKey,					// [13]
	LunaKey_space,			// [14]
	LunaKey_DEL,			// [15]
	LunaKey_XFER,			// [16]
	LunaKey_VALID,			// [17]
	LunaKey_PF11,			// [18]
	LunaKey_PF12,			// [19]
	LunaKey_PF13,			// [1a]
	LunaKey_PF14,			// [1b]
	LunaKey_up,				// [1c]
	LunaKey_left,			// [1d]
	LunaKey_right,			// [1e]
	LunaKey_down,			// [1f]

	LunaKey_INSERT,			// [20]
	LunaKey_COPY,			// [21]
	LunaKey_1,				// [22]
	LunaKey_2,				// [23]
	LunaKey_3,				// [24]
	LunaKey_4,				// [25]
	LunaKey_5,				// [26]
	LunaKey_6,				// [27]
	LunaKey_7,				// [28]
	LunaKey_8,				// [29]
	LunaKey_9,				// [2a]
	LunaKey_0,				// [2b]
	LunaKey_minus,			// [2c]
	LunaKey_circum,			// [2d]
	LunaKey_backslash,		// [2e]
	LunaKey_buzzer,			// [2f]

	LunaKey_CUT,			// [30]
	LunaKey_PASTE,			// [31]
	LunaKey_Q,				// [32]
	LunaKey_W,				// [33]
	LunaKey_E,				// [34]
	LunaKey_R,				// [35]
	LunaKey_T,				// [36]
	LunaKey_Y,				// [37]
	LunaKey_U,				// [38]
	LunaKey_I,				// [39]
	LunaKey_O,				// [3a]
	LunaKey_P,				// [3b]
	LunaKey_at,				// [3c]
	LunaKey_bracketleft,	// [3d]
	NoKey,					// [3e]
	NoKey,					// [3f]

	NoKey,					// [40]
	NoKey,					// [41]
	LunaKey_A,				// [42]
	LunaKey_S,				// [43]
	LunaKey_D,				// [44]
	LunaKey_F,				// [45]
	LunaKey_G,				// [46]
	LunaKey_H,				// [47]
	LunaKey_J,				// [48]
	LunaKey_K,				// [49]
	LunaKey_L,				// [4a]
	LunaKey_semicolon,		// [4b]
	LunaKey_colon,			// [4c]
	LunaKey_bracketright,	// [4d]
	NoKey,					// [4e]
	NoKey,					// [4f]

	NoKey,					// [50]
	NoKey,					// [51]
	LunaKey_Z,				// [52]
	LunaKey_X,				// [53]
	LunaKey_C,				// [54]
	LunaKey_V,				// [55]
	LunaKey_B,				// [56]
	LunaKey_N,				// [57]
	LunaKey_M,				// [58]
	LunaKey_comma,			// [59]
	LunaKey_period,			// [5a]
	LunaKey_slash,			// [5b]
	LunaKey_underscore,		// [5c]
	NoKey,					// [5d]
	NoKey,					// [5e]
	NoKey,					// [5f]

	LunaKey_PAD_HOME,		// [60]
	LunaKey_PAD_plus,		// [61]
	LunaKey_PAD_minus,		// [62]
	LunaKey_PAD_7,			// [63]
	LunaKey_PAD_8,			// [64]
	LunaKey_PAD_9,			// [65]
	LunaKey_PAD_4,			// [66]
	LunaKey_PAD_5,			// [67]
	LunaKey_PAD_6,			// [68]
	LunaKey_PAD_1,			// [69]
	LunaKey_PAD_2,			// [6a]
	LunaKey_PAD_3,			// [6b]
	LunaKey_PAD_0,			// [6c]
	LunaKey_PAD_decimal,	// [6d]
	LunaKey_PAD_enter,		// [6e]
	NoKey,					// [6f]

	NoKey,					// [70]
	NoKey,					// [71]
	LunaKey_F1,				// [72]
	LunaKey_F2,				// [73]
	LunaKey_F3,				// [74]
	LunaKey_F4,				// [75]
	LunaKey_F5,				// [76]
	LunaKey_F6,				// [77]
	LunaKey_F7,				// [78]
	LunaKey_F8,				// [79]
	LunaKey_F9,				// [7a]
	LunaKey_F10,			// [7b]
	LunaKey_PAD_multiply,	// [7c]
	LunaKey_PAD_divide,		// [7d]
	LunaKey_PAD_equal,		// [7e]
	LunaKey_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.