--- nono/vm/x68kkbd.cpp 2026/04/29 17:04:28 1.1 +++ nono/vm/x68kkbd.cpp 2026/04/29 17:05:03 1.1.1.8 @@ -1,21 +1,43 @@ // // nono -// Copyright (C) 2018 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // +// 送信タイミングについて +// キーボードは 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 "mfp.h" +#include "scc.h" +#include "sysport.h" // コンストラクタ X68030Keyboard::X68030Keyboard() { - // MFP へキー入力を通知するイベント -#if 0 - event.dev = gMFP; - event.func = (DeviceCallback_t)&MFPDevice::Rx; - event.time = 0; - event.name = "KeyInput"; -#endif + led.resize(7); + + rept_event.func = (DeviceCallback_t)&X68030Keyboard::ReptCallback; + rept_event.time = 0; + rept_event.SetName("Keyboard Repeat"); + + for (int i = 0; i < mouse_event.size(); i++) { + mouse_event[i].dev = this; + mouse_event[i].func = (DeviceCallback_t)&X68030Keyboard::MouseCallback; + // 700us 後から 11bit を 4800 bps の速度で送る + mouse_event[i].time = 700_usec + 1_sec * 11 * (i + 1) / 4800; + mouse_event[i].SetName(string_format("Mouse Data %d", i)); + } } // デストラクタ @@ -23,69 +45,316 @@ X68030Keyboard::~X68030Keyboard() { } -// キーを押した +// 本体リセットスイッチによるリセット void -X68030Keyboard::MakeKey(uint keycode) +X68030Keyboard::ResetHard() { - uint x68kkey; + inherited::ResetHard(); - // 共通キーコードから X680x0 キーコードに - x68kkey = keycode2x68kkey(keycode); + rept_delay = 500_msec; + rept_time = 110_msec; + rept_key = KC_none; + rept_event.Stop(); - // X680x0 にないキーは無視 - if (x68kkey == NoKey) { - return; + for (int i = 0; i < led.size(); i++) { + led[i] = false; } + led_darkness = 0; - putlog(1, "MakeKey %d(%s)", x68kkey, keyname[x68kkey]); + for (int i = 0; i < mouse_event.size(); i++) { + mouse_event[i].Stop(); + } +} - // MFP に通知 - event.code = x68kkey; -// gScheduler->AddEvent(&event); +// 共通キーコードを X680x0 キーコードに変換して返す。 +// 対応する X680x0 のキーがなければ 0 を返す。 +uint +X68030Keyboard::KeyToMDKey(uint keycode) const +{ + if (keycode >= countof(keycode2x68kkey_table)) { + return 0; + } + + return keycode2x68kkey_table[keycode]; +} + +// シフト状態なら true を返す +bool +X68030Keyboard::IsShift() const +{ + return pressed[KC_SHIFT]; } -// キーを離した +// キー入力を1つ処理する void -X68030Keyboard::BreakKey(uint keycode) +X68030Keyboard::KeyInput(uint keystat) { - uint x68kkey; + uint keycode = KC_CODE(keystat); + bool ismake = KC_IS_MAKE(keystat); - // 共通キーコードから X680x0 キーコードに - x68kkey = keycode2x68kkey(keycode); + assert(keycode < KC_max); - // X680x0 にないキーは無視 - if (x68kkey == NoKey) { + // 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) { + // KC_SHIFT_L, R に対する pressed は内部の OR 処理用 + pressed[keycode] = ismake; + if (keycode == KC_SHIFT_L && pressed[KC_SHIFT_R]) { + return; + } + if (keycode == KC_SHIFT_R && pressed[KC_SHIFT_L]) { + return; + } + keycode = KC_SHIFT; + + // keycode を変更したので keystat も更新 + keystat = (keystat & 0xff00) | keycode; + } + + // 共通処理へ + if (KeyInputCommon(keystat) == false) { return; } - putlog(2, "BreakKey %d(%s)", x68kkey, keyname[x68kkey]); + // LED は MPU 側から点灯/消灯指示が来るのでここでは何もしなくてよい - // MFP に通知 - event.code = x68kkey | 0x80; -// gScheduler->AddEvent(&event); + // ここでキーリピートのイベント開始処理 + if (ismake) { + // いま起きているリピートは中止 + rept_event.Stop(); + rept_key = keycode; + rept_event.time = rept_delay; + rept_event.Start(); + } else { + if (keycode == rept_key) { + // 同じキーのブレークでのみリピートを終了する + rept_event.Stop(); + rept_key = KC_none; + } + } } -// 共通キーコードを X680x0 キーコードに変換して返す。 -// X680x0 キーがなければ NoKey を返す。 -uint -X68030Keyboard::keycode2x68kkey(uint keycode) +void +X68030Keyboard::ReptCallback(Event& ev) { - if (keycode >= countof(keycode2x68kkey_table)) { - return NoKey; + // リピート + sendqueue.Enqueue(rept_key); + SendStart(); + + // 二回目以降のリピートは rept_time 間隔で発生 + rept_event.time = rept_time; + rept_event.Start(); +} + +// MFP、システムポートと、キーボードの関係。 +// +// キーボードの Ready 信号は MFP の RR とシステムポートの KeyCtrl の +// AND で作られているが、この AND 部分はキーボードが担当することにする。 +// そのほうが都合がいいため。 +// +// <- 本体 || キーボード -> +// MFP || +// --------+ : || +----- +// | : +--+ || | +// RR|o-------| | || | +// --------+ : | |-----|T0 (Ready) +// +-----| | || | +// Sysport | : +--+ || | +// --------+ | : || | +// KeyCtrl|--+ : || +----- +// | : || +// :エミュレータでは点線から右をキーボードが担当 + +// キーデータを送信 +void +X68030Keyboard::SendStart() +{ + if (gMFP->RxIsReady()) { + // 本体が Ready ならここで送信 + Ready(); + } else { + // そうでない場合、ここでは処理は不要。 + // MFP 側が受信可能になると Ready() がコールされ、そっちで処理される } +} - return keycode2x68kkey_table[keycode]; +// MFP からの Ready 通知 +// 実際にはキーボード側が MFP (を含む本体側) が Ready になるのを待っている +// のだが、待っているを作るのは大変なので。 +void +X68030Keyboard::Ready() +{ + if (gSysport->GetKeyCtrl()) { + // KEYCTRL が有効なら処理する + + // ここで取り出せるのは keystat。 + uint keystat; + if (sendqueue.Dequeue(&keystat)) { + // あれば送信 + + // 呼ばれたからには送信できるはず + assert(gMFP->RxIsReady() == true); + + uint keycode = KC_CODE(keystat); + // ここで X68k キーコードに変換 (変換できないはずはない) + uint x68kkey = KeyToMDKey(keycode); + assert(x68kkey != 0); + + // 送信 + if (KC_IS_BREAK(keystat)) { + x68kkey |= 0x80; + } + gMFP->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; + + // 最後のバイトを送るまでは、次は受け付けられない + if (mouse_event[2].IsRunning()) { + 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_event[0].code = status; + mouse_event[1].code = mouse_x; + mouse_event[2].code = mouse_y; + + mouse_x = 0; + mouse_y = 0; + + mouse_event[0].Start(); + mouse_event[1].Start(); + mouse_event[2].Start(); +} + +void +X68030Keyboard::MouseCallback(Event& ev) +{ + // LUNA とオブジェクトが違うだけ + gSCC->Rx(1, ev.code); +} + +// ホストからの制御 +void +X68030Keyboard::Command(uint32 data) +{ + if (data < 0x40) { + // $00-$3f TV 制御 + // (実装不要) + putlog(2, "Command TV 制御 $%02x", (data & 0x1f)); + + } else if (data < 0x48) { + // $40-$47 MSCTRL 信号制御 + // こいつは頻度が高いのでログレベルを上げておく + putlog(3, "Command MSCTRL 制御 %d", (data & 1)); + + } else if (data < 0x50) { + // $48-$4f キーデータ送出許可 + putlog(1, "Command キー送出許可 %d (未実装)", (data & 1)); + + } else if (data < 0x54) { + // $50-$53 ディスプレイ制御モード選択 + // (実装不要) + putlog(2, "Command ディスプレイ制御 X68000モード %d", (data & 1)); + + } else if (data < 0x58) { + // $54-$57 LED 明るさ選択 + // 0 が明るく 3 が暗いので、明るさというより暗さ。 + led_darkness = (data & 3); + putlog(1, "Command LED 明るさ %d", led_darkness); + + } else if (data < 0x5c) { + // $58-$5b 本体からのディスプレイ制御許可 + // (実装不要) + putlog(2, "Command 本体からディスプレイ制御 %d", (data & 1)); + + } else if (data < 0x60) { + // $5c-$5f OPT.2 によるディスプレイ制御許可 + // (実装不要) + putlog(2, "Command OPT2からディスプレイ制御 %d", (data & 1)); + + } else if (data < 0x70) { + // $60-$6f リピートまでの時間 + rept_delay = 200_msec + (data & 15) * 100_msec; + putlog(1, "Command Repeat Delay %d (%d msec)", (data & 15), + (int)(rept_delay / 1_msec)); + + } else if (data < 0x80) { + // $70-$7f リピート間隔 + rept_time = 30_msec + (data & 15) * (data & 15) * 5_msec; + putlog(1, "Command Repeat time %d (%d msec)", (data & 15), + (int)(rept_time / 1_msec)); + + } else { + // $80-$ff LED 制御 + data &= 0x7f; + putlog(1, "Command LED 制御 $%02x", data); + uint32 b = 0x01; + for (int i = 0; i < led.size(); i++) { + // data は %0 で点灯、led[] は true で点灯 + led[i] = ((data & b) == 0); + b <<= 1; + } + } } // キーコード変換表 // 共通キーコードから X680x0 キーコードに変換する。 /*static*/ const uint -X68030Keyboard::keycode2x68kkey_table[133] = { +X68030Keyboard::keycode2x68kkey_table[KC_max] = { NoKey, // [00] X68kKey_BREAK, // [01] X68kKey_COPY, // [02] X68kKey_romaji, // [03] - X68kKey_kana, // [04] + X68kKey_code, // [04] X68kKey_kigou, // [05] X68kKey_touroku, // [06] X68kKey_HELP, // [07] @@ -94,7 +363,7 @@ X68030Keyboard::keycode2x68kkey_table[13 X68kKey_CTRL, // [0a] X68kKey_kana, // [0b] X68kKey_SHIFT, // [0c] - NoKey, // [0d] + X68kKey_SHIFT, // [0d] X68kKey_CAPS, // [0e] NoKey, // [0f] @@ -173,8 +442,8 @@ X68030Keyboard::keycode2x68kkey_table[13 X68kKey_C, // [54] X68kKey_V, // [55] X68kKey_B, // [56] - X68kKey_M, // [57] - X68kKey_N, // [58] + X68kKey_N, // [57] + X68kKey_M, // [58] X68kKey_comma, // [59] X68kKey_period, // [5a] X68kKey_slash, // [5b] @@ -222,132 +491,5 @@ X68030Keyboard::keycode2x68kkey_table[13 X68kKey_XF3, // [82] X68kKey_XF4, // [83] X68kKey_XF5, // [84] -}; - -/*static*/ const char * const -X68030Keyboard::keyname[] = -{ - "", // 00 - "ESC", // 01 - "1!", // 02 - "2\"", // 03 - "3#", // 04 - "4$", // 05 - "5%", // 06 - "6&", // 07 - "7'", // 08 - "8(", // 09 - "9)", // 0a - "0", // 0b - "-=", // 0c - "^~", // 0d - "\\|", // 0e - "BS", // 0f - - "TAB", // 10 - "Q", // 11 - "W", // 12 - "E", // 13 - "R", // 14 - "T", // 15 - "Y", // 16 - "U", // 17 - "I", // 18 - "O", // 19 - "P", // 1a - "@`", // 1b - "[{", // 1c - "Enter", // 1d - "A", // 1e - "S", // 1f - - "D", // 20 - "F", // 21 - "G", // 22 - "H", // 23 - "J", // 24 - "K", // 25 - "L", // 26 - ";+", // 27 - ":*", // 28 - "]}", // 29 - "Z", // 2a - "X", // 2b - "C", // 2c - "V", // 2d - "B", // 2e - "N", // 2f - - "M", // 30 - ",<", // 31 - ".>", // 32 - "/?", // 33 - " _", // 34 - "Space", // 35 - "HOME", // 36 - "DEL", // 37 - "ROLLUP", // 38 - "ROLLDOWN", // 39 - "UNDO", // 3a - "←", // 3b - "↑", // 3c - "→", // 3d - "↓", // 3e - "PADCLR", // 3f - - "PAD/", // 40 - "PAD*", // 41 - "PAD-", // 42 - "PAD7", // 43 - "PAD8", // 44 - "PAD9", // 45 - "PAD+", // 46 - "PAD4", // 47 - "PAD5", // 48 - "PAD6", // 49 - "PAD=", // 4a - "PAD1", // 4b - "PAD2", // 4c - "PAD3", // 4d - "PADEnter", // 4e - "PAD0", // 4f - - "PAD,", // 50 - "PAD.", // 51 - "記号", // 52 - "登録", // 53 - "HELP", // 54 - "XF1", // 55 - "XF2", // 56 - "XF3", // 57 - "XF4", // 58 - "XF5", // 59 - "かな", // 5a - "ローマ字", // 5b - "コード", // 5c - "CAPS", // 5d - "INS", // 5e - "ひらがな", // 5f - - "全角", // 60 - "BREAK", // 61 - "COPY", // 62 - "F1", // 63 - "F2", // 64 - "F3", // 65 - "F4", // 66 - "F5", // 67 - "F6", // 68 - "F7", // 69 - "F8", // 6a - "F9", // 6b - "F10", // 6c - "", // 6d - "", // 6e - "", // 6f - - "SHIFT", // 70 - "CTRL", // 71 - "OPT1", // 72 - "OPT2", // 73 + X68kKey_SHIFT, // [85] };