KEYMAPPER: Introduce a Virtual Mouse event source

The Virtual Mouse is meant to provide a way to control the mouse cursor
on system without a physical mouse. It provides keymapper actions that
are expected to be bound to game controller axes or buttons.
This commit is contained in:
Bastien Bouclet 2020-03-02 20:24:43 +01:00
parent e66e35a3fd
commit 568d882e80
11 changed files with 360 additions and 8 deletions

View File

@ -30,7 +30,7 @@
#include "backends/events/default/default-events.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymapper.h"
#include "backends/keymapper/remap-widget.h"
#include "backends/keymapper/virtual-mouse.h"
#include "backends/vkeybd/virtual-keyboard.h"
#include "engines/engine.h"
@ -58,13 +58,15 @@ DefaultEventManager::DefaultEventManager(Common::EventSource *boss) :
#ifdef ENABLE_VKEYBD
_vk = nullptr;
#endif
_virtualMouse = new Common::VirtualMouse(&_dispatcher);
_keymapper = new Common::Keymapper(this);
// EventDispatcher will automatically free the keymapper
_dispatcher.registerMapper(_keymapper);
_remap = false;
}
DefaultEventManager::~DefaultEventManager() {
delete _virtualMouse;
#ifdef ENABLE_VKEYBD
delete _vk;
#endif
@ -372,6 +374,8 @@ Common::Keymap *DefaultEventManager::getGlobalKeymap() {
act->setEvent(EVENT_DEBUGGER);
globalKeymap->addAction(act);
_virtualMouse->addActionsToKeymap(globalKeymap);
return globalKeymap;
}

View File

@ -31,6 +31,7 @@ class Keymapper;
#ifdef ENABLE_VKEYBD
class VirtualKeyboard;
#endif
class VirtualMouse;
}
@ -39,8 +40,9 @@ class DefaultEventManager : public Common::EventManager, Common::EventObserver {
Common::VirtualKeyboard *_vk;
#endif
Common::VirtualMouse *_virtualMouse;
Common::Keymapper *_keymapper;
bool _remap;
Common::ArtificialEventSource _artificialEventSource;

View File

@ -68,6 +68,12 @@ public:
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;

View File

@ -171,9 +171,19 @@ Keymap::ActionArray Keymap::getMappedActions(const Event &event) const {
return _hwActionMap[hardwareInput];
}
case EVENT_JOYAXIS_MOTION: {
bool positiveHalf = event.joystick.position >= 0;
HardwareInput hardwareInput = HardwareInput::createJoystickHalfAxis("", event.joystick.axis, positiveHalf, "");
return _hwActionMap[hardwareInput];
if (event.joystick.position != 0) {
bool positiveHalf = event.joystick.position >= 0;
HardwareInput hardwareInput = HardwareInput::createJoystickHalfAxis("", event.joystick.axis, positiveHalf, "");
return _hwActionMap[hardwareInput];
} else {
// Axis position zero is part of both half axes, and triggers actions bound to both
Keymap::ActionArray actions;
HardwareInput hardwareInputPos = HardwareInput::createJoystickHalfAxis("", event.joystick.axis, true, "");
HardwareInput hardwareInputNeg = HardwareInput::createJoystickHalfAxis("", event.joystick.axis, false, "");
actions.push_back(_hwActionMap[hardwareInputPos]);
actions.push_back(_hwActionMap[hardwareInputNeg]);
return actions;
}
}
case EVENT_CUSTOM_BACKEND_HARDWARE: {
HardwareInput hardwareInput = HardwareInput::createCustom("", event.customType, "");

View File

@ -261,6 +261,23 @@ 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;

View File

@ -0,0 +1,219 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "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) {
_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_gui.isActive()) {
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());
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;
}
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 = sqrtf(analogX * analogX + analogY * analogY);
if (magnitude >= deadZone) {
float scalingFactor = 1.0f / magnitude * (magnitude - deadZone) / (JOYAXIS_MAX - deadZone);
float speedFactor = computeJoystickMouseSpeedFactor();
_mouseVelocityX = analogX * scalingFactor * speedFactor;
_mouseVelocityY = analogY * scalingFactor * speedFactor;
} else {
_mouseVelocityX = 0.f;
_mouseVelocityY = 0.f;
}
}
float VirtualMouse::computeJoystickMouseSpeedFactor() const {
switch (ConfMan.getInt("kbdmouse_speed")) {
case 0:
return 0.25; // 0.25 keyboard pointer speed
case 1:
return 0.5; // 0.5 speed
case 2:
return 0.75; // 0.75 speed
case 3:
return 1.0; // 1.0 speed
case 4:
return 1.25; // 1.25 speed
case 5:
return 1.5; // 1.5 speed
case 6:
return 1.75; // 1.75 speed
case 7:
return 2.0; // 2.0 speed
default:
return 1.0;
}
}
} // End of namespace Common

View File

@ -0,0 +1,91 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#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

View File

@ -15,6 +15,7 @@ MODULE_OBJS := \
keymapper/keymapper.o \
keymapper/remap-widget.o \
keymapper/standard-actions.o \
keymapper/virtual-mouse.o \
log/log.o \
midi/alsa.o \
midi/dmedia.o \

View File

@ -203,7 +203,7 @@ void OSystem_SDL::initBackend() {
// Create the default event source, in case a custom backend
// manager didn't provide one yet.
if (_eventSource == 0)
_eventSource = new LegacySdlEventSource();
_eventSource = new SdlEventSource();
if (_eventManager == nullptr) {
DefaultEventManager *eventManager = new DefaultEventManager(_eventSource);

View File

@ -76,6 +76,7 @@ enum EventType {
EVENT_CUSTOM_BACKEND_ACTION_START = 18,
EVENT_CUSTOM_BACKEND_ACTION_END = 19,
EVENT_CUSTOM_BACKEND_ACTION_AXIS = 34,
EVENT_CUSTOM_ENGINE_ACTION_START = 20,
EVENT_CUSTOM_ENGINE_ACTION_END = 21,

View File

@ -65,6 +65,7 @@ backends/graphics/surfacesdl/surfacesdl-graphics.cpp
backends/graphics/sdl/sdl-graphics.cpp
backends/keymapper/hardware-input.cpp
backends/keymapper/remap-widget.cpp
backends/keymapper/virtual-mouse.cpp
backends/midi/windows.cpp
backends/networking/sdl_net/handlers/createdirectoryhandler.cpp
backends/networking/sdl_net/handlers/downloadfilehandler.cpp