--- nono/vm/keyboard.cpp 2026/04/29 17:04:52 1.1.1.5 +++ nono/vm/keyboard.cpp 2026/04/29 17:05:38 1.1.1.15 @@ -4,6 +4,10 @@ // Licensed under nono-license.txt // +// +// キーボード (基本クラス) +// + // 物理キーボードとソフトウェアキーボードについて // // 物理キーボード入力 ---------->+ @@ -62,28 +66,70 @@ // 物理 [SHIFT] キーを押し続けたまま、ソフトウェアキーボードから左クリック // (押下後即開放) すると、物理キーを押しているにも関わらず [SHIFT] キーは // 開放される。これは上述の通り仕様である。 +// +// LUNA のキーボードは左右の SHIFT キーの区別がある。このため、 LUNA は +// 特別処理をする必要はない。 +// ただしゲスト上の入力ルーチンがこの区別をしているかは別問題である。 -// LUNA のキーボードは左右の SHIFT キーの区別がある。このため、 LUNA は -// 特別処理をする必要はない。 -// ただしゲスト上の入力ルーチンがこの区別をしているかは別問題である。 +// 処理の流れ (SendStart まで) +// +// MakeKey(), BreakKey() までは基本的に VM スレッドに処理を渡すだけ。 +// +// UI スレッド : VM スレッド +// +// キャラクタ入力 : +// | +// 直接キー入力 | : +// | v +// | CharInput() : +// | | +// +------------+ : +// v +// MakeKey()/BreakKey() : +// | +// | KEY_INPUT Message : +// | - - - - - - - - - - - -> KeyInputMessage() +// o | +// : MD::KeyInput() +// | +// : KeyInputCommon() +// | +// : MD::SendStart() +// v +// 機種ごとに接続先に送信 #include "keyboard.h" +#include "config.h" #include "scheduler.h" +#include "syncer.h" +#include "uimessage.h" +#include -std::unique_ptr gKeyboard; +// +// 文字入力しか持たないキーボードの下位クラス +// // コンストラクタ -Keyboard::Keyboard() +DumbKeyboard::DumbKeyboard() + : inherited(OBJ_KEYBOARD) { - logname = "keyboard"; - devname = "Keyboard"; +} - pressed.resize(KC_max); +// デストラクタ +DumbKeyboard::~DumbKeyboard() +{ +} - keyinput_event.dev = this; - keyinput_event.func = (DeviceCallback_t)&Keyboard::KeyInputCallback; - keyinput_event.time = 0; - keyinput_event.SetName("Keyboard Input"); + +// +// 通常のキーボード +// + +// コンストラクタ +Keyboard::Keyboard() + : inherited() +{ + pressed.resize(KC_max); } // デストラクタ @@ -91,127 +137,288 @@ Keyboard::~Keyboard() { } -// リセット +// 初期化 +bool +Keyboard::Init() +{ + syncer = GetSyncer(); + + scheduler->ConnectMessage(MessageID::KEY_INPUT, this, + ToMessageCallback(&Keyboard::KeyInputMessage)); + return true; +} + +// 電源オン/リセット void -Keyboard::ResetHard() +Keyboard::ResetHard(bool poweron) { - // 内部状態を全部リセット + if (poweron) { + is_connected = gConfig->Find("keyboard-connect").AsInt(); + + if (is_connected) { + DoConnect(); + } else { + DoDisconnect(); + } - inputqueue.Clear(); + // キー通知 + syncer->RequestKeyPressed(Any()); + } + + // 内部状態をリセット sendqueue.Clear(); +} + +// 電源オフ +void +Keyboard::PowerOff() +{ + // LED は全部消灯 + std::fill(led.begin(), led.end(), false); + + // キーボードから手を離したことにする std::fill(pressed.begin(), pressed.end(), false); - num_of_pressed = 0; + // キーが全部解放されたので通知 - gScheduler->RequestKeyPressed(false); + syncer->RequestKeyPressed(false); + + DoDisconnect(); +} + +// キーボードを接続する (外部から呼ばれる) +void +Keyboard::Connect(bool connect) +{ + uint code = connect ? CC_connect : CC_disconnect; + + // ここは UI スレッドからも呼ばれるのでメッセージを送る + scheduler->SendMessage(MessageID::KEY_INPUT, code); } // キーを押した (外部から、共通キーコードで呼ばれる) void Keyboard::MakeKey(uint keycode) { - // ここは UI スレッドからも呼ばれるのでキューに突っ込むだけ - { - std::unique_lock lock(mtx); - inputqueue.Enqueue(keycode | KC_FLAG_MAKE); - } - keyinput_event.Start(); + keycode |= KC_FLAG_MAKE; + + // ここは UI スレッドからも呼ばれるのでメッセージを送る + scheduler->SendMessage(MessageID::KEY_INPUT, keycode); } // キーを離した (外部から、共通キーコードで呼ばれる) void Keyboard::BreakKey(uint keycode) { - // ここは UI スレッドからも呼ばれるのでキューに突っ込むだけ - { - std::unique_lock lock(mtx); - inputqueue.Enqueue(keycode | KC_FLAG_BREAK); - } - keyinput_event.Start(); + keycode |= KC_FLAG_BREAK; + + // ここは UI スレッドからも呼ばれるのでメッセージを送る + scheduler->SendMessage(MessageID::KEY_INPUT, keycode); } -// キー入力コールバック +// 文字による入力 +// WX での文字入力モード、CLI からの入力、ターミナルエミュレーションでの +// 入力によりキーボードの押し下げをエミュレートする。 void -Keyboard::KeyInputCallback(Event& ev) +Keyboard::CharInput(uint charcode) { + if (charcode >= char_key.size()) { + return; + } - for (;;) { - uint keystat; - { - std::unique_lock lock(mtx); - if (inputqueue.Empty()) { - return; - } - keystat = inputqueue.Dequeue(); - } + uint key = char_key[charcode]; + uint code = KC_CODE(key); + if ((key & KM_SHIFT)) { + MakeKey(KC_SHIFT_L | KC_FLAG_POST); + MakeKey(KC_SHIFT_R | KC_FLAG_POST); + } else { + BreakKey(KC_SHIFT_L); + BreakKey(KC_SHIFT_R); + } + if ((key & KM_CTRL)) { + MakeKey(KC_CTRL | KC_FLAG_POST); + } else { + BreakKey(KC_CTRL); + } + MakeKey(code | KC_FLAG_POST); + BreakKey(code); + if ((key & KM_CTRL)) { + BreakKey(KC_CTRL); + } + if ((key & KM_SHIFT)) { + BreakKey(KC_SHIFT_L); + BreakKey(KC_SHIFT_R); + } +} + +// キー入力メッセージコールバック +void +Keyboard::KeyInputMessage(MessageID msgid, uint32 arg) +{ + uint keystat = arg; + switch (keystat) { + case CC_connect: + DoConnect(); + break; + case CC_disconnect: + DoDisconnect(); + break; + default: KeyInput(keystat); + break; } } -// キー入力を1つ処理する +// キーボードの接続処理の実体。 void -Keyboard::KeyInput(uint keystat) +Keyboard::DoConnect() { - uint keycode = KC_CODE(keystat); - bool ismake = KC_IS_MAKE(keystat); + is_connected = true; - assert(keycode < KC_max); + MDConnect(); - // キーのプリプロセス - keystat = PreprocessKey(keystat); - keycode = KC_CODE(keystat); - if (keycode == KC_none) { - return; + // LED は全部消灯で初期化 + std::fill(led.begin(), led.end(), false); + + // 接続されたので押しっぱなしになっているキーをスキャンして送る + for (int i = 0; i < pressed.size(); i++) { + if (pressed[i]) { + KeyInput(i | KC_FLAG_BREAK); + KeyInput(i); + } } + // UI に通知する + UIMessage::Post(UIMessage::KEYBOARD); +} + +void +Keyboard::DoDisconnect() +{ + is_connected = false; + + // 電源が切れたので LED は全部消す + std::fill(led.begin(), led.end(), false); + + MDDisconnect(); + + // UI に通知する + UIMessage::Post(UIMessage::KEYBOARD); +} + +// キー入力を1つ処理する。 +// keystat が押下・開放されたキーの物理的な状態。 +// キー入力を受け取らずに破棄した場合は false を返す。 +bool +Keyboard::KeyInputCommon(uint keystat) +{ + uint keycode = KC_CODE(keystat); + bool ismake = KC_IS_MAKE(keystat); + uint ledflag = KC_FLAG_LED(keystat); + // 対応する機種別キーコードがなければ無視。 // (ここでは調べるだけでまだ変換はしない) - if (KeyToMDKey(keycode) == 0) { - return; + if (KeyToMDKey(keystat) == 0) { + return false; } // このキーの状態が変化しないのなら何もしない。 - // X680x0 のキーリピートは X680x0Keyboard 側で処理する。 - // LUNA のキーボードにはリピートがないので、ここで何もしないだけでいい。 if (IsPressed(keycode) == ismake) { - return; - } - - // MD 固有の処理(?) - if (ProcessKey(keystat) == false) { - return; + return false; } // 処理することが確定したのでログ // (離すほうは押すほうよりログレベルを1つ上げてある) - if (ismake) { - putlog(1, "MakeKey %d(%s)", keycode, GetName(keycode).c_str()); - } else { - putlog(2, "BreakKey %d(%s)", keycode, GetName(keycode).c_str()); + if (loglevel >= 1) { + std::string msg; + + msg += string_format("%u(", keycode); + msg += GetKeyName(keycode); + msg += ")"; + + if (ledflag) { + // LUNA の LED キーの場合、sendstat も明示 + if (ledflag == KC_FLAG_LED_DROP) { + msg += ", drop"; + } else { + msg += ", send "; + if (ledflag == KC_FLAG_LED_MAKE) { + msg += "Make"; + } else { + msg += "Break"; + } + } + } + + if (ismake) { + putlogn( "MakeKey %s", msg.c_str()); + } else { + putlog(2, "BreakKey %s", msg.c_str()); + } } - // キーが押されてる間は VM の高速実行を抑制する - // すべてのキーが離されたら高速モードを許可する - pressed[keycode] = ismake; + // キーがどれか1つでも押されてる間は VM の高速実行を抑制する。 + // すべてのキーが離されたら高速モードを許可する。 if (ismake) { - if (num_of_pressed++ == 0) { - gScheduler->RequestKeyPressed(true); + bool prev = Any(); + pressed[keycode] = true; + if (prev == false) { + syncer->RequestKeyPressed(true); } } else { - if (--num_of_pressed == 0) { - gScheduler->RequestKeyPressed(false); + pressed[keycode] = false; + if (Any() == false) { + syncer->RequestKeyPressed(false); } } - // 送信キューに登録 - sendqueue.Enqueue(keystat); + // キーの状態が変わったので UI に通知。 + // KC_FLAG_POST が立っていればキーコードも送信 (char 入力モード用)。 + // 通知を受け取った UI 側が IsPressed() などでキーの現状を調べることが + // あるため pressed[] 更新後に通知する。 + // XXX LED 変更が間に合わない (see lunakbd.cpp) + uint arg; + if (KC_IS_POST(keystat)) { + arg = keycode; + } else { + arg = 0; + } + UIMessage::Post(UIMessage::KEYBOARD, arg); + + // LUNA の LED キー消灯はキー開放を送信 + if (ledflag == KC_FLAG_LED_BREAK) { + keystat |= KC_FLAG_BREAK; + } + + // キーボードが接続されていなければここまで処理して false を返す + if (is_connected == false) { + return false; + } + + if (ledflag != KC_FLAG_LED_DROP) { + // 送信キューに登録 + + // ここでは変換できるはず + uint mdkey = KeyToMDKey(keystat); + sendqueue.Enqueue(mdkey); - // 機種依存のキー送信処理 - SendStart(); + // 機種依存のキー送信処理 + SendStart(); + } + + return true; +} + +// キーが1つでも押されていれば true を返す。 +bool +Keyboard::Any() const +{ + auto it = std::find(pressed.begin(), pressed.end(), true); + return (it != pressed.end()); } // キー名を返す (主にデバッグ用) const std::string -Keyboard::GetName(uint keycode) +Keyboard::GetKeyName(uint keycode) { if (keycode >= keyname.size()) { return string_format("(keycode=0x%x, out of range)", keycode); @@ -232,7 +439,7 @@ Keyboard::keyname { "kigou", // 05 "touroku", // 06 "HELP", // 07 - "HOME", // 08 + "SHIFT", // 08 "TAB", // 09 "CTRL", // 0a "kana", // 0b @@ -273,7 +480,7 @@ Keyboard::keyname { "minus", // 2c "circum", // 2d "backslash", // 2e - "UNDO", // 2f + "", // 2f "OPT1", // 30 "OPT2", // 31 @@ -292,8 +499,8 @@ Keyboard::keyname { "hira", // 3e "zenkaku", // 3f - NULL, // 40 - NULL, // 41 + "HOME", // 40 + "UNDO", // 41 "A", // 42 "S", // 43 "D", // 44 @@ -309,8 +516,8 @@ Keyboard::keyname { NULL, // 4e NULL, // 4f - NULL, // 50 - NULL, // 51 + "XF1", // 50 + "XF2", // 51 "Z", // 52 "X", // 53 "C", // 54 @@ -322,9 +529,9 @@ Keyboard::keyname { "period", // 5a "slash", // 5b "underscore", // 5c - NULL, // 5d - NULL, // 5e - NULL, // 5f + "XF3", // 5d + "XF4", // 5e + "XF5", // 5f NULL, // 60 "PAD_plus", // 61 @@ -359,11 +566,162 @@ Keyboard::keyname { "PAD_divide", // 7d "PAD_equal", // 7e "PAD_comma", // 7f +}; - "XF1", // 80 - "XF2", // 81 - "XF3", // 82 - "XF4", // 83 - "XF5", // 84 - "SHIFT", // 85 +// 文字対キーストローク表 +// 文字による入力エミュレーションで使用。 +/*static*/ std::vector +Keyboard::char_key = { + 0, // NONE + KC_A + KM_CTRL, // 0x01 ^A + KC_B + KM_CTRL, // 0x02 ^B + KC_C + KM_CTRL, // 0x03 ^C + KC_D + KM_CTRL, // 0x04 ^D + KC_E + KM_CTRL, // 0x05 ^E + KC_F + KM_CTRL, // 0x06 ^F + KC_G + KM_CTRL, // 0x07 ^G + KC_BS, // 0x08 ^H + KC_TAB, // 0x09 ^I + KC_J + KM_CTRL, // 0x0a ^J + KC_K + KM_CTRL, // 0x0b ^K + KC_L + KM_CTRL, // 0x0c ^L + KC_enter, // 0x0d ^M + KC_N + KM_CTRL, // 0x0e ^N + KC_O + KM_CTRL, // 0x0f ^O + KC_P + KM_CTRL, // 0x10 ^P + KC_Q + KM_CTRL, // 0x11 ^Q + KC_R + KM_CTRL, // 0x12 ^R + KC_S + KM_CTRL, // 0x13 ^S + KC_T + KM_CTRL, // 0x14 ^T + KC_U + KM_CTRL, // 0x15 ^U + KC_V + KM_CTRL, // 0x16 ^V + KC_W + KM_CTRL, // 0x17 ^W + KC_X + KM_CTRL, // 0x18 ^X + KC_Y + KM_CTRL, // 0x19 ^Y + KC_Z + KM_CTRL, // 0x1a ^Z + + KC_ESC, // 0x1b ^[ + KC_backslash + KM_CTRL, // 0x1c ^'\' + KC_bracketright + KM_CTRL, // 0x1d ^] + KC_circum + KM_CTRL, // 0x1e ^^ + KC_underscore + KM_CTRL, // 0x1f ^_ + + KC_space, // 0x20 ' ' + KC_1 + KM_SHIFT, // 0x21 ! + KC_2 + KM_SHIFT, // 0x22 " + KC_3 + KM_SHIFT, // 0x23 # + KC_4 + KM_SHIFT, // 0x24 $ + KC_5 + KM_SHIFT, // 0x25 % + KC_6 + KM_SHIFT, // 0x26 & + KC_7 + KM_SHIFT, // 0x27 ' + KC_8 + KM_SHIFT, // 0x28 ( + KC_9 + KM_SHIFT, // 0x29 ) + KC_colon + KM_SHIFT, // 0x2a * + KC_semicolon + KM_SHIFT, // 0x2b + + KC_comma, // 0x2c , + KC_minus, // 0x2d - + KC_period, // 0x2e . + KC_slash, // 0x2f / + + KC_0, // 0x30 0 + KC_1, // 0x31 1 + KC_2, // 0x32 2 + KC_3, // 0x33 3 + KC_4, // 0x34 4 + KC_5, // 0x35 5 + KC_6, // 0x36 6 + KC_7, // 0x37 7 + KC_8, // 0x38 8 + KC_9, // 0x39 9 + KC_colon, // 0x3a : + KC_semicolon, // 0x3b ; + KC_comma + KM_SHIFT, // 0x3c < + KC_minus + KM_SHIFT, // 0x3d = + KC_period + KM_SHIFT, // 0x3e > + KC_slash + KM_SHIFT, // 0x3f ? + + KC_at, // 0x40 @ + KC_A + KM_SHIFT, // 0x41 A + KC_B + KM_SHIFT, // 0x42 B + KC_C + KM_SHIFT, // 0x43 C + KC_D + KM_SHIFT, // 0x44 D + KC_E + KM_SHIFT, // 0x45 E + KC_F + KM_SHIFT, // 0x46 F + KC_G + KM_SHIFT, // 0x47 G + KC_H + KM_SHIFT, // 0x48 H + KC_I + KM_SHIFT, // 0x49 I + KC_J + KM_SHIFT, // 0x4a J + KC_K + KM_SHIFT, // 0x4b K + KC_L + KM_SHIFT, // 0x4c L + KC_M + KM_SHIFT, // 0x4d M + KC_N + KM_SHIFT, // 0x4e N + KC_O + KM_SHIFT, // 0x4f O + KC_P + KM_SHIFT, // 0x50 P + KC_Q + KM_SHIFT, // 0x51 Q + KC_R + KM_SHIFT, // 0x52 R + KC_S + KM_SHIFT, // 0x53 S + KC_T + KM_SHIFT, // 0x54 T + KC_U + KM_SHIFT, // 0x55 U + KC_V + KM_SHIFT, // 0x56 V + KC_W + KM_SHIFT, // 0x57 W + KC_X + KM_SHIFT, // 0x58 X + KC_Y + KM_SHIFT, // 0x59 Y + KC_Z + KM_SHIFT, // 0x5a Z + + KC_bracketleft, // 0x5b [ + KC_backslash, // 0x5c '\' + KC_bracketright, // 0x5d ] + KC_circum, // 0x5e ^ + KC_underscore + KM_SHIFT, // 0x5f _ + + KC_at + KM_SHIFT, // 0x60 ` + KC_A, // 0x61 a + KC_B, // 0x62 b + KC_C, // 0x63 c + KC_D, // 0x64 d + KC_E, // 0x65 e + KC_F, // 0x66 f + KC_G, // 0x67 g + KC_H, // 0x68 h + KC_I, // 0x69 i + KC_J, // 0x6a j + KC_K, // 0x6b k + KC_L, // 0x6c l + KC_M, // 0x6d m + KC_N, // 0x6e n + KC_O, // 0x6f o + KC_P, // 0x70 p + KC_Q, // 0x71 q + KC_R, // 0x72 r + KC_S, // 0x73 s + KC_T, // 0x74 t + KC_U, // 0x75 u + KC_V, // 0x76 v + KC_W, // 0x77 w + KC_X, // 0x78 x + KC_Y, // 0x79 y + KC_Z, // 0x7a z + + KC_bracketleft + KM_SHIFT, // 0x7b { + KC_backslash + KM_SHIFT, // 0x7c | + KC_bracketright + KM_SHIFT, // 0x7d } + KC_circum + KM_SHIFT, // 0x7e ~ + KC_none, // 0x7f DEL + + KC_at + KM_CTRL, // 0x80 CC_NUL + KC_F1, // 0x81 CC_F1 + KC_F2, // 0x82 CC_F2 + KC_F3, // 0x83 CC_F3 + KC_F4, // 0x84 CC_F4 + KC_F5, // 0x85 CC_F5 + KC_F6, // 0x86 CC_F6 + KC_F7, // 0x87 CC_F7 + KC_F8, // 0x88 CC_F8 + KC_F9, // 0x89 CC_F9 + KC_F10, // 0x8a CC_F10 + 0, // 0x8b + KC_up, // 0x8c CC_up + KC_left, // 0x8d CC_left + KC_right, // 0x8e CC_right + KC_down, // 0x8f CC_down };