BizHawk/ExternalCoreProjects/Virtu/Keyboard.cs

384 lines
8.8 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace Jellyfish.Virtu
{
[Flags]
internal enum Keys : ulong
{
// https://archive.org/stream/Apple_IIe_Technical_Reference_Manual
// 56 basic keys as described in the reference manual
[Description("Delete")]
Delete = 1UL,
[Description("Left")]
Left = 2UL,
[Description("Tab")]
Tab = 4UL,
[Description("Down")]
Down = 8UL,
[Description("Up")]
Up = 16UL,
[Description("Return")]
Return = 32UL,
[Description("Right")]
Right = 64UL,
[Description("Escape")]
Escape = 128UL,
[Description("Space")]
Space = 256UL,
[Description("'")]
Apostrophe = 512UL,
[Description(",")]
Comma = 1024UL,
[Description("-")]
Dash = 2048UL,
[Description(".")]
Period = 4096UL,
[Description("/")]
Slash = 8192UL,
[Description("0")]
Key0 = 16384UL,
[Description("1")]
Key1 = 32768UL,
[Description("2")]
Key2 = 65536UL,
[Description("3")]
Key3 = 131072UL,
[Description("4")]
Key4 = 262144UL,
[Description("5")]
Key5 = 524288UL,
[Description("6")]
Key6 = 1048576UL,
[Description("7")]
Key7 = 2097152UL,
[Description("8")]
Key8 = 4194304UL,
[Description("9")]
Key9 = 8388608UL,
[Description(";")]
Semicolon = 16777216UL,
[Description("=")]
Equals = 33554432UL,
[Description("[")]
LeftBracket = 67108864UL,
[Description("\\")]
Backslash = 134217728UL,
[Description("]")]
RightBracket = 268435456UL,
[Description("`")]
Backtick = 536870912UL,
[Description("A")]
A = 1073741824UL,
[Description("B")]
B = 2147483648UL,
[Description("C")]
C = 4294967296UL,
[Description("D")]
D = 8589934592UL,
[Description("E")]
E = 17179869184UL,
[Description("F")]
F = 34359738368UL,
[Description("G")]
G = 68719476736UL,
[Description("H")]
H = 137438953472UL,
[Description("I")]
I = 274877906944UL,
[Description("J")]
J = 549755813888UL,
[Description("K")]
K = 1099511627776UL,
[Description("L")]
L = 2199023255552UL,
[Description("M")]
M = 4398046511104UL,
[Description("N")]
N = 8796093022208UL,
[Description("O")]
O = 17592186044416UL,
[Description("P")]
P = 35184372088832UL,
[Description("Q")]
Q = 70368744177664UL,
[Description("R")]
R = 140737488355328UL,
[Description("S")]
S = 281474976710656UL,
[Description("T")]
T = 562949953421312UL,
[Description("U")]
U = 1125899906842624UL,
[Description("V")]
V = 2251799813685248UL,
[Description("W")]
W = 4503599627370496UL,
[Description("X")]
X = 9007199254740992UL,
[Description("Y")]
Y = 18014398509481984UL,
[Description("Z")]
Z = 36028797018963968UL,
// three modifier keys, cannot be read directly
[Description("Control")]
Control = 72057594037927936UL,
[Description("Shift")]
Shift = 144115188075855872UL,
[Description("Caps Lock")]
CapsLock = 288230376151711744UL,
// three special keys
[Description("White Apple")]
WhiteApple = 576460752303423488UL, // connected to GAME1
[Description("Black Apple")]
BlackApple = 1152921504606846976UL, // connected to GAME2
[Description("Reset")]
Reset = 2305843009213693952UL,
}
#pragma warning disable MA0104 // unlikely to conflict with System.Windows.Input.Keyboard
public sealed class Keyboard
#pragma warning restore MA0104
{
static Keyboard()
{
for (int i = 0; i < 62; i++)
{
// http://stackoverflow.com/questions/2650080/how-to-get-c-sharp-enum-description-from-value
Keys value = (Keys)(1UL << i);
var fi = typeof(Keys).GetField(value.ToString());
var attr = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
string name = attr[0].Description;
DescriptionsToKeys[name] = value;
}
}
// ReSharper disable once UnusedMember.Global
public static IEnumerable<string> GetKeyNames() => DescriptionsToKeys.Keys.ToList();
private static readonly uint[] KeyAsciiData =
{
// https://archive.org/stream/Apple_IIe_Technical_Reference_Manual#page/n47/mode/2up
// 0xNNCCSSBB normal, control, shift both
// keys in same order as above
0x7f7f7f7f,
0x08080808,
0x09090909,
0x0a0a0a0a,
0x0b0b0b0b,
0x0d0d0d0d,
0x15151515,
0x1b1b1b1b,
0x20202020,
0x27272222,
0x2c2c3c3c,
0x2d1f5f1f,
0x2e2e3e3e,
0x2f2f3f3f,
0x30302929, // 0
0x31312121,
0x32004000,
0x33332323,
0x34342424,
0x35352525,
0x361e5e1e,
0x37372626,
0x38382a2a,
0x39392828, // 9
0x3b3b3a3a,
0x3d3d2b2b,
0x5b1b7b1b,
0x5c1c7c1c,
0x5d1d7d1d,
0x60607e7e,
0x61014101, // a
0x62024202,
0x63034303,
0x64044404,
0x65054505,
0x66064606,
0x67074707,
0x68084808,
0x69094909,
0x6a0a4a0a,
0x6b0b4b0b,
0x6c0c4c0c,
0x6d0d4d0d,
0x6e0e4e0e,
0x6f0f4f0f,
0x70105010,
0x71115111,
0x72125212,
0x73135313,
0x74145414,
0x75155515,
0x76165616,
0x77175717,
0x78185818,
0x79195919,
0x7a1a5a1a, // z
};
// key: 0 - 55
private static int KeyToAscii(int key, bool control, bool shift)
{
int s = control ? shift ? 0 : 16 : shift ? 8 : 24;
return (int)(KeyAsciiData[key] >> s & 0x7f);
}
// ReSharper disable once InconsistentNaming
private static readonly Dictionary<string, Keys> DescriptionsToKeys = new Dictionary<string, Keys>();
private static Keys FromStrings(IEnumerable<string> keynames)
{
Keys ret = 0;
foreach (string s in keynames)
{
ret |= DescriptionsToKeys[s];
}
return ret;
}
#pragma warning disable CA2211 // public field
public static bool WhiteAppleDown;
public static bool BlackAppleDown;
#pragma warning restore CA2211
/// <summary>
/// Call this at 60hz with all of the currently pressed keys
/// </summary>
// ReSharper disable once UnusedMember.Global
public void SetKeys(IEnumerable<string> keynames)
{
Keys keys = FromStrings(keynames);
WhiteAppleDown = keys.HasFlag(Keys.WhiteApple);
BlackAppleDown = keys.HasFlag(Keys.BlackApple);
if (keys.HasFlag(Keys.Reset) && keys.HasFlag(Keys.Control)) { } // TODO: reset console
bool control = keys.HasFlag(Keys.Control);
bool shift = keys.HasFlag(Keys.Shift);
bool caps = keys.HasFlag(Keys.CapsLock);
if (caps && !_currentCapsLockState) // leading edge: toggle CapsLock
{
CapsActive = !CapsActive;
}
_currentCapsLockState = caps;
shift ^= CapsActive;
// work with only the first 56 real keys
long k = (long)keys & 0xffffffffffffffL;
IsAnyKeyDown = k != 0;
if (!IsAnyKeyDown)
{
_currentKeyPressed = -1;
return;
}
// TODO: on real hardware, multiple keys pressed in physical would cause a conflict
// that would be somehow resolved by the scan pattern. we don't emulate that.
// instead, just arbitrarily choose the lowest key in our list
// BSF
int newKeyPressed = 0;
while ((k & 1) == 0)
{
k >>= 1;
newKeyPressed++;
}
if (newKeyPressed != _currentKeyPressed)
{
// strobe, start new repeat cycle
Strobe = true;
Latch = KeyToAscii(newKeyPressed, control, shift);
_framesToRepeat = KeyRepeatStart;
}
else
{
// check for repeat
_framesToRepeat--;
if (_framesToRepeat == 0)
{
Strobe = true;
Latch = KeyToAscii(newKeyPressed, control, shift);
_framesToRepeat = KeyRepeatRate;
}
}
_currentKeyPressed = newKeyPressed;
}
public void ResetStrobe()
{
Strobe = false;
}
public void Sync(IComponentSerializer ser)
{
ser.Sync("Latch", ref _latch);
ser.Sync("Strobe", ref _strobe);
ser.Sync("CapsActive", ref _capsActive);
ser.Sync(nameof(_currentCapsLockState), ref _currentCapsLockState);
ser.Sync(nameof(_framesToRepeat), ref _framesToRepeat);
}
/// <summary>
/// true if any of the 56 basic keys are pressed
/// </summary>
public bool IsAnyKeyDown { get; private set; }
/// <summary>
/// the currently latched key; 7 bits.
/// </summary>
public int Latch
{
get => _latch;
private set => _latch = value;
}
public bool Strobe
{
get => _strobe;
private set => _strobe = value;
}
private int _latch;
private bool _strobe;
/// <summary>
/// true if caps lock is active
/// </summary>
private bool CapsActive
{
get => _capsActive;
set => _capsActive = value;
}
private bool _capsActive;
private bool _currentCapsLockState;
/// <summary>
/// 0-55, -1 = none
/// </summary>
private int _currentKeyPressed;
private int _framesToRepeat;
private const int KeyRepeatRate = 6; // 10hz
private const int KeyRepeatStart = 40; // ~666ms?
}
}