--- nono/vm/lunakbd.cpp 2026/04/29 17:04:42 1.1.1.6 +++ nono/vm/lunakbd.cpp 2026/04/29 17:05:50 1.1.1.17 @@ -4,46 +4,32 @@ // Licensed under nono-license.txt // -// キーボードとマウス // +// LUNA のキーボードとマウス +// + // キーボード/マウスからシリアルポートへの入力は、1バイトの送信自体に約1msec // (9600bps、スタートビット1、ストップビット1) で送信後に 1msec 空けるそう // なので、ここでは単純に 2msec を1スロットとする。 -// マウスは 20msec ごとにサンプリングして連続する3バイトを送り、 -// キーボードは任意のスロットで 1バイト送ってくるので、概念的には -// 10スロットのうち 0, 1, 2 をマウスが、3..9 をキーボードが使用する -// ということにする。 +// +// ブザー発声はキー入力とは本来あまり関係ないが、ブザー発声中もキー入力中と +// 同様に高速走行を抑制したいため、キー扱いとしていることに留意。 #include "lunakbd.h" -#include "mystring.h" +#include "event.h" +#include "monitor.h" +#include "scheduler.h" #include "sio.h" +#include "uimessage.h" // コンストラクタ LunaKeyboard::LunaKeyboard() { - monitor_size = nnSize(30, 1); - - memset(reptable, 0, sizeof(reptable)); led.resize(2); - key_event.dev = this; - key_event.func = (DeviceCallback_t)&LunaKeyboard::KeyboardCallback; - key_event.time = 0; - key_event.SetName("KeyInput"); - - // マウスイベント#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 = gMonitorManager->Regist(ID_MONITOR_KEYBOARD, this); + monitor->func = ToMonitorCallback(&LunaKeyboard::MonitorUpdate); + monitor->SetSize(40, 4); } // デストラクタ @@ -51,154 +37,214 @@ LunaKeyboard::~LunaKeyboard() { } -// リセット -void -LunaKeyboard::ResetHard() +// 初期化 +bool +LunaKeyboard::Init() { - // 動作中のタイマーは一旦とめる - key_event.Stop(); - for (int i = 0; i < countof(mouse_event); i++) { - mouse_event[i].Stop(); + if (inherited::Init() == false) { + return false; } - // で、マウスサンプリングは電源オン時から有効らしい - mouse_event[0].Start(); + // 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::MonitorUpdate(TextScreen& monitor) +LunaKeyboard::MDConnect() { - int x, y; - monitor.Clear(); + // 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); - 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; - monitor.Print(0, 0, "Mouse: x=%-4d y=%-4d %c %c %c", - x, y, - (mouse_l ? 'L' : '-'), - (mouse_m ? 'M' : '-'), - (mouse_r ? 'R' : '-')); + // デフォルトでマウス有効 + MouseOn(); } -// LUNA キーコードから LED 番号を返す。LED キーでなければ -1 を返す。 -int -LunaKeyboard::LunaKey2LED(uint lunakey) const +// キーボード取り外し時の MD 固有処理 +void +LunaKeyboard::MDDisconnect() { - // 2つしかないのでこのくらいでいいか - if (lunakey == LunaKey_kana) - return 0; - if (lunakey == LunaKey_CAPS) - return 1; + // 動作中のタイマーは停止 + scheduler->StopEvent(event); - return -1; + // 電源が切れるのでブザー停止 + BreakKey(KC_buzzer); + + // マウスサンプリングは停止 + MouseOff(); } -// キーを押した +// モニター void -LunaKeyboard::MakeKey(uint keycode) +LunaKeyboard::MonitorUpdate(Monitor *, TextScreen& screen) { - uint lunakey; - - // 共通キーコードから LUNA キーコードに - lunakey = keycode2lunakey(keycode); - - // LUNA にないキーは無視 - if (lunakey == NoKey) { - return; + 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; } - // LED はトグル? - int n = LunaKey2LED(lunakey); - if (n >= 0) { - led[n] = !led[n]; + uint mdkey = keycode2lunakey_table[keycode]; + if (mdkey && KC_IS_BREAK(keystat)) { + mdkey |= 0x80; } + return mdkey; +} - // キーが押された - putlog(1, "MakeKey %d(%s)", lunakey, keyname[lunakey]); - reptable[lunakey] = 1; - - // イベントを登録 - // (LED キーの自動 Break はイベントコールバック側で処理する) - SetKeyEvent(lunakey); +// シフト状態なら 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) { - return; + // 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; + } } - // LED キーはここでは処理しない (MakeKey() で処理してある) - if (LunaKey2LED(lunakey) >= 0) { + // 共通処理へ + if (KeyInputCommon(keystat) == false) { return; } - BreakKeyCommon(lunakey); -} + // 処理できたらここで LED の状態を更新 + if (n >= 0 && ismake) { + led[n] = !led[n]; -// キーを離した (通常キーと LED キーの場合の両方から呼ばれる) -void -LunaKeyboard::BreakKeyCommon(uint lunakey) -{ - // キーが離された - putlog(2, "BreakKey %d(%s)", lunakey, keyname[lunakey]); - reptable[lunakey] = 0; + // LED 状態が変更になったので全走査指示。 + // KeyInputCommon() 内で一度ポストしてるので無駄気味だが仕方ない… + uimessage->Post(UIMessage::KEYBOARD); + } - // イベントを登録 - SetKeyEvent(lunakey | 0x80); + // LUNA のキーボードはキーボード側でキーリピートしないので + // ここでは何もしなくてよい } -// キー入力用の空きスロットにイベントをセット -void -LunaKeyboard::SetKeyEvent(uint code) +// 共通キーコードから LED 番号を返す。LED キーでなければ -1 を返す。 +int +LunaKeyboard::Keycode2LED(uint keycode) const { - uint64 now = gScheduler->GetVirtTime(); - - // t は現在の仮想時刻に対応するスロット - uint64 t = (now / 2_msec) % 10; - // n はその次のスロット - uint64 n = (t + 1) % 10; + // 2つしかないのでこのくらいでいいか + if (keycode == KC_kana) + return 0; + if (keycode == KC_CAPS) + return 1; - // マウススロットなら避ける - if (n < 3) - n = 3; + return -1; +} - // このイベントが起きる時間を再計算 - uint64 target = (now / 20_msec) * 20_msec + n * 2_msec; - if (n < t) { - // n が折り返している場合は n は次の20msec 枠内 - target += 20_msec; - } +// LED 番号から共通キーコードを返す。LED 番号が不正なら KC_none を返す。 +uint +LunaKeyboard::LED2Keycode(int ledcode) const +{ + // 2つしかないのでこのくらいでいいか + if (ledcode == 0) + return KC_kana; + if (ledcode == 1) + return KC_CAPS; - // イベントを登録。 - if (key_sending == false) { - key_sending = true; - key_event.time = target - now; - key_event.code = code; - key_event.Start(); - } + return KC_none; } // マウス入力 @@ -208,62 +254,89 @@ LunaKeyboard::MouseInput(int x, int y, b // ホストからマウスイベントのたびに呼ばれるので、ここで積算。 // 20msec ごとのサンプリングのタイミングで積算結果を処理する。 // LUNA のマウスの Y 方向は上に動く方向がプラスなので、ホスト入力とは逆。 - mouse_x += x; - mouse_y -= y; - mouse_r = rb; - mouse_l = lb; - mouse_m = mb; + if (mouse_on) { + mouse_x += x; + mouse_y -= y; + mouse_r = rb; + mouse_l = lb; + mouse_m = mb; + } } -// キー入力コールバック -// SIO に送信するタイミングで呼ばれる。 +// キー送信を開始 (Keyboard から呼ばれる) void -LunaKeyboard::KeyboardCallback(Event& ev) +LunaKeyboard::SendStart() { - int& code = ev.code; - - gSIO->Rx(1, code); - key_sending = false; + // ここでは次の 2msec 単位にイベントコールバックを起動するだけ。 + Start(2_msec); +} - // かなと CAP キーの押下なら、続けてキーの復旧も送る。 - if (LunaKey2LED(code) >= 0) { - BreakKeyCommon(code); +// イベントを次の 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); } } -// マウスサンプリングコールバック -// 20msec ごとに呼ばれる。 +// イベントコールバック。 +// キー入力、マウス入力、ブザー出力のいずれかが発生している間 2msec ごとに +// 呼ばれる。 void -LunaKeyboard::MouseSamplingCallback(Event& ev) +LunaKeyboard::Callback(Event *ev) { - int reset_x = 0; - int reset_y = 0; + uint64 now = scheduler->GetVirtTime(); + uint mdkey; - // 移動量±1は送信しないが次回に向けた積算は続けるそうだ。 - if (mouse_x == -1 || mouse_x == 1) { - reset_x = mouse_x; - mouse_x = 0; + // マウスサンプリングオンなら 20msec ごとにサンプリングを行う。 + if (mouse_on && (now / 2_msec) % 10 == 0) { + MouseSampling(); } - if (mouse_y == -1 || mouse_y == 1) { - reset_y = mouse_y; - mouse_y = 0; + + // キューから取り出す。ここで取り出せるのは MDKey またはマウスデータ + if (sendqueue.Dequeue(&mdkey)) { + // 送信 + sio->Rx(1, mdkey); + } + + if (IsPressed(KC_buzzer) && now > buzzer_end) { + // VM スレッド内なので BreakKey() ではなくこっちを直接呼ぶ + KeyInput(KC_buzzer | KC_FLAG_BREAK); + } + + 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 != 0 || - mouse_y != 0 || + 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_r ? 'L' : '-'), + (mouse_l ? 'L' : '-'), (mouse_m ? 'M' : '-'), - (mouse_l ? 'R' : '-')); + (mouse_r ? 'R' : '-')); // 1バイト目、ボタン状態。 - // mouse_[rlm] 変数は押し下げが true、LUNA のマウスは押し下げが %0 + // mouse_[lmr] 変数は押し下げが true、LUNA のマウスは押し下げが %0 uint8 b = 0x80 | (mouse_l ? 0 : 0x04) | (mouse_m ? 0 : 0x02) | @@ -281,62 +354,136 @@ LunaKeyboard::MouseSamplingCallback(Even if (mouse_y > 127) mouse_y = 127; - // 1バイト目はここで送信 - gSIO->Rx(1, b); + // キューに入れておく + sendqueue.Enqueue(b); + sendqueue.Enqueue((uint8)mouse_x); + sendqueue.Enqueue((uint8)mouse_y); - // 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 = 0; + mouse_y = 0; + prev_r = mouse_r; + prev_l = mouse_l; + prev_m = mouse_m; } +} - // 積算リセット - mouse_x = reset_x; - mouse_y = reset_y; - prev_r = mouse_r; - prev_l = mouse_l; - prev_m = mouse_m; +// マウスサンプリングを ON にする +void +LunaKeyboard::MouseOn() +{ + // 厳密なタイミングは分からないので、 + // 実装都合で次のマウススロットからサンプリングを開始(再開)する。 + mouse_on = true; - // 20msec 後に再び自分を呼び出す - ev.Start(); + // 次の 20msec 単位後から開始 + Start(20_msec); } -// マウスの後続バイトを送出するイベントコールバック +// マウスサンプリングを OFF にする void -LunaKeyboard::MouseCallback(Event& ev) +LunaKeyboard::MouseOff() { - gSIO->Rx(1, ev.code); + 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::Command(uint32 data) +LunaKeyboard::MouseSendStart() { } -// 共通キーコードを LUNA キーコードに変換して返す。 -// LUNA キーがなければ NoKey を返す。 -uint -LunaKeyboard::keycode2lunakey(uint keycode) +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) { - if (keycode >= countof(keycode2lunakey_table)) { - return NoKey; + 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 + buzzer_msec * 1_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; } - - return keycode2lunakey_table[keycode]; } // キーコード変換表 // 共通キーコードから LUNA キーコードに変換する。 // ぶっちゃけた話共通キーコードは LUNA キーコードをベースにしているので、 // 穴がある以外は全部透過。 -/*static*/ const uint -LunaKeyboard::keycode2lunakey_table[128] = { +/*static*/ uint +LunaKeyboard::keycode2lunakey_table[KC_max] = { NoKey, // [00] NoKey, // [01] NoKey, // [02] @@ -371,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] @@ -386,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] @@ -439,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] @@ -473,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 -};