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