File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / keyboard.cpp
Revision 1.1.1.19 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:01 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// 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 は
//	特別処理をする必要はない。
//	ただしゲスト上の入力ルーチンがこの区別をしているかは別問題である。

// 処理の流れ (SendStart まで)
//
//	MakeKey(), BreakKey() までは基本的に VM スレッドに処理を渡すだけ。
//
//	    UI スレッド          :     VM スレッド
//
//	        キャラクタ入力   :
//	                |
//	直接キー入力    |        :
//	   |            v
//	   |       CharInput()   :
//	   |            |
//	   +------------+        :
//	   v
//	MakeKey()/BreakKey()     :
//	   |
//	   | KEY_INPUT Message   :
//	   | - - - - - - - - - - - -> KeyInputMessage()
//	   o                               |
//	                         :       MD::KeyInput()
//	                                   |
//	                         :    KeyInputCommon()
//	                                   |
//	                         :       MD::SendStart()
//	                                   v
//	                              機種ごとに接続先に送信

#include "keyboard.h"
#include "config.h"
#include "mainapp.h"
#include "scheduler.h"
#include "syncer.h"
#include "uimessage.h"
#include <algorithm>

//
// 文字入力しか持たないキーボードの下位クラス
//

// コンストラクタ
DumbKeyboard::DumbKeyboard()
	: inherited(OBJ_KEYBOARD)
{
}

// デストラクタ
DumbKeyboard::~DumbKeyboard()
{
}


//
// 通常のキーボード
//

// コンストラクタ
Keyboard::Keyboard()
	: inherited()
{
	pressed.resize(KC_max);
}

// デストラクタ
Keyboard::~Keyboard()
{
}

// 初期化
bool
Keyboard::Init()
{
	// LUNA はキーリピートをソフトウェアで処理するため。
	// X68030 も Human68k を高速モードで使う際に必要。
	syncer = GetSyncer();

	uimessage = gMainApp.GetUIMessage();

	scheduler->ConnectMessage(MessageID::KEY_INPUT, this,
		ToMessageCallback(&Keyboard::KeyInputMessage));
	return true;
}

// 電源オン/リセット
void
Keyboard::ResetHard(bool poweron)
{
	if (poweron) {
		is_connected = gConfig->Find("keyboard-connect").AsBool();

		if (is_connected) {
			DoConnect();
		} else {
			DoDisconnect();
		}

		// キー通知
		syncer->NotifyKeyPressed(Any());
	}

	// 内部状態をリセット
	sendqueue.Clear();
}

// 電源オフ
void
Keyboard::PowerOff()
{
	// LED は全部消灯
	std::fill(led.begin(), led.end(), false);

	// キーボードから手を離したことにする
	std::fill(pressed.begin(), pressed.end(), false);

	// キーが全部解放されたので通知
	syncer->NotifyKeyPressed(false);

	DoDisconnect();
}

// キーボードを接続する (外部から呼ばれる)
void
Keyboard::Connect(bool connect)
{
	uint code = connect ? CC_connect : CC_disconnect;

	// ここは UI スレッドからも呼ばれるのでメッセージを送る
	scheduler->SendMessage(MessageID::KEY_INPUT, code);
}

// キーを押した (外部から、共通キーコードで呼ばれる)
void
Keyboard::MakeKey(uint keycode)
{
	keycode |= KC_FLAG_MAKE;

	// ここは UI スレッドからも呼ばれるのでメッセージを送る
	scheduler->SendMessage(MessageID::KEY_INPUT, keycode);
}

// キーを離した (外部から、共通キーコードで呼ばれる)
void
Keyboard::BreakKey(uint keycode)
{
	keycode |= KC_FLAG_BREAK;

	// ここは UI スレッドからも呼ばれるのでメッセージを送る
	scheduler->SendMessage(MessageID::KEY_INPUT, keycode);
}

// 文字による入力
// WX での文字入力モード、CLI からの入力、ターミナルエミュレーションでの
// 入力によりキーボードの押し下げをエミュレートする。
void
Keyboard::CharInput(uint charcode)
{
	if (charcode >= char_key.size()) {
		return;
	}

	uint key = char_key[charcode];
	uint code = KC_CODE(key);
	if ((key & KM_SHIFT)) {
		MakeKey(KC_SHIFT_L | KC_FLAG_POST);
		MakeKey(KC_SHIFT_R | KC_FLAG_POST);
	} else {
		BreakKey(KC_SHIFT_L);
		BreakKey(KC_SHIFT_R);
	}
	if ((key & KM_CTRL)) {
		MakeKey(KC_CTRL | KC_FLAG_POST);
	} else {
		BreakKey(KC_CTRL);
	}
	MakeKey(code | KC_FLAG_POST);
	BreakKey(code);
	if ((key & KM_CTRL)) {
		BreakKey(KC_CTRL);
	}
	if ((key & KM_SHIFT)) {
		BreakKey(KC_SHIFT_L);
		BreakKey(KC_SHIFT_R);
	}
}

// キー入力メッセージコールバック
void
Keyboard::KeyInputMessage(MessageID msgid, uint32 arg)
{
	uint keystat = arg;
	switch (keystat) {
	 case CC_connect:
		DoConnect();
		break;
	 case CC_disconnect:
		DoDisconnect();
		break;
	 default:
		KeyInput(keystat);
		break;
	}
}

// キーボードの接続処理の実体。
void
Keyboard::DoConnect()
{
	is_connected = true;

	MDConnect();

	// LED は全部消灯で初期化
	std::fill(led.begin(), led.end(), false);

	// 接続されたので押しっぱなしになっているキーをスキャンして送る
	for (int i = 0; i < pressed.size(); i++) {
		if (pressed[i]) {
			KeyInput(i | KC_FLAG_BREAK);
			KeyInput(i);
		}
	}

	// UI に通知する
	uimessage->Post(UIMessage::KEYBOARD);
}

void
Keyboard::DoDisconnect()
{
	is_connected = false;

	// 電源が切れたので LED は全部消す
	std::fill(led.begin(), led.end(), false);

	MDDisconnect();

	// UI に通知する
	uimessage->Post(UIMessage::KEYBOARD);
}

// キーの押し下げ状態を設定する。
// 初期化時以外で pressed を変更するときはこの関数を使用すること。
void
Keyboard::SetPressed(uint keycode, bool ismake)
{
	// キーがどれか1つでも押されてる間は VM の高速実行を抑制する。
	// すべてのキーが離されたら高速モードを許可する。
	if (ismake) {
		bool prev = Any();
		pressed[keycode] = true;
		if (prev == false) {
			syncer->NotifyKeyPressed(true);
		}
	} else {
		pressed[keycode] = false;
		if (Any() == false) {
			syncer->NotifyKeyPressed(false);
		}
	}
}

// キー入力を1つ処理する。
// keystat が押下・開放されたキーの物理的な状態。
// キー入力を受け取らずに破棄した場合は false を返す。
bool
Keyboard::KeyInputCommon(uint keystat)
{
	uint keycode = KC_CODE(keystat);
	bool ismake = KC_IS_MAKE(keystat);
	uint ledflag = KC_FLAG_LED(keystat);

	// 対応する機種別キーコードがなければ無視。
	// (ここでは調べるだけでまだ変換はしない)
	if (KeyToMDKey(keystat) == 0) {
		return false;
	}

	// このキーの状態が変化しないのなら何もしない。
	if (IsPressed(keycode) == ismake) {
		return false;
	}

	// 処理することが確定したのでログ
	// (離すほうは押すほうよりログレベルを1つ上げてある)
	if (loglevel >= 1) {
		std::string msg;

		msg += string_format("%u(", keycode);
		msg += GetKeyName(keycode);
		msg += ")";

		if (ledflag) {
			// LUNA の LED キーの場合、sendstat も明示
			if (ledflag == KC_FLAG_LED_DROP) {
				msg += ", drop";
			} else {
				msg += ", send ";
				if (ledflag == KC_FLAG_LED_MAKE) {
					msg += "Make";
				} else {
					msg += "Break";
				}
			}
		}

		if (ismake) {
			putlogn(  "MakeKey  %s", msg.c_str());
		} else {
			putlog(2, "BreakKey %s", msg.c_str());
		}
	}

	SetPressed(keycode, ismake);

	// キーの状態が変わったので UI に通知。
	// KC_FLAG_POST が立っていればキーコードも送信 (char 入力モード用)。
	// 通知を受け取った UI 側が IsPressed() などでキーの現状を調べることが
	// あるため pressed[] 更新後に通知する。
	// XXX LED 変更が間に合わない (see lunakbd.cpp)
	uint arg;
	if (KC_IS_POST(keystat)) {
		arg = keycode;
	} else {
		arg = 0;
	}
	uimessage->Post(UIMessage::KEYBOARD, arg);

	// LUNA の LED キー消灯はキー開放を送信
	if (ledflag == KC_FLAG_LED_BREAK) {
		keystat |= KC_FLAG_BREAK;
	}

	// キーボードが接続されていなければここまで処理して false を返す
	if (is_connected == false) {
		return false;
	}

	if (ledflag != KC_FLAG_LED_DROP) {
		// 送信キューに登録

		// ここでは変換できるはず
		uint mdkey = KeyToMDKey(keystat);
		sendqueue.Enqueue(mdkey);

		// 機種依存のキー送信処理
		SendStart();
	}

	return true;
}

// キーが1つでも押されていれば true を返す。
bool
Keyboard::Any() const
{
	auto it = std::find(pressed.begin(), pressed.end(), true);
	return (it != pressed.end());
}

// キー名を返す (主にデバッグ用)
const std::string
Keyboard::GetKeyName(uint keycode)
{
	if (keycode >= keyname.size()) {
		return string_format("(keycode=0x%x, out of range)", keycode);
	}
	if (keyname[keycode] == NULL) {
		return string_format("(keycode=0x%x)", keycode);
	}
	return std::string(keyname[keycode]);
}

/*static*/ std::vector<const char *>
Keyboard::keyname {
	"(none)",		// 00
	"BREAK",		// 01
	NULL,			// 02
	"romaji",		// 03
	"code",			// 04
	"kigou",		// 05
	"touroku",		// 06
	"HELP",			// 07
	"SHIFT",		// 08
	"TAB",			// 09
	"CTRL",			// 0a
	"kana",			// 0b
	"SHIFT_L",		// 0c
	"SHIFT_R",		// 0d
	"CAPS",			// 0e
	"SF",			// 0f

	"ESC",			// 10
	"BS",			// 11
	"enter",		// 12
	NULL,			// 13
	"space",		// 14
	"DEL",			// 15
	"XFER",			// 16
	"VALID",		// 17
	"PF11",			// 18
	"PF12",			// 19
	"PF13",			// 1a
	"PF14",			// 1b
	"up",			// 1c
	"left",			// 1d
	"right",		// 1e
	"down",			// 1f

	"INS",			// 20
	"COPY",			// 21
	"1",			// 22
	"2",			// 23
	"3",			// 24
	"4",			// 25
	"5",			// 26
	"6",			// 27
	"7",			// 28
	"8",			// 29
	"9",			// 2a
	"0",			// 2b
	"minus",		// 2c
	"circum",		// 2d
	"backslash",	// 2e
	"<buzzer>",		// 2f

	"CUT",			// 30
	"PASTE",		// 31
	"Q",			// 32
	"W",			// 33
	"E",			// 34
	"R",			// 35
	"T",			// 36
	"Y",			// 37
	"U",			// 38
	"I",			// 39
	"O",			// 3a
	"P",			// 3b
	"at",			// 3c
	"bracketleft",	// 3d
	"hira",			// 3e
	"zenkaku",		// 3f

	NULL,			// 40
	"UNDO",			// 41
	"A",			// 42
	"S",			// 43
	"D",			// 44
	"F",			// 45
	"G",			// 46
	"H",			// 47
	"J",			// 48
	"K",			// 49
	"L",			// 4a
	"semicolon",	// 4b
	"colon",		// 4c
	"bracketright",	// 4d
	"OPT1",			// 4e
	"OPT2",			// 4f

	"XF1",			// 50
	"XF2",			// 51
	"Z",			// 52
	"X",			// 53
	"C",			// 54
	"V",			// 55
	"B",			// 56
	"N",			// 57
	"M",			// 58
	"comma",		// 59
	"period",		// 5a
	"slash",		// 5b
	"underscore",	// 5c
	"XF3",			// 5d
	"XF4",			// 5e
	"XF5",			// 5f

	"HOME",			// 60
	"PAD_plus",		// 61
	"PAD_minus",	// 62
	"PAD_7",		// 63
	"PAD_8",		// 64
	"PAD_9",		// 65
	"PAD_4",		// 66
	"PAD_5",		// 67
	"PAD_6",		// 68
	"PAD_1",		// 69
	"PAD_2",		// 6a
	"PAD_3",		// 6b
	"PAD_0",		// 6c
	"PAD_decimal",	// 6d
	"PAD_enter",	// 6e
	"PAD_CLR",		// 6f

	"ROLLUP",		// 70
	"ROLLDOWN",		// 71
	"F1",			// 72
	"F2",			// 73
	"F3",			// 74
	"F4",			// 75
	"F5",			// 76
	"F6",			// 77
	"F7",			// 78
	"F8",			// 79
	"F9",			// 7a
	"F10",			// 7b
	"PAD_multiply",	// 7c
	"PAD_divide",	// 7d
	"PAD_equal",	// 7e
	"PAD_comma",	// 7f
};

// 文字対キーストローク表
// 文字による入力エミュレーションで使用。
/*static*/ std::vector<uint>
Keyboard::char_key = {
	0,							// NONE
	KC_A			+ KM_CTRL,	// 0x01 ^A
	KC_B			+ KM_CTRL,	// 0x02 ^B
	KC_C			+ KM_CTRL,	// 0x03 ^C
	KC_D			+ KM_CTRL,	// 0x04 ^D
	KC_E			+ KM_CTRL,	// 0x05 ^E
	KC_F			+ KM_CTRL,	// 0x06 ^F
	KC_G			+ KM_CTRL,	// 0x07 ^G
	KC_BS,						// 0x08 ^H
	KC_TAB,						// 0x09 ^I
	KC_J			+ KM_CTRL,	// 0x0a ^J
	KC_K			+ KM_CTRL,	// 0x0b ^K
	KC_L			+ KM_CTRL,	// 0x0c ^L
	KC_enter,					// 0x0d ^M
	KC_N			+ KM_CTRL,	// 0x0e ^N
	KC_O			+ KM_CTRL,	// 0x0f ^O
	KC_P			+ KM_CTRL,	// 0x10 ^P
	KC_Q			+ KM_CTRL,	// 0x11 ^Q
	KC_R			+ KM_CTRL,	// 0x12 ^R
	KC_S			+ KM_CTRL,	// 0x13 ^S
	KC_T			+ KM_CTRL,	// 0x14 ^T
	KC_U			+ KM_CTRL,	// 0x15 ^U
	KC_V			+ KM_CTRL,	// 0x16 ^V
	KC_W			+ KM_CTRL,	// 0x17 ^W
	KC_X			+ KM_CTRL,	// 0x18 ^X
	KC_Y			+ KM_CTRL,	// 0x19 ^Y
	KC_Z			+ KM_CTRL,	// 0x1a ^Z

	KC_ESC,						// 0x1b ^[
	KC_backslash	+ KM_CTRL,	// 0x1c	^'\'
	KC_bracketright	+ KM_CTRL,	// 0x1d ^]
	KC_circum		+ KM_CTRL,	// 0x1e ^^
	KC_underscore	+ KM_CTRL,	// 0x1f ^_

	KC_space,					// 0x20 ' '
	KC_1			+ KM_SHIFT,	// 0x21 !
	KC_2			+ KM_SHIFT,	// 0x22 "
	KC_3			+ KM_SHIFT,	// 0x23 #
	KC_4			+ KM_SHIFT,	// 0x24 $
	KC_5			+ KM_SHIFT,	// 0x25 %
	KC_6			+ KM_SHIFT,	// 0x26 &
	KC_7			+ KM_SHIFT,	// 0x27 '
	KC_8			+ KM_SHIFT,	// 0x28 (
	KC_9			+ KM_SHIFT,	// 0x29 )
	KC_colon		+ KM_SHIFT,	// 0x2a *
	KC_semicolon	+ KM_SHIFT,	// 0x2b +
	KC_comma,					// 0x2c ,
	KC_minus,					// 0x2d -
	KC_period,					// 0x2e .
	KC_slash,					// 0x2f /

	KC_0,						// 0x30 0
	KC_1,						// 0x31 1
	KC_2,						// 0x32 2
	KC_3,						// 0x33 3
	KC_4,						// 0x34 4
	KC_5,						// 0x35 5
	KC_6,						// 0x36 6
	KC_7,						// 0x37 7
	KC_8,						// 0x38 8
	KC_9,						// 0x39 9
	KC_colon,					// 0x3a :
	KC_semicolon,				// 0x3b ;
	KC_comma		+ KM_SHIFT,	// 0x3c <
	KC_minus		+ KM_SHIFT,	// 0x3d =
	KC_period		+ KM_SHIFT,	// 0x3e >
	KC_slash		+ KM_SHIFT,	// 0x3f ?

	KC_at,						// 0x40 @
	KC_A			+ KM_SHIFT,	// 0x41 A
	KC_B			+ KM_SHIFT,	// 0x42 B
	KC_C			+ KM_SHIFT,	// 0x43 C
	KC_D			+ KM_SHIFT,	// 0x44 D
	KC_E			+ KM_SHIFT,	// 0x45 E
	KC_F			+ KM_SHIFT,	// 0x46 F
	KC_G			+ KM_SHIFT,	// 0x47 G
	KC_H			+ KM_SHIFT,	// 0x48 H
	KC_I			+ KM_SHIFT,	// 0x49 I
	KC_J			+ KM_SHIFT,	// 0x4a J
	KC_K			+ KM_SHIFT,	// 0x4b K
	KC_L			+ KM_SHIFT,	// 0x4c L
	KC_M			+ KM_SHIFT,	// 0x4d M
	KC_N			+ KM_SHIFT,	// 0x4e N
	KC_O			+ KM_SHIFT,	// 0x4f O
	KC_P			+ KM_SHIFT,	// 0x50 P
	KC_Q			+ KM_SHIFT,	// 0x51 Q
	KC_R			+ KM_SHIFT,	// 0x52 R
	KC_S			+ KM_SHIFT,	// 0x53 S
	KC_T			+ KM_SHIFT,	// 0x54 T
	KC_U			+ KM_SHIFT,	// 0x55 U
	KC_V			+ KM_SHIFT,	// 0x56 V
	KC_W			+ KM_SHIFT,	// 0x57 W
	KC_X			+ KM_SHIFT,	// 0x58 X
	KC_Y			+ KM_SHIFT,	// 0x59 Y
	KC_Z			+ KM_SHIFT,	// 0x5a Z

	KC_bracketleft,				// 0x5b [
	KC_backslash,				// 0x5c '\'
	KC_bracketright,			// 0x5d ]
	KC_circum,					// 0x5e ^
	KC_underscore	+ KM_SHIFT,	// 0x5f _

	KC_at			+ KM_SHIFT,	// 0x60 `
	KC_A,						// 0x61 a
	KC_B,						// 0x62 b
	KC_C,						// 0x63 c
	KC_D,						// 0x64 d
	KC_E,						// 0x65 e
	KC_F,						// 0x66 f
	KC_G,						// 0x67 g
	KC_H,						// 0x68 h
	KC_I,						// 0x69 i
	KC_J,						// 0x6a j
	KC_K,						// 0x6b k
	KC_L,						// 0x6c l
	KC_M,						// 0x6d m
	KC_N,						// 0x6e n
	KC_O,						// 0x6f o
	KC_P,						// 0x70 p
	KC_Q,						// 0x71 q
	KC_R,						// 0x72 r
	KC_S,						// 0x73 s
	KC_T,						// 0x74 t
	KC_U,						// 0x75 u
	KC_V,						// 0x76 v
	KC_W,						// 0x77 w
	KC_X,						// 0x78 x
	KC_Y,						// 0x79 y
	KC_Z,						// 0x7a z

	KC_bracketleft	+ KM_SHIFT,	// 0x7b {
	KC_backslash	+ KM_SHIFT,	// 0x7c |
	KC_bracketright	+ KM_SHIFT,	// 0x7d }
	KC_circum		+ KM_SHIFT,	// 0x7e ~
	KC_none,					// 0x7f DEL

	KC_at			+ KM_CTRL,	// 0x80 CC_NUL
	KC_F1,						// 0x81 CC_F1
	KC_F2,						// 0x82 CC_F2
	KC_F3,						// 0x83 CC_F3
	KC_F4,						// 0x84 CC_F4
	KC_F5,						// 0x85 CC_F5
	KC_F6,						// 0x86 CC_F6
	KC_F7,						// 0x87 CC_F7
	KC_F8,						// 0x88 CC_F8
	KC_F9,						// 0x89 CC_F9
	KC_F10,						// 0x8a CC_F10
	0,							// 0x8b
	KC_up,						// 0x8c CC_up
	KC_left,					// 0x8d CC_left
	KC_right,					// 0x8e CC_right
	KC_down,					// 0x8f CC_down
};

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.