--- nono/vm/keyboard.cpp 2026/04/29 17:04:28 1.1 +++ nono/vm/keyboard.cpp 2026/04/29 17:05:06 1.1.1.9 @@ -1,18 +1,115 @@ // // nono -// Copyright (C) 2018 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // +// 物理キーボードとソフトウェアキーボードについて +// +// 物理キーボード入力 ---------->+ +// OR --> VM Keyboard ---> VM (MFP/SIO) +// ソフトウェアキーボード 入力 ->+ | +// | +// ソフトウェアキーボード 表示 <-------------+ +// +// 物理キーボードとソフトウェアキーボードからの入力は並列で対等とする。 +// つまり優劣を付けず後から成立したイベントで現在の状態を上書きする。 +// これは特に物理キーボードから [ALT]+[TAB] でウィンドウを切り替えた際に +// [TAB] の Make は発生するが Break が VM に届かない (そのため高速モード +// が抑制されたままになる) という事態をソフトウェアキーボードから復旧する +// ために必要。 +// +// 例えば、ソフトウェアキーボードであるキーを(右クリックで)長押ししたまま +// (A)、物理キーボードで同じキーを押して(B)離す(C)と、まず A で Make が +// 成立、B は無視して (この時点ですでに Make 状態なので)、C で Break が +// 成立、となる。 +// Break が成立したのでソフトウェアキーボードの描画上もキー開放になる。 +// 逆に物理キーボードであるキーを押したまま(A)、ソフトウェアキーボードで +// 同じキーをクリックする、つまり押して(B)離す(C) と、まず A で Make が +// 成立、B は無視され、C で Break が成立までは同じだが、引き続き物理キー +// が押されたままならおそらくホストの動作によってもう一度 Make が入ること +// になる。この動作は(望ましいかどうかはともかく)仕様として許容する。 +// +// また、ソフトウェアキーボードの表示は上図の通り、ソフトウェアキーボード +// 自身(のみ)の状態ではなく VM Keyboard 全体の現状を表示する表示器とする。 + +// SHIFT キーについて +// 物理キーボード層、ソフトウェアキーボード層、VM の Keyboard クラスは +// いずれも左右の [SHIFT] キーは別個のキーとして管理しているが、X680x0 の +// キーボードは左右の [SHIFT] キーが独立しておらず一つのキーのように +// 振る舞っている(はず)。 +// +// ヒューマンインタフェース的には例えば、 +// 物理キーボードで 左[SHIFT]押下(A) -> 右[SHIFT]押下(B) -> 右[SHIFT]開放 +// (C) -> 左[SHIFT]開放(D) の順で操作すると、A で Make が成立、B は無視 +// ([SHIFT] はすでに押されているので)、C は無視([SHIFT] はまだ押されてる +// ので)、D で Break が成立、となってほしい。これは明らかに人間がそのように +// キーを操作しているため。 +// 一方、ソフトウェアキーボードでは、右[SHIFT]を右クリック(押下維持)する +// と両方の [SHIFT] キーが点灯する。この状態で左[SHIFT] を左クリック +// (押下後即開放)すると、両方の [SHIFT] が開放されてほしい。これは、 +// ユーザからはどちらのキーが押下維持で点灯していて、どちらが押されてない +// のに付随点灯しているかは区別できないため。 +// +// このことから、 +// o 物理キー入力は左右 [SHIFT] を区別する +// o ソフトウェアキーボードは左右どちらのキーに対する操作でも、内部では +// 左右両方の [SHIFT] を押下開放する +// o この結果、右 [SHIFT] と左 [SHIFT] の OR を代表値とし、 +// MFP への通知や表示(左右の [SHIFT] は常に連動) を行う。 +// でどうか。 +// +// 物理 [SHIFT] キーを押し続けたまま、ソフトウェアキーボードから左クリック +// (押下後即開放) すると、物理キーを押しているにも関わらず [SHIFT] キーは +// 開放される。これは上述の通り仕様である。 +// +// LUNA のキーボードは左右の SHIFT キーの区別がある。このため、 LUNA は +// 特別処理をする必要はない。 +// ただしゲスト上の入力ルーチンがこの区別をしているかは別問題である。 + +// 処理の流れ (SendStart まで) +// +// MakeKey(), BreakKey() までは基本的に VM スレッドに処理を渡すだけ。 +// +// UI スレッド : VM スレッド +// +// キャラクタ入力 : +// | +// 直接キー入力 | : +// | v +// | CharInput() : +// | | +// +------------+ : +// v +// MakeKey()/BreakKey() : +// | +// | inputqueue + : +// | keyinput_event +// | - - - - - - - - - - - -> KeyInputCallback() +// o | +// : MD::KeyInput() +// | +// : KeyInputCommon() +// | +// : MD::SendStart() +// v +// 機種ごとに接続先に送信 + #include "keyboard.h" -#include "mystring.h" +#include "scheduler.h" +#include "uimessage.h" -Keyboard *gKeyboard; +std::unique_ptr gKeyboard; // コンストラクタ Keyboard::Keyboard() + : inherited("Keyboard") { - logname = "keyboard"; - devname = "Keyboard"; + pressed.resize(KC_max); + + keyinput_event.func = (DeviceCallback_t)&Keyboard::KeyInputCallback; + keyinput_event.time = 0; + keyinput_event.SetName("Keyboard Input"); } // デストラクタ @@ -20,17 +117,219 @@ Keyboard::~Keyboard() { } +// リセット +void +Keyboard::ResetHard() +{ + // 内部状態を全部リセット + + inputqueue.Clear(); + sendqueue.Clear(); + std::fill(pressed.begin(), pressed.end(), false); + active = 0; + // キーが全部解放されたので通知 + gScheduler->RequestKeyPressed(false); +} + +// キーを押した (外部から、共通キーコードで呼ばれる) +void +Keyboard::MakeKey(uint keycode) +{ + // ここは UI スレッドからも呼ばれるのでキューに突っ込むだけ + { + std::unique_lock lock(mtx); + inputqueue.Enqueue(keycode | KC_FLAG_MAKE); + } + keyinput_event.Start(); +} + +// キーを離した (外部から、共通キーコードで呼ばれる) +void +Keyboard::BreakKey(uint keycode) +{ + // ここは UI スレッドからも呼ばれるのでキューに突っ込むだけ + { + std::unique_lock lock(mtx); + inputqueue.Enqueue(keycode | KC_FLAG_BREAK); + } + keyinput_event.Start(); +} + +// 文字による入力 +// WX での文字入力モード、CLI からの入力、ターミナルエミュレーションでの +// 入力によりキーボードの押し下げをエミュレートする。 +void +Keyboard::CharInput(uint charcode) +{ + if (charcode >= char_key.size()) { + return; + } + + 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::KeyInputCallback(Event& ev) +{ + for (;;) { + uint keystat; + { + std::unique_lock lock(mtx); + if (inputqueue.Empty()) { + return; + } + keystat = inputqueue.Dequeue(); + } + KeyInput(keystat); + } +} + +// キー入力を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(keystat) == 0) { + return false; + } + + // このキーの状態が変化しないのなら何もしない。 + if (IsPressed(keycode) == ismake) { + return false; + } + + // 処理することが確定したのでログ + // (離すほうは押すほうよりログレベルを1つ上げてある) + if (loglevel >= 1) { + std::string msg; + + msg += string_format("%d(", 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; + if (ismake) { + Acquire(); + } else { + Release(); + } + + // キーの状態が変わったので 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; + } + + if (ledflag != KC_FLAG_LED_DROP) { + // 送信キューに登録 + + // ここでは変換できるはず + uint mdkey = KeyToMDKey(keystat); + sendqueue.Enqueue(mdkey); + + // 機種依存のキー送信処理 + SendStart(); + } + + return true; +} + +// アクティブカウンタを 1 増やす。 +// 0 -> 1 の遷移でスケジューラに通知する。 +void +Keyboard::Acquire() +{ + if (active++ == 0) { + gScheduler->RequestKeyPressed(true); + } +} + +// アクティブカウンタを 1 減らす。 +// 1 -> 0 の遷移でスケジューラに通知する。 +void +Keyboard::Release() +{ + if (--active == 0) { + gScheduler->RequestKeyPressed(false); + } +} + // キー名を返す (主にデバッグ用) const std::string -Keyboard::GetName(uint code) +Keyboard::GetKeyName(uint keycode) { - if (code >= keyname.size()) { - return string_format("(keycode=0x%x, out of range)", code); + if (keycode >= keyname.size()) { + return string_format("(keycode=0x%x, out of range)", keycode); } - if (keyname[code] == NULL) { - return string_format("(keycode=0x%x)", code); + if (keyname[keycode] == NULL) { + return string_format("(keycode=0x%x)", keycode); } - return std::string(keyname[code]); + return std::string(keyname[keycode]); } /*static*/ std::vector @@ -43,7 +342,7 @@ Keyboard::keyname { "kigou", // 05 "touroku", // 06 "HELP", // 07 - "HOME", // 08 + "SHIFT", // 08 "TAB", // 09 "CTRL", // 0a "kana", // 0b @@ -84,7 +383,7 @@ Keyboard::keyname { "minus", // 2c "circum", // 2d "backslash", // 2e - "UNDO", // 2f + NULL, // 2f "OPT1", // 30 "OPT2", // 31 @@ -103,8 +402,8 @@ Keyboard::keyname { "hira", // 3e "zenkaku", // 3f - NULL, // 40 - NULL, // 41 + "HOME", // 40 + "UNDO", // 41 "A", // 42 "S", // 43 "D", // 44 @@ -120,8 +419,8 @@ Keyboard::keyname { NULL, // 4e NULL, // 4f - NULL, // 50 - NULL, // 51 + "XF1", // 50 + "XF2", // 51 "Z", // 52 "X", // 53 "C", // 54 @@ -133,9 +432,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 @@ -170,10 +469,162 @@ Keyboard::keyname { "PAD_divide", // 7d "PAD_equal", // 7e "PAD_comma", // 7f +}; - "XF1", // 80 - "XF2", // 81 - "XF3", // 82 - "XF4", // 83 - "XF5", // 84 +// 文字対キーストローク表 +// 文字による入力エミュレーションで使用。 +/*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 };