scummvm/engines/sci/event.cpp
2016-08-19 13:57:40 -05:00

425 lines
15 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "common/system.h"
#include "common/events.h"
#include "common/file.h"
#include "sci/sci.h"
#include "sci/event.h"
#include "sci/console.h"
#include "sci/engine/state.h"
#include "sci/engine/kernel.h"
#ifdef ENABLE_SCI32
#include "sci/graphics/cursor32.h"
#include "sci/graphics/frameout.h"
#endif
#include "sci/graphics/screen.h"
namespace Sci {
struct ScancodeRow {
int offset;
const char *keys;
};
static const ScancodeRow scancodeAltifyRows[] = {
{ 0x10, "QWERTYUIOP[]" },
{ 0x1e, "ASDFGHJKL;'\\" },
{ 0x2c, "ZXCVBNM,./" }
};
static const byte codePageMap88591ToDOS[0x80] = {
'?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', // 0x8x
'?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', // 0x9x
'?', 0xad, 0x9b, 0x9c, '?', 0x9d, '?', 0x9e, '?', '?', 0xa6, 0xae, 0xaa, '?', '?', '?', // 0xAx
'?', '?', '?', '?', '?', '?', '?', '?', '?', '?', 0xa7, 0xaf, 0xac, 0xab, '?', 0xa8, // 0xBx
'?', '?', '?', '?', 0x8e, 0x8f, 0x92, 0x80, '?', 0x90, '?', '?', '?', '?', '?', '?', // 0xCx
'?', 0xa5, '?', '?', '?', '?', 0x99, '?', '?', '?', '?', '?', 0x9a, '?', '?', 0xe1, // 0xDx
0x85, 0xa0, 0x83, '?', 0x84, 0x86, 0x91, 0x87, 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b, // 0xEx
'?', 0xa4, 0x95, 0xa2, 0x93, '?', 0x94, '?', '?', 0x97, 0xa3, 0x96, 0x81, '?', '?', 0x98 // 0xFx
};
struct SciKeyConversion {
Common::KeyCode scummVMKey;
int sciKeyNumlockOff;
int sciKeyNumlockOn;
};
static const SciKeyConversion keyMappings[] = {
{ Common::KEYCODE_UP , SCI_KEY_UP , SCI_KEY_UP },
{ Common::KEYCODE_DOWN , SCI_KEY_DOWN , SCI_KEY_DOWN },
{ Common::KEYCODE_RIGHT , SCI_KEY_RIGHT , SCI_KEY_RIGHT },
{ Common::KEYCODE_LEFT , SCI_KEY_LEFT , SCI_KEY_LEFT },
{ Common::KEYCODE_INSERT , SCI_KEY_INSERT , SCI_KEY_INSERT },
{ Common::KEYCODE_HOME , SCI_KEY_HOME , SCI_KEY_HOME },
{ Common::KEYCODE_END , SCI_KEY_END , SCI_KEY_END },
{ Common::KEYCODE_PAGEUP , SCI_KEY_PGUP , SCI_KEY_PGUP },
{ Common::KEYCODE_PAGEDOWN , SCI_KEY_PGDOWN , SCI_KEY_PGDOWN },
{ Common::KEYCODE_DELETE , SCI_KEY_DELETE , SCI_KEY_DELETE },
// Keypad
{ Common::KEYCODE_KP0 , SCI_KEY_INSERT , '0' },
{ Common::KEYCODE_KP1 , SCI_KEY_END , '1' },
{ Common::KEYCODE_KP2 , SCI_KEY_DOWN , '2' },
{ Common::KEYCODE_KP3 , SCI_KEY_PGDOWN , '3' },
{ Common::KEYCODE_KP4 , SCI_KEY_LEFT , '4' },
{ Common::KEYCODE_KP5 , SCI_KEY_CENTER , '5' },
{ Common::KEYCODE_KP6 , SCI_KEY_RIGHT , '6' },
{ Common::KEYCODE_KP7 , SCI_KEY_HOME , '7' },
{ Common::KEYCODE_KP8 , SCI_KEY_UP , '8' },
{ Common::KEYCODE_KP9 , SCI_KEY_PGUP , '9' },
{ Common::KEYCODE_KP_PERIOD , SCI_KEY_DELETE , '.' },
{ Common::KEYCODE_KP_ENTER , SCI_KEY_ENTER , SCI_KEY_ENTER },
{ Common::KEYCODE_KP_PLUS , '+' , '+' },
{ Common::KEYCODE_KP_MINUS , '-' , '-' },
{ Common::KEYCODE_KP_MULTIPLY , '*' , '*' },
{ Common::KEYCODE_KP_DIVIDE , '/' , '/' },
};
struct MouseEventConversion {
Common::EventType commonType;
short sciType;
};
static const MouseEventConversion mouseEventMappings[] = {
{ Common::EVENT_LBUTTONDOWN, SCI_EVENT_MOUSE_PRESS },
{ Common::EVENT_RBUTTONDOWN, SCI_EVENT_MOUSE_PRESS },
{ Common::EVENT_MBUTTONDOWN, SCI_EVENT_MOUSE_PRESS },
{ Common::EVENT_LBUTTONUP, SCI_EVENT_MOUSE_RELEASE },
{ Common::EVENT_RBUTTONUP, SCI_EVENT_MOUSE_RELEASE },
{ Common::EVENT_MBUTTONUP, SCI_EVENT_MOUSE_RELEASE }
};
EventManager::EventManager(bool fontIsExtended) : _fontIsExtended(fontIsExtended) {
}
EventManager::~EventManager() {
}
static int altify(int ch) {
// Calculates a PC keyboard scancode from a character */
int row;
int c = toupper((char)ch);
for (row = 0; row < ARRAYSIZE(scancodeAltifyRows); row++) {
const char *keys = scancodeAltifyRows[row].keys;
int offset = scancodeAltifyRows[row].offset;
while (*keys) {
if (*keys == c)
return offset << 8;
offset++;
keys++;
}
}
return ch;
}
SciEvent EventManager::getScummVMEvent() {
#ifdef ENABLE_SCI32
SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() };
SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() };
#else
SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point() };
SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point() };
#endif
Common::EventManager *em = g_system->getEventManager();
Common::Event ev;
bool found = em->pollEvent(ev);
// Don't generate events for mouse movement
while (found && ev.type == Common::EVENT_MOUSEMOVE)
found = em->pollEvent(ev);
// Save the mouse position
//
// We call getMousePos of the event manager here, since we also want to
// store the mouse position in case of keyboard events, which do not feature
// any mouse position information itself.
// This should be safe, since the mouse position in the event manager should
// only be updated when a mouse related event has been taken from the queue
// via pollEvent.
// We also adjust the position based on the scaling of the screen.
Common::Point mousePos = em->getMousePos();
#if ENABLE_SCI32
if (getSciVersion() >= SCI_VERSION_2) {
const Buffer &screen = g_sci->_gfxFrameout->getCurrentBuffer();
if (ev.type == Common::EVENT_MOUSEMOVE) {
// This will clamp `mousePos` according to the restricted zone,
// so any cursor or screen item associated with the mouse position
// does not bounce when it hits the edge (or ignore the edge)
g_sci->_gfxCursor32->deviceMoved(mousePos);
}
Common::Point mousePosSci = mousePos;
mulru(mousePosSci, Ratio(screen.scriptWidth, screen.screenWidth), Ratio(screen.scriptHeight, screen.screenHeight));
noEvent.mousePosSci = input.mousePosSci = mousePosSci;
} else {
#endif
g_sci->_gfxScreen->adjustBackUpscaledCoordinates(mousePos.y, mousePos.x);
#if ENABLE_SCI32
}
#endif
noEvent.mousePos = input.mousePos = mousePos;
if (!found || ev.type == Common::EVENT_MOUSEMOVE) {
int modifiers = em->getModifierState();
noEvent.modifiers =
((modifiers & Common::KBD_ALT) ? SCI_KEYMOD_ALT : 0) |
((modifiers & Common::KBD_CTRL) ? SCI_KEYMOD_CTRL : 0) |
((modifiers & Common::KBD_SHIFT) ? SCI_KEYMOD_LSHIFT | SCI_KEYMOD_RSHIFT : 0);
return noEvent;
}
if (ev.type == Common::EVENT_QUIT) {
input.type = SCI_EVENT_QUIT;
return input;
}
int scummVMKeyFlags;
switch (ev.type) {
case Common::EVENT_KEYDOWN:
case Common::EVENT_KEYUP:
// Use keyboard modifiers directly in case this is a keyboard event
scummVMKeyFlags = ev.kbd.flags;
break;
default:
// Otherwise get them from EventManager
scummVMKeyFlags = em->getModifierState();
break;
}
input.modifiers =
((scummVMKeyFlags & Common::KBD_ALT) ? SCI_KEYMOD_ALT : 0) |
((scummVMKeyFlags & Common::KBD_CTRL) ? SCI_KEYMOD_CTRL : 0) |
((scummVMKeyFlags & Common::KBD_SHIFT) ? SCI_KEYMOD_LSHIFT | SCI_KEYMOD_RSHIFT : 0);
// Caps lock and Scroll lock have been removed, cause we already handle upper
// case keys and Scroll lock doesn't seem to be used anywhere
//((ourModifiers & Common::KBD_CAPS) ? SCI_KEYMOD_CAPSLOCK : 0) |
//((ourModifiers & Common::KBD_SCRL) ? SCI_KEYMOD_SCRLOCK : 0) |
// Handle mouse events
for (int i = 0; i < ARRAYSIZE(mouseEventMappings); i++) {
if (mouseEventMappings[i].commonType == ev.type) {
input.type = mouseEventMappings[i].sciType;
// Sierra passed keyboard modifiers for mouse events, too.
// Sierra also set certain modifiers within their mouse interrupt handler
// This whole thing was probably meant for people using a mouse, that only featured 1 button
// So the user was able to press Ctrl and click the mouse button to create a right click.
switch (ev.type) {
case Common::EVENT_RBUTTONDOWN: // right button
case Common::EVENT_RBUTTONUP:
input.modifiers |= (SCI_KEYMOD_RSHIFT | SCI_KEYMOD_LSHIFT); // this value was hardcoded in the mouse interrupt handler
break;
case Common::EVENT_MBUTTONDOWN: // middle button
case Common::EVENT_MBUTTONUP:
input.modifiers |= SCI_KEYMOD_CTRL; // this value was hardcoded in the mouse interrupt handler
break;
default:
break;
}
return input;
}
}
// If we reached here, make sure that it's a keydown event
if (ev.type != Common::EVENT_KEYDOWN)
return noEvent;
// Check for Control-Shift-D (debug console)
if (ev.kbd.hasFlags(Common::KBD_CTRL | Common::KBD_SHIFT) && ev.kbd.keycode == Common::KEYCODE_d) {
// Open debug console
Console *con = g_sci->getSciDebugger();
con->attach();
return noEvent;
}
// Process keyboard events
bool numlockOn = (ev.kbd.flags & Common::KBD_NUM);
Common::KeyCode scummVMKeycode = ev.kbd.keycode;
input.character = ev.kbd.ascii;
input.type = SCI_EVENT_KEYBOARD;
if (scummVMKeycode >= Common::KEYCODE_KP0 && scummVMKeycode <= Common::KEYCODE_KP9) {
if (!(scummVMKeyFlags & Common::KBD_NUM)) {
// HACK: Num-Lock not enabled
// We shouldn't get a valid ascii code in these cases. We fix it here, so that cursor keys
// on the numpad work properly.
input.character = 0;
}
}
if ((input.character) && (input.character <= 0xFF)) {
// Directly accept most common keys without conversion
if ((input.character >= 0x80) && (input.character <= 0xFF)) {
// If there is no extended font, we will just clear the
// current event.
// Sierra SCI actually accepted those characters, but
// didn't display them inside text edit controls because
// the characters were missing inside the font(s).
// We filter them out for non-multilingual games because
// of that.
if (!_fontIsExtended)
return noEvent;
// Convert 8859-1 characters to DOS (cp850/437) for
// multilingual SCI01 games
input.character = codePageMap88591ToDOS[input.character & 0x7f];
}
if (scummVMKeycode == Common::KEYCODE_TAB) {
input.character = SCI_KEY_TAB;
if (scummVMKeyFlags & Common::KBD_SHIFT)
input.character = SCI_KEY_SHIFT_TAB;
}
if (scummVMKeycode == Common::KEYCODE_DELETE)
input.character = SCI_KEY_DELETE;
} else if ((scummVMKeycode >= Common::KEYCODE_F1) && scummVMKeycode <= Common::KEYCODE_F10) {
// SCI_K_F1 == 59 << 8
// SCI_K_SHIFT_F1 == 84 << 8
if (!(scummVMKeyFlags & Common::KBD_SHIFT))
input.character = SCI_KEY_F1 + ((scummVMKeycode - Common::KEYCODE_F1)<<8);
else
input.character = SCI_KEY_SHIFT_F1 + ((scummVMKeycode - Common::KEYCODE_F1)<<8);
} else {
// Special keys that need conversion
for (int i = 0; i < ARRAYSIZE(keyMappings); i++) {
if (keyMappings[i].scummVMKey == scummVMKeycode) {
input.character = numlockOn ? keyMappings[i].sciKeyNumlockOn : keyMappings[i].sciKeyNumlockOff;
break;
}
}
}
// When Ctrl AND Alt are pressed together with a regular key, Linux will give us control-key, Windows will give
// us the actual key. My opinion is that windows is right, because under DOS the keys worked the same, anyway
// we support the other case as well
if ((scummVMKeyFlags & Common::KBD_ALT) && input.character > 0 && input.character < 27)
input.character += 96; // 0x01 -> 'a'
// Scancodify if appropriate
if (scummVMKeyFlags & Common::KBD_ALT)
input.character = altify(input.character);
if (getSciVersion() <= SCI_VERSION_1_MIDDLE && (scummVMKeyFlags & Common::KBD_CTRL) && input.character > 0 && input.character < 27)
input.character += 96; // 0x01 -> 'a'
#ifdef ENABLE_SCI32
if (getSciVersion() >= SCI_VERSION_2 && (scummVMKeyFlags & Common::KBD_CTRL) && input.character == 'c') {
input.character = SCI_KEY_ETX;
}
#endif
// If no actual key was pressed (e.g. if only a modifier key was pressed),
// ignore the event
if (!input.character)
return noEvent;
return input;
}
void EventManager::updateScreen() {
// Update the screen here, since it's called very often.
// Throttle the screen update rate to 60fps.
EngineState *s = g_sci->getEngineState();
if (g_system->getMillis() - s->_screenUpdateTime >= 1000 / 60) {
g_system->updateScreen();
s->_screenUpdateTime = g_system->getMillis();
// Throttle the checking of shouldQuit() to 60fps as well, since
// Engine::shouldQuit() invokes 2 virtual functions
// (EventManager::shouldQuit() and EventManager::shouldRTL()),
// which is very expensive to invoke constantly without any
// throttling at all.
if (g_engine->shouldQuit())
s->abortScriptProcessing = kAbortQuitGame;
}
}
SciEvent EventManager::getSciEvent(uint32 mask) {
#ifdef ENABLE_SCI32
SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() };
#else
SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point() };
#endif
EventManager::updateScreen();
// Get all queued events from graphics driver
do {
event = getScummVMEvent();
if (event.type != SCI_EVENT_NONE)
_events.push_back(event);
} while (event.type != SCI_EVENT_NONE);
// Search for matching event in queue
Common::List<SciEvent>::iterator iter = _events.begin();
while (iter != _events.end() && !(iter->type & mask))
++iter;
if (iter != _events.end()) {
// Event found
event = *iter;
// If not peeking at the queue, remove the event
if (!(mask & SCI_EVENT_PEEK))
_events.erase(iter);
} else {
// No event found: we must return a SCI_EVT_NONE event.
// Because event.type is SCI_EVT_NONE already here,
// there is no need to change it.
}
return event;
}
void SciEngine::sleep(uint32 msecs) {
uint32 time;
const uint32 wakeUpTime = g_system->getMillis() + msecs;
while (true) {
// let backend process events and update the screen
_eventMan->getSciEvent(SCI_EVENT_PEEK);
time = g_system->getMillis();
if (time + 10 < wakeUpTime) {
g_system->delayMillis(10);
} else {
if (time < wakeUpTime)
g_system->delayMillis(wakeUpTime - time);
break;
}
}
}
} // End of namespace Sci