mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-16 14:50:17 +00:00
480 lines
16 KiB
C++
480 lines
16 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 , kSciKeyUp , kSciKeyUp },
|
|
{ Common::KEYCODE_DOWN , kSciKeyDown , kSciKeyDown },
|
|
{ Common::KEYCODE_RIGHT , kSciKeyRight , kSciKeyRight },
|
|
{ Common::KEYCODE_LEFT , kSciKeyLeft , kSciKeyLeft },
|
|
{ Common::KEYCODE_INSERT , kSciKeyInsert , kSciKeyInsert },
|
|
{ Common::KEYCODE_HOME , kSciKeyHome , kSciKeyHome },
|
|
{ Common::KEYCODE_END , kSciKeyEnd , kSciKeyEnd },
|
|
{ Common::KEYCODE_PAGEUP , kSciKeyPageUp , kSciKeyPageUp },
|
|
{ Common::KEYCODE_PAGEDOWN , kSciKeyPageDown , kSciKeyPageDown },
|
|
{ Common::KEYCODE_DELETE , kSciKeyDelete , kSciKeyDelete },
|
|
{ Common::KEYCODE_KP0 , kSciKeyInsert , '0' },
|
|
{ Common::KEYCODE_KP1 , kSciKeyEnd , '1' },
|
|
{ Common::KEYCODE_KP2 , kSciKeyDown , '2' },
|
|
{ Common::KEYCODE_KP3 , kSciKeyPageDown , '3' },
|
|
{ Common::KEYCODE_KP4 , kSciKeyLeft , '4' },
|
|
{ Common::KEYCODE_KP5 , kSciKeyCenter , '5' },
|
|
{ Common::KEYCODE_KP6 , kSciKeyRight , '6' },
|
|
{ Common::KEYCODE_KP7 , kSciKeyHome , '7' },
|
|
{ Common::KEYCODE_KP8 , kSciKeyUp , '8' },
|
|
{ Common::KEYCODE_KP9 , kSciKeyPageUp , '9' },
|
|
{ Common::KEYCODE_KP_PERIOD , kSciKeyDelete , '.' },
|
|
{ Common::KEYCODE_KP_ENTER , kSciKeyEnter , kSciKeyEnter },
|
|
{ Common::KEYCODE_KP_PLUS , '+' , '+' },
|
|
{ Common::KEYCODE_KP_MINUS , '-' , '-' },
|
|
{ Common::KEYCODE_KP_MULTIPLY , '*' , '*' },
|
|
{ Common::KEYCODE_KP_DIVIDE , '/' , '/' }
|
|
};
|
|
|
|
struct MouseEventConversion {
|
|
Common::EventType commonType;
|
|
SciEventType sciType;
|
|
};
|
|
|
|
static const MouseEventConversion mouseEventMappings[] = {
|
|
{ Common::EVENT_LBUTTONDOWN , kSciEventMousePress },
|
|
{ Common::EVENT_RBUTTONDOWN , kSciEventMousePress },
|
|
{ Common::EVENT_MBUTTONDOWN , kSciEventMousePress },
|
|
{ Common::EVENT_LBUTTONUP , kSciEventMouseRelease },
|
|
{ Common::EVENT_RBUTTONUP , kSciEventMouseRelease },
|
|
{ Common::EVENT_MBUTTONUP , kSciEventMouseRelease }
|
|
};
|
|
|
|
EventManager::EventManager(bool fontIsExtended) :
|
|
_fontIsExtended(fontIsExtended)
|
|
#ifdef ENABLE_SCI32
|
|
, _hotRectanglesActive(false)
|
|
#endif
|
|
{}
|
|
|
|
EventManager::~EventManager() {
|
|
}
|
|
|
|
/**
|
|
* Calculates the IBM keyboard alt-key scancode of a printable character.
|
|
*/
|
|
static int altify(char ch) {
|
|
const char c = toupper(ch);
|
|
|
|
for (int 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 = { kSciEventNone, kSciKeyModNone, 0, Common::Point(), Common::Point(), -1 };
|
|
SciEvent noEvent = { kSciEventNone, kSciKeyModNone, 0, Common::Point(), Common::Point(), -1 };
|
|
#else
|
|
SciEvent input = { kSciEventNone, kSciKeyModNone, 0, Common::Point() };
|
|
SciEvent noEvent = { kSciEventNone, kSciKeyModNone, 0, Common::Point() };
|
|
#endif
|
|
|
|
Common::EventManager *em = g_system->getEventManager();
|
|
Common::Event ev;
|
|
|
|
// SCI does not generate separate events for mouse movement (it puts the
|
|
// current mouse position on every event, including non-mouse events), so
|
|
// skip past all mousemove events in the event queue
|
|
bool found;
|
|
do {
|
|
found = em->pollEvent(ev);
|
|
} while (found && ev.type == Common::EVENT_MOUSEMOVE);
|
|
|
|
Common::Point mousePos = em->getMousePos();
|
|
|
|
#if ENABLE_SCI32
|
|
if (getSciVersion() >= SCI_VERSION_2) {
|
|
const GfxFrameout *gfxFrameout = g_sci->_gfxFrameout;
|
|
|
|
// 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(gfxFrameout->getScriptWidth(), gfxFrameout->getScreenWidth()), Ratio(gfxFrameout->getScriptHeight(), gfxFrameout->getScreenHeight()));
|
|
noEvent.mousePosSci = input.mousePosSci = mousePosSci;
|
|
|
|
if (_hotRectanglesActive) {
|
|
checkHotRectangles(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();
|
|
if (modifiers & Common::KBD_ALT)
|
|
noEvent.modifiers |= kSciKeyModAlt;
|
|
if (modifiers & Common::KBD_CTRL)
|
|
noEvent.modifiers |= kSciKeyModCtrl;
|
|
if (modifiers & Common::KBD_SHIFT)
|
|
noEvent.modifiers |= kSciKeyModShift;
|
|
|
|
return noEvent;
|
|
}
|
|
if (ev.type == Common::EVENT_QUIT || ev.type == Common::EVENT_RTL) {
|
|
input.type = kSciEventQuit;
|
|
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;
|
|
}
|
|
|
|
// Caps lock and scroll lock are not handled here because we already
|
|
// handle upper case keys elsewhere, and scroll lock doesn't seem to
|
|
// ever be used
|
|
input.modifiers = kSciKeyModNone;
|
|
if (scummVMKeyFlags & Common::KBD_ALT)
|
|
input.modifiers |= kSciKeyModAlt;
|
|
if (scummVMKeyFlags & Common::KBD_CTRL)
|
|
input.modifiers |= kSciKeyModCtrl;
|
|
if (scummVMKeyFlags & Common::KBD_SHIFT)
|
|
input.modifiers |= kSciKeyModShift;
|
|
|
|
// 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 |= kSciKeyModShift; // this value was hardcoded in the mouse interrupt handler
|
|
break;
|
|
case Common::EVENT_MBUTTONDOWN: // middle button
|
|
case Common::EVENT_MBUTTONUP:
|
|
input.modifiers |= kSciKeyModCtrl; // this value was hardcoded in the mouse interrupt handler
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return input;
|
|
}
|
|
}
|
|
|
|
// Handle keyboard events for the rest of the function
|
|
if (ev.type != Common::EVENT_KEYDOWN && ev.type != Common::EVENT_KEYUP) {
|
|
return noEvent;
|
|
}
|
|
|
|
// Check for Control-Shift-D (debug console)
|
|
if (ev.type == Common::EVENT_KEYDOWN && 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;
|
|
}
|
|
|
|
// The IBM keyboard driver prior to SCI1.1 only sent keydown events to the
|
|
// interpreter
|
|
if (ev.type != Common::EVENT_KEYDOWN && getSciVersion() < SCI_VERSION_1_1) {
|
|
return noEvent;
|
|
}
|
|
|
|
const Common::KeyCode scummVMKeycode = ev.kbd.keycode;
|
|
|
|
input.character = ev.kbd.ascii;
|
|
|
|
if (scummVMKeycode >= Common::KEYCODE_KP0 && scummVMKeycode <= Common::KEYCODE_KP9 && !(scummVMKeyFlags & Common::KBD_NUM)) {
|
|
// TODO: Leaky abstractions from SDL should not be handled in game
|
|
// engines!
|
|
// SDL may provide a character value for numpad keys even if numlock is
|
|
// turned off, but arrow/navigation keys won't get mapped below if a
|
|
// character value is provided
|
|
input.character = 0;
|
|
}
|
|
|
|
if (input.character && input.character <= 0xFF) {
|
|
// Extended characters need to be converted to the old to DOS CP850/437
|
|
// character sets for multilingual games
|
|
if (input.character >= 0x80 && input.character <= 0xFF) {
|
|
// SSCI accepted all input scan codes, regardless of locale, and
|
|
// just didn't display any characters that were missing from fonts
|
|
// used by text input controls. We intentionally filter them out
|
|
// entirely for non-multilingual games here instead, so we can have
|
|
// better error detection for bugs in the text controls
|
|
if (!_fontIsExtended) {
|
|
return noEvent;
|
|
}
|
|
|
|
input.character = codePageMap88591ToDOS[input.character & 0x7f];
|
|
}
|
|
|
|
if (scummVMKeycode == Common::KEYCODE_TAB) {
|
|
input.character = kSciKeyTab;
|
|
if (scummVMKeyFlags & Common::KBD_SHIFT)
|
|
input.character = kSciKeyShiftTab;
|
|
}
|
|
|
|
if (scummVMKeycode == Common::KEYCODE_DELETE)
|
|
input.character = kSciKeyDelete;
|
|
} else if (scummVMKeycode >= Common::KEYCODE_F1 && scummVMKeycode <= Common::KEYCODE_F10) {
|
|
if (scummVMKeyFlags & Common::KBD_SHIFT)
|
|
input.character = kSciKeyShiftF1 + ((scummVMKeycode - Common::KEYCODE_F1) << 8);
|
|
else
|
|
input.character = kSciKeyF1 + ((scummVMKeycode - Common::KEYCODE_F1) << 8);
|
|
} else {
|
|
// Arrow keys, numpad keys, etc.
|
|
for (int i = 0; i < ARRAYSIZE(keyMappings); i++) {
|
|
if (keyMappings[i].scummVMKey == scummVMKeycode) {
|
|
const bool numlockOn = (ev.kbd.flags & Common::KBD_NUM);
|
|
input.character = numlockOn ? keyMappings[i].sciKeyNumlockOn : keyMappings[i].sciKeyNumlockOff;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (g_sci->getLanguage() == Common::RU_RUS) {
|
|
// Convert UTF16 to CP866
|
|
if (input.character >= 0x400 && input.character <= 0x4ff) {
|
|
if (input.character >= 0x440)
|
|
input.character = input.character - 0x410 + 0xb0;
|
|
else
|
|
input.character = input.character - 0x410 + 0x80;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Leaky abstractions from SDL should not be handled in game engines!
|
|
// When Ctrl and Alt are pressed together with a printable key, SDL1 on
|
|
// Linux will give us a control character instead of the printable
|
|
// character we need to convert to an alt scancode
|
|
if ((scummVMKeyFlags & Common::KBD_ALT) && input.character > 0 && input.character < 27)
|
|
input.character += 96; // 0x01 -> 'a'
|
|
|
|
if (scummVMKeyFlags & Common::KBD_ALT) {
|
|
input.character = altify(input.character & 0xFF);
|
|
} else if ((scummVMKeyFlags & Common::KBD_NON_STICKY) == Common::KBD_CTRL && input.character >= 'a' && input.character <= 'z') {
|
|
// In SSCI, Ctrl+<key> generates ASCII control characters, but the
|
|
// backends usually give us a printable character + Ctrl flag, so
|
|
// convert this combo back into what is expected by game scripts
|
|
input.character -= 96;
|
|
}
|
|
|
|
// In SCI1.1, if only a modifier key is pressed, the IBM keyboard driver
|
|
// sends an event the same as if a key had been released
|
|
if (getSciVersion() != SCI_VERSION_1_1 && !input.character) {
|
|
return noEvent;
|
|
} else if (!input.character || ev.type == Common::EVENT_KEYUP) {
|
|
input.type = kSciEventKeyUp;
|
|
|
|
// SCI32 includes the released key character code in keyup messages, but
|
|
// the IBM keyboard driver in SCI1.1 sends a special character value
|
|
// instead. This is necessary to prevent at least Island of Dr Brain
|
|
// from processing keyup events as though they were keydown events in
|
|
// the word search puzzle
|
|
if (getSciVersion() == SCI_VERSION_1_1) {
|
|
input.character = 0x8000;
|
|
}
|
|
} else {
|
|
input.type = kSciEventKeyDown;
|
|
}
|
|
|
|
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(SciEventType mask) {
|
|
#ifdef ENABLE_SCI32
|
|
SciEvent event = { kSciEventNone, kSciKeyModNone, 0, Common::Point(), Common::Point(), -1 };
|
|
#else
|
|
SciEvent event = { kSciEventNone, kSciKeyModNone, 0, Common::Point() };
|
|
#endif
|
|
|
|
if (getSciVersion() < SCI_VERSION_2) {
|
|
updateScreen();
|
|
}
|
|
|
|
// Get all queued events from graphics driver
|
|
do {
|
|
event = getScummVMEvent();
|
|
if (event.type != kSciEventNone)
|
|
_events.push_back(event);
|
|
} while (event.type != kSciEventNone);
|
|
|
|
// 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 & kSciEventPeek))
|
|
_events.erase(iter);
|
|
} else {
|
|
// No event found: we must return a kSciEventNone event.
|
|
|
|
// Because event.type is kSciEventNone already here,
|
|
// there is no need to change it.
|
|
}
|
|
|
|
return event;
|
|
}
|
|
|
|
void EventManager::flushEvents() {
|
|
Common::EventManager *em = g_system->getEventManager();
|
|
Common::Event event;
|
|
while (em->pollEvent(event)) {}
|
|
_events.clear();
|
|
}
|
|
|
|
#ifdef ENABLE_SCI32
|
|
void EventManager::setHotRectanglesActive(const bool active) {
|
|
_hotRectanglesActive = active;
|
|
}
|
|
|
|
void EventManager::setHotRectangles(const Common::Array<Common::Rect> &rects) {
|
|
_hotRects = rects;
|
|
_activeRectIndex = -1;
|
|
}
|
|
|
|
void EventManager::checkHotRectangles(const Common::Point &mousePosition) {
|
|
int lastActiveRectIndex = _activeRectIndex;
|
|
_activeRectIndex = -1;
|
|
|
|
for (int16 i = 0; i < (int16)_hotRects.size(); ++i) {
|
|
if (_hotRects[i].contains(mousePosition)) {
|
|
_activeRectIndex = i;
|
|
if (i != lastActiveRectIndex) {
|
|
SciEvent hotRectEvent;
|
|
hotRectEvent.type = kSciEventHotRectangle;
|
|
hotRectEvent.hotRectangleIndex = i;
|
|
_events.push_front(hotRectEvent);
|
|
break;
|
|
}
|
|
|
|
lastActiveRectIndex = _activeRectIndex;
|
|
}
|
|
}
|
|
|
|
if (lastActiveRectIndex != _activeRectIndex && lastActiveRectIndex != -1) {
|
|
_activeRectIndex = -1;
|
|
SciEvent hotRectEvent;
|
|
hotRectEvent.type = kSciEventHotRectangle;
|
|
hotRectEvent.hotRectangleIndex = -1;
|
|
_events.push_front(hotRectEvent);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
} // End of namespace Sci
|