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 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 DescriptionsToKeys = new Dictionary(); private static Keys FromStrings(IEnumerable 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 /// /// Call this at 60hz with all of the currently pressed keys /// // ReSharper disable once UnusedMember.Global public void SetKeys(IEnumerable 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); } /// /// true if any of the 56 basic keys are pressed /// public bool IsAnyKeyDown { get; private set; } /// /// the currently latched key; 7 bits. /// public int Latch { get => _latch; private set => _latch = value; } public bool Strobe { get => _strobe; private set => _strobe = value; } private int _latch; private bool _strobe; /// /// true if caps lock is active /// private bool CapsActive { get => _capsActive; set => _capsActive = value; } private bool _capsActive; private bool _currentCapsLockState; /// /// 0-55, -1 = none /// private int _currentKeyPressed; private int _framesToRepeat; private const int KeyRepeatRate = 6; // 10hz private const int KeyRepeatStart = 40; // ~666ms? } }