Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymap.h"
namespace Common {
Action::Action(const char *i, const U32String &des) :
id(i),
description(des),
_shouldTriggerOnKbdRepeats(false) {
assert(i);
}
void Action::addDefaultInputMapping(const String &hwId) {
if (hwId.empty()) {
return;
}
// Don't allow an input to map to the same action multiple times
Array<String>::const_iterator found = find(_defaultInputMapping.begin(), _defaultInputMapping.end(), hwId);
if (found == _defaultInputMapping.end()) {
_defaultInputMapping.push_back(hwId);
}
}
} // End of namespace Common

153
backends/keymapper/action.h Normal file
View File

@@ -0,0 +1,153 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COMMON_ACTION_H
#define COMMON_ACTION_H
#include "common/scummsys.h"
#include "common/array.h"
#include "common/events.h"
#include "common/str.h"
#include "common/ustr.h"
namespace Common {
struct KeyActionEntry {
const char *id;
const KeyState ks;
const char *defaultHwId;
const char *description;
};
struct Action {
/** unique id used for saving/loading to config */
const char *id;
/** Human readable description */
U32String description;
/** Event to be sent when mapped key is pressed */
Event event;
private:
Array<String> _defaultInputMapping;
bool _shouldTriggerOnKbdRepeats;
public:
Action(const char *id, const U32String &description);
void setEvent(const Event &evt) {
event = evt;
}
void setEvent(const EventType evtType) {
event = Event();
event.type = evtType;
}
void setCustomBackendActionEvent(const CustomEventType evtType) {
event = Event();
event.type = EVENT_CUSTOM_BACKEND_ACTION_START;
event.customType = evtType;
}
void setCustomBackendActionAxisEvent(const CustomEventType evtType) {
event = Event();
event.type = EVENT_CUSTOM_BACKEND_ACTION_AXIS;
event.customType = evtType;
}
void setCustomEngineActionEvent(const CustomEventType evtType) {
event = Event();
event.type = EVENT_CUSTOM_ENGINE_ACTION_START;
event.customType = evtType;
}
void setKeyEvent(const KeyState &ks) {
event = Event();
event.type = EVENT_KEYDOWN;
event.kbd = ks;
}
void setLeftClickEvent() {
setEvent(EVENT_LBUTTONDOWN);
}
void setMiddleClickEvent() {
setEvent(EVENT_MBUTTONDOWN);
}
void setRightClickEvent() {
setEvent(EVENT_RBUTTONDOWN);
}
void setMouseWheelUpEvent() {
setEvent(EVENT_WHEELUP);
}
void setMouseWheelDownEvent() {
setEvent(EVENT_WHEELDOWN);
}
void setX1ClickEvent() {
setEvent(EVENT_X1BUTTONDOWN);
}
void setX2ClickEvent() {
setEvent(EVENT_X2BUTTONDOWN);
}
/**
* Allows an action bound to a keyboard event to be repeatedly
* triggered by key repeats
*
* Note that key repeat events should probably not be used for anything
* else than text input as they do not trigger when the action is bound
* to something else than a keyboard key. Furthermore, the frequency at
* which they trigger and whether they trigger at all is operating system
* controlled.
*/
void allowKbdRepeats() {
_shouldTriggerOnKbdRepeats = true;
}
bool shouldTriggerOnKbdRepeats() const { return _shouldTriggerOnKbdRepeats; }
/**
* Add a default input mapping for the action
*
* Unknown hardware inputs will be silently ignored.
* Having keyboard bindings by default will not cause trouble
* on devices without a keyboard.
*
* @param hwId Hardware input identifier as registered with the keymapper
*/
void addDefaultInputMapping(const String &hwId);
const Array<String> &getDefaultInputMapping() const {
return _defaultInputMapping;
}
};
} // End of namespace Common
#endif // #ifndef COMMON_ACTION_H

View File

@@ -0,0 +1,618 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/keymapper/hardware-input.h"
#include "backends/keymapper/keymapper.h"
#include "common/tokenizer.h"
#include "common/translation.h"
namespace Common {
// TODO: Maybe make 'Command' a separate mac-specific modifier so we can define
// defaults key bindings from the original mac game versions without binding
// them to the meta key on other platforms?
#if defined(WIN32)
#define META_KEY_NAME "Win"
#elif defined(MACOSX) || defined(IPHONE)
#define META_KEY_NAME "Cmd"
#else
#define META_KEY_NAME "Meta"
#endif
const KeyTableEntry defaultKeys[] = {
{"BACKSPACE", KEYCODE_BACKSPACE, "Backspace"},
{"TAB", KEYCODE_TAB, "Tab"},
{"CLEAR", KEYCODE_CLEAR, "Clear"},
{"RETURN", KEYCODE_RETURN, "Return"},
{"PAUSE", KEYCODE_PAUSE, "Pause"},
{"ESCAPE", KEYCODE_ESCAPE, "Esc"},
{"SPACE", KEYCODE_SPACE, "Space"},
{"EXCLAIM", KEYCODE_EXCLAIM, "!"},
{"QUOTEDBL", KEYCODE_QUOTEDBL, "\""},
{"HASH", KEYCODE_HASH, "#"},
{"DOLLAR", KEYCODE_DOLLAR, "$"},
{"PERCENT", KEYCODE_PERCENT, "%"},
{"AMPERSAND", KEYCODE_AMPERSAND, "&"},
{"QUOTE", KEYCODE_QUOTE, "'"},
{"LEFTPAREN", KEYCODE_LEFTPAREN, "("},
{"RIGHTPAREN", KEYCODE_RIGHTPAREN, ")"},
{"ASTERISK", KEYCODE_ASTERISK, "*"},
{"PLUS", KEYCODE_PLUS, "+"},
{"COMMA", KEYCODE_COMMA, ","},
{"MINUS", KEYCODE_MINUS, "-"},
{"PERIOD", KEYCODE_PERIOD, "."},
{"SLASH", KEYCODE_SLASH, "/"},
{"0", KEYCODE_0, "0"},
{"1", KEYCODE_1, "1"},
{"2", KEYCODE_2, "2"},
{"3", KEYCODE_3, "3"},
{"4", KEYCODE_4, "4"},
{"5", KEYCODE_5, "5"},
{"6", KEYCODE_6, "6"},
{"7", KEYCODE_7, "7"},
{"8", KEYCODE_8, "8"},
{"9", KEYCODE_9, "9"},
{"COLON", KEYCODE_COLON, ":"},
{"SEMICOLON", KEYCODE_SEMICOLON, ";"},
{"LESS", KEYCODE_LESS, "<"},
{"EQUALS", KEYCODE_EQUALS, "="},
{"GREATER", KEYCODE_GREATER, ">"},
{"QUESTION", KEYCODE_QUESTION, "?"},
{"AT", KEYCODE_AT, "@"},
{"LEFTBRACKET", KEYCODE_LEFTBRACKET, "["},
{"BACKSLASH", KEYCODE_BACKSLASH, "\\"},
{"RIGHTBRACKET", KEYCODE_RIGHTBRACKET, "]"},
{"CARET", KEYCODE_CARET, "^"},
{"UNDERSCORE", KEYCODE_UNDERSCORE, "_"},
{"BACKQUOTE", KEYCODE_BACKQUOTE, "`"},
{"a", KEYCODE_a, "a"},
{"b", KEYCODE_b, "b"},
{"c", KEYCODE_c, "c"},
{"d", KEYCODE_d, "d"},
{"e", KEYCODE_e, "e"},
{"f", KEYCODE_f, "f"},
{"g", KEYCODE_g, "g"},
{"h", KEYCODE_h, "h"},
{"i", KEYCODE_i, "i"},
{"j", KEYCODE_j, "j"},
{"k", KEYCODE_k, "k"},
{"l", KEYCODE_l, "l"},
{"m", KEYCODE_m, "m"},
{"n", KEYCODE_n, "n"},
{"o", KEYCODE_o, "o"},
{"p", KEYCODE_p, "p"},
{"q", KEYCODE_q, "q"},
{"r", KEYCODE_r, "r"},
{"s", KEYCODE_s, "s"},
{"t", KEYCODE_t, "t"},
{"u", KEYCODE_u, "u"},
{"v", KEYCODE_v, "v"},
{"w", KEYCODE_w, "w"},
{"x", KEYCODE_x, "x"},
{"y", KEYCODE_y, "y"},
{"z", KEYCODE_z, "z"},
{"DELETE", KEYCODE_DELETE, "Del"},
// Numeric keypad
{"KP0", KEYCODE_KP0, "KP0"},
{"KP1", KEYCODE_KP1, "KP1"},
{"KP2", KEYCODE_KP2, "KP2"},
{"KP3", KEYCODE_KP3, "KP3"},
{"KP4", KEYCODE_KP4, "KP4"},
{"KP5", KEYCODE_KP5, "KP5"},
{"KP6", KEYCODE_KP6, "KP6"},
{"KP7", KEYCODE_KP7, "KP7"},
{"KP8", KEYCODE_KP8, "KP8"},
{"KP9", KEYCODE_KP9, "KP9"},
{"KP_PERIOD", KEYCODE_KP_PERIOD, "KP."},
{"KP_DIVIDE", KEYCODE_KP_DIVIDE, "KP/"},
{"KP_MULTIPLY", KEYCODE_KP_MULTIPLY, "KP*"},
{"KP_MINUS", KEYCODE_KP_MINUS, "KP-"},
{"KP_PLUS", KEYCODE_KP_PLUS, "KP+"},
{"KP_ENTER", KEYCODE_KP_ENTER, "KP enter"},
{"KP_EQUALS", KEYCODE_KP_EQUALS, "KP="},
// Arrows + Home/End pad
{"UP", KEYCODE_UP, "Up"},
{"DOWN", KEYCODE_DOWN, "Down"},
{"RIGHT", KEYCODE_RIGHT, "Right"},
{"LEFT", KEYCODE_LEFT, "Left"},
{"INSERT", KEYCODE_INSERT, "Insert"},
{"HOME", KEYCODE_HOME, "Home"},
{"END", KEYCODE_END, "End"},
{"PAGEUP", KEYCODE_PAGEUP, "PgUp"},
{"PAGEDOWN", KEYCODE_PAGEDOWN, "PgDn"},
// Function keys
{"F1", KEYCODE_F1, "F1"},
{"F2", KEYCODE_F2, "F2"},
{"F3", KEYCODE_F3, "F3"},
{"F4", KEYCODE_F4, "F4"},
{"F5", KEYCODE_F5, "F5"},
{"F6", KEYCODE_F6, "F6"},
{"F7", KEYCODE_F7, "F7"},
{"F8", KEYCODE_F8, "F8"},
{"F9", KEYCODE_F9, "F9"},
{"F10", KEYCODE_F10, "F10"},
{"F11", KEYCODE_F11, "F11"},
{"F12", KEYCODE_F12, "F12"},
{"F13", KEYCODE_F13, "F13"},
{"F14", KEYCODE_F14, "F14"},
{"F15", KEYCODE_F15, "F15"},
{"F16", KEYCODE_F16, "F16"},
{"F17", KEYCODE_F17, "F17"},
{"F18", KEYCODE_F18, "F18"},
// Miscellaneous function keys
{"HELP", KEYCODE_HELP, "Help"},
{"PRINT", KEYCODE_PRINT, "Print"},
{"SYSREQ", KEYCODE_SYSREQ, "SysRq"},
{"BREAK", KEYCODE_BREAK, "Break"},
{"MENU", KEYCODE_MENU, "Menu"},
// Power Macintosh power key
{"POWER", KEYCODE_POWER, "Power"},
// Some european keyboards
{"EURO", KEYCODE_EURO, "Euro"},
// Atari keyboard has Undo
{"UNDO", KEYCODE_UNDO, "Undo"},
{"SLEEP", KEYCODE_SLEEP, "Sleep"},
{"MUTE", KEYCODE_MUTE, "Mute"},
{"EJECT", KEYCODE_EJECT, "Eject"},
{"VOLUMEUP", KEYCODE_VOLUMEUP, "Volume up"},
{"VOLUMEDOWN", KEYCODE_VOLUMEDOWN, "Volume down"},
{"LEFTSOFT", KEYCODE_LEFTSOFT, "Left soft"},
{"RIGHTSOFT", KEYCODE_RIGHTSOFT, "Right soft"},
{"CALL", KEYCODE_CALL, "Call"},
{"HANGUP", KEYCODE_HANGUP, "Hang up"},
{"CAMERA", KEYCODE_CAMERA, "Camera"},
{"WWW", KEYCODE_WWW, "WWW"},
{"MAIL", KEYCODE_MAIL, "Mail"},
{"CALCULATOR", KEYCODE_CALCULATOR, "Calculator"},
{"CUT", KEYCODE_CUT, "Cut"},
{"COPY", KEYCODE_COPY, "Copy"},
{"PASTE", KEYCODE_PASTE, "Paste"},
{"SELECT", KEYCODE_SELECT, "Select"},
{"CANCEL", KEYCODE_CANCEL, "Cancel"},
// Action keys
{"AC_SEARCH", KEYCODE_AC_SEARCH, "AC search"},
{"AC_HOME", KEYCODE_AC_HOME, "AC home"},
{"AC_BACK", KEYCODE_AC_BACK, "AC back"},
{"AC_FORWARD", KEYCODE_AC_FORWARD, "AC forward"},
{"AC_STOP", KEYCODE_AC_STOP, "AC stop"},
{"AC_REFRESH", KEYCODE_AC_REFRESH, "AC refresh"},
{"AC_BOOKMARKS", KEYCODE_AC_BOOKMARKS, "AC bookmarks"},
// Audio keys
{"AUDIONEXT", KEYCODE_AUDIONEXT, "Audio next"},
{"AUDIOPREV", KEYCODE_AUDIOPREV, "Audio previous"},
{"AUDIOSTOP", KEYCODE_AUDIOSTOP, "Audio stop"},
{"AUDIOPLAY", KEYCODE_AUDIOPLAY, "Audio play"},
{"AUDIOPAUSE", KEYCODE_AUDIOPAUSE, "Audio pause"},
{"AUDIOPLAYPAUSE", KEYCODE_AUDIOPLAYPAUSE, "Audio play/pause"},
{"AUDIOMUTE", KEYCODE_AUDIOMUTE, "Audio mute"},
{"AUDIOREWIND", KEYCODE_AUDIOREWIND, "Audio rewind"},
{"AUDIOFASTFORWARD", KEYCODE_AUDIOFASTFORWARD, "Audio fast-forward"},
// Modifier keys
{"SCROLLOCK", KEYCODE_SCROLLOCK, "Scroll lock" },
{"CAPSLOCK", KEYCODE_CAPSLOCK, "Caps lock" },
{"NUMLOCK", KEYCODE_NUMLOCK, "Num lock" },
{"LSHIFT", KEYCODE_LSHIFT, "Left shift" },
{"RSHIFT", KEYCODE_RSHIFT, "Right shift" },
{"LALT", KEYCODE_LALT, "Left alt" },
{"RALT", KEYCODE_RALT, "Right alt" },
{"LCTRL", KEYCODE_LCTRL, "Left control" },
{"RCTRL", KEYCODE_RCTRL, "Right control" },
{"LMETA", KEYCODE_LMETA, "Left " META_KEY_NAME },
{"RMETA", KEYCODE_RMETA, "Right " META_KEY_NAME },
{nullptr, KEYCODE_INVALID, nullptr}
};
// TODO: Add NUM_LOCK
const ModifierTableEntry defaultModifiers[] = {
{ KBD_CTRL, "C", "Ctrl+" },
{ KBD_SHIFT, "S", "Shift+" },
{ KBD_ALT, "A", "Alt+" },
{ KBD_META, "M", META_KEY_NAME "+" },
{ 0, nullptr, nullptr }
};
const HardwareInputTableEntry defaultMouseButtons[] = {
{ "MOUSE_LEFT", MOUSE_BUTTON_LEFT, _s("Left mouse button") },
{ "MOUSE_RIGHT", MOUSE_BUTTON_RIGHT, _s("Right mouse button") },
{ "MOUSE_MIDDLE", MOUSE_BUTTON_MIDDLE, _s("Middle mouse button") },
{ "MOUSE_WHEEL_UP", MOUSE_WHEEL_UP, _s("Mouse wheel up") },
{ "MOUSE_WHEEL_DOWN", MOUSE_WHEEL_DOWN, _s("Mouse wheel down") },
{ "MOUSE_X1", MOUSE_BUTTON_X1, _s("X1 mouse button") },
{ "MOUSE_X2", MOUSE_BUTTON_X2, _s("X2 mouse button") },
{ nullptr, 0, nullptr }
};
const HardwareInputTableEntry defaultJoystickButtons[] = {
{ "JOY_A", JOYSTICK_BUTTON_A, _s("Joy A") },
{ "JOY_B", JOYSTICK_BUTTON_B, _s("Joy B") },
{ "JOY_X", JOYSTICK_BUTTON_X, _s("Joy X") },
{ "JOY_Y", JOYSTICK_BUTTON_Y, _s("Joy Y") },
{ "JOY_BACK", JOYSTICK_BUTTON_BACK, _s("Joy back") },
{ "JOY_GUIDE", JOYSTICK_BUTTON_GUIDE, _s("Joy guide") },
{ "JOY_START", JOYSTICK_BUTTON_START, _s("Joy start") },
{ "JOY_LEFT_STICK", JOYSTICK_BUTTON_LEFT_STICK, _s("Left stick") },
{ "JOY_RIGHT_STICK", JOYSTICK_BUTTON_RIGHT_STICK, _s("Right stick") },
{ "JOY_LEFT_SHOULDER", JOYSTICK_BUTTON_LEFT_SHOULDER, _s("Left shoulder") },
{ "JOY_RIGHT_SHOULDER", JOYSTICK_BUTTON_RIGHT_SHOULDER, _s("Right shoulder") },
{ "JOY_UP", JOYSTICK_BUTTON_DPAD_UP, _s("D-pad up") },
{ "JOY_DOWN", JOYSTICK_BUTTON_DPAD_DOWN, _s("D-pad down") },
{ "JOY_LEFT", JOYSTICK_BUTTON_DPAD_LEFT, _s("D-pad left") },
{ "JOY_RIGHT", JOYSTICK_BUTTON_DPAD_RIGHT, _s("D-pad right") },
{ "JOY_CENTER", JOYSTICK_BUTTON_DPAD_CENTER, _s("D-pad center") },
{ nullptr, 0, nullptr }
};
const AxisTableEntry defaultJoystickAxes[] = {
{ "JOY_LEFT_TRIGGER", JOYSTICK_AXIS_LEFT_TRIGGER, kAxisTypeHalf, _s("Left trigger") },
{ "JOY_RIGHT_TRIGGER", JOYSTICK_AXIS_RIGHT_TRIGGER, kAxisTypeHalf, _s("Right trigger") },
{ "JOY_LEFT_STICK_X", JOYSTICK_AXIS_LEFT_STICK_X, kAxisTypeFull, _s("Left stick X") },
{ "JOY_LEFT_STICK_Y", JOYSTICK_AXIS_LEFT_STICK_Y, kAxisTypeFull, _s("Left stick Y") },
{ "JOY_RIGHT_STICK_X", JOYSTICK_AXIS_RIGHT_STICK_X, kAxisTypeFull, _s("Right stick X") },
{ "JOY_RIGHT_STICK_Y", JOYSTICK_AXIS_RIGHT_STICK_Y, kAxisTypeFull, _s("Right stick Y") },
{ "JOY_HAT_X", JOYSTICK_AXIS_HAT_X, kAxisTypeFull, _s("Hat X") },
{ "JOY_HAT_Y", JOYSTICK_AXIS_HAT_Y, kAxisTypeFull, _s("Hat Y") },
{ nullptr, 0, kAxisTypeFull, nullptr }
};
HardwareInputSet::~HardwareInputSet() {
}
KeyboardHardwareInputSet::KeyboardHardwareInputSet(const KeyTableEntry *keys, const ModifierTableEntry *modifiers) :
_keys(keys),
_modifiers(modifiers) {
assert(_keys);
assert(_modifiers);
}
HardwareInput KeyboardHardwareInputSet::findHardwareInput(const String &id) const {
StringTokenizer tokenizer(id, "+");
byte modifierFlags = 0;
// TODO: Normalize modifier order
U32String fullKeyDesc;
String token;
while (!tokenizer.empty()) {
token = tokenizer.nextToken();
const ModifierTableEntry *modifier = nullptr;
for (modifier = _modifiers; modifier->id; modifier++) {
if (token == modifier->id) {
break;
}
}
if (modifier && modifier->id) {
modifierFlags |= modifier->flag;
fullKeyDesc += _(modifier->desc);
} else {
// We reached the end of the modifiers, the token is a keycode
break;
}
}
if (!tokenizer.empty()) {
return HardwareInput();
}
const KeyTableEntry *key = nullptr;
for (key = _keys; key->hwId; key++) {
if (token.equals(key->hwId)) {
break;
}
}
if (!key || !key->hwId) {
return HardwareInput();
}
const KeyState keystate = KeyState(key->keycode, 0, modifierFlags);
return HardwareInput::createKeyboard(id, keystate, fullKeyDesc + _(key->desc));
}
HardwareInput KeyboardHardwareInputSet::findHardwareInput(const Event &event) const {
switch (event.type) {
case EVENT_KEYDOWN:
case EVENT_KEYUP: {
KeyState normalizedKeystate = normalizeKeyState(event.kbd);
const KeyTableEntry *key = nullptr;
for (key = _keys; key->hwId; key++) {
if (normalizedKeystate.keycode == key->keycode) {
break;
}
}
if (!key || !key->hwId) {
return HardwareInput();
}
String id;
U32String fullKeyDesc;
byte modifierFlags = 0;
for (const ModifierTableEntry *modifier = _modifiers; modifier->id; modifier++) {
if (normalizedKeystate.flags & modifier->flag) {
id += modifier->id;
id += "+";
fullKeyDesc += _(modifier->desc);
modifierFlags |= modifier->flag;
}
}
const KeyState keystate = KeyState(key->keycode, 0, modifierFlags);
return HardwareInput::createKeyboard(id + key->hwId, keystate, fullKeyDesc + _(key->desc));
}
default:
return HardwareInput();
}
}
KeyState KeyboardHardwareInputSet::normalizeKeyState(const KeyState &keystate) {
KeyState normalizedKeystate = keystate;
// We ignore the sticky modifiers as they traditionally
// have no impact on the outcome of key presses.
// TODO: Maybe Num Lock should act as a modifier for the keypad.
normalizedKeystate.flags &= ~KBD_STICKY;
// Modifier keypresses ignore the corresponding modifier flag.
// That way, for example, `Left Shift` is not identified
// as `Shift+Left Shift` by the keymapper.
switch (normalizedKeystate.keycode) {
case KEYCODE_LSHIFT:
case KEYCODE_RSHIFT:
normalizedKeystate.flags &= ~KBD_SHIFT;
break;
case KEYCODE_LCTRL:
case KEYCODE_RCTRL:
normalizedKeystate.flags &= ~KBD_CTRL;
break;
case KEYCODE_LALT:
case KEYCODE_RALT:
normalizedKeystate.flags &= ~KBD_ALT;
break;
case KEYCODE_LMETA:
case KEYCODE_RMETA:
normalizedKeystate.flags &= ~KBD_META;
break;
case KEYCODE_SCROLLOCK:
normalizedKeystate.flags &= ~KBD_SCRL;
break;
case KEYCODE_CAPSLOCK:
normalizedKeystate.flags &= ~KBD_CAPS;
break;
case KEYCODE_NUMLOCK:
normalizedKeystate.flags &= ~KBD_NUM;
break;
default:
break;
}
return normalizedKeystate;
}
MouseHardwareInputSet::MouseHardwareInputSet(const HardwareInputTableEntry *buttonEntries) :
_buttonEntries(buttonEntries) {
assert(_buttonEntries);
}
HardwareInput MouseHardwareInputSet::findHardwareInput(const String &id) const {
const HardwareInputTableEntry *hw = HardwareInputTableEntry::findWithId(_buttonEntries, id);
if (!hw || !hw->hwId) {
return HardwareInput();
}
return HardwareInput::createMouse(hw->hwId, hw->code, _(hw->desc));
}
HardwareInput MouseHardwareInputSet::findHardwareInput(const Event &event) const {
int button;
switch (event.type) {
case EVENT_LBUTTONDOWN:
case EVENT_LBUTTONUP:
button = MOUSE_BUTTON_LEFT;
break;
case EVENT_RBUTTONDOWN:
case EVENT_RBUTTONUP:
button = MOUSE_BUTTON_RIGHT;
break;
case EVENT_MBUTTONDOWN:
case EVENT_MBUTTONUP:
button = MOUSE_BUTTON_MIDDLE;
break;
case Common::EVENT_WHEELUP:
button = MOUSE_WHEEL_UP;
break;
case Common::EVENT_WHEELDOWN:
button = MOUSE_WHEEL_DOWN;
break;
case EVENT_X1BUTTONDOWN:
case EVENT_X1BUTTONUP:
button = MOUSE_BUTTON_X1;
break;
case EVENT_X2BUTTONDOWN:
case EVENT_X2BUTTONUP:
button = MOUSE_BUTTON_X2;
break;
default:
button = -1;
break;
}
if (button == -1) {
return HardwareInput();
}
const HardwareInputTableEntry *hw = HardwareInputTableEntry::findWithCode(_buttonEntries, button);
if (!hw || !hw->hwId) {
return HardwareInput();
}
return HardwareInput::createMouse(hw->hwId, hw->code, _(hw->desc));
}
JoystickHardwareInputSet::JoystickHardwareInputSet(const HardwareInputTableEntry *buttonEntries, const AxisTableEntry *axisEntries) :
_buttonEntries(buttonEntries),
_axisEntries(axisEntries) {
assert(_buttonEntries);
assert(_axisEntries);
}
HardwareInput JoystickHardwareInputSet::findHardwareInput(const String &id) const {
const HardwareInputTableEntry *hw = HardwareInputTableEntry::findWithId(_buttonEntries, id);
if (hw && hw->hwId) {
return HardwareInput::createJoystickButton(hw->hwId, hw->code, _(hw->desc));
}
bool hasHalfSuffix = id.lastChar() == '-' || id.lastChar() == '+';
Common::String tableId = hasHalfSuffix ? Common::String(id.c_str(), id.size() - 1) : id;
const AxisTableEntry *axis = AxisTableEntry::findWithId(_axisEntries, tableId);
if (axis && axis->hwId) {
if (hasHalfSuffix && axis->type == kAxisTypeHalf) {
return HardwareInput(); // Half axes can't be split in halves
} else if (!hasHalfSuffix && axis->type == kAxisTypeFull) {
return HardwareInput(); // For now it's only possible to bind half axes
}
if (axis->type == kAxisTypeHalf) {
return HardwareInput::createJoystickHalfAxis(axis->hwId, axis->code, true, _(axis->desc));
} else {
bool positiveHalf = id.lastChar() == '+';
Common::U32String desc = U32String::format("%S%c", _(axis->desc).c_str(), id.lastChar());
return HardwareInput::createJoystickHalfAxis(id, axis->code, positiveHalf, desc);
}
}
return HardwareInput();
}
HardwareInput JoystickHardwareInputSet::findHardwareInput(const Event &event) const {
switch (event.type) {
case EVENT_JOYBUTTON_DOWN:
case EVENT_JOYBUTTON_UP: {
const HardwareInputTableEntry *hw = HardwareInputTableEntry::findWithCode(_buttonEntries, event.joystick.button);
if (!hw || !hw->hwId) {
return HardwareInput();
}
return HardwareInput::createJoystickButton(hw->hwId, hw->code, _(hw->desc));
}
case EVENT_JOYAXIS_MOTION: {
if (ABS(event.joystick.position) < (JOYAXIS_MAX / 2)) {
return HardwareInput(); // Ignore incomplete presses for remapping purposes
}
const AxisTableEntry *hw = AxisTableEntry::findWithCode(_axisEntries, event.joystick.axis);
if (!hw || !hw->hwId) {
return HardwareInput();
}
if (hw->type == kAxisTypeHalf) {
return HardwareInput::createJoystickHalfAxis(hw->hwId, hw->code, true, _(hw->desc));
} else {
bool positiveHalf = event.joystick.position >= 0;
char halfSuffix = positiveHalf ? '+' : '-';
Common::String hwId = String::format("%s%c", hw->hwId, halfSuffix);
Common::U32String desc = U32String::format("%S%c", _(hw->desc).c_str(), halfSuffix);
return HardwareInput::createJoystickHalfAxis(hwId, hw->code, positiveHalf, desc);
}
}
default:
return HardwareInput();
}
}
CustomHardwareInputSet::CustomHardwareInputSet(const HardwareInputTableEntry *hardwareEntries) :
_hardwareEntries(hardwareEntries) {
assert(_hardwareEntries);
}
HardwareInput CustomHardwareInputSet::findHardwareInput(const String &id) const {
const HardwareInputTableEntry *hw = HardwareInputTableEntry::findWithId(_hardwareEntries, id);
if (!hw || !hw->hwId) {
return HardwareInput();
}
return HardwareInput::createCustom(hw->hwId, hw->code, _(hw->desc));
}
HardwareInput CustomHardwareInputSet::findHardwareInput(const Event &event) const {
switch (event.type) {
case EVENT_CUSTOM_BACKEND_HARDWARE: {
const HardwareInputTableEntry *hw = HardwareInputTableEntry::findWithCode(_hardwareEntries, event.customType);
if (!hw || !hw->hwId) {
return HardwareInput();
}
return HardwareInput::createCustom(hw->hwId, hw->code, _(hw->desc));
}
default:
return HardwareInput();
}
}
CompositeHardwareInputSet::~CompositeHardwareInputSet() {
for (uint i = 0; i < _inputSets.size(); i++) {
delete _inputSets[i];
}
}
HardwareInput CompositeHardwareInputSet::findHardwareInput(const String &id) const {
for (uint i = 0; i < _inputSets.size(); i++) {
HardwareInput hardwareInput = _inputSets[i]->findHardwareInput(id);
if (hardwareInput.type != kHardwareInputTypeInvalid) {
return hardwareInput;
}
}
return HardwareInput();
}
HardwareInput CompositeHardwareInputSet::findHardwareInput(const Event &event) const {
for (uint i = 0; i < _inputSets.size(); i++) {
HardwareInput hardwareInput = _inputSets[i]->findHardwareInput(event);
if (hardwareInput.type != kHardwareInputTypeInvalid) {
return hardwareInput;
}
}
return HardwareInput();
}
void CompositeHardwareInputSet::addHardwareInputSet(HardwareInputSet *hardwareInputSet) {
_inputSets.push_back(hardwareInputSet);
}
} //namespace Common

View File

@@ -0,0 +1,333 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COMMON_HARDWARE_KEY_H
#define COMMON_HARDWARE_KEY_H
#include "common/scummsys.h"
#include "common/array.h"
#include "common/events.h"
#include "common/keyboard.h"
#include "common/str.h"
namespace Common {
typedef uint32 HardwareInputCode;
enum HardwareInputType {
/** Empty / invalid input type */
kHardwareInputTypeInvalid,
/** Keyboard input that sends -up and -down events */
kHardwareInputTypeKeyboard,
/** Mouse input that sends -up and -down events */
kHardwareInputTypeMouse,
/** Joystick input that sends -up and -down events */
kHardwareInputTypeJoystickButton,
/** Joystick input that sends "analog" values */
kHardwareInputTypeJoystickHalfAxis,
/** Input that sends single events */
kHardwareInputTypeCustom
};
/**
* Describes an available hardware input
*/
struct HardwareInput {
/** unique id used for saving/loading to config */
String id;
/** Human readable description */
U32String description;
/** Type tag */
HardwareInputType type;
/**
* A platform specific unique identifier for an input event
* generated when this input is triggered.
* This is only relevant when type == kHardwareInputTypeGeneric
*/
HardwareInputCode inputCode;
/**
* The KeyState that is generated by the back-end
* when this hardware key is pressed.
* This is only relevant when type == kHardwareInputTypeKeyboard
*/
KeyState key;
HardwareInput()
: inputCode(0), type(kHardwareInputTypeInvalid) { }
static HardwareInput createCustom(const String &i, HardwareInputCode ic, const U32String &desc) {
return createSimple(kHardwareInputTypeCustom, i, ic, desc);
}
static HardwareInput createKeyboard(const String &i, KeyState ky, const U32String &desc) {
HardwareInput hardwareInput;
hardwareInput.id = i;
hardwareInput.description = desc;
hardwareInput.type = kHardwareInputTypeKeyboard;
hardwareInput.inputCode = 0;
hardwareInput.key = ky;
return hardwareInput;
}
static HardwareInput createJoystickButton(const String &i, uint8 button, const U32String &desc) {
return createSimple(kHardwareInputTypeJoystickButton, i, button, desc);
}
static HardwareInput createJoystickHalfAxis(const String &i, uint8 axis, bool positiveHalf, const U32String &desc) {
return createSimple(kHardwareInputTypeJoystickHalfAxis, i, axis * 2 + (positiveHalf ? 1 : 0), desc);
}
static HardwareInput createMouse(const String &i, uint8 button, const U32String &desc) {
return createSimple(kHardwareInputTypeMouse, i, button, desc);
}
private:
static HardwareInput createSimple(HardwareInputType type, const String &i, HardwareInputCode ic, const U32String &desc) {
HardwareInput hardwareInput;
hardwareInput.id = i;
hardwareInput.description = desc;
hardwareInput.type = type;
hardwareInput.inputCode = ic;
return hardwareInput;
}
};
/**
* Entry in a static table of custom backend hardware inputs
*/
struct HardwareInputTableEntry {
const char *hwId;
HardwareInputCode code;
const char *desc;
static const HardwareInputTableEntry *findWithCode(const HardwareInputTableEntry *_entries, HardwareInputCode code) {
for (const HardwareInputTableEntry *hw = _entries; hw->hwId; hw++) {
if (hw->code == code) {
return hw;
}
}
return nullptr;
}
static const HardwareInputTableEntry *findWithId(const HardwareInputTableEntry *_entries, const String &id) {
for (const HardwareInputTableEntry *hw = _entries; hw->hwId; hw++) {
if (id.equals(hw->hwId)) {
return hw;
}
}
return nullptr;
}
};
/**
* Entry in a static table of available non-modifier keys
*/
struct KeyTableEntry {
const char *hwId;
KeyCode keycode;
const char *desc;
};
/**
* Entry in a static table of available key modifiers
*/
struct ModifierTableEntry {
byte flag;
const char *id;
const char *desc;
};
enum AxisType {
/** An axis that sends "analog" values from JOYAXIS_MIN to JOYAXIS_MAX. e.g. a gamepad stick axis */
kAxisTypeFull,
/** An axis that sends "analog" values from 0 to JOYAXIS_MAX. e.g. a gamepad trigger */
kAxisTypeHalf
};
struct AxisTableEntry {
const char *hwId;
HardwareInputCode code;
AxisType type;
const char *desc;
static const AxisTableEntry *findWithCode(const AxisTableEntry *_entries, HardwareInputCode code) {
for (const AxisTableEntry *hw = _entries; hw->hwId; hw++) {
if (hw->code == code) {
return hw;
}
}
return nullptr;
}
static const AxisTableEntry *findWithId(const AxisTableEntry *_entries, const String &id) {
for (const AxisTableEntry *hw = _entries; hw->hwId; hw++) {
if (id.equals(hw->hwId)) {
return hw;
}
}
return nullptr;
}
};
/**
* Interface for querying information about a hardware input device
*/
class HardwareInputSet {
public:
virtual ~HardwareInputSet();
/**
* Retrieve a hardware input description from an unique identifier
*
* In case no input was found with the specified id, an empty
* HardwareInput structure is return with the type set to
* kHardwareInputTypeInvalid.
*/
virtual HardwareInput findHardwareInput(const String &id) const = 0;
/**
* Retrieve a hardware input description from one of the events
* produced when the input is triggered.
*
* In case the specified event is not produced by this device,
* an empty HardwareInput structure is return with the type set to
* kHardwareInputTypeInvalid.
*/
virtual HardwareInput findHardwareInput(const Event &event) const = 0;
};
/**
* A keyboard input device
*
* Describes the keys and key + modifiers combinations as HardwareInputs
*/
class KeyboardHardwareInputSet : public HardwareInputSet {
public:
KeyboardHardwareInputSet(const KeyTableEntry *keys, const ModifierTableEntry *modifiers);
// HardwareInputSet API
HardwareInput findHardwareInput(const String &id) const override;
HardwareInput findHardwareInput(const Event &event) const override;
/** Transform a keystate into a canonical form that can be used to unambiguously identify the keypress */
static KeyState normalizeKeyState(const KeyState &keystate);
private:
const KeyTableEntry *_keys;
const ModifierTableEntry *_modifiers;
};
/**
* A mouse input device
*
* Describes the mouse buttons
*/
class MouseHardwareInputSet : public HardwareInputSet {
public:
MouseHardwareInputSet(const HardwareInputTableEntry *buttonEntries);
// HardwareInputSet API
HardwareInput findHardwareInput(const String &id) const override;
HardwareInput findHardwareInput(const Event &event) const override;
private:
const HardwareInputTableEntry *_buttonEntries;
};
/**
* A joystick input device
*/
class JoystickHardwareInputSet : public HardwareInputSet {
public:
JoystickHardwareInputSet(const HardwareInputTableEntry *buttonEntries, const AxisTableEntry *axisEntries);
// HardwareInputSet API
HardwareInput findHardwareInput(const String &id) const override;
HardwareInput findHardwareInput(const Event &event) const override;
private:
const HardwareInputTableEntry *_buttonEntries;
const AxisTableEntry *_axisEntries;
};
/**
* A custom backend input device
*
* @todo This is currently unused. Perhaps it should be removed.
*/
class CustomHardwareInputSet : public HardwareInputSet {
public:
CustomHardwareInputSet(const HardwareInputTableEntry *hardwareEntries);
// HardwareInputSet API
HardwareInput findHardwareInput(const String &id) const override;
HardwareInput findHardwareInput(const Event &event) const override;
private:
const HardwareInputTableEntry *_hardwareEntries;
};
/**
* A composite input device that delegates to a set of actual input devices.
*/
class CompositeHardwareInputSet : public HardwareInputSet {
public:
~CompositeHardwareInputSet() override;
// HardwareInputSet API
HardwareInput findHardwareInput(const String &id) const override;
HardwareInput findHardwareInput(const Event &event) const override;
/**
* Add an input device to this composite device
*
* Takes ownership of the hardware input set
*/
void addHardwareInputSet(HardwareInputSet *hardwareInputSet);
private:
Array<HardwareInputSet *> _inputSets;
};
/** A standard set of keyboard keys */
extern const KeyTableEntry defaultKeys[];
/** A standard set of keyboard modifiers */
extern const ModifierTableEntry defaultModifiers[];
/** A standard set of mouse buttons */
extern const HardwareInputTableEntry defaultMouseButtons[];
/** A standard set of joystick buttons based on the ScummVM event model */
extern const HardwareInputTableEntry defaultJoystickButtons[];
/** A standard set of joystick axes based on the ScummVM event model */
extern const AxisTableEntry defaultJoystickAxes[];
} // End of namespace Common
#endif // #ifndef COMMON_HARDWARE_KEY_H

View File

@@ -0,0 +1,97 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/keymapper/input-watcher.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymapper.h"
namespace Common {
InputWatcher::InputWatcher(EventDispatcher *eventDispatcher, Keymapper *keymapper) :
_eventDispatcher(eventDispatcher),
_keymapper(keymapper),
_watching(false) {
}
void InputWatcher::startWatching() {
assert(!_watching);
assert(_hwInput.type == kHardwareInputTypeInvalid);
_keymapper->setEnabled(false);
_eventDispatcher->registerObserver(this, EventManager::kEventRemapperPriority, false);
_watching = true;
}
void InputWatcher::stopWatching() {
_keymapper->setEnabled(true);
_eventDispatcher->unregisterObserver(this);
_watching = false;
}
bool InputWatcher::isWatching() const {
return _watching;
}
bool InputWatcher::notifyEvent(const Event &event) {
assert(_watching);
assert(_hwInput.type == kHardwareInputTypeInvalid);
switch (event.type) {
case EVENT_KEYDOWN:
case EVENT_JOYBUTTON_DOWN:
case EVENT_LBUTTONDOWN:
case EVENT_RBUTTONDOWN:
case EVENT_MBUTTONDOWN:
case EVENT_X1BUTTONDOWN:
case EVENT_X2BUTTONDOWN:
return true;
case EVENT_KEYUP:
case EVENT_JOYBUTTON_UP:
case EVENT_JOYAXIS_MOTION:
case EVENT_LBUTTONUP:
case EVENT_RBUTTONUP:
case EVENT_MBUTTONUP:
case EVENT_WHEELUP:
case EVENT_WHEELDOWN:
case EVENT_X1BUTTONUP:
case EVENT_X2BUTTONUP:
case EVENT_CUSTOM_BACKEND_HARDWARE:
_hwInput = _keymapper->findHardwareInput(event);
if (_hwInput.type != kHardwareInputTypeInvalid) {
stopWatching();
}
return true;
default:
break;
}
return false;
}
HardwareInput InputWatcher::checkForCapturedInput() {
HardwareInput hwInput = _hwInput;
_hwInput = HardwareInput();
return hwInput;
}
} // End of namespace Common

View File

@@ -0,0 +1,65 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COMMON_INPUT_WATCHER_H
#define COMMON_INPUT_WATCHER_H
#include "common/scummsys.h"
#include "backends/keymapper/hardware-input.h"
#include "common/events.h"
namespace Common {
struct HardwareInput;
/**
* Watches events for inputs that can be bound to actions
*
* When the watch mode is enabled, the watcher disables the Keymapper
* and sets itself as an event observer. Once an event corresponding
* to a hardware input is received, it is saved for later retrieval.
*
* Used by the remap dialog to capture input.
*/
class InputWatcher : private EventObserver {
public:
InputWatcher(EventDispatcher *eventDispatcher, Keymapper *keymapper);
void startWatching();
void stopWatching();
bool isWatching() const;
HardwareInput checkForCapturedInput();
private:
bool notifyEvent(const Event &event) override;
EventDispatcher *_eventDispatcher;
Keymapper *_keymapper;
bool _watching;
HardwareInput _hwInput;
};
} // End of namespace Common
#endif // #ifndef COMMON_INPUT_WATCHER_H

View File

@@ -0,0 +1,373 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/keymapper/keymap.h"
#include "common/system.h"
#include "common/tokenizer.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/hardware-input.h"
#include "backends/keymapper/keymapper-defaults.h"
#define KEYMAP_KEY_PREFIX "keymap_"
namespace Common {
Keymap::Keymap(KeymapType type, const String &id, const U32String &description) :
_type(type),
_id(id),
_description(description),
_partialMatchAllowed(true),
_enabled(true),
_configDomain(nullptr),
_hardwareInputSet(nullptr),
_backendDefaultBindings(nullptr) {
}
Keymap::Keymap(KeymapType type, const String &id, const String &description) :
_type(type),
_id(id),
_description(U32String(description)),
_partialMatchAllowed(true),
_enabled(true),
_configDomain(nullptr),
_hardwareInputSet(nullptr),
_backendDefaultBindings(nullptr) {
}
Keymap::~Keymap() {
for (auto *action : _actions)
delete action;
}
void Keymap::addAction(Action *action) {
if (findAction(action->id))
error("Action with id %s already in KeyMap", action->id);
_actions.push_back(action);
}
void Keymap::registerMapping(Action *action, const HardwareInput &hwInput) {
ActionArray &actionArray = _hwActionMap.getOrCreateVal(hwInput);
// Don't allow an input to map to the same action multiple times
ActionArray::const_iterator found = find(actionArray.begin(), actionArray.end(), action);
if (found == actionArray.end()) {
actionArray.push_back(action);
}
}
void Keymap::unregisterMapping(Action *action) {
// Remove the action from all the input mappings
for (auto &hwAction : _hwActionMap) {
for (auto &itAction : hwAction._value) {
if (itAction == action) {
hwAction._value.erase(&itAction);
break;
}
}
if (hwAction._value.empty()) {
_hwActionMap.erase(hwAction._key);
}
}
}
void Keymap::resetMapping(Action *action) {
unregisterMapping(action);
StringArray hwInputIds = getActionDefaultMappings(action);
registerMappings(action, hwInputIds);
}
struct HardwareInputTypeIdComparator {
bool operator()(const HardwareInput &x, const HardwareInput &y) const {
if (x.type != y.type) {
return x.type < y.type;
}
return x.id.compareTo(y.id);
}
};
Array<HardwareInput> Keymap::getActionMapping(const Action *action) const {
Array<HardwareInput> inputs;
for (auto &itInput : _hwActionMap) {
for (auto &itAction : itInput._value) {
if (itAction == action) {
inputs.push_back(itInput._key);
break;
}
}
}
// Sort the inputs by type and then id for the remap dialog
Common::sort(inputs.begin(), inputs.end(), HardwareInputTypeIdComparator());
return inputs;
}
const Action *Keymap::findAction(const char *id) const {
for (const auto &action : _actions) {
if (strcmp(action->id, id) == 0)
return action;
}
return nullptr;
}
Keymap::KeymapMatch Keymap::getMappedActions(const Event &event, ActionArray &actions) const {
switch (event.type) {
case EVENT_KEYDOWN:
case EVENT_KEYUP: {
KeyState normalizedKeystate = KeyboardHardwareInputSet::normalizeKeyState(event.kbd);
HardwareInput hardwareInput = HardwareInput::createKeyboard("", normalizedKeystate, U32String());
actions.push_back(_hwActionMap.getValOrDefault(hardwareInput));
if (!actions.empty()) {
return kKeymapMatchExact;
}
if (_partialMatchAllowed && normalizedKeystate.flags & KBD_NON_STICKY) {
// If no matching actions and non-sticky keyboard modifiers are down,
// check again for matches without the exact keyboard modifiers
for (const auto &itInput : _hwActionMap) {
if (itInput._key.type == kHardwareInputTypeKeyboard && itInput._key.key.keycode == normalizedKeystate.keycode) {
int flags = itInput._key.key.flags;
if (flags & KBD_NON_STICKY && (flags & normalizedKeystate.flags) == flags) {
actions.push_back(itInput._value);
return kKeymapMatchPartial;
}
}
}
// Lastly check again for matches no non-sticky keyboard modifiers
normalizedKeystate.flags &= ~KBD_NON_STICKY;
hardwareInput = HardwareInput::createKeyboard("", normalizedKeystate, U32String());
actions.push_back(_hwActionMap.getValOrDefault(hardwareInput));
return actions.empty() ? kKeymapMatchNone : kKeymapMatchPartial;
}
break;
}
case EVENT_LBUTTONDOWN:
case EVENT_LBUTTONUP: {
HardwareInput hardwareInput = HardwareInput::createMouse("", MOUSE_BUTTON_LEFT, U32String());
actions.push_back(_hwActionMap.getValOrDefault(hardwareInput));
break;
}
case EVENT_RBUTTONDOWN:
case EVENT_RBUTTONUP: {
HardwareInput hardwareInput = HardwareInput::createMouse("", MOUSE_BUTTON_RIGHT, U32String());
actions.push_back(_hwActionMap.getValOrDefault(hardwareInput));
break;
}
case EVENT_MBUTTONDOWN:
case EVENT_MBUTTONUP: {
HardwareInput hardwareInput = HardwareInput::createMouse("", MOUSE_BUTTON_MIDDLE, U32String());
actions.push_back(_hwActionMap.getValOrDefault(hardwareInput));
break;
}
case Common::EVENT_WHEELUP: {
HardwareInput hardwareInput = HardwareInput::createMouse("", MOUSE_WHEEL_UP, U32String());
actions.push_back(_hwActionMap.getValOrDefault(hardwareInput));
break;
}
case Common::EVENT_WHEELDOWN: {
HardwareInput hardwareInput = HardwareInput::createMouse("", MOUSE_WHEEL_DOWN, U32String());
actions.push_back(_hwActionMap.getValOrDefault(hardwareInput));
break;
}
case EVENT_X1BUTTONDOWN:
case EVENT_X1BUTTONUP: {
HardwareInput hardwareInput = HardwareInput::createMouse("", MOUSE_BUTTON_X1, U32String());
actions.push_back(_hwActionMap.getValOrDefault(hardwareInput));
break;
}
case EVENT_X2BUTTONDOWN:
case EVENT_X2BUTTONUP: {
HardwareInput hardwareInput = HardwareInput::createMouse("", MOUSE_BUTTON_X2, U32String());
actions.push_back(_hwActionMap.getValOrDefault(hardwareInput));
break;
}
case EVENT_JOYBUTTON_DOWN:
case EVENT_JOYBUTTON_UP: {
HardwareInput hardwareInput = HardwareInput::createJoystickButton("", event.joystick.button, U32String());
actions.push_back(_hwActionMap.getValOrDefault(hardwareInput));
break;
}
case EVENT_JOYAXIS_MOTION: {
if (event.joystick.position != 0) {
bool positiveHalf = event.joystick.position >= 0;
HardwareInput hardwareInput = HardwareInput::createJoystickHalfAxis("", event.joystick.axis, positiveHalf, U32String());
actions.push_back(_hwActionMap.getValOrDefault(hardwareInput));
} else {
// Axis position zero is part of both half axes, and triggers actions bound to both
HardwareInput hardwareInputPos = HardwareInput::createJoystickHalfAxis("", event.joystick.axis, true, U32String());
HardwareInput hardwareInputNeg = HardwareInput::createJoystickHalfAxis("", event.joystick.axis, false, U32String());
actions.push_back(_hwActionMap.getValOrDefault(hardwareInputPos));
actions.push_back(_hwActionMap.getValOrDefault(hardwareInputNeg));
}
break;
}
case EVENT_CUSTOM_BACKEND_HARDWARE: {
HardwareInput hardwareInput = HardwareInput::createCustom("", event.customType, U32String());
actions.push_back(_hwActionMap.getValOrDefault(hardwareInput));
break;
}
default:
break;
}
return actions.empty() ? kKeymapMatchNone : kKeymapMatchExact;
}
void Keymap::setConfigDomain(ConfigManager::Domain *configDomain) {
_configDomain = configDomain;
}
void Keymap::setHardwareInputs(HardwareInputSet *hardwareInputSet) {
_hardwareInputSet = hardwareInputSet;
}
void Keymap::setBackendDefaultBindings(const KeymapperDefaultBindings *backendDefaultBindings) {
_backendDefaultBindings = backendDefaultBindings;
}
StringArray Keymap::getActionDefaultMappings(Action *action) {
// Backend default mappings overrides keymap default mappings, so backends can resolve mapping conflicts.
// Empty mappings are valid and mean the action should not be mapped by default.
if (_backendDefaultBindings) {
KeymapperDefaultBindings::const_iterator it = _backendDefaultBindings->findDefaultBinding(_id, action->id);
if (it != _backendDefaultBindings->end()) {
return it->_value;
}
// If no keymap-specific default mapping was found, look for a standard action binding
it = _backendDefaultBindings->findDefaultBinding(kStandardActionsKeymapName, action->id);
if (it != _backendDefaultBindings->end()) {
return it->_value;
}
}
return action->getDefaultInputMapping();
}
void Keymap::loadMappings() {
assert(_configDomain);
assert(_hardwareInputSet);
if (_actions.empty()) {
return;
}
String prefix = KEYMAP_KEY_PREFIX + _id + "_";
_hwActionMap.clear();
for (auto &action : _actions) {
String confKey = prefix + action->id;
StringArray hwInputIds;
if (_configDomain->contains(confKey)) {
// The configuration value is a list of space separated hardware input ids
StringTokenizer hwInputTokenizer = _configDomain->getVal(confKey);
while (!hwInputTokenizer.empty()) {
hwInputIds.push_back(hwInputTokenizer.nextToken());
}
} else {
// If the configuration key was not found, use the default mapping
hwInputIds = getActionDefaultMappings(action);
}
registerMappings(action, hwInputIds);
}
}
void Keymap::registerMappings(Action *action, const StringArray &hwInputIds) {
assert(_hardwareInputSet);
for (uint i = 0; i < hwInputIds.size(); i++) {
HardwareInput hwInput = _hardwareInputSet->findHardwareInput(hwInputIds[i]);
if (hwInput.type == kHardwareInputTypeInvalid) {
// Silently ignore unknown hardware ids because the current device may not have inputs matching the defaults
debug(1, "HardwareInput with ID '%s' not known", hwInputIds[i].c_str());
continue;
}
// map the key
registerMapping(action, hwInput);
}
}
void Keymap::saveMappings() {
if (!_configDomain)
return;
String prefix = KEYMAP_KEY_PREFIX + _id + "_";
for (const auto &action : _actions) {
Array<HardwareInput> mappedInputs = getActionMapping(action);
if (areMappingsIdentical(mappedInputs, getActionDefaultMappings(action))) {
// If the current mapping is the default, don't write anything to the config manager
_configDomain->erase(prefix + action->id);
continue;
}
// The configuration value is a list of space separated hardware input ids
String confValue;
for (uint j = 0; j < mappedInputs.size(); j++) {
if (!confValue.empty()) {
confValue += " ";
}
confValue += mappedInputs[j].id;
}
_configDomain->setVal(prefix + action->id, confValue);
}
}
bool Keymap::areMappingsIdentical(const Array<HardwareInput> &mappingsA, const StringArray &mappingsB) {
// Assumes array values are not duplicated, but registerMapping and addDefaultInputMapping ensure that
uint foundCount = 0;
uint validDefaultMappings = 0;
for (uint i = 0; i < mappingsB.size(); i++) {
// We resolve the hardware input to make sure it is not a default for some hardware we don't have currently
HardwareInput mappingB = _hardwareInputSet->findHardwareInput(mappingsB[i]);
if (mappingB.type == kHardwareInputTypeInvalid) continue;
validDefaultMappings++;
for (uint j = 0; j < mappingsA.size(); j++) {
if (mappingsA[j].id == mappingB.id) {
foundCount++;
break;
}
}
}
return foundCount == mappingsA.size() && foundCount == validDefaultMappings;
}
} // End of namespace Common

202
backends/keymapper/keymap.h Normal file
View File

@@ -0,0 +1,202 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COMMON_KEYMAP_H
#define COMMON_KEYMAP_H
#include "common/scummsys.h"
#include "backends/keymapper/hardware-input.h"
#include "common/config-manager.h"
#include "common/func.h"
#include "common/hashmap.h"
#include "common/hash-ptr.h"
#include "common/list.h"
#include "common/str-array.h"
namespace Common {
const char *const kStandardActionsKeymapName = "standard-actions";
struct Action;
struct Event;
struct HardwareInput;
class HardwareInputSet;
class KeymapperDefaultBindings;
struct HardwareInput_EqualTo {
bool operator()(const HardwareInput& x, const HardwareInput& y) const {
return (x.type == y.type)
&& (x.key.keycode == y.key.keycode)
&& (x.key.flags == y.key.flags)
&& (x.inputCode == y.inputCode);
}
};
struct HardwareInput_Hash {
uint operator()(const HardwareInput& x) const {
uint hash = 7;
hash = 31 * hash + x.type;
hash = 31 * hash + x.key.keycode;
hash = 31 * hash + x.key.flags;
hash = 31 * hash + x.inputCode;
return hash;
}
};
class Keymap {
public:
enum KeymapType {
kKeymapTypeGlobal,
kKeymapTypeGui,
kKeymapTypeGame
};
enum KeymapMatch {
kKeymapMatchNone,
kKeymapMatchPartial,
kKeymapMatchExact
};
typedef Array<Action *> ActionArray;
Keymap(KeymapType type, const String &id, const U32String &description);
Keymap(KeymapType type, const String &id, const String &description);
~Keymap();
void setConfigDomain(ConfigManager::Domain *configDomain);
void setHardwareInputs(HardwareInputSet *hardwareInputSet);
void setBackendDefaultBindings(const KeymapperDefaultBindings *backendDefaultBindings);
/**
* Registers a HardwareInput to the given Action
* @param action Action in this Keymap
* @param key pointer to HardwareInput to map
* @see Action::mapKey
*/
void registerMapping(Action *action, const HardwareInput &input);
/**
* Unregisters a HardwareInput from the given Action (if one is mapped)
* @param action Action in this Keymap
* @see Action::mapKey
*/
void unregisterMapping(Action *action);
/**
* Reset an action's mapping to its defaults
* @param action
*/
void resetMapping(Action *action);
/**
* Find the hardware input an action is mapped to, if any
*/
Array<HardwareInput> getActionMapping(const Action *action) const;
/**
* Find the Actions that a hardware input is mapped to
* @param hardwareInput the input that is mapped to the required Action
* @param actions an array containing pointers to the actions
* @return the matching status for the retieved actions
*/
KeymapMatch getMappedActions(const Event &event, ActionArray &actions) const;
/**
* Adds a new Action to this Map
*
* Takes ownership of the action.
*
* @param action the Action to add
*/
void addAction(Action *action);
/**
* Get the list of all the Actions contained in this Keymap
*/
const ActionArray &getActions() const { return _actions; }
/**
* Get the default input mappings for an action.
*
* Backend-specific mappings replace the default mappings
* specified when creating the keymap.
*/
StringArray getActionDefaultMappings(Action *action);
/**
* Load this keymap's mappings from the config manager.
* @param hwInputs the set to retrieve hardware input pointers from
*/
void loadMappings();
/**
* Save this keymap's mappings to the config manager
* @note Changes are *not* flushed to disk, to do so call ConfMan.flushToDisk()
*/
void saveMappings();
const String &getId() const { return _id; }
const U32String &getDescription() const { return _description; }
KeymapType getType() const { return _type; }
bool isPartialMatchAllowed() const { return _partialMatchAllowed; }
void setPartialMatchAllowed(bool partialMatchAllowed) { _partialMatchAllowed = partialMatchAllowed; }
/**
* Defines if the keymap is considered when mapping events
*/
bool isEnabled() const { return _enabled; }
void setEnabled(bool enabled) { _enabled = enabled; }
/** Helper to return an array with a single keymap element */
static Array<Keymap *> arrayOf(Keymap *keymap) {
return Array<Keymap *>(1, keymap);
}
private:
const Action *findAction(const char *id) const;
void registerMappings(Action *action, const StringArray &hwInputIds);
bool areMappingsIdentical(const Array<HardwareInput> &inputs, const StringArray &mapping);
typedef HashMap<HardwareInput, ActionArray, HardwareInput_Hash, HardwareInput_EqualTo> HardwareActionMap;
KeymapType _type;
String _id;
U32String _description;
bool _partialMatchAllowed;
bool _enabled;
ActionArray _actions;
HardwareActionMap _hwActionMap;
ConfigManager::Domain *_configDomain;
HardwareInputSet *_hardwareInputSet;
const KeymapperDefaultBindings *_backendDefaultBindings;
};
typedef Array<Keymap *> KeymapArray;
} // End of namespace Common
#endif // #ifndef COMMON_KEYMAP_H

View File

@@ -0,0 +1,84 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef KEYMAPPER_DEFAULTS_H
#define KEYMAPPER_DEFAULTS_H
#include "common/scummsys.h"
#include "common/hashmap.h"
#include "common/str.h"
#include "common/hash-str.h"
namespace Common {
class KeymapperDefaultBindings : public HashMap<String, StringArray> {
public:
/**
* This sets a default hwInput for a given Keymap Action
* @param keymapId String representing Keymap id (Keymap.name)
* @param actionId String representing Action id (Action.id)
* @param hwInputId String representing the HardwareInput id (HardwareInput.id)
*/
void setDefaultBinding(String keymapId, String actionId, String hwInputId) {
setVal(keymapId + "_" + actionId, hwInputId.empty() ? StringArray() : StringArray(1, hwInputId));
}
/**
* This adds a default hwInput for a given Keymap Action
* @param keymapId String representing Keymap id (Keymap.name)
* @param actionId String representing Action id (Action.id)
* @param hwInputId String representing the HardwareInput id (HardwareInput.id)
*/
void addDefaultBinding(String keymapId, String actionId, String hwInputId) {
// NOTE: addDefaultBinding() cannot be used to remove bindings;
// use setDefaultBinding() with a nullptr or empty string as hwInputId instead.
if (hwInputId.empty()) {
return;
}
KeymapperDefaultBindings::iterator it = findDefaultBinding(keymapId, actionId);
if (it != end()) {
// Don't allow an input to map to the same action multiple times
StringArray &itv = it->_value;
Array<String>::const_iterator found = Common::find(itv.begin(), itv.end(), hwInputId);
if (found == itv.end()) {
itv.push_back(hwInputId);
}
} else {
setDefaultBinding(keymapId, actionId, hwInputId);
}
}
/**
* This retrieves the assigned default hwKey for a given Keymap Action
* @param keymapId String representing Keymap id (Keymap.name)
* @param actionId String representing Action id (Action.id)
* @return StringArray representing the list of HardwareInput ids (HardwareInput.id) that are mapped to Keymap Action
*/
const_iterator findDefaultBinding(String keymapId, String actionId) const {
return find(keymapId + "_" + actionId);
}
};
} //namespace Common
#endif // #ifndef KEYMAPPER_DEFAULTS_H

View File

@@ -0,0 +1,453 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/keymapper/keymapper.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/hardware-input.h"
#include "backends/keymapper/keymapper-defaults.h"
#include "common/system.h"
namespace Common {
// These magic numbers are provided by fuzzie and WebOS
static const uint32 kDelayKeyboardEventMillis = 250;
static const uint32 kDelayMouseEventMillis = 50;
Keymapper::Keymapper(EventManager *eventMan) :
_eventMan(eventMan),
_hardwareInputs(nullptr),
_backendDefaultBindings(nullptr),
_delayedEventSource(new DelayedEventSource()),
_enabled(true),
_enabledKeymapType(Keymap::kKeymapTypeGame) {
_eventMan->getEventDispatcher()->registerSource(_delayedEventSource, true);
resetInputState();
}
Keymapper::~Keymapper() {
clear();
}
void Keymapper::clear() {
for (auto *keymap : _keymaps) {
delete keymap;
}
_keymaps.clear();
delete _backendDefaultBindings;
_backendDefaultBindings = nullptr;
delete _hardwareInputs;
_hardwareInputs = nullptr;
}
void Keymapper::registerHardwareInputSet(HardwareInputSet *inputs, KeymapperDefaultBindings *backendDefaultBindings) {
bool reloadMappings = false;
if (_hardwareInputs) {
reloadMappings = true;
delete _hardwareInputs;
}
if (_backendDefaultBindings) {
reloadMappings = true;
delete _backendDefaultBindings;
}
if (!inputs) {
warning("No hardware input were defined, using defaults");
CompositeHardwareInputSet *compositeInputs = new CompositeHardwareInputSet();
compositeInputs->addHardwareInputSet(new MouseHardwareInputSet(defaultMouseButtons));
compositeInputs->addHardwareInputSet(new KeyboardHardwareInputSet(defaultKeys, defaultModifiers));
inputs = compositeInputs;
}
_hardwareInputs = inputs;
_backendDefaultBindings = backendDefaultBindings;
if (reloadMappings) {
reloadAllMappings();
}
}
void Keymapper::addGlobalKeymap(Keymap *keymap) {
assert(keymap->getType() == Keymap::kKeymapTypeGlobal
|| keymap->getType() == Keymap::kKeymapTypeGui);
ConfigManager::Domain *keymapperDomain = ConfMan.getDomain(ConfigManager::kKeymapperDomain);
initKeymap(keymap, keymapperDomain);
_keymaps.push_back(keymap);
}
void Keymapper::addGameKeymap(Keymap *keymap) {
assert(keymap->getType() == Keymap::kKeymapTypeGame);
ConfigManager::Domain *gameDomain = ConfMan.getActiveDomain();
if (!gameDomain) {
error("Call to Keymapper::addGameKeymap when no game loaded");
}
initKeymap(keymap, gameDomain);
_keymaps.push_back(keymap);
}
void Keymapper::initKeymap(Keymap *keymap, ConfigManager::Domain *domain) {
if (!_hardwareInputs) {
warning("No hardware inputs were registered yet (%s)", keymap->getId().c_str());
return;
}
keymap->setConfigDomain(domain);
reloadKeymapMappings(keymap);
}
void Keymapper::reloadKeymapMappings(Keymap *keymap) {
keymap->setHardwareInputs(_hardwareInputs);
keymap->setBackendDefaultBindings(_backendDefaultBindings);
keymap->loadMappings();
}
void Keymapper::cleanupGameKeymaps() {
// Flush all game specific keymaps
KeymapArray::iterator it = _keymaps.begin();
while (it != _keymaps.end()) {
if ((*it)->getType() == Keymap::kKeymapTypeGame) {
delete *it;
it = _keymaps.erase(it);
} else {
it++;
}
}
}
void Keymapper::setGameKeymapState(const String &id, bool enable) {
Keymap *keymap = getKeymap(id);
if (keymap) {
keymap->setEnabled(enable);
}
}
void Keymapper::disableAllGameKeymaps() {
for (auto &keymap : _keymaps) {
if (keymap->getType() == Keymap::kKeymapTypeGame) {
keymap->setEnabled(false);
}
}
}
Keymap *Keymapper::getKeymap(const String &id) const {
for (const auto &keymap : _keymaps) {
if (keymap->getId() == id) {
return keymap;
}
}
return nullptr;
}
void Keymapper::reloadAllMappings() {
for (uint i = 0; i < _keymaps.size(); i++) {
reloadKeymapMappings(_keymaps[i]);
}
}
void Keymapper::setEnabledKeymapType(Keymap::KeymapType type) {
assert(type == Keymap::kKeymapTypeGui || type == Keymap::kKeymapTypeGame);
_enabledKeymapType = type;
}
bool Keymapper::mapEvent(const Event &ev, List<Event> &mappedEvents) {
if (!_enabled) {
return false;
}
hardcodedEventMapping(ev);
Keymap::ActionArray actions;
Keymap::KeymapMatch match = getMappedActions(ev, actions, _enabledKeymapType);
if (match != Keymap::kKeymapMatchExact) {
// If we found exact matching actions this input in the game / gui keymaps,
// no need to look at the global keymaps. An input resulting in actions
// from system and game keymaps would lead to unexpected user experience.
Keymap::ActionArray globalActions;
match = getMappedActions(ev, globalActions, Keymap::kKeymapTypeGlobal);
if (match == Keymap::kKeymapMatchExact || actions.empty()) {
actions = globalActions;
}
}
bool matchedAction = !actions.empty();
for (const auto &action : actions) {
Event mappedEvent = executeAction(action, ev);
if (mappedEvent.type == EVENT_INVALID) {
continue;
}
// In case we mapped a mouse event to something else, we need to generate an artificial
// mouse move event so event observers can keep track of the mouse position.
// Makes it possible to reliably use the mouse position from EventManager when consuming
// custom action events.
if (isMouseEvent(ev) && !isMouseEvent(mappedEvent)) {
Event fakeMouseEvent;
fakeMouseEvent.type = EVENT_MOUSEMOVE;
fakeMouseEvent.mouse = ev.mouse;
mappedEvents.push_back(fakeMouseEvent);
}
mappedEvents.push_back(mappedEvent);
}
if (ev.type == EVENT_JOYAXIS_MOTION && ev.joystick.axis < ARRAYSIZE(_joystickAxisPreviouslyPressed)) {
if (ABS<int32>(ev.joystick.position) >= kJoyAxisPressedTreshold) {
_joystickAxisPreviouslyPressed[ev.joystick.axis] = true;
} else if (ABS<int32>(ev.joystick.position) < kJoyAxisUnpressedTreshold) {
_joystickAxisPreviouslyPressed[ev.joystick.axis] = false;
}
}
return matchedAction;
}
Keymap::KeymapMatch Keymapper::getMappedActions(const Event &event, Keymap::ActionArray &actions, Keymap::KeymapType keymapType) const {
Keymap::KeymapMatch match = Keymap::kKeymapMatchNone;
for (uint i = 0; i < _keymaps.size(); i++) {
if (!_keymaps[i]->isEnabled() || _keymaps[i]->getType() != keymapType) {
continue;
}
Keymap::ActionArray array;
Keymap::KeymapMatch match2 = _keymaps[i]->getMappedActions(event, array);
if (match2 == match) {
actions.push_back(array);
} else if (match2 > match) {
match = match2;
actions.clear();
actions.push_back(array);
}
}
return match;
}
Keymapper::IncomingEventType Keymapper::convertToIncomingEventType(const Event &ev) const {
if (ev.type == EVENT_CUSTOM_BACKEND_HARDWARE
|| ev.type == EVENT_WHEELDOWN
|| ev.type == EVENT_WHEELUP) {
return kIncomingEventInstant;
} else if (ev.type == EVENT_JOYAXIS_MOTION) {
if (ev.joystick.axis >= ARRAYSIZE(_joystickAxisPreviouslyPressed)) {
return kIncomingEventIgnored;
}
if (!_joystickAxisPreviouslyPressed[ev.joystick.axis] && ABS<int32>(ev.joystick.position) >= kJoyAxisPressedTreshold) {
return kIncomingEventStart;
} else if (_joystickAxisPreviouslyPressed[ev.joystick.axis] && ABS<int32>(ev.joystick.position) < kJoyAxisUnpressedTreshold) {
return kIncomingEventEnd;
} else {
return kIncomingEventIgnored;
}
} else if (ev.type == EVENT_KEYDOWN
|| ev.type == EVENT_LBUTTONDOWN
|| ev.type == EVENT_RBUTTONDOWN
|| ev.type == EVENT_MBUTTONDOWN
|| ev.type == EVENT_X1BUTTONDOWN
|| ev.type == EVENT_X2BUTTONDOWN
|| ev.type == EVENT_JOYBUTTON_DOWN) {
return kIncomingEventStart;
} else {
return kIncomingEventEnd;
}
}
Event Keymapper::executeAction(const Action *action, const Event &incomingEvent) {
Event outgoingEvent = Event(action->event);
IncomingEventType incomingType = convertToIncomingEventType(incomingEvent);
if (outgoingEvent.type == EVENT_JOYAXIS_MOTION
|| outgoingEvent.type == EVENT_CUSTOM_BACKEND_ACTION_AXIS) {
if (incomingEvent.type == EVENT_JOYAXIS_MOTION) {
// At the moment only half-axes can be bound to actions, hence taking
// the absolute value. If full axes were to be mappable, the action
// could carry the information allowing to distinguish cases here.
outgoingEvent.joystick.position = ABS(incomingEvent.joystick.position);
} else if (incomingType == kIncomingEventStart) {
outgoingEvent.joystick.position = JOYAXIS_MAX;
} else if (incomingType == kIncomingEventEnd) {
outgoingEvent.joystick.position = 0;
}
return outgoingEvent;
}
if (incomingType == kIncomingEventIgnored) {
outgoingEvent.type = EVENT_INVALID;
return outgoingEvent;
}
if (incomingEvent.type == EVENT_KEYDOWN && incomingEvent.kbdRepeat && !action->shouldTriggerOnKbdRepeats()) {
outgoingEvent.type = EVENT_INVALID;
return outgoingEvent;
}
EventType convertedType = convertStartToEnd(outgoingEvent.type);
// hardware keys need to send up instead when they are up
if (incomingType == kIncomingEventEnd) {
outgoingEvent.type = convertedType;
}
if (outgoingEvent.type == EVENT_KEYDOWN && incomingEvent.type == EVENT_KEYDOWN) {
outgoingEvent.kbdRepeat = incomingEvent.kbdRepeat;
}
if (isMouseEvent(outgoingEvent)) {
if (isMouseEvent(incomingEvent)) {
outgoingEvent.mouse = incomingEvent.mouse;
} else {
outgoingEvent.mouse = _eventMan->getMousePos();
}
}
// Check if the event is coming from a non-key hardware event
// that is mapped to a key event
if (incomingType == kIncomingEventInstant && convertedType != EVENT_INVALID) {
// WORKAROUND: Delay the down events coming from non-key hardware events
// with a zero delay. This is to prevent DOWN1 DOWN2 UP1 UP2.
_delayedEventSource->scheduleEvent(outgoingEvent, 0);
// non-keys need to send up as well
// WORKAROUND: Delay the up events coming from non-key hardware events
// This is for engines that run scripts that check on key being down
outgoingEvent.type = convertedType;
const uint32 delay = (convertedType == EVENT_KEYUP ? kDelayKeyboardEventMillis : kDelayMouseEventMillis);
_delayedEventSource->scheduleEvent(outgoingEvent, delay);
}
return outgoingEvent;
}
EventType Keymapper::convertStartToEnd(EventType type) {
EventType result = EVENT_INVALID;
switch (type) {
case EVENT_KEYDOWN:
result = EVENT_KEYUP;
break;
case EVENT_LBUTTONDOWN:
result = EVENT_LBUTTONUP;
break;
case EVENT_RBUTTONDOWN:
result = EVENT_RBUTTONUP;
break;
case EVENT_MBUTTONDOWN:
result = EVENT_MBUTTONUP;
break;
case EVENT_X1BUTTONDOWN:
result = EVENT_X1BUTTONUP;
break;
case EVENT_X2BUTTONDOWN:
result = EVENT_X2BUTTONUP;
break;
case EVENT_JOYBUTTON_DOWN:
result = EVENT_JOYBUTTON_UP;
break;
case EVENT_CUSTOM_BACKEND_ACTION_START:
result = EVENT_CUSTOM_BACKEND_ACTION_END;
break;
case EVENT_CUSTOM_ENGINE_ACTION_START:
result = EVENT_CUSTOM_ENGINE_ACTION_END;
break;
default:
break;
}
return result;
}
HardwareInput Keymapper::findHardwareInput(const Event &event) {
return _hardwareInputs->findHardwareInput(event);
}
void Keymapper::hardcodedEventMapping(Event ev) {
// TODO: Either add support for long presses to the keymapper
// or move this elsewhere as an event observer + source
// Trigger virtual keyboard on long press of more than 1 second
// of middle mouse button.
const uint32 vkeybdTime = 1000;
static uint32 vkeybdThen = 0;
if (ev.type == EVENT_MBUTTONDOWN) {
vkeybdThen = g_system->getMillis();
}
if (ev.type == EVENT_MBUTTONUP) {
if ((g_system->getMillis() - vkeybdThen) >= vkeybdTime) {
Event vkeybdEvent;
vkeybdEvent.type = EVENT_VIRTUAL_KEYBOARD;
// Avoid blocking event from engine.
_delayedEventSource->scheduleEvent(vkeybdEvent, 100);
}
}
}
void Keymapper::resetInputState() {
for (uint i = 0; i < ARRAYSIZE(_joystickAxisPreviouslyPressed); i++) {
_joystickAxisPreviouslyPressed[i] = false;
}
}
void DelayedEventSource::scheduleEvent(const Event &ev, uint32 delayMillis) {
if (_delayedEvents.empty()) {
_delayedEffectiveTime = g_system->getMillis() + delayMillis;
delayMillis = 0;
}
DelayedEventsEntry entry = DelayedEventsEntry(delayMillis, ev);
_delayedEvents.push(entry);
}
bool DelayedEventSource::pollEvent(Event &event) {
if (_delayedEvents.empty()) {
return false;
}
uint32 now = g_system->getMillis(true);
if (now >= _delayedEffectiveTime) {
event = _delayedEvents.pop().event;
if (!_delayedEvents.empty()) {
_delayedEffectiveTime += _delayedEvents.front().timerOffset;
}
return true;
}
return false;
}
bool DelayedEventSource::allowMapping() const {
return false; // Events from this source have already been mapped, and should not be mapped again
}
} // End of namespace Common

View File

@@ -0,0 +1,224 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COMMON_KEYMAPPER_H
#define COMMON_KEYMAPPER_H
#include "common/scummsys.h"
#include "backends/keymapper/keymap.h"
#include "common/array.h"
#include "common/config-manager.h"
#include "common/events.h"
namespace Common {
const char *const kGuiKeymapName = "gui";
const char *const kGlobalKeymapName = "global";
struct Action;
class DelayedEventSource;
struct HardwareInput;
class HardwareInputSet;
class KeymapperDefaultBindings;
class Keymapper : public Common::EventMapper {
public:
Keymapper(EventManager *eventMan);
~Keymapper();
// EventMapper interface
virtual bool mapEvent(const Event &ev, List<Event> &mappedEvents);
/**
* Registers a HardwareInputSet and platform-specific default mappings with the Keymapper
*
* Transfers ownership to the Keymapper
*/
void registerHardwareInputSet(HardwareInputSet *inputs, KeymapperDefaultBindings *backendDefaultBindings);
/**
* Add a keymap to the global domain.
* If a saved key setup exists for it in the ini file it will be used.
* Else, the key setup will be automatically mapped.
*
* Transfers ownership of the keymap to the Keymapper
*/
void addGlobalKeymap(Keymap *keymap);
/**
* Add a keymap to the game domain.
*
* Transfers ownership of the keymap to the Keymapper
*
* @see addGlobalKeyMap
* @note initGame() should be called before any game keymaps are added.
*/
void addGameKeymap(Keymap *keymap);
/**
* Should be called at end of game to tell Keymapper to deactivate and free
* any game keymaps that are loaded.
*/
void cleanupGameKeymaps();
/**
* This allows to specify which Game Keymaps are enabled or disabled.
* @param id ID of the game keymap to enable/disable.
* @param enable Whether the keymap is enabled(True means enabled)
*/
void setGameKeymapState(const String &id, bool enable);
/**
* Disables all game keymaps that are loaded.
*/
void disableAllGameKeymaps();
/**
* Obtain a keymap of the given name from the keymapper.
* Game keymaps have priority over global keymaps
* @param id name of the keymap to return
*/
Keymap *getKeymap(const String &id) const;
/**
* Obtain a list of all the keymaps registered with the keymapper
*/
const KeymapArray &getKeymaps() const { return _keymaps; }
/**
* reload the mappings for all the keymaps from the configuration manager
*/
void reloadAllMappings();
/**
* Set which kind of keymap is currently used to map events
*
* Keymaps with the global type are always enabled
*/
void setEnabledKeymapType(Keymap::KeymapType type);
Keymap::KeymapType enabledKeymapType() const { return _enabledKeymapType; }
/**
* Enable/disable the keymapper
*/
void setEnabled(bool enabled) { _enabled = enabled; }
/**
* Return the keymapper's enabled state
*/
bool isEnabled() const { return _enabled; }
/**
* Clear all the keymaps and hardware input sets
*/
void clear();
/**
* Return a HardwareInput pointer for the given event
*/
HardwareInput findHardwareInput(const Event &event);
void initKeymap(Keymap *keymap, ConfigManager::Domain *domain);
void reloadKeymapMappings(Keymap *keymap);
private:
EventManager *_eventMan;
HardwareInputSet *_hardwareInputs;
KeymapperDefaultBindings *_backendDefaultBindings;
DelayedEventSource *_delayedEventSource;
enum IncomingEventType {
kIncomingEventIgnored,
kIncomingEventStart,
kIncomingEventEnd,
kIncomingEventInstant
};
enum {
kJoyAxisPressedTreshold = Common::JOYAXIS_MAX / 2,
kJoyAxisUnpressedTreshold = Common::JOYAXIS_MAX / 4
};
bool _enabled;
Keymap::KeymapType _enabledKeymapType;
KeymapArray _keymaps;
bool _joystickAxisPreviouslyPressed[8]; // size should match the number of valid axis entries of defaultJoystickAxes (in hardware-input.cpp)
Keymap::KeymapMatch getMappedActions(const Event &event, Keymap::ActionArray &actions, Keymap::KeymapType keymapType) const;
Event executeAction(const Action *act, const Event &incomingEvent);
EventType convertStartToEnd(EventType eventType);
IncomingEventType convertToIncomingEventType(const Event &ev) const;
void hardcodedEventMapping(Event ev);
void resetInputState();
};
/**
* RAII helper to temporarily enable a keymap type
*/
class KeymapTypeEnabler {
public:
KeymapTypeEnabler(Keymapper *keymapper, Keymap::KeymapType keymapType) :
_keymapper(keymapper) {
assert(keymapper);
_previousKeymapType = keymapper->enabledKeymapType();
keymapper->setEnabledKeymapType(keymapType);
}
~KeymapTypeEnabler() {
_keymapper->setEnabledKeymapType(_previousKeymapType);
}
private:
Keymapper *_keymapper;
Keymap::KeymapType _previousKeymapType;
};
class DelayedEventSource : public EventSource {
public:
// EventSource API
bool pollEvent(Event &event) override;
bool allowMapping() const override;
/**
* Schedule an event to be produced after the specified delay
*/
void scheduleEvent(const Event &ev, uint32 delayMillis);
private:
struct DelayedEventsEntry {
const uint32 timerOffset;
const Event event;
DelayedEventsEntry(const uint32 offset, const Event ev) : timerOffset(offset), event(ev) { }
};
Queue<DelayedEventsEntry> _delayedEvents;
uint32 _delayedEffectiveTime;
};
} // End of namespace Common
#endif // #ifndef COMMON_KEYMAPPER_H

View File

@@ -0,0 +1,333 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/keymapper/remap-widget.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/hardware-input.h"
#include "backends/keymapper/input-watcher.h"
#include "backends/keymapper/keymap.h"
#include "backends/keymapper/keymapper.h"
#include "common/system.h"
#include "gui/gui-manager.h"
#include "gui/widgets/scrollcontainer.h"
#include "gui/ThemeEval.h"
#include "common/translation.h"
namespace Common {
enum {
kRemapCmd = 'REMP',
kClearCmd = 'CLER',
kResetActionCmd = 'RTAC',
kResetKeymapCmd = 'RTKM',
kCloseCmd = 'CLOS'
};
RemapWidget::RemapWidget(GuiObject *boss, const Common::String &name, const KeymapArray &keymaps) :
OptionsContainerWidget(boss, name, "", ""),
_keymapTable(keymaps),
_remapKeymap(nullptr),
_remapAction(nullptr),
_remapTimeout(0) {
Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
assert(keymapper);
EventDispatcher *eventDispatcher = g_system->getEventManager()->getEventDispatcher();
_remapInputWatcher = new InputWatcher(eventDispatcher, keymapper);
}
RemapWidget::~RemapWidget() {
for (uint i = 0; i < _keymapTable.size(); i++) {
delete _keymapTable[i];
}
delete _remapInputWatcher;
}
void RemapWidget::load() {
debug(3, "RemapWidget::load keymaps: %d", _keymapTable.size());
_changes = false;
loadKeymap();
refreshKeymap();
reflowActionWidgets();
}
bool RemapWidget::save() {
bool changes = _changes;
if (_changes) {
for (uint i = 0; i < _keymapTable.size(); i++) {
_keymapTable[i]->saveMappings();
}
_changes = false;
}
return changes;
}
void RemapWidget::handleInputChanged() {
Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
assert(keymapper);
for (uint i = 0; i < _keymapTable.size(); i++) {
keymapper->reloadKeymapMappings(_keymapTable[i]);
}
refreshKeymap();
}
void RemapWidget::reflowLayout() {
OptionsContainerWidget::reflowLayout();
reflowActionWidgets();
}
void RemapWidget::reflowActionWidgets() {
int buttonHeight = g_gui.xmlEval()->getVar("Globals.Button.Height", 0);
int spacing = g_gui.xmlEval()->getVar("Globals.KeyMapper.Spacing");
int keyButtonWidth = g_gui.xmlEval()->getVar("Globals.KeyMapper.ButtonWidth");
int resetButtonWidth = g_gui.xmlEval()->getVar("Globals.KeyMapper.ResetWidth");
int labelWidth = _w - (spacing + keyButtonWidth + spacing);
labelWidth = MAX(0, labelWidth);
uint textYOff = (buttonHeight - kLineHeight) / 2;
uint y = spacing;
Keymap *previousKeymap = nullptr;
for (uint i = 0; i < _actions.size(); i++) {
ActionRow &row = _actions[i];
if (previousKeymap != row.keymap) {
previousKeymap = row.keymap;
// Insert a keymap separator
uint descriptionX = 2 * spacing + keyButtonWidth;
uint resetX = _w - spacing - resetButtonWidth;
KeymapTitleRow keymapTitle = _keymapSeparators[row.keymap];
if (keymapTitle.descriptionText) {
int descriptionWidth = resetX - descriptionX - spacing;
int descriptionFullWidth = g_gui.getStringWidth(keymapTitle.descriptionText->getLabel());
if (descriptionWidth < descriptionFullWidth) {
descriptionX -= (descriptionFullWidth - descriptionWidth);
descriptionWidth = descriptionFullWidth;
} else if (descriptionWidth < 0) {
descriptionWidth = 0;
}
keymapTitle.descriptionText->resize(descriptionX, y + textYOff, descriptionWidth, kLineHeight, false);
keymapTitle.resetButton->resize(resetX, y, resetButtonWidth, buttonHeight, false);
}
y += buttonHeight + spacing;
}
uint x = spacing;
row.keyButton->resize(x, y, keyButtonWidth, buttonHeight, false);
x += keyButtonWidth + spacing;
row.actionText->resize(x, y + textYOff, labelWidth, kLineHeight, false);
y += buttonHeight + spacing;
}
}
void RemapWidget::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
debug(3, "RemapWidget::handleCommand %u %u", cmd, data);
if (cmd >= kRemapCmd && cmd < kRemapCmd + _actions.size()) {
startRemapping(cmd - kRemapCmd);
} else if (cmd >= kClearCmd && cmd < kClearCmd + _actions.size()) {
clearMapping(cmd - kClearCmd);
} else if (cmd >= kResetActionCmd && cmd < kResetActionCmd + _actions.size()) {
resetMapping(cmd - kResetActionCmd);
} else if (cmd >= kResetKeymapCmd && cmd < kResetKeymapCmd + _actions.size()) {
resetKeymap(cmd - kResetKeymapCmd);
} else {
OptionsContainerWidget::handleCommand(sender, cmd, data);
}
}
void RemapWidget::clearMapping(uint actionIndex) {
debug(3, "clear the mapping %u", actionIndex);
Action *action = _actions[actionIndex].action;
Keymap *keymap = _actions[actionIndex].keymap;
keymap->unregisterMapping(action);
_changes = true;
stopRemapping();
refreshKeymap();
}
void RemapWidget::resetMapping(uint actionIndex) {
debug(3, "Reset the mapping %u", actionIndex);
Action *action = _actions[actionIndex].action;
Keymap *keymap = _actions[actionIndex].keymap;
keymap->resetMapping(action);
_changes = true;
stopRemapping();
refreshKeymap();
}
void RemapWidget::resetKeymap(uint actionIndex) {
debug(3, "Reset the keymap %u", actionIndex);
Keymap *keymap = _actions[actionIndex].keymap;
for (uint i = 0; i < _actions.size(); i++) {
ActionRow &row = _actions[i];
if (row.keymap == keymap) {
keymap->resetMapping(row.action);
}
}
_changes = true;
stopRemapping();
refreshKeymap();
}
void RemapWidget::startRemapping(uint actionIndex) {
if (_remapInputWatcher->isWatching()) {
// Handle a second click on the button as a stop to remapping
stopRemapping();
return;
}
_remapKeymap = _actions[actionIndex].keymap;
_remapAction = _actions[actionIndex].action;
uint32 remapTimeoutDelay = kRemapMinTimeoutDelay;
if (ConfMan.hasKey("remap_timeout_delay_ms") && ((uint32)ConfMan.getInt("remap_timeout_delay_ms") > kRemapMinTimeoutDelay)) {
remapTimeoutDelay = (uint32)ConfMan.getInt("remap_timeout_delay_ms");
}
_remapTimeout = g_system->getMillis() + remapTimeoutDelay;
_remapInputWatcher->startWatching();
_actions[actionIndex].keyButton->setLabel("...");
_actions[actionIndex].keyButton->setTooltip("");
_actions[actionIndex].keyButton->markAsDirty();
g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
}
void RemapWidget::stopRemapping() {
g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
_remapKeymap = nullptr;
_remapAction = nullptr;
refreshKeymap();
_remapInputWatcher->stopWatching();
}
void RemapWidget::handleMouseDown(int x, int y, int button, int clickCount) {
if (_remapInputWatcher->isWatching())
stopRemapping();
else
OptionsContainerWidget::handleMouseDown(x, y, button, clickCount);
}
void RemapWidget::handleTickle() {
const HardwareInput hardwareInput = _remapInputWatcher->checkForCapturedInput();
if (hardwareInput.type != kHardwareInputTypeInvalid) {
_remapKeymap->registerMapping(_remapAction, hardwareInput);
_changes = true;
stopRemapping();
}
if (_remapInputWatcher->isWatching() && g_system->getMillis() > _remapTimeout)
stopRemapping();
OptionsContainerWidget::handleTickle();
}
void RemapWidget::loadKeymap() {
assert(_actions.empty());
for (const auto &km : _keymapTable) {
for (Keymap::ActionArray::const_iterator it = km->getActions().begin(); it != km->getActions().end(); ++it) {
ActionRow row;
row.keymap = km;
row.action = *it;
_actions.push_back(row);
}
}
}
void RemapWidget::refreshKeymap() {
for (uint i = 0; i < _actions.size(); i++) {
ActionRow &row = _actions[i];
if (!row.actionText) {
row.actionText = new GUI::StaticTextWidget(widgetsBoss(), 0, 0, 0, 0, U32String(), Graphics::kTextAlignStart, U32String(), GUI::ThemeEngine::kFontStyleNormal);
row.actionText->setLabel(row.action->description);
row.keyButton = new GUI::DropdownButtonWidget(widgetsBoss(), 0, 0, 0, 0, U32String(), U32String(), kRemapCmd + i);
row.keyButton->appendEntry(_("Reset to defaults"), kResetActionCmd + i);
row.keyButton->appendEntry(_("Clear mapping"), kClearCmd + i);
}
Array<HardwareInput> mappedInputs = row.keymap->getActionMapping(row.action);
U32String keysLabel;
for (uint j = 0; j < mappedInputs.size(); j++) {
if (!keysLabel.empty()) {
keysLabel += Common::U32String(", ");
}
keysLabel += mappedInputs[j].description;
}
if (!keysLabel.empty()) {
row.keyButton->setLabel(keysLabel);
row.keyButton->setTooltip(keysLabel);
} else {
row.keyButton->setLabel("-");
row.keyButton->setTooltip("");
}
KeymapTitleRow &keymapTitle = _keymapSeparators[row.keymap];
if (!keymapTitle.descriptionText) {
keymapTitle.descriptionText = new GUI::StaticTextWidget(widgetsBoss(), 0, 0, 0, 0, row.keymap->getDescription(), Graphics::kTextAlignStart);
keymapTitle.resetButton = new GUI::ButtonWidget(widgetsBoss(), 0, 0, 0, 0, U32String(), U32String(), kResetKeymapCmd + i);
// I18N: Button to reset keymap mappings to defaults
keymapTitle.resetButton->setLabel(_("Reset"));
keymapTitle.resetButton->setTooltip(_("Reset to defaults"));
}
}
}
} // End of namespace Common

View File

@@ -0,0 +1,102 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef REMAP_WIDGET_H
#define REMAP_WIDGET_H
#include "common/scummsys.h"
#include "common/hash-ptr.h"
#include "gui/widget.h"
namespace GUI {
class ButtonWidget;
class DropdownButtonWidget;
class PopUpWidget;
class StaticTextWidget;
}
namespace Common {
struct Action;
class Keymap;
class Keymapper;
class InputWatcher;
class RemapWidget : public GUI::OptionsContainerWidget {
public:
typedef Common::Array<Keymap *> KeymapArray;
RemapWidget(GuiObject *boss, const Common::String &name, const KeymapArray &keymaps);
~RemapWidget() override;
void load() override;
bool save() override;
void handleInputChanged();
void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) override;
void handleMouseDown(int x, int y, int button, int clickCount) override;
void handleTickle() override;
void reflowLayout() override;
protected:
struct ActionRow {
Keymap *keymap;
Common::Action *action;
GUI::StaticTextWidget *actionText;
GUI::DropdownButtonWidget *keyButton;
ActionRow() : keymap(nullptr), action(nullptr), actionText(nullptr), keyButton(nullptr) {}
};
struct KeymapTitleRow {
GUI::StaticTextWidget *descriptionText;
GUI::ButtonWidget *resetButton;
KeymapTitleRow() : descriptionText(nullptr), resetButton(nullptr) {}
};
void loadKeymap();
void refreshKeymap();
void reflowActionWidgets();
void clearMapping(uint actionIndex);
void resetMapping(uint actionIndex);
void resetKeymap(uint actionIndex);
void startRemapping(uint actionIndex);
void stopRemapping();
KeymapArray _keymapTable;
InputWatcher *_remapInputWatcher;
Keymap *_remapKeymap;
Action *_remapAction;
uint32 _remapTimeout;
static const uint32 kRemapMinTimeoutDelay = 3000;
bool _changes;
Array<ActionRow> _actions;
HashMap<Keymap *, KeymapTitleRow> _keymapSeparators;
};
} // End of namespace Common
#endif // #ifndef REMAP_WIDGET_H

View File

@@ -0,0 +1,45 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/keymapper/standard-actions.h"
namespace Common {
const char *const kStandardActionLeftClick = "LCLK";
const char *const kStandardActionMiddleClick = "MCLK";
const char *const kStandardActionRightClick = "RCLK";
const char *const kStandardActionInteract = "INTRCT";
const char *const kStandardActionSkip = "SKIP";
const char *const kStandardActionPause = "PAUSE";
const char *const kStandardActionMoveUp = "UP";
const char *const kStandardActionMoveDown = "DOWN";
const char *const kStandardActionMoveLeft = "LEFT";
const char *const kStandardActionMoveRight = "RIGHT";
const char *const kStandardActionOpenMainMenu = "MENU";
const char *const kStandardActionLoad = "LOAD";
const char *const kStandardActionSave = "SAVE";
const char *const kStandardActionOpenSettings = "OPTS";
const char *const kStandardActionEE = "EEKY";
const char *const kStandardActionCut = "CUT";
const char *const kStandardActionCopy = "COPY";
const char *const kStandardActionPaste = "PASTE";
} //namespace Common

View File

@@ -0,0 +1,61 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_KEYMAPPER_STANDARD_ACTIONS_H
#define BACKENDS_KEYMAPPER_STANDARD_ACTIONS_H
#include "common/scummsys.h"
/**
* @file
* @brief A set of well known keymapper actions.
*
* The actions in this file are meant to be used by game engines
* when defining their key mappings.
* Backends can provide default key mappings for some of these actions
* so users don't have to manually configure the action mappings for
* the input devices.
*/
namespace Common {
extern const char *const kStandardActionLeftClick;
extern const char *const kStandardActionMiddleClick;
extern const char *const kStandardActionRightClick;
extern const char *const kStandardActionInteract;
extern const char *const kStandardActionSkip;
extern const char *const kStandardActionPause;
extern const char *const kStandardActionMoveUp;
extern const char *const kStandardActionMoveDown;
extern const char *const kStandardActionMoveLeft;
extern const char *const kStandardActionMoveRight;
extern const char *const kStandardActionOpenMainMenu;
extern const char *const kStandardActionLoad;
extern const char *const kStandardActionSave;
extern const char *const kStandardActionOpenSettings;
extern const char *const kStandardActionEE;
extern const char *const kStandardActionCut;
extern const char *const kStandardActionCopy;
extern const char *const kStandardActionPaste;
} //namespace Common
#endif // BACKENDS_KEYMAPPER_STANDARD_ACTIONS_H

View File

@@ -0,0 +1,226 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/keymapper/virtual-mouse.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymap.h"
#include "common/config-manager.h"
#include "common/system.h"
#include "common/translation.h"
#include "gui/gui-manager.h"
namespace Common {
VirtualMouse::VirtualMouse(EventDispatcher *eventDispatcher) :
_eventDispatcher(eventDispatcher),
_inputAxisPositionX(0),
_inputAxisPositionY(0),
_mouseVelocityX(0.f),
_mouseVelocityY(0.f),
_slowModifier(1.f),
_subPixelRemainderX(0.f),
_subPixelRemainderY(0.f),
_lastUpdateMillis(0) {
ConfMan.registerDefault("kbdmouse_speed", 3);
ConfMan.registerDefault("joystick_deadzone", 3);
_eventDispatcher->registerSource(this, false);
_eventDispatcher->registerObserver(this, 10, false);
}
VirtualMouse::~VirtualMouse() {
_eventDispatcher->unregisterObserver(this);
_eventDispatcher->unregisterSource(this);
}
bool VirtualMouse::pollEvent(Event &event) {
// Update the virtual mouse once per frame (assuming 60Hz)
uint32 curTime = g_system->getMillis(true);
if (curTime < _lastUpdateMillis + kUpdateDelay) {
return false;
}
_lastUpdateMillis = curTime;
// Adjust the speed of the cursor according to the virtual screen resolution
Common::Rect screenSize;
if (g_system->isOverlayVisible()) {
screenSize = Common::Rect(g_system->getOverlayWidth(), g_system->getOverlayHeight());
} else {
screenSize = Common::Rect(g_system->getWidth(), g_system->getHeight());
}
float screenSizeSpeedModifier = screenSize.width() / (float)kDefaultScreenWidth;
// Compute the movement delta when compared to the previous update
float deltaX = _subPixelRemainderX + _mouseVelocityX * _slowModifier * screenSizeSpeedModifier * 10.f;
float deltaY = _subPixelRemainderY + _mouseVelocityY * _slowModifier * screenSizeSpeedModifier * 10.f;
Common::Point delta;
delta.x = deltaX;
delta.y = deltaY;
// Keep track of sub-pixel movement so the cursor ultimately moves,
// even when configured at very low speeds.
_subPixelRemainderX = deltaX - delta.x;
_subPixelRemainderY = deltaY - delta.y;
if (delta.x == 0 && delta.y == 0) {
return false;
}
// Send a mouse event
Common::Point oldPos = g_system->getEventManager()->getMousePos();
event.type = Common::EVENT_MOUSEMOVE;
event.mouse = oldPos + delta;
event.mouse.x = CLIP<int16>(event.mouse.x, 0, screenSize.width());
event.mouse.y = CLIP<int16>(event.mouse.y, 0, screenSize.height());
event.relMouse.x = delta.x;
event.relMouse.y = delta.y;
g_system->warpMouse(event.mouse.x, event.mouse.y);
return true;
}
bool VirtualMouse::notifyEvent(const Event &event) {
if (event.type != EVENT_CUSTOM_BACKEND_ACTION_AXIS) {
return false;
}
switch (event.customType) {
case kCustomActionVirtualAxisUp:
if (event.joystick.position == 0 && _inputAxisPositionY > 0) {
return true; // Ignore axis reset events if we are already going in the other direction
}
handleAxisMotion(_inputAxisPositionX, -event.joystick.position);
return true;
case kCustomActionVirtualAxisDown:
if (event.joystick.position == 0 && _inputAxisPositionY < 0) {
return true;
}
handleAxisMotion(_inputAxisPositionX, event.joystick.position);
return true;
case kCustomActionVirtualAxisLeft:
if (event.joystick.position == 0 && _inputAxisPositionX > 0) {
return true;
}
handleAxisMotion(-event.joystick.position, _inputAxisPositionY);
return true;
case kCustomActionVirtualAxisRight:
if (event.joystick.position == 0 && _inputAxisPositionX < 0) {
return true;
}
handleAxisMotion(event.joystick.position, _inputAxisPositionY);
return true;
case kCustomActionVirtualMouseSlow:
_slowModifier = 0.9f * (1.f - event.joystick.position / (float)JOYAXIS_MAX) + 0.1f;
return true;
default:
break;
}
return false;
}
void VirtualMouse::addActionsToKeymap(Keymap *keymap) {
Action *act;
act = new Action("VMOUSEUP", _("Virtual mouse up"));
act->addDefaultInputMapping("JOY_LEFT_STICK_Y-");
act->setCustomBackendActionAxisEvent(VirtualMouse::kCustomActionVirtualAxisUp);
keymap->addAction(act);
act = new Action("VMOUSEDOWN", _("Virtual mouse down"));
act->addDefaultInputMapping("JOY_LEFT_STICK_Y+");
act->setCustomBackendActionAxisEvent(VirtualMouse::kCustomActionVirtualAxisDown);
keymap->addAction(act);
act = new Action("VMOUSELEFT", _("Virtual mouse left"));
act->addDefaultInputMapping("JOY_LEFT_STICK_X-");
act->setCustomBackendActionAxisEvent(VirtualMouse::kCustomActionVirtualAxisLeft);
keymap->addAction(act);
act = new Action("VMOUSERIGHT", _("Virtual mouse right"));
act->addDefaultInputMapping("JOY_LEFT_STICK_X+");
act->setCustomBackendActionAxisEvent(VirtualMouse::kCustomActionVirtualAxisRight);
keymap->addAction(act);
act = new Action("VMOUSESLOW", _("Slow down virtual mouse"));
act->addDefaultInputMapping("JOY_RIGHT_SHOULDER");
act->setCustomBackendActionAxisEvent(VirtualMouse::kCustomActionVirtualMouseSlow);
keymap->addAction(act);
}
void VirtualMouse::handleAxisMotion(int16 axisPositionX, int16 axisPositionY) {
_inputAxisPositionX = axisPositionX;
_inputAxisPositionY = axisPositionY;
float analogX = (float)_inputAxisPositionX;
float analogY = (float)_inputAxisPositionY;
float deadZone = (float)ConfMan.getInt("joystick_deadzone") * 1000.0f;
float magnitude = sqrt(analogX * analogX + analogY * analogY);
if (magnitude >= deadZone) {
float scalingFactor = 1.0f / magnitude * (magnitude - deadZone) / (JOYAXIS_MAX - deadZone);
float speedFactor = computeJoystickMouseSpeedFactor();
_mouseVelocityX = analogX * scalingFactor * speedFactor;
_mouseVelocityY = analogY * scalingFactor * speedFactor;
} else {
_mouseVelocityX = 0.f;
_mouseVelocityY = 0.f;
}
}
float VirtualMouse::computeJoystickMouseSpeedFactor() const {
switch (ConfMan.getInt("kbdmouse_speed")) {
case 0:
return 0.25; // 0.25 keyboard pointer speed
case 1:
return 0.5; // 0.5 speed
case 2:
return 0.75; // 0.75 speed
case 3:
return 1.0; // 1.0 speed
case 4:
return 1.25; // 1.25 speed
case 5:
return 1.5; // 1.5 speed
case 6:
return 1.75; // 1.75 speed
case 7:
return 2.0; // 2.0 speed
default:
return 1.0;
}
}
} // End of namespace Common

View File

@@ -0,0 +1,90 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_KEYMAPPER_VIRTUAL_MOUSE_H
#define BACKENDS_KEYMAPPER_VIRTUAL_MOUSE_H
#include "common/scummsys.h"
#include "common/events.h"
namespace Common {
class EventDispatcher;
class Keymap;
/**
* The Virtual Mouse can produce mouse move events on systems without a physical mouse.
*
* It is useful for moving the mouse cursor using a gamepad or a keyboard.
*
* This class defines a keymap with actions for moving the cursor in all four directions.
* The keymapper produces custom backend events whenever keys bound to these actions are
* pressed. This class handles the events through its EventObserver interface and produces
* mouse move events when necesssary through its EventSource interface.
*/
class VirtualMouse : public EventSource, public EventObserver {
public:
VirtualMouse(EventDispatcher *eventDispatcher);
~VirtualMouse() override;
// EventSource API
bool pollEvent(Event &event) override;
// EventObserver API
bool notifyEvent(const Event &event) override;
/** Add the virtual mouse keymapper actions to a keymap */
void addActionsToKeymap(Keymap *keymap);
private:
static const int32 kUpdateDelay = 12;
static const int32 kDefaultScreenWidth = 640;
enum {
kCustomActionVirtualAxisUp = 10000,
kCustomActionVirtualAxisDown = 10001,
kCustomActionVirtualAxisLeft = 10002,
kCustomActionVirtualAxisRight = 10003,
kCustomActionVirtualMouseSlow = 10004
};
void handleAxisMotion(int16 axisPositionX, int16 axisPositionY);
float computeJoystickMouseSpeedFactor() const;
EventDispatcher *_eventDispatcher;
int16 _inputAxisPositionX;
int16 _inputAxisPositionY;
float _mouseVelocityX;
float _mouseVelocityY;
float _slowModifier;
float _subPixelRemainderX;
float _subPixelRemainderY;
uint32 _lastUpdateMillis;
};
} // End of namespace Common
#endif // #ifndef BACKENDS_KEYMAPPER_VIRTUAL_MOUSE_H