--- nono/vm/lunakbd.cpp 2026/04/29 17:04:28 1.1.1.1 +++ nono/vm/lunakbd.cpp 2026/04/29 17:04:55 1.1.1.10 @@ -1,21 +1,48 @@ // // nono -// Copyright (C) 2018 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // +// キーボードとマウス +// +// キーボード/マウスからシリアルポートへの入力は、1バイトの送信自体に約1msec +// (9600bps、スタートビット1、ストップビット1) で送信後に 1msec 空けるそう +// なので、ここでは単純に 2msec を1スロットとする。 +// マウスは 20msec ごとにサンプリングして連続する3バイトを送り、 +// キーボードは任意のスロットで 1バイト送ってくるので、概念的には +// 10スロットのうち 0, 1, 2 をマウスが、3..9 をキーボードが使用する +// ということにする。 + #include "lunakbd.h" #include "sio.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"; + key_event.func = (DeviceCallback_t)&LunaKeyboard::KeyboardCallback; + key_event.time = 0; + key_event.SetName("Keyboard Slot"); + + // マウスイベント#0 はサンプリング用も兼ねているので常にオン + mouse_event[0].dev = this; + mouse_event[0].func = + (DeviceCallback_t)&LunaKeyboard::MouseSamplingCallback; + mouse_event[0].time = 20_msec; + mouse_event[0].SetName("Mouse Sampling"); + + for (int i = 1; i < 3; i++) { + mouse_event[i].dev = this; + mouse_event[i].func = (DeviceCallback_t)&LunaKeyboard::MouseCallback; + mouse_event[i].time = 2_msec * i; + mouse_event[i].SetName(string_format("Mouse Slot%d", i)); + } + + monitor.func = (MonitorCallback_t)&LunaKeyboard::MonitorUpdate; + monitor.SetSize(30, 1); + monitor.Regist(ID_MONITOR_KEYBOARD); } // デストラクタ @@ -23,75 +50,315 @@ LunaKeyboard::~LunaKeyboard() { } -// キーを押した +// 電源オン +bool +LunaKeyboard::PowerOn() +{ + // 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; + + return true; +} + +// リセット void -LunaKeyboard::MakeKey(uint keycode) +LunaKeyboard::ResetHard() { - uint lunakey; + inherited::ResetHard(); - // 共通キーコードから LUNA キーコードに - lunakey = keycode2lunakey(keycode); + // 動作中のタイマーは一旦とめる + key_event.Stop(); + for (int i = 0; i < countof(mouse_event); i++) { + mouse_event[i].Stop(); + } - // LUNA にないキーは無視 - if (lunakey == NoKey) { - return; + // で、マウスサンプリングは電源オン時から有効らしい + mouse_event[0].Start(); +} + +// モニター +void +LunaKeyboard::MonitorUpdate(Monitor *, TextScreen& screen) +{ + int x, y; + screen.Clear(); + + x = mouse_x; + y = mouse_y; + if (x < -999) + x = -999; + if (x > 999) + x = 999; + if (y < -999) + y = -999; + if (y > 999) + y = 999; + screen.Print(0, 0, "Mouse: x=%-4d y=%-4d %c %c %c", + x, y, + (mouse_l ? 'L' : '-'), + (mouse_m ? 'M' : '-'), + (mouse_r ? 'R' : '-')); +} + +// 共通キーコードを LUNA キーコードに変換して返す。 +// 対応する LUNA のキーがなければ 0 を返す。 +uint +LunaKeyboard::KeyToMDKey(uint keycode) const +{ + if (keycode >= countof(keycode2lunakey_table)) { + return 0; } - // すでにこのキーが押されていれば送らない - if (reptable[lunakey]) { + return keycode2lunakey_table[keycode]; +} + +// シフト状態なら 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); + + // 共通処理へ + if (KeyInputCommon(keystat) == false) { return; } - // キーが押された - putlog(1, "MakeKey %d(%s)", lunakey, keyname[lunakey]); - reptable[lunakey] = 1; + // LED キーの処理 + if (ismake) { + // LED はトグル? + int n = Keycode2LED(keycode); + if (n >= 0) { + led[n] = !led[n]; + } + } - // SIO に通知 - event.code = lunakey; - gScheduler->AddEvent(&event); + // 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; +} + +// マウス入力 void -LunaKeyboard::BreakKey(uint keycode) +LunaKeyboard::MouseInput(int x, int y, bool rb, bool lb, bool mb) { - uint lunakey; + // ホストからマウスイベントのたびに呼ばれるので、ここで積算。 + // 20msec ごとのサンプリングのタイミングで積算結果を処理する。 + // LUNA のマウスの Y 方向は上に動く方向がプラスなので、ホスト入力とは逆。 + mouse_x += x; + mouse_y -= y; + mouse_r = rb; + mouse_l = lb; + mouse_m = mb; +} - // 共通キーコードから LUNA キーコードに - lunakey = keycode2lunakey(keycode); +// キーイベントを起こせる空きスロットを計算して、イベントを開始 +void +LunaKeyboard::SendStart() +{ + // 例えば、スロット 4 期間内のどこかでキー入力(K)が起きると + // スロット 5 の先頭時刻にイベント(E)を起こす。 + // + // 通算スロット 44 45 46 + // スロット番号 4 5 6 + // +-----+-----+-----+.. + // | K--->E + // +-----+-----+-----+.. + // + // スロット 9 期間内のどこかでキー入力(K)が起きると、(0, 1, 2 はマウス用 + // のスロットにしているので、これを飛ばして) スロット 3 の先頭でイベント + // (E)を起こす。 + // + // 通算スロット 209 210 211 212 213 + // スロット番号 9 0 1 2 3 + // +-----+-----+-----+-----+-----+.. + // | K------------------>E + // +-----+-----+-----+-----+-----+.. - // LUNA にないキーは無視 - if (lunakey == NoKey) { + // イベントが進行中ならなにもせず帰る + if (key_event.IsRunning()) { return; } - // キーが離された - putlog(2, "BreakKey %d(%s)", lunakey, keyname[lunakey]); - reptable[lunakey] = 0; - - // SIO に通知 - event.code = lunakey | 0x80; - gScheduler->AddEvent(&event); + uint64 now = gMPU->GetVirtTime(); + + // t は現在の仮想時刻に対応する通算スロット + uint64 t = now / 2_msec; + // n はその次の通算スロット + uint64 n = t + 1; + + // マウススロットなら避ける + while (n % 10 < 3) { + n++; + } + + // n の時刻がイベントを起こす時刻 + uint64 target = n * 2_msec; + + // イベントを登録。 + key_event.time = target - now; + key_event.Start(); } -// 共通キーコードを LUNA キーコードに変換して返す。 -// LUNA キーがなければ NoKey を返す。 -uint -LunaKeyboard::keycode2lunakey(uint keycode) +// キー入力コールバック +// SIO に送信するタイミングで呼ばれる。 +void +LunaKeyboard::KeyboardCallback(Event& ev) { - if (keycode >= countof(keycode2lunakey_table)) { - return NoKey; + // 呼ばれたということはキューは空でないはず + assert(sendqueue.Empty() == false); + + // キューから取り出す。ここで取り出せるのは keystat + uint keystat = sendqueue.Dequeue(); + uint keycode = KC_CODE(keystat); + // ここで LUNA キーコードに変換 (変換できないはずはない) + uint mdkey = KeyToMDKey(keycode); + assert(mdkey != 0); + + // 送信 + if (KC_IS_BREAK(keystat)) { + mdkey |= 0x80; } + gSIO->Rx(1, mdkey); - return keycode2lunakey_table[keycode]; + if (Keycode2LED(keystat) >= 0) { + // かなと CAP キーの押下なら、続けてキーの復旧も送る。 + // 押下の時だけここに来ればいいので keycode じゃなく keystat。 + + // (BreakKey() が内部でもう一度イベントをキックしている) + BreakKey(keycode); + } + + // キューにまだあるならここで次のイベントをキックする必要がある + if (sendqueue.Empty() == false) { + SendStart(); + } +} + +// マウスサンプリングコールバック +// 20msec ごとに呼ばれる。 +void +LunaKeyboard::MouseSamplingCallback(Event& ev) +{ + int reset_x = 0; + int reset_y = 0; + + // 移動量±1は送信しないが次回に向けた積算は続けるそうだ。 + if (mouse_x == -1 || mouse_x == 1) { + reset_x = mouse_x; + mouse_x = 0; + } + if (mouse_y == -1 || mouse_y == 1) { + reset_y = mouse_y; + mouse_y = 0; + } + + // 移動量が±1 を超えるかボタンがどれかでも変化すれば送信。 + if (mouse_x != 0 || + mouse_y != 0 || + 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_r ? 'L' : '-'), + (mouse_m ? 'M' : '-'), + (mouse_l ? 'R' : '-')); + + // 1バイト目、ボタン状態。 + // mouse_[rlm] 変数は押し下げが 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; + + // 1バイト目はここで送信 + gSIO->Rx(1, b); + + // 2バイト目は 2msec 秒後に送信 + mouse_event[1].code = (uint8)mouse_x; + mouse_event[1].Start(); + + // 3バイト目は 4msec 秒後に送信 + mouse_event[2].code = (uint8)mouse_y; + mouse_event[2].Start(); + } else { + putlog(3, "Mouse Sampling inactive"); + } + + // 積算リセット + mouse_x = reset_x; + mouse_y = reset_y; + prev_r = mouse_r; + prev_l = mouse_l; + prev_m = mouse_m; + + // 20msec 後に再び自分を呼び出す + ev.Start(); } +// マウスの後続バイトを送出するイベントコールバック +void +LunaKeyboard::MouseCallback(Event& ev) +{ + gSIO->Rx(1, ev.code); +} + +// ホストからの制御 +void +LunaKeyboard::Command(uint32 data) +{ +} + + // キーコード変換表 // 共通キーコードから LUNA キーコードに変換する。 // ぶっちゃけた話共通キーコードは LUNA キーコードをベースにしているので、 // 穴がある以外は全部透過。 /*static*/ const uint -LunaKeyboard::keycode2lunakey_table[128] = { +LunaKeyboard::keycode2lunakey_table[0x80] = { NoKey, // [00] NoKey, // [01] NoKey, // [02] @@ -228,143 +495,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 -};