AppleWin/source/Keyboard.cpp

490 lines
12 KiB
C++

/*
AppleWin : An Apple //e emulator for Windows
Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2007, Tom Charlesworth, Michael Pohoreski
AppleWin is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
AppleWin is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with AppleWin; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* Description: Keyboard emulation
*
* Author: Various
*/
#include "StdAfx.h"
#include "Keyboard.h"
#include "Windows/AppleWin.h"
#include "Core.h"
#include "Interface.h"
#include "Utilities.h"
#include "Pravets.h"
#include "YamlHelper.h"
static BYTE asciicode[3][10] = {
// VK_LEFT/UP/RIGHT/DOWN/SELECT, VK_PRINT/EXECUTE/SNAPSHOT/INSERT/DELETE
{0x08,0x00,0x15,0x00,0x00, 0x00,0x00,0x00,0x00,0x00}, // Apple II
{0x08,0x0B,0x15,0x0A,0x00, 0x00,0x00,0x00,0x00,0x7F}, // Apple //e
{0x08,0x00,0x15,0x00,0x00, 0x00,0x00,0x00,0x00,0x7F} // Base 64A (like Apple II but with DELETE)
}; // Convert PC arrow keys to Apple keycodes
bool g_bShiftKey = false;
bool g_bCtrlKey = false;
bool g_bAltKey = false;
static bool g_bTK3KModeKey = false; //TK3000 //e |Mode| key
static bool g_bCapsLock = true; //Caps lock key for Apple2 and Lat/Cyr lock for Pravets8
static BYTE keycode = 0; // Current Apple keycode
static BOOL keywaiting = 0;
static bool g_bAltGrSendsWM_CHAR = false;
//
// ----- ALL GLOBALLY ACCESSIBLE FUNCTIONS ARE BELOW THIS LINE -----
//
void KeybSetAltGrSendsWM_CHAR(bool state)
{
g_bAltGrSendsWM_CHAR = state;
}
void KeybSetCapsLock(bool state)
{
g_bCapsLock = state;
}
//===========================================================================
void KeybReset()
{
keycode = 0;
keywaiting = 0;
}
//===========================================================================
bool KeybGetCapsStatus()
{
return g_bCapsLock;
}
//===========================================================================
bool KeybGetAltStatus()
{
return g_bAltKey;
}
//===========================================================================
bool KeybGetCtrlStatus()
{
return g_bCtrlKey;
}
//===========================================================================
bool KeybGetShiftStatus()
{
return g_bShiftKey;
}
//===========================================================================
void KeybUpdateCtrlShiftStatus()
{
g_bAltKey = (GetKeyState( VK_MENU ) < 0) ? true : false; // L or R alt
g_bCtrlKey = (GetKeyState( VK_CONTROL) < 0) ? true : false; // L or R ctrl
g_bShiftKey = (GetKeyState( VK_SHIFT ) < 0) ? true : false; // L or R shift
}
//===========================================================================
BYTE KeybGetKeycode () // Used by IORead_C01x() and TapeRead() for Pravets8A
{
return keycode;
}
//===========================================================================
static bool IsVirtualKeyAnAppleIIKey(WPARAM wparam);
void KeybQueueKeypress (WPARAM key, Keystroke_e bASCII)
{
if (bASCII == ASCII) // WM_CHAR
{
if (GetFrame().g_bFreshReset && key == VK_CANCEL) // OLD HACK: 0x03
{
GetFrame().g_bFreshReset = false;
return; // Swallow spurious CTRL-C caused by CTRL-BREAK
}
GetFrame().g_bFreshReset = false;
if (GetApple2Type() == A2TYPE_TK30002E && (key > 0x7F) && !g_bTK3KModeKey) // When in TK3000 mode, we have special keys which need remapping
return;
// Initially default to non-clone behaviour:
if (IsAppleIIeOrAbove(GetApple2Type()))
{
if (!IS_CLONE() && key > 0x7F) // accented chars, eg. AltGr+A
return;
if (g_bCapsLock && key >= 'a' && key <='z')
keycode = key - 32;
else
keycode = key;
}
else
{
if (!IS_CLONE() && (key == '`' || key >= '{')) // `,{,|,},~,DEL and >0x7F (excludes a..z)
return;
if (key >= 'a' && key <='z')
keycode = key - 32;
else
keycode = key;
}
// Next apply any clone override:
if (IS_CLONE())
{
keycode &= 0x7F; // for accented chars, eg. AltGr+A
if (IsPravets(GetApple2Type()))
keycode = GetPravets().ConvertToKeycode(key, keycode);
// Remap for the TK3000 //e, which had a special |Mode| key for displaying accented chars on screen
// Borrowed from Fábio Belavenuto's TK3000e emulator (Copyright (C) 2004) - http://code.google.com/p/tk3000e/
if (GetApple2Type() == A2TYPE_TK30002E && g_bTK3KModeKey) // We already switch this on only if the the TK3000 is currently being emulated
{
if ((key >= 0xC0) && (key <= 0xDA)) key += 0x20; // Convert uppercase to lowercase
switch (key)
{
case 0xE0: key = '_'; break; // à
case 0xE1: key = '@'; break; // á
case 0xE2: key = '\\'; break; // â
case 0xE3: key = '['; break; // ã
case 0xE7: key = ']'; break; // ç
case 0xE9: key = '`'; break; // é
case 0xEA: key = '&'; break; // ê
case 0xED: key = '{'; break; // í
case 0xF3: key = '~'; break; // ó
case 0xF4: key = '}'; break; // ô
case 0xF5: key = '#'; break; // õ
case 0xFA: key = '|'; break; // ú
}
if (key > 0x7F)
return;
if ((key >= 'a') && (key <= 'z') && (g_bCapsLock))
keycode = key - ('a'-'A');
else
keycode = key;
}
}
}
else //(bASCII != ASCII) // WM_KEYDOWN
{
// Note: VK_CANCEL is Control-Break
if ((key == VK_CANCEL) && (GetKeyState(VK_CONTROL) < 0))
{
GetFrame().g_bFreshReset = true;
CtrlReset();
return;
}
if ((key == VK_INSERT) && (GetKeyState(VK_SHIFT) < 0))
{
// Shift+Insert
ClipboardInitiatePaste();
return;
}
if (key == VK_SCROLL)
{ // For the TK3000 //e we use Scroll Lock to switch between Apple ][ and accented chars modes
if (GetApple2Type() == A2TYPE_TK30002E)
{
g_bTK3KModeKey = (GetKeyState(VK_SCROLL) & 1) ? true : false; // Sync with the Scroll Lock status
GetFrame().FrameRefreshStatus(DRAW_LEDS | DRAW_DISK_STATUS); // TODO: Implement |Mode| LED in the UI; make it appear only when in TK3000 mode
GetFrame().VideoRedrawScreen(); // TODO: Still need to implement page mode switching and 'whatnot'
}
return;
}
if (key >= VK_LEFT && key <= VK_DELETE)
{
UINT model = IsCopamBase64A(GetApple2Type()) ? 2 : IS_APPLE2 ? 0 : 1;
BYTE n = asciicode[model][key - VK_LEFT]; // Convert to Apple arrow keycode
if (!n)
return;
keycode = n;
}
else if (g_bAltGrSendsWM_CHAR && (GetKeyState(VK_RMENU) < 0)) // Right Alt (aka Alt Gr) - GH#558, GH#625
{
if (IsVirtualKeyAnAppleIIKey(key))
{
// When Alt Gr is down, then WM_CHAR is not posted - so fix this.
// NB. Still get WM_KEYDOWN/WM_KEYUP for the virtual key, so AKD works.
WPARAM newKey = key;
// Translate if shift or ctrl is down
if (key >= 'A' && key <= 'Z')
{
if ( (GetKeyState(VK_SHIFT) >= 0) && !g_bCapsLock )
newKey += 'a' - 'A'; // convert to lowercase key
else if (GetHookAltGrControl() && GetKeyState(VK_CONTROL) < 0)
newKey -= 'A' - 1; // convert to control-key
}
PostMessage(GetFrame().g_hFrameWindow, WM_CHAR, newKey, 0);
}
return;
}
else
{
return;
}
}
keywaiting = 1;
}
//===========================================================================
static HGLOBAL hglb = NULL;
static LPTSTR lptstr = NULL;
static bool g_bPasteFromClipboard = false;
static bool g_bClipboardActive = false;
void ClipboardInitiatePaste()
{
if (g_bClipboardActive)
return;
g_bPasteFromClipboard = true;
}
static void ClipboardDone()
{
if (g_bClipboardActive)
{
g_bClipboardActive = false;
GlobalUnlock(hglb);
CloseClipboard();
}
}
static void ClipboardInit()
{
ClipboardDone();
if (!IsClipboardFormatAvailable(CF_TEXT))
return;
if (!OpenClipboard(GetFrame().g_hFrameWindow))
return;
hglb = GetClipboardData(CF_TEXT);
if (hglb == NULL)
{
CloseClipboard();
return;
}
lptstr = (char*) GlobalLock(hglb);
if (lptstr == NULL)
{
CloseClipboard();
return;
}
g_bPasteFromClipboard = false;
g_bClipboardActive = true;
}
static char ClipboardCurrChar(bool bIncPtr)
{
char nKey;
int nInc = 1;
if((lptstr[0] == 0x0D) && (lptstr[1] == 0x0A))
{
nKey = 0x0D;
nInc = 2;
}
else
{
nKey = lptstr[0];
}
if(bIncPtr)
lptstr += nInc;
return nKey;
}
static BYTE ClipboardReadOrPeek(bool incPtr)
{
if (g_bPasteFromClipboard)
ClipboardInit();
if (g_bClipboardActive)
{
if (*lptstr == 0)
ClipboardDone();
else
return 0x80 | ClipboardCurrChar(incPtr);
}
return 0;
}
//===========================================================================
const UINT kAKDNumElements = 256/64;
static uint64_t g_AKDFlags[2][kAKDNumElements] = { {0,0,0,0}, // normal
{0,0,0,0}}; // extended
static bool IsVirtualKeyAnAppleIIKey(WPARAM wparam)
{
if (wparam == VK_BACK ||
wparam == VK_TAB ||
wparam == VK_RETURN ||
wparam == VK_ESCAPE ||
wparam == VK_SPACE ||
(wparam >= VK_LEFT && wparam <= VK_DOWN) ||
wparam == VK_DELETE ||
(wparam >= '0' && wparam <= '9') ||
(wparam >= 'A' && wparam <= 'Z') ||
(wparam >= VK_NUMPAD0 && wparam <= VK_NUMPAD9) ||
(wparam >= VK_MULTIPLY && wparam <= VK_DIVIDE) ||
(wparam >= VK_OEM_1 && wparam <= VK_OEM_3) || // 7 in total
(wparam >= VK_OEM_4 && wparam <= VK_OEM_8) || // 5 in total
(wparam == VK_OEM_102))
{
return true;
}
return false;
}
// NB. Don't need to be concerned about if numpad/cursors are used for joystick,
// since parent calls JoyProcessKey() just before this.
void KeybAnyKeyDown(UINT message, WPARAM wparam, bool bIsExtended)
{
if (wparam > 255)
{
_ASSERT(0);
return;
}
if (IsVirtualKeyAnAppleIIKey(wparam))
{
UINT offset = wparam >> 6;
UINT bit = wparam & 0x3f;
UINT idx = !bIsExtended ? 0 : 1;
if (message == WM_KEYDOWN)
g_AKDFlags[idx][offset] |= (1LL<<bit);
else
g_AKDFlags[idx][offset] &= ~(1LL<<bit);
}
}
static bool IsAKD(void)
{
uint64_t* p = &g_AKDFlags[0][0];
for (UINT i=0; i<sizeof(g_AKDFlags)/sizeof(g_AKDFlags[0][0]); i++)
if (p[i])
return true;
return false;
}
//===========================================================================
BYTE KeybReadData (void)
{
LogFileTimeUntilFirstKeyRead();
BYTE res = ClipboardReadOrPeek(false);
if (res)
return res;
return keycode | (keywaiting ? 0x80 : 0);
}
//===========================================================================
BYTE KeybClearStrobe(void)
{
keywaiting = 0;
return ClipboardReadOrPeek(true);
}
BYTE KeybReadFlag (void)
{
_ASSERT(!IS_APPLE2); // And also not Pravets machines?
BYTE res = KeybClearStrobe();
if (res)
return res;
// AKD
return keycode | (IsAKD() ? 0x80 : 0);
}
//===========================================================================
void KeybToggleCapsLock ()
{
if (!IS_APPLE2)
{
g_bCapsLock = (GetKeyState(VK_CAPITAL) & 1);
GetFrame().FrameRefreshStatus(DRAW_LEDS | DRAW_DISK_STATUS);
}
}
//===========================================================================
#define SS_YAML_KEY_LASTKEY "Last Key"
#define SS_YAML_KEY_KEYWAITING "Key Waiting"
static const std::string& KeybGetSnapshotStructName(void)
{
static const std::string name("Keyboard");
return name;
}
void KeybSaveSnapshot(YamlSaveHelper& yamlSaveHelper)
{
YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", KeybGetSnapshotStructName().c_str());
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_LASTKEY, keycode);
yamlSaveHelper.SaveBool(SS_YAML_KEY_KEYWAITING, keywaiting ? true : false);
}
void KeybLoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version)
{
if (!yamlLoadHelper.GetSubMap(KeybGetSnapshotStructName()))
return;
keycode = (BYTE) yamlLoadHelper.LoadUint(SS_YAML_KEY_LASTKEY);
if (version >= 2)
keywaiting = (BOOL) yamlLoadHelper.LoadBool(SS_YAML_KEY_KEYWAITING);
yamlLoadHelper.PopMap();
}