--- nono/vm/x68kkbd.cpp 2026/04/29 17:04:30 1.1.1.2 +++ nono/vm/x68kkbd.cpp 2026/04/29 17:05:14 1.1.1.11 @@ -1,21 +1,47 @@ // // nono -// Copyright (C) 2018 isaki@NetBSD.org +// 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 "mfp.h" +#include "scc.h" +#include "scheduler.h" +#include "sysport.h" // コンストラクタ X68030Keyboard::X68030Keyboard() { - // MFP へキー入力を通知するイベント -#if 0 - event.dev = gMFP; - event.func = (DeviceCallback_t)&MFPDevice::Rx; - event.time = 0; - event.SetName("KeyInput"); -#endif + led.resize(7); + + rept_event.func = ToEventCallback(&X68030Keyboard::ReptCallback); + rept_event.time = 0; + rept_event.Regist("Keyboard Repeat"); + + mouse_event.func = ToEventCallback(&X68030Keyboard::MouseCallback); + mouse_event.Regist("Mouse"); + + monitor.func = ToMonitorCallback(&X68030Keyboard::MonitorUpdate); + monitor.SetSize(47, 5); + monitor.Regist(ID_MONITOR_KEYBOARD); } // デストラクタ @@ -23,84 +49,419 @@ X68030Keyboard::~X68030Keyboard() { } -// キーを押した +// キーボード接続時の MD 固有処理 +void +X68030Keyboard::MDConnect() +{ + rept_delay_data = 3; + rept_delay = 500_msec; + rept_time_data = 4; + rept_time = 110_msec; + rept_key = KC_none; + gScheduler->StopEvent(rept_event); + + led_darkness = 0; + + // XXX マウスの接続取り外しは未定 + gScheduler->StopEvent(mouse_event); + mouse_event.code = 0; +} + +// キーボード取り外し時の MD 固有処理 void -X68030Keyboard::MakeKey(uint keycode) +X68030Keyboard::MDDisconnect() { - uint x68kkey; + gScheduler->StopEvent(rept_event); + gScheduler->StopEvent(mouse_event); + mouse_event.code = 0; +} - // 共通キーコードから X680x0 キーコードに - x68kkey = keycode2x68kkey(keycode); +// モニター +void +X68030Keyboard::MonitorUpdate(Monitor *, TextScreen& screen) +{ + int y; - // X680x0 にないキーは無視 - if (x68kkey == NoKey) { - return; + 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: Zen Hira INS CAPS Code Roma Kana ($xx) + // LED Darkness: x + + // KEY_EN, CTRL_EN, OPT2_EN, MSCTRL は未実装 + + // キーリピート + screen.Puts(0, y, "Key Repeat:"); + screen.Print(12, y++, "Delay $%x(%4dmsec)", + rept_delay_data, (int)(rept_delay / 1_msec)); + screen.Print(12, y++, "Repeat $%x(%4dmsec)", + rept_time_data, (int)(rept_time / 1_msec)); + + // マウス + 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(7 + i * 5, y, TA::OnOff(led[n]), ledname[i]); + if (led[n] == 0) { + val |= 1 << n; + } + } + screen.Print(42, y, "($%02x)", val); + y++; + + // LED の明るさ (というより暗さ) + screen.Print(0, y++, "LED Darkness: %d", led_darkness); +} + +// 共通 keystat を X680x0 キーコードに変換して返す。 +// 対応する X680x0 のキーがなければ 0 を返す。 +uint +X68030Keyboard::KeyToMDKey(uint keystat) const +{ + uint keycode = KC_CODE(keystat); + if (keycode >= countof(keycode2x68kkey_table)) { + return 0; } - putlog(1, "MakeKey %d(%s)", x68kkey, keyname[x68kkey]); + uint mdkey = keycode2x68kkey_table[keycode]; + if (mdkey && KC_IS_BREAK(keystat)) { + mdkey |= 0x80; + } + return mdkey; +} - // MFP に通知 - event.code = x68kkey; -// gScheduler->AddEvent(&event); +// シフト状態なら 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); + + 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) { + // 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; - // 共通キーコードから X680x0 キーコードに - x68kkey = keycode2x68kkey(keycode); + // keycode を変更したので keystat も更新 + keystat = (keystat & 0xff00) | keycode; + } - // X680x0 にないキーは無視 - if (x68kkey == NoKey) { + // 共通処理へ + if (KeyInputCommon(keystat) == false) { return; } - putlog(2, "BreakKey %d(%s)", x68kkey, keyname[x68kkey]); + // LED は MPU 側から点灯/消灯指示が来るのでここでは何もしなくてよい + + // ここでキーリピートのイベント開始処理 + if (ismake) { + // いま起きているリピートは中止 + rept_key = keycode; + rept_event.time = rept_delay; + gScheduler->RestartEvent(rept_event); + } else { + if (keycode == rept_key) { + // 同じキーのブレークでのみリピートを終了する + gScheduler->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; + gScheduler->StartEvent(rept_event); +} + +// 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() がコールされ、そっちで処理される + } +} + +// MFP からの Ready 通知 +// 実際にはキーボード側が MFP (を含む本体側) が Ready になるのを待っている +// のだが、待っているを作るのは大変なので。 +void +X68030Keyboard::Ready() +{ + if (gSysport->GetKeyCtrl()) { + // KEYCTRL が有効なら処理する - // MFP に通知 - event.code = x68kkey | 0x80; -// gScheduler->AddEvent(&event); + // ここで取り出せるのは MDKey。 + uint x68kkey; + if (sendqueue.Dequeue(&x68kkey)) { + // あれば送信 + + // 呼ばれたからには送信できるはず + assert(gMFP->RxIsReady() == true); + + assert(x68kkey != 0); + + // 送信 + 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; } -// 共通キーコードを X680x0 キーコードに変換して返す。 -// X680x0 キーがなければ NoKey を返す。 -uint -X68030Keyboard::keycode2x68kkey(uint keycode) +// マウスデータを VM に送信 +void +X68030Keyboard::MouseSendStart() { - if (keycode >= countof(keycode2x68kkey_table)) { - return NoKey; + 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 以上後から送信。 + mouse_event.time = 700_usec; + mouse_event.code = 1; + gScheduler->RestartEvent(mouse_event); +} + +// マウスデータのイベントコールバック +void +X68030Keyboard::MouseCallback(Event& ev) +{ + // マウスは 4800bps で、ストップビット 2 なので11ビット。 + // 1/4800 * 11 = 2.29166 msec なので、1バイトあたり 2.3 msec にしておく。 + + // code は 1 なら1バイト目 ([0]) + // : + // code が 4 なら送らずに終了 + + if (mouse_event.code < 4) { + // 1, 2, 3 バイト目送信 + gSCC->Rx(1, mouse_data[mouse_event.code - 1]); + + // 次のイベント + mouse_event.time = 2300_usec; + mouse_event.code++; + gScheduler->StartEvent(mouse_event); + } else { + // 3 バイト目の送信が終わって、1文字送信分の時間が経過したので + // ここで code を 0 に戻す。 + // XXX 実際のタイミングがどうかは知らない。 + mouse_event.code = 0; + } +} - return keycode2x68kkey_table[keycode]; +// ホストからの制御 +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 %d", (data & 1)); + + } else if (data < 0x50) { + // $48-$4f キーデータ送出許可 + putlog(1, "Command Key send enable %d (NOT IMPLEMENTED)", (data & 1)); + + } else if (data < 0x54) { + // $50-$53 ディスプレイ制御モード選択 + // (実装不要) + putlog(2, "Command Display control mode %d", (data & 1)); + + } else if (data < 0x58) { + // $54-$57 LED 明るさ選択 + // 0 が明るく 3 が暗いので、明るさというより暗さ。 + led_darkness = (data & 3); + putlog(1, "Command LED brightness %d", led_darkness); + + } else if (data < 0x5c) { + // $58-$5b 本体からのディスプレイ制御許可 + // (実装不要) + putlog(2, "Command Display control enable %d", (data & 1)); + + } else if (data < 0x60) { + // $5c-$5f OPT.2 によるディスプレイ制御許可 + // (実装不要) + putlog(2, "Command OPT2 control enable %d", (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 %d (%d msec)", rept_delay_data, + (int)(rept_delay / 1_msec)); + + } 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 (%d msec)", rept_time_data, + (int)(rept_time / 1_msec)); + + } 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; + } + } } // キーコード変換表 // 共通キーコードから 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] - X68kKey_HOME, // [08] + X68kKey_SHIFT, // [08] X68kKey_TAB, // [09] X68kKey_CTRL, // [0a] X68kKey_kana, // [0b] X68kKey_SHIFT, // [0c] - NoKey, // [0d] + X68kKey_SHIFT, // [0d] X68kKey_CAPS, // [0e] NoKey, // [0f] @@ -136,7 +497,7 @@ X68030Keyboard::keycode2x68kkey_table[13 X68kKey_minus, // [2c] X68kKey_circum, // [2d] X68kKey_backslash, // [2e] - X68kKey_UNDO, // [2f] + NoKey, // [2f] X68kKey_OPT1, // [30] X68kKey_OPT2, // [31] @@ -155,8 +516,8 @@ X68030Keyboard::keycode2x68kkey_table[13 X68kKey_hira, // [3e] X68kKey_zenkaku, // [3f] - NoKey, // [40] - NoKey, // [41] + X68kKey_HOME, // [40] + X68kKey_UNDO, // [41] X68kKey_A, // [42] X68kKey_S, // [43] X68kKey_D, // [44] @@ -172,22 +533,22 @@ X68030Keyboard::keycode2x68kkey_table[13 NoKey, // [4e] NoKey, // [4f] - NoKey, // [50] - NoKey, // [51] + X68kKey_XF1, // [50] + X68kKey_XF2, // [51] X68kKey_Z, // [52] X68kKey_X, // [53] 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] X68kKey_underscore, // [5c] - NoKey, // [5d] - NoKey, // [5e] - NoKey, // [5f] + X68kKey_XF3, // [5d] + X68kKey_XF4, // [5e] + X68kKey_XF5, // [5f] NoKey, // [60] X68kKey_PAD_plus, // [61] @@ -222,138 +583,4 @@ X68030Keyboard::keycode2x68kkey_table[13 X68kKey_PAD_divide, // [7d] X68kKey_PAD_equal, // [7e] X68kKey_PAD_comma, // [7f] - - X68kKey_XF1, // [80] - X68kKey_XF2, // [81] - 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 };