Initial commit
This commit is contained in:
47
backends/keymapper/action.cpp
Normal file
47
backends/keymapper/action.cpp
Normal 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
153
backends/keymapper/action.h
Normal 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
|
||||
618
backends/keymapper/hardware-input.cpp
Normal file
618
backends/keymapper/hardware-input.cpp
Normal 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
|
||||
333
backends/keymapper/hardware-input.h
Normal file
333
backends/keymapper/hardware-input.h
Normal 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
|
||||
97
backends/keymapper/input-watcher.cpp
Normal file
97
backends/keymapper/input-watcher.cpp
Normal 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
|
||||
65
backends/keymapper/input-watcher.h
Normal file
65
backends/keymapper/input-watcher.h
Normal 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
|
||||
373
backends/keymapper/keymap.cpp
Normal file
373
backends/keymapper/keymap.cpp
Normal 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
202
backends/keymapper/keymap.h
Normal 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
|
||||
84
backends/keymapper/keymapper-defaults.h
Normal file
84
backends/keymapper/keymapper-defaults.h
Normal 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
|
||||
453
backends/keymapper/keymapper.cpp
Normal file
453
backends/keymapper/keymapper.cpp
Normal 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
|
||||
224
backends/keymapper/keymapper.h
Normal file
224
backends/keymapper/keymapper.h
Normal 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
|
||||
333
backends/keymapper/remap-widget.cpp
Normal file
333
backends/keymapper/remap-widget.cpp
Normal 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
|
||||
102
backends/keymapper/remap-widget.h
Normal file
102
backends/keymapper/remap-widget.h
Normal 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
|
||||
45
backends/keymapper/standard-actions.cpp
Normal file
45
backends/keymapper/standard-actions.cpp
Normal 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
|
||||
61
backends/keymapper/standard-actions.h
Normal file
61
backends/keymapper/standard-actions.h
Normal 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
|
||||
226
backends/keymapper/virtual-mouse.cpp
Normal file
226
backends/keymapper/virtual-mouse.cpp
Normal 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
|
||||
90
backends/keymapper/virtual-mouse.h
Normal file
90
backends/keymapper/virtual-mouse.h
Normal 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
|
||||
Reference in New Issue
Block a user