--- nono/vm/lunakbd.cpp 2026/04/29 17:04:28 1.1 +++ nono/vm/lunakbd.cpp 2026/04/29 17:06:00 1.1.1.18 @@ -1,21 +1,35 @@ // // nono -// Copyright (C) 2018 isaki@NetBSD.org +// 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() { - memset(reptable, 0, sizeof(reptable)); + led.resize(2); - // SIO へキー入力を通知するイベント - event.dev = gSIO; - event.func = (DeviceCallback_t)&SIODevice::RxCh1; - event.time = 0; - event.name = "KeyInput"; + monitor = gMonitorManager->Regist(ID_MONITOR_KEYBOARD, this); + monitor->SetCallback(&LunaKeyboard::MonitorScreen); + monitor->SetSize(40, 4); } // デストラクタ @@ -23,75 +37,453 @@ 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::MakeKey(uint keycode) +LunaKeyboard::MDDisconnect() { - uint lunakey; + // 動作中のタイマーは停止 + scheduler->StopEvent(event); - // 共通キーコードから LUNA キーコードに - lunakey = keycode2lunakey(keycode); + // 電源が切れるのでブザー停止 + BreakKey(KC_buzzer); - // LUNA にないキーは無視 - if (lunakey == NoKey) { - return; + // マウスサンプリングは停止 + 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"); } +} - // すでにこのキーが押されていれば送らない - if (reptable[lunakey]) { - return; +// 共通 keystat を LUNA キーコードに変換して返す。 +// 対応する LUNA のキーがなければ 0 を返す。 +uint +LunaKeyboard::KeyToMDKey(uint keystat) const +{ + uint keycode = KC_CODE(keystat); + if (keycode >= countof(keycode2lunakey_table)) { + return 0; } - // キーが押された - putlog(1, "MakeKey %d(%s)", lunakey, keyname[lunakey]); - reptable[lunakey] = 1; + uint mdkey = keycode2lunakey_table[keycode]; + if (mdkey && KC_IS_BREAK(keystat)) { + mdkey |= 0x80; + } + return mdkey; +} - // SIO に通知 - event.code = lunakey; - gScheduler->AddEvent(&event); +// シフト状態なら true を返す +bool +LunaKeyboard::IsShift() const +{ + return pressed[KC_SHIFT_L] || pressed[KC_SHIFT_R]; } -// キーを離した +// キー入力を1つ処理する void -LunaKeyboard::BreakKey(uint keycode) +LunaKeyboard::KeyInput(uint keystat) { - uint lunakey; + uint keycode = KC_CODE(keystat); + bool ismake = KC_IS_MAKE(keystat); - // 共通キーコードから LUNA キーコードに - lunakey = keycode2lunakey(keycode); + assert(keycode < KC_max); - // LUNA にないキーは無視 - if (lunakey == NoKey) { + // 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; } - // キーが離された - putlog(2, "BreakKey %d(%s)", lunakey, keyname[lunakey]); - reptable[lunakey] = 0; + // 処理できたらここで LED の状態を更新 + if (n >= 0 && ismake) { + led[n] = !led[n]; + + // LED 状態が変更になったので全走査指示。 + // KeyInputCommon() 内で一度ポストしてるので無駄気味だが仕方ない… + uimessage->Post(UIMessage::KEYBOARD); + } - // SIO に通知 - event.code = lunakey | 0x80; - gScheduler->AddEvent(&event); + // LUNA のキーボードはキーボード側でキーリピートしないので + // ここでは何もしなくてよい } -// 共通キーコードを LUNA キーコードに変換して返す。 -// LUNA キーがなければ NoKey を返す。 +// 共通キーコードから 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::keycode2lunakey(uint keycode) +LunaKeyboard::LED2Keycode(int ledcode) const { - if (keycode >= countof(keycode2lunakey_table)) { - return NoKey; + // 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); } - return keycode2lunakey_table[keycode]; + 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*/ const uint -LunaKeyboard::keycode2lunakey_table[128] = { +/*static*/ uint +LunaKeyboard::keycode2lunakey_table[KC_max] = { NoKey, // [00] NoKey, // [01] NoKey, // [02] @@ -126,8 +518,8 @@ LunaKeyboard::keycode2lunakey_table[128] LunaKey_right, // [1e] LunaKey_down, // [1f] - NoKey, // [20] - NoKey, // [21] + LunaKey_INSERT, // [20] + LunaKey_COPY, // [21] LunaKey_1, // [22] LunaKey_2, // [23] LunaKey_3, // [24] @@ -141,10 +533,10 @@ LunaKeyboard::keycode2lunakey_table[128] LunaKey_minus, // [2c] LunaKey_circum, // [2d] LunaKey_backslash, // [2e] - NoKey, // [2f] + LunaKey_buzzer, // [2f] - NoKey, // [30] - NoKey, // [31] + LunaKey_CUT, // [30] + LunaKey_PASTE, // [31] LunaKey_Q, // [32] LunaKey_W, // [33] LunaKey_E, // [34] @@ -194,7 +586,7 @@ LunaKeyboard::keycode2lunakey_table[128] NoKey, // [5e] NoKey, // [5f] - NoKey, // [60] + LunaKey_PAD_HOME, // [60] LunaKey_PAD_plus, // [61] LunaKey_PAD_minus, // [62] LunaKey_PAD_7, // [63] @@ -228,143 +620,3 @@ LunaKeyboard::keycode2lunakey_table[128] LunaKey_PAD_equal, // [7e] LunaKey_PAD_comma, // [7f] }; - -/*static*/ const char * const -LunaKeyboard::keyname[] = -{ - "", // 00 - "", // 01 - "", // 02 - "", // 03 - "", // 04 - "", // 05 - "", // 06 - "", // 07 - "", // 08 - "TAB", // 09 - "CTRL", // 0a - "かな", // 0b - "SHIFT", // 0c - "SHIFT", // 0d - "CAPS", // 0e - "S.F", // 0f - - "ESC", // 10 - "BS", // 11 - "Enter", // 12 - "", // 13 - "Space", // 14 - "DEL", // 15 - "XFER", // 16 - "VALID", // 17 - "PF11", // 18 - "PF12", // 19 - "PF13", // 1a - "PF14", // 1b - "↑", // 1c - "←", // 1d - "→", // 1e - "↓", // 1f - - "", // 20 - "", // 21 - "1!", // 22 - "2\"", // 23 - "3#", // 24 - "4$", // 25 - "5%", // 26 - "6&", // 27 - "7'", // 28 - "8(", // 29 - "9)", // 2a - "0", // 2b - "-=", // 2c - "^~", // 2d - "\\|", // 2e - "", // 2f - - "", // 30 - "", // 31 - "Q", // 32 - "W", // 33 - "E", // 34 - "R", // 35 - "T", // 36 - "Y", // 37 - "U", // 38 - "I", // 39 - "O", // 3a - "P", // 3b - "@`", // 3c - "[{", // 3d - "", // 3e - "", // 3f - - "", // 40 - "", // 41 - "A", // 42 - "S", // 43 - "D", // 44 - "F", // 45 - "G", // 46 - "H", // 47 - "J", // 48 - "K", // 49 - "L", // 4a - ";+", // 4b - ":*", // 4c - "]}", // 4d - "", // 4e - "", // 4f - - "", // 50 - "", // 51 - "Z", // 52 - "X", // 53 - "C", // 54 - "V", // 55 - "B", // 56 - "N", // 57 - "M", // 58 - ",<", // 59 - ".>", // 5a - "/?", // 5b - " _", // 5c - "", // 5d - "", // 5e - "", // 5f - - "", // 60 - "PAD+", // 61 - "PAD-", // 62 - "PAD7", // 63 - "PAD8", // 64 - "PAD9", // 65 - "PAD4", // 66 - "PAD5", // 67 - "PAD6", // 68 - "PAD1", // 69 - "PAD2", // 6a - "PAD3", // 6b - "PAD0", // 6c - "PAD.", // 6d - "PADEn", // 6e - "", // 6f - - "", // 70 - "", // 71 - "F1", // 72 - "F2", // 73 - "F3", // 74 - "F4", // 75 - "F5", // 76 - "F6", // 77 - "F7", // 78 - "F8", // 79 - "F9", // 7a - "F10", // 7b - "PAD*", // 7c - "PAD/", // 7d - "PAD=", // 7e - "PAD,", // 7f -};