https://github.com/scummvm/scummvm
Raw File
Tip revision: 41b49444b67e82b3a7a6b63a9f40a6dc1abb2fea authored by Eugene Sandulenko on 22 September 2019, 22:12:30 UTC
RELEASE: This is 2.2.0git
Tip revision: 41b4944
event.cpp
/* 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
back to top