--- nono/vm/keyboard.cpp 2026/04/29 17:04:28 1.1 +++ nono/vm/keyboard.cpp 2026/04/29 17:04:52 1.1.1.5 @@ -1,18 +1,89 @@ // // 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 は +// 特別処理をする必要はない。 +// ただしゲスト上の入力ルーチンがこの区別をしているかは別問題である。 + #include "keyboard.h" -#include "mystring.h" +#include "scheduler.h" -Keyboard *gKeyboard; +std::unique_ptr gKeyboard; // コンストラクタ Keyboard::Keyboard() { logname = "keyboard"; devname = "Keyboard"; + + pressed.resize(KC_max); + + keyinput_event.dev = this; + keyinput_event.func = (DeviceCallback_t)&Keyboard::KeyInputCallback; + keyinput_event.time = 0; + keyinput_event.SetName("Keyboard Input"); } // デストラクタ @@ -20,17 +91,135 @@ Keyboard::~Keyboard() { } +// リセット +void +Keyboard::ResetHard() +{ + // 内部状態を全部リセット + + inputqueue.Clear(); + sendqueue.Clear(); + std::fill(pressed.begin(), pressed.end(), false); + num_of_pressed = 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(); +} + +// キー入力コールバック +void +Keyboard::KeyInputCallback(Event& ev) +{ + + for (;;) { + uint keystat; + { + std::unique_lock lock(mtx); + if (inputqueue.Empty()) { + return; + } + keystat = inputqueue.Dequeue(); + } + KeyInput(keystat); + } +} + +// キー入力を1つ処理する +void +Keyboard::KeyInput(uint keystat) +{ + uint keycode = KC_CODE(keystat); + bool ismake = KC_IS_MAKE(keystat); + + assert(keycode < KC_max); + + // キーのプリプロセス + keystat = PreprocessKey(keystat); + keycode = KC_CODE(keystat); + if (keycode == KC_none) { + return; + } + + // 対応する機種別キーコードがなければ無視。 + // (ここでは調べるだけでまだ変換はしない) + if (KeyToMDKey(keycode) == 0) { + return; + } + + // このキーの状態が変化しないのなら何もしない。 + // X680x0 のキーリピートは X680x0Keyboard 側で処理する。 + // LUNA のキーボードにはリピートがないので、ここで何もしないだけでいい。 + if (IsPressed(keycode) == ismake) { + return; + } + + // MD 固有の処理(?) + if (ProcessKey(keystat) == false) { + return; + } + + // 処理することが確定したのでログ + // (離すほうは押すほうよりログレベルを1つ上げてある) + if (ismake) { + putlog(1, "MakeKey %d(%s)", keycode, GetName(keycode).c_str()); + } else { + putlog(2, "BreakKey %d(%s)", keycode, GetName(keycode).c_str()); + } + + // キーが押されてる間は VM の高速実行を抑制する + // すべてのキーが離されたら高速モードを許可する + pressed[keycode] = ismake; + if (ismake) { + if (num_of_pressed++ == 0) { + gScheduler->RequestKeyPressed(true); + } + } else { + if (--num_of_pressed == 0) { + gScheduler->RequestKeyPressed(false); + } + } + + // 送信キューに登録 + sendqueue.Enqueue(keystat); + + // 機種依存のキー送信処理 + SendStart(); +} + // キー名を返す (主にデバッグ用) const std::string -Keyboard::GetName(uint code) +Keyboard::GetName(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 @@ -176,4 +365,5 @@ Keyboard::keyname { "XF3", // 82 "XF4", // 83 "XF5", // 84 + "SHIFT", // 85 };