|
|
1.1 root 1: //
2: // nono
1.1.1.4 root 3: // Copyright (C) 2020 nono project
4: // Licensed under nono-license.txt
1.1 root 5: //
6:
1.1.1.2 root 7: //
1.1.1.13 root 8: // LUNA のキーボードとマウス
9: //
10:
1.1.1.2 root 11: // キーボード/マウスからシリアルポートへの入力は、1バイトの送信自体に約1msec
12: // (9600bps、スタートビット1、ストップビット1) で送信後に 1msec 空けるそう
13: // なので、ここでは単純に 2msec を1スロットとする。
1.1.1.14! root 14: //
! 15: // ブザー発声はキー入力とは本来あまり関係ないが、ブザー発声中もキー入力中と
! 16: // 同様に高速走行を抑制したいため、キー扱いとしていることに留意。
1.1.1.2 root 17:
1.1 root 18: #include "lunakbd.h"
1.1.1.13 root 19: #include "scheduler.h"
1.1 root 20: #include "sio.h"
1.1.1.12 root 21: #include "uimessage.h"
1.1 root 22:
23: // コンストラクタ
24: LunaKeyboard::LunaKeyboard()
25: {
1.1.1.4 root 26: led.resize(2);
1.1 root 27:
1.1.1.13 root 28: event.func = ToEventCallback(&LunaKeyboard::Callback);
29: event.Regist("Keyboard/Mouse");
1.1.1.10 root 30:
1.1.1.13 root 31: monitor.func = ToMonitorCallback(&LunaKeyboard::MonitorUpdate);
1.1.1.12 root 32: monitor.SetSize(40, 4);
1.1.1.10 root 33: monitor.Regist(ID_MONITOR_KEYBOARD);
1.1 root 34: }
35:
36: // デストラクタ
37: LunaKeyboard::~LunaKeyboard()
38: {
39: }
40:
1.1.1.14! root 41: // 初期化
! 42: bool
! 43: LunaKeyboard::Init()
! 44: {
! 45: if (inherited::Init() == false) {
! 46: return false;
! 47: }
! 48:
! 49: sio = GetSIODevice();
! 50:
! 51: return true;
! 52: }
! 53:
1.1.1.13 root 54: // キーボード接続時の MD 固有処理
55: void
56: LunaKeyboard::MDConnect()
1.1.1.9 root 57: {
58: // XXX リセットはマウスには届かないような気がするので
1.1.1.13 root 59: // 接続時(電源オン)で初期化。
1.1.1.9 root 60: mouse_x = 0;
61: mouse_y = 0;
62: mouse_r = false;
63: mouse_l = false;
64: mouse_m = false;
65: prev_r = false;
66: prev_l = false;
67: prev_m = false;
68:
1.1.1.13 root 69: // ブザー停止状態で初期化すると定義されている
1.1.1.14! root 70: BreakKey(KC_buzzer);
1.1.1.12 root 71:
1.1.1.13 root 72: // デフォルトでマウス有効
73: MouseOn();
1.1.1.9 root 74: }
75:
1.1.1.13 root 76: // キーボード取り外し時の MD 固有処理
1.1.1.2 root 77: void
1.1.1.13 root 78: LunaKeyboard::MDDisconnect()
1.1.1.2 root 79: {
1.1.1.13 root 80: // 動作中のタイマーは停止
1.1.1.14! root 81: scheduler->StopEvent(event);
1.1.1.9 root 82:
1.1.1.13 root 83: // 電源が切れるのでブザー停止
1.1.1.14! root 84: BreakKey(KC_buzzer);
1.1.1.6 root 85:
1.1.1.13 root 86: // マウスサンプリングは停止
87: MouseOff();
1.1.1.2 root 88: }
89:
90: // モニター
1.1.1.5 root 91: void
1.1.1.10 root 92: LunaKeyboard::MonitorUpdate(Monitor *, TextScreen& screen)
1.1.1.2 root 93: {
1.1.1.12 root 94: int x;
95: int y;
96: int mx;
97: int my;
98:
1.1.1.10 root 99: screen.Clear();
1.1.1.12 root 100: y = 0;
1.1.1.2 root 101:
1.1.1.12 root 102: // 0123456789012345678901234567890123456789
103: // LED: CAP Kana
104: // Buzzer: 150msec 6000Hz
105: // Mouse: sampling=off x=-999 y=-999 L M R
106: x = 8;
107: screen.Puts(0, y, "LED:");
108: screen.Puts(x, y, TA::OnOff(led[1]), "CAP");
109: screen.Puts(x + 4, y, TA::OnOff(led[0]), "Kana");
110: y++;
111: screen.Puts(0, y, "Buzzer:");
1.1.1.14! root 112: screen.Print(x, y, IsPressed(KC_buzzer) ? TA::Normal : TA::Disable,
1.1.1.12 root 113: "%umsec %uHz", buzzer_msec, buzzer_freq);
114: y++;
115:
116: // マウスとの間を1行開ける
117: y++;
118:
119: mx = mouse_x;
120: my = mouse_y;
121: if (mx < -999)
122: mx = -999;
123: if (mx > 999)
124: mx = 999;
125: if (my < -999)
126: my = -999;
127: if (my > 999)
128: my = 999;
129:
130: screen.Puts(0, y, "Mouse:");
131: screen.Print(x, y, "sampling=%s", mouse_on ? "on" : "off");
132: screen.Print(x + 13, y, (mouse_on ? TA::Normal : TA::Disable),
133: "x=%-4d y=%-4d", mx, my);
134: if (mouse_on) {
135: screen.Puts(x + 27, y, TA::OnOff(mouse_l), "L");
136: screen.Puts(x + 29, y, TA::OnOff(mouse_m), "M");
137: screen.Puts(x + 31, y, TA::OnOff(mouse_r), "R");
138: } else {
139: screen.Puts(x + 27, y, TA::Disable, "L M R");
140: }
1.1.1.2 root 141: }
142:
1.1.1.12 root 143: // 共通 keystat を LUNA キーコードに変換して返す。
1.1.1.9 root 144: // 対応する LUNA のキーがなければ 0 を返す。
145: uint
1.1.1.12 root 146: LunaKeyboard::KeyToMDKey(uint keystat) const
1.1.1.4 root 147: {
1.1.1.12 root 148: uint keycode = KC_CODE(keystat);
1.1.1.9 root 149: if (keycode >= countof(keycode2lunakey_table)) {
1.1.1.4 root 150: return 0;
151: }
152:
1.1.1.12 root 153: uint mdkey = keycode2lunakey_table[keycode];
154: if (mdkey && KC_IS_BREAK(keystat)) {
155: mdkey |= 0x80;
156: }
157: return mdkey;
1.1 root 158: }
159:
1.1.1.9 root 160: // シフト状態なら true を返す
161: bool
162: LunaKeyboard::IsShift() const
1.1 root 163: {
1.1.1.9 root 164: return pressed[KC_SHIFT_L] || pressed[KC_SHIFT_R];
1.1.1.4 root 165: }
166:
1.1.1.10 root 167: // キー入力を1つ処理する
168: void
169: LunaKeyboard::KeyInput(uint keystat)
1.1.1.4 root 170: {
1.1.1.10 root 171: uint keycode = KC_CODE(keystat);
172: bool ismake = KC_IS_MAKE(keystat);
173:
174: assert(keycode < KC_max);
175:
1.1.1.12 root 176: // LUNA キーボードの LED キーは、
177: // LED 消灯中の押下で Make、点灯中の押下で Break を送り、
178: // LED キーの開放は VM に対しては何もしない。Issue#83
179: //
180: // LED ismake keystat 送信コード
181: // ------------ -------------
182: // off MAKE : MAKE MAKE キー押下、Make を入力 (LED を on)
183: // on MAKE : MAKE BREAK キー押下、Break を入力 (LED を off)
184: // off BREAK : BREAK none キー開放
185: // on BREAK : BREAK none キー開放
186: int n = Keycode2LED(keycode);
187: if (n >= 0) {
188: if (ismake) {
189: keystat |= (led[n] ? KC_FLAG_LED_BREAK : KC_FLAG_LED_MAKE);
190: } else {
191: // Break 時はキー入力はしない (キーの開放処理自体は必要)
192: keystat |= KC_FLAG_LED_DROP;
193: }
194: }
195:
1.1.1.10 root 196: // 共通処理へ
197: if (KeyInputCommon(keystat) == false) {
198: return;
199: }
200:
1.1.1.12 root 201: // 処理できたらここで LED の状態を更新
202: if (n >= 0 && ismake) {
203: led[n] = !led[n];
204:
205: // LED 状態が変更になったので全走査指示。
206: // KeyInputCommon() 内で一度ポストしてるので無駄気味だが仕方ない…
207: UIMessage::Post(UIMessage::KEYBOARD);
1.1.1.9 root 208: }
1.1 root 209:
1.1.1.10 root 210: // LUNA のキーボードはキーボード側でキーリピートしないので
211: // ここでは何もしなくてよい
1.1.1.2 root 212: }
213:
1.1.1.9 root 214: // 共通キーコードから LED 番号を返す。LED キーでなければ -1 を返す。
215: int
216: LunaKeyboard::Keycode2LED(uint keycode) const
1.1.1.2 root 217: {
1.1.1.9 root 218: // 2つしかないのでこのくらいでいいか
219: if (keycode == KC_kana)
220: return 0;
221: if (keycode == KC_CAPS)
222: return 1;
1.1.1.2 root 223:
1.1.1.9 root 224: return -1;
1.1.1.2 root 225: }
226:
1.1.1.12 root 227: // LED 番号から共通キーコードを返す。LED 番号が不正なら KC_none を返す。
228: uint
229: LunaKeyboard::LED2Keycode(int ledcode) const
230: {
231: // 2つしかないのでこのくらいでいいか
232: if (ledcode == 0)
233: return KC_kana;
234: if (ledcode == 1)
235: return KC_CAPS;
236:
237: return KC_none;
238: }
239:
1.1.1.2 root 240: // マウス入力
241: void
242: LunaKeyboard::MouseInput(int x, int y, bool rb, bool lb, bool mb)
243: {
244: // ホストからマウスイベントのたびに呼ばれるので、ここで積算。
245: // 20msec ごとのサンプリングのタイミングで積算結果を処理する。
246: // LUNA のマウスの Y 方向は上に動く方向がプラスなので、ホスト入力とは逆。
1.1.1.12 root 247: if (mouse_on) {
248: mouse_x += x;
249: mouse_y -= y;
250: mouse_r = rb;
251: mouse_l = lb;
252: mouse_m = mb;
253: }
1.1.1.2 root 254: }
255:
1.1.1.12 root 256: // キー送信を開始 (Keyboard から呼ばれる)
1.1.1.9 root 257: void
258: LunaKeyboard::SendStart()
259: {
1.1.1.12 root 260: // ここでは次の 2msec 単位にイベントコールバックを起動するだけ。
261: Start(2_msec);
262: }
1.1.1.9 root 263:
1.1.1.12 root 264: // イベントを次の period nsec 単位後に開始。
265: void
266: LunaKeyboard::Start(uint64 period)
267: {
1.1.1.14! root 268: uint64 now = scheduler->GetVirtTime();
1.1.1.12 root 269: uint64 target = ((now / period) + 1) * period;
270: // XXX メンバの event.vtime 参照してるけどどうするかね
271: if (event.IsRunning() == false || target < event.vtime) {
272: event.time = target - now;
1.1.1.14! root 273: scheduler->RestartEvent(event);
1.1.1.9 root 274: }
275: }
276:
1.1.1.12 root 277: // イベントコールバック。
278: // キー入力、マウス入力、ブザー出力のいずれかが発生している間 2msec ごとに
279: // 呼ばれる。
1.1.1.2 root 280: void
1.1.1.12 root 281: LunaKeyboard::Callback(Event& ev)
1.1.1.2 root 282: {
1.1.1.14! root 283: uint64 now = scheduler->GetVirtTime();
1.1.1.12 root 284: uint mdkey;
1.1.1.6 root 285:
1.1.1.12 root 286: // マウスサンプリングオンなら 20msec ごとにサンプリングを行う。
287: if (mouse_on && (now / 2_msec) % 10 == 0) {
288: MouseSampling();
1.1.1.9 root 289: }
290:
1.1.1.13 root 291: // キューから取り出す。ここで取り出せるのは MDKey またはマウスデータ
1.1.1.12 root 292: if (sendqueue.Dequeue(&mdkey)) {
293: // 送信
1.1.1.14! root 294: sio->Rx(1, mdkey);
1.1.1.12 root 295: }
1.1.1.4 root 296:
1.1.1.14! root 297: if (IsPressed(KC_buzzer) && now > buzzer_end) {
! 298: // VM スレッド内なので BreakKey() ではなくこっちを直接呼ぶ
! 299: KeyInput(KC_buzzer | KC_FLAG_BREAK);
1.1.1.9 root 300: }
301:
1.1.1.14! root 302: if (sendqueue.Empty() == false || IsPressed(KC_buzzer)) {
1.1.1.12 root 303: // 送信データが残っていれば次は 2msec 後
304: Start(2_msec);
305: } else if (mouse_on) {
306: // そうでなくて、マウスサンプリングがオンなら次の 20msec 単位後
307: Start(20_msec);
1.1.1.4 root 308: }
1.1.1.2 root 309: }
310:
1.1.1.12 root 311: // マウスサンプリング
1.1.1.2 root 312: void
1.1.1.12 root 313: LunaKeyboard::MouseSampling()
1.1.1.2 root 314: {
315: // 移動量が±1 を超えるかボタンがどれかでも変化すれば送信。
1.1.1.11 root 316: if (mouse_x < -1 || mouse_x > 1 ||
317: mouse_y < -1 || mouse_y > 1 ||
1.1.1.2 root 318: mouse_r != prev_r ||
319: mouse_l != prev_l ||
320: mouse_m != prev_m)
321: {
1.1.1.12 root 322: putlog(2, "Mouse Sampling x=%d y=%d %c%c%c",
323: mouse_x, mouse_y,
324: (mouse_l ? 'L' : '-'),
325: (mouse_m ? 'M' : '-'),
326: (mouse_r ? 'R' : '-'));
327:
328: // 1バイト目、ボタン状態。
329: // mouse_[lmr] 変数は押し下げが true、LUNA のマウスは押し下げが %0
330: uint8 b = 0x80 |
331: (mouse_l ? 0 : 0x04) |
332: (mouse_m ? 0 : 0x02) |
333: (mouse_r ? 0 : 0x01);
334:
335: // 2バイト目が X、3バイト目が Y。
336: // 移動量は絶対値 0x7f でサチる (signed char ではない)。
337: if (mouse_x < -127)
338: mouse_x = -127;
339: if (mouse_x > 127)
340: mouse_x = 127;
341:
342: if (mouse_y < -127)
343: mouse_y = -127;
344: if (mouse_y > 127)
345: mouse_y = 127;
346:
347: // キューに入れておく
348: sendqueue.Enqueue(b);
349: sendqueue.Enqueue((uint8)mouse_x);
350: sendqueue.Enqueue((uint8)mouse_y);
351:
352: // 積算リセット
353: mouse_x = 0;
354: mouse_y = 0;
355: prev_r = mouse_r;
356: prev_l = mouse_l;
357: prev_m = mouse_m;
1.1.1.2 root 358: }
1.1.1.11 root 359: }
360:
1.1.1.12 root 361: // マウスサンプリングを ON にする
1.1.1.11 root 362: void
1.1.1.12 root 363: LunaKeyboard::MouseOn()
1.1.1.11 root 364: {
1.1.1.12 root 365: // 厳密なタイミングは分からないので、
366: // 実装都合で次のマウススロットからサンプリングを開始(再開)する。
367: mouse_on = true;
1.1.1.2 root 368:
1.1.1.12 root 369: // 次の 20msec 単位後から開始
370: Start(20_msec);
371: }
372:
373: // マウスサンプリングを OFF にする
374: void
375: LunaKeyboard::MouseOff()
376: {
377: mouse_on = false;
1.1.1.11 root 378: mouse_x = 0;
379: mouse_y = 0;
1.1.1.12 root 380: mouse_r = false;
381: mouse_l = false;
382: mouse_m = false;
383: prev_r = false;
384: prev_l = false;
385: prev_m = false;
1.1.1.2 root 386: }
387:
1.1.1.12 root 388: // ?
1.1.1.2 root 389: void
1.1.1.12 root 390: LunaKeyboard::MouseSendStart()
1.1.1.2 root 391: {
1.1 root 392: }
393:
1.1.1.12 root 394: static const uint buzzer_msec_table[] = { 40, 150, 400, 700 };
395: static const uint buzzer_freq_table[] = {
396: 6000, 3000, 1500, 1000, 600, 300, 150, 100
397: };
398:
1.1.1.4 root 399: // ホストからの制御
400: void
401: LunaKeyboard::Command(uint32 data)
402: {
1.1.1.12 root 403: switch (data & 0xe0) {
404: case 0x00: // LED 点灯コマンド
405: {
406: // 7 6 5 4 3 2 1 0
407: // +---+---+---+---+---+---+---+---+
408: // | 0 0 0 |LED| x x x |COD|
409: // +---+---+---+---+---+---+---+---+
410: // | +--- 0:かな 1:CAP
411: // +------------------- 0:消灯 1:点灯
412: uint code = data & 0x01;
413: uint on = data & 0x10;
414: led[code] = (bool)on;
415: UIMessage::Post(UIMessage::KEYBOARD);
416: if (loglevel >= 1) {
417: const auto name = GetKeyName(LED2Keycode(code));
1.1.1.13 root 418: putlogn("Command $%02x LED $%x(%s) %s",
419: data, code, name.c_str(), (on ? "ON" : "OFF"));
1.1.1.12 root 420: }
421: break;
422: }
1.1.1.4 root 423:
1.1.1.12 root 424: case 0x40: // ブザーコマンド
425: {
426: // 7 6 5 4 3 2 1 0
427: // +---+---+---+---+---+---+---+---+
428: // | 0 1 0 |T2 T1 |F3 F2 F1 |
429: // +---+---+---+---+---+---+---+---+
430: // | +------- 周波数
431: // +----------------- 時間
432: uint t = (data >> 3) & 3;
433: uint f = data & 7;
434: buzzer_msec = buzzer_msec_table[t];
435: buzzer_freq = buzzer_freq_table[f];
1.1.1.13 root 436: putlog(1, "Command $%02x Buzzer %dmsec, %dHz",
437: data, buzzer_msec, buzzer_freq);
1.1.1.12 root 438:
1.1.1.14! root 439: uint64 now = scheduler->GetVirtTime();
1.1.1.12 root 440: buzzer_end = now + buzzer_msec * 1_msec;
1.1.1.14! root 441: // VM スレッド内なので MakeKey() ではなく KeyInput() を直接呼ぶ
! 442: KeyInput(KC_buzzer | KC_FLAG_MAKE);
1.1.1.12 root 443: Start(2_msec);
444: break;
445: }
446:
447: case 0x20: // マウス OFF コマンド
448: // 7 6 5 4 3 2 1 0
449: // +---+---+---+---+---+---+---+---+
450: // | 0 0 1 | x x x x x |
451: // +---+---+---+---+---+---+---+---+
1.1.1.13 root 452: putlog(1, "Command $%02x Mouse Sampling Off", data);
1.1.1.12 root 453: MouseOff();
454: break;
455:
456: case 0x60: // マウス ON コマンド
457: // 7 6 5 4 3 2 1 0
458: // +---+---+---+---+---+---+---+---+
459: // | 0 1 1 | x x x x x |
460: // +---+---+---+---+---+---+---+---+
1.1.1.13 root 461: putlog(1, "Command $%02x Mouse Sampling On", data);
1.1.1.12 root 462: MouseOn();
463: break;
464:
465: default:
1.1.1.13 root 466: putlog(1, "Command $%02x Undefined", data);
1.1.1.12 root 467: break;
468: }
469: }
1.1 root 470:
471: // キーコード変換表
472: // 共通キーコードから LUNA キーコードに変換する。
473: // ぶっちゃけた話共通キーコードは LUNA キーコードをベースにしているので、
474: // 穴がある以外は全部透過。
475: /*static*/ const uint
1.1.1.12 root 476: LunaKeyboard::keycode2lunakey_table[KC_max] = {
1.1 root 477: NoKey, // [00]
478: NoKey, // [01]
479: NoKey, // [02]
480: NoKey, // [03]
481: NoKey, // [04]
482: NoKey, // [05]
483: NoKey, // [06]
484: NoKey, // [07]
485: NoKey, // [08]
486: LunaKey_TAB, // [09]
487: LunaKey_CTRL, // [0a]
488: LunaKey_kana, // [0b]
489: LunaKey_SHIFT_L, // [0c]
490: LunaKey_SHIFT_R, // [0d]
491: LunaKey_CAPS, // [0e]
492: LunaKey_SF, // [0f]
493:
494: LunaKey_ESC, // [10]
495: LunaKey_BS, // [11]
496: LunaKey_enter, // [12]
497: NoKey, // [13]
498: LunaKey_space, // [14]
499: LunaKey_DEL, // [15]
500: LunaKey_XFER, // [16]
501: LunaKey_VALID, // [17]
502: LunaKey_PF11, // [18]
503: LunaKey_PF12, // [19]
504: LunaKey_PF13, // [1a]
505: LunaKey_PF14, // [1b]
506: LunaKey_up, // [1c]
507: LunaKey_left, // [1d]
508: LunaKey_right, // [1e]
509: LunaKey_down, // [1f]
510:
511: NoKey, // [20]
512: NoKey, // [21]
513: LunaKey_1, // [22]
514: LunaKey_2, // [23]
515: LunaKey_3, // [24]
516: LunaKey_4, // [25]
517: LunaKey_5, // [26]
518: LunaKey_6, // [27]
519: LunaKey_7, // [28]
520: LunaKey_8, // [29]
521: LunaKey_9, // [2a]
522: LunaKey_0, // [2b]
523: LunaKey_minus, // [2c]
524: LunaKey_circum, // [2d]
525: LunaKey_backslash, // [2e]
1.1.1.14! root 526: LunaKey_buzzer, // [2f]
1.1 root 527:
528: NoKey, // [30]
529: NoKey, // [31]
530: LunaKey_Q, // [32]
531: LunaKey_W, // [33]
532: LunaKey_E, // [34]
533: LunaKey_R, // [35]
534: LunaKey_T, // [36]
535: LunaKey_Y, // [37]
536: LunaKey_U, // [38]
537: LunaKey_I, // [39]
538: LunaKey_O, // [3a]
539: LunaKey_P, // [3b]
540: LunaKey_at, // [3c]
541: LunaKey_bracketleft, // [3d]
542: NoKey, // [3e]
543: NoKey, // [3f]
544:
545: NoKey, // [40]
546: NoKey, // [41]
547: LunaKey_A, // [42]
548: LunaKey_S, // [43]
549: LunaKey_D, // [44]
550: LunaKey_F, // [45]
551: LunaKey_G, // [46]
552: LunaKey_H, // [47]
553: LunaKey_J, // [48]
554: LunaKey_K, // [49]
555: LunaKey_L, // [4a]
556: LunaKey_semicolon, // [4b]
557: LunaKey_colon, // [4c]
558: LunaKey_bracketright, // [4d]
559: NoKey, // [4e]
560: NoKey, // [4f]
561:
562: NoKey, // [50]
563: NoKey, // [51]
564: LunaKey_Z, // [52]
565: LunaKey_X, // [53]
566: LunaKey_C, // [54]
567: LunaKey_V, // [55]
568: LunaKey_B, // [56]
569: LunaKey_N, // [57]
570: LunaKey_M, // [58]
571: LunaKey_comma, // [59]
572: LunaKey_period, // [5a]
573: LunaKey_slash, // [5b]
574: LunaKey_underscore, // [5c]
575: NoKey, // [5d]
576: NoKey, // [5e]
577: NoKey, // [5f]
578:
579: NoKey, // [60]
580: LunaKey_PAD_plus, // [61]
581: LunaKey_PAD_minus, // [62]
582: LunaKey_PAD_7, // [63]
583: LunaKey_PAD_8, // [64]
584: LunaKey_PAD_9, // [65]
585: LunaKey_PAD_4, // [66]
586: LunaKey_PAD_5, // [67]
587: LunaKey_PAD_6, // [68]
588: LunaKey_PAD_1, // [69]
589: LunaKey_PAD_2, // [6a]
590: LunaKey_PAD_3, // [6b]
591: LunaKey_PAD_0, // [6c]
592: LunaKey_PAD_decimal, // [6d]
593: LunaKey_PAD_enter, // [6e]
594: NoKey, // [6f]
595:
596: NoKey, // [70]
597: NoKey, // [71]
598: LunaKey_F1, // [72]
599: LunaKey_F2, // [73]
600: LunaKey_F3, // [74]
601: LunaKey_F4, // [75]
602: LunaKey_F5, // [76]
603: LunaKey_F6, // [77]
604: LunaKey_F7, // [78]
605: LunaKey_F8, // [79]
606: LunaKey_F9, // [7a]
607: LunaKey_F10, // [7b]
608: LunaKey_PAD_multiply, // [7c]
609: LunaKey_PAD_divide, // [7d]
610: LunaKey_PAD_equal, // [7e]
611: LunaKey_PAD_comma, // [7f]
612: };
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.