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