ALL: synced with ScummVM commit 09bf38c120

This commit is contained in:
Pawel Kolodziejski
2020-05-09 20:05:54 +02:00
parent b5d73d4c22
commit b586571900
414 changed files with 111155 additions and 38279 deletions
+24
View File
@@ -0,0 +1,24 @@
{
BasedOnStyle: LLVM,
UseTab: ForIndentation,
IndentWidth: 4,
TabWidth: 4,
BreakBeforeBraces: Attach,
IndentCaseLabels: false,
ColumnLimit: 0,
AccessModifierOffset: -4,
NamespaceIndentation: None,
SpaceBeforeParens: ControlStatements,
PointerAlignment: Right,
SpaceAfterCStyleCast: false,
SpaceAfterTemplateKeyword: false,
SpaceBeforeAssignmentOperators: true,
SpaceBeforeCtorInitializerColon: true,
SpaceBeforeInheritanceColon: true,
SpaceBeforeSquareBrackets: false,
SpaceInEmptyParentheses: false,
SpacesInAngles: false,
SpacesInConditionalStatement: false,
SpacesInParentheses: false,
SpacesInSquareBrackets: false,
}
+5
View File
@@ -0,0 +1,5 @@
[*]
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
+8
View File
@@ -0,0 +1,8 @@
/po/cs_CZ.po encoding=iso-8859-2
/po/hu_HU.po encoding=iso-8859-2
/po/pl_PL.po encoding=iso-8859-2
/po/be_BY.po encoding=iso-8859-5
/po/ru_RU.po encoding=iso-8859-5
/po/uk_UA.po encoding=iso-8859-5
/po/el.po encoding=iso-8859-7
/po/he.po encoding=iso-8859-8
+50 -20
View File
@@ -10,6 +10,7 @@ lib*.a
/config.log
/residualvm
/libresidualvm.so
/residualvm-static
/ResidualVMDockTilePlugin*
/config.h
@@ -27,12 +28,17 @@ lib*.a
/*,e1f
/residualvm-ps3.pkg
/*.ipk
/*.dmg
/map.txt
*.elf
/*.nds
/.project
/.cproject
/.settings
/.autotools
/Icon.*
/residualvm-conf.cpp
/tmp_*.cpp
/*.apk
/*.m4b
/*.lab
/*.dat
@@ -42,7 +48,7 @@ lib*.a
/NEWS
/NEWS.html
/build
/build*
/staging
/portdist
@@ -64,14 +70,14 @@ project.xcworkspace
/dists/macosx/residualvm.xcodeproj
/dists/macosx/create_project
/dists/msvc*/[Dd]ebug*/
/dists/msvc*/[Rr]elease*/
/dists/msvc*/[Aa]nalysis*/
/dists/msvc*/*.lib
/dists/msvc*/*.SAV
/dists/msvc*/*.dat
/dists/msvc*/*.dll
/dists/msvc*/test_runner.cpp
/dists/msvc/[Dd]ebug*/
/dists/msvc/[Rr]elease*/
/dists/msvc/[Aa]nalysis*/
/dists/msvc/*.lib
/dists/msvc/*.SAV
/dists/msvc/*.dat
/dists/msvc/*.dll
/dists/msvc/test_runner.cpp
/dists/engine-data/testbed-audiocd-files/testbed.config
/dists/engine-data/testbed-audiocd-files/testbed.out
@@ -132,6 +138,7 @@ ipch/
*.tss
*.VC.db
.vs/
UpgradeLog.htm
#Ignore default Visual Studio build folders
[Dd]ebug/
@@ -143,6 +150,9 @@ ipch/
LLVM32/
LLVM64/
#Ignore files generated by Visual Studio Code
.vscode/
#Ignore gettext generated files
/messages.mo
@@ -159,30 +169,50 @@ ResidualVM.includes
.DS_Store
#Ignore MS Visual C++ temporary files/subdirectories (except create_project.bat)
dists/msvc*/**
!dists/msvc*/create_project.bat
dists/msvc/**
!dists/msvc/create_project.bat
#Ignore bison debug output
*.output
#Ignore CMake build files
CMakeFiles
CMakeCache.txt
cmake_install.cmake
.ninja_*
*.ninja
#Ignore Xcode output/project files
out/
/*.xcodeproj
#Ignore PSP2 files
psp2pkg/
*.velf
*.vpk
#Ignore Switch files
switch_release/
residualvm.elf
residualvm.nro
residualvm_switch.zip
#Ignore gmon.out created by gprof
gmon.out
#Ignore Eclipse related files (such as a Java plugin for Visual Studio Code)
.settings
.project
#Ignore Android Studio files
.idea
#Ingore temporary Android project folder
android_project
# Emacs files
*~
.#*
\#*\#
\#*
TAGS
#Ignore Visual Studio Code files
/.vscode
#Ignore testing .dll files
libogg.dll
libvorbis.dll
libvorbisfile.dll
+12 -12
View File
@@ -1,12 +1,12 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
@@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
@@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
@@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
@@ -225,7 +225,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
@@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
@@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
+78 -2
View File
@@ -13,7 +13,7 @@ all: $(EXECUTABLE).dwp
endif
ifdef USE_PANDOC
all: README$(PANDOCEXT) NEWS$(PANDOCEXT)
all: PANDOC_CONVERT
endif
######################################################################
@@ -28,9 +28,9 @@ MODULES := test devtools base $(MODULES)
# After the game specific modules follow the shared modules
MODULES += \
engines \
gui \
backends \
engines \
video \
image \
graphics \
@@ -299,8 +299,84 @@ ifdef USE_SDL_NET
DIST_FILES_NETWORKING:=$(addprefix $(srcdir)/dists/networking/,wwwroot.zip)
endif
# Virtual keyboard files
DIST_FILES_VKEYBD=
ifdef ENABLE_VKEYBD
DIST_FILES_VKEYBD:=$(addprefix $(srcdir)/backends/vkeybd/packs/,vkeybd_default.zip vkeybd_small.zip)
endif
# Engine data files
DIST_FILES_ENGINEDATA=
ifdef ENABLE_ACCESS
DIST_FILES_ENGINEDATA+=access.dat
endif
ifdef ENABLE_CRYO
DIST_FILES_ENGINEDATA+=cryo.dat
endif
ifdef ENABLE_CRYOMNI3D
DIST_FILES_ENGINEDATA+=cryomni3d.dat
endif
ifdef ENABLE_DRASCULA
DIST_FILES_ENGINEDATA+=drascula.dat
endif
ifdef ENABLE_HUGO
DIST_FILES_ENGINEDATA+=hugo.dat
endif
ifdef ENABLE_KYRA
DIST_FILES_ENGINEDATA+=kyra.dat
endif
ifdef ENABLE_LURE
DIST_FILES_ENGINEDATA+=lure.dat
endif
ifdef ENABLE_MORTEVIELLE
DIST_FILES_ENGINEDATA+=mort.dat
endif
ifdef ENABLE_NEVERHOOD
DIST_FILES_ENGINEDATA+=neverhood.dat
endif
ifdef ENABLE_QUEEN
DIST_FILES_ENGINEDATA+=queen.tbl
endif
ifdef ENABLE_SKY
DIST_FILES_ENGINEDATA+=sky.cpt
endif
ifdef ENABLE_SUPERNOVA
DIST_FILES_ENGINEDATA+=supernova.dat
endif
ifdef ENABLE_TEENAGENT
DIST_FILES_ENGINEDATA+=teenagent.dat
endif
ifdef ENABLE_TITANIC
DIST_FILES_ENGINEDATA+=titanic.dat
endif
ifdef ENABLE_TONY
DIST_FILES_ENGINEDATA+=tony.dat
endif
ifdef ENABLE_TOON
DIST_FILES_ENGINEDATA+=toon.dat
endif
ifdef ENABLE_ULTIMA
DIST_FILES_ENGINEDATA+=ultima.dat
endif
ifdef ENABLE_WINTERMUTE
DIST_FILES_ENGINEDATA+=wintermute.zip
endif
ifdef ENABLE_MACVENTURE
DIST_FILES_ENGINEDATA+=macventure.dat
endif
ifdef ENABLE_XEEN
DIST_FILES_ENGINEDATA+=xeen.ccs
endif
ifdef USE_FREETYPE2
DIST_FILES_ENGINEDATA+=fonts.dat
endif
DIST_FILES_ENGINEDATA:=$(addprefix $(srcdir)/dists/engine-data/,$(DIST_FILES_ENGINEDATA))
# pred.dic is currently only used for the AGI engine
ifdef ENABLE_AGI
DIST_FILES_ENGINEDATA+=$(srcdir)/dists/pred.dic
endif
ifdef ENABLE_GRIM
DIST_FILES_ENGINEDATA+=residualvm-grim-patch.lab
endif
+7 -3
View File
@@ -159,11 +159,15 @@ RewindableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream, Dispos
break;
}
stream->seek(pos + length + (length & 1)); // ensure we're also word-aligned
uint32 seekPos = pos + length;
if (seekPos < (uint32)stream->size()) {
seekPos += (length & 1); // ensure we're word-aligned
}
stream->seek(seekPos);
}
if (!foundCOMM) {
warning("makeAIFFStream: Cound not find 'COMM' chunk");
warning("makeAIFFStream: Could not find 'COMM' chunk");
if (!dataStream && disposeAfterUse == DisposeAfterUse::YES)
delete stream;
@@ -173,7 +177,7 @@ RewindableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream, Dispos
}
if (!foundSSND) {
warning("makeAIFFStream: Cound not find 'SSND' chunk");
warning("makeAIFFStream: Could not find 'SSND' chunk");
if (disposeAfterUse == DisposeAfterUse::YES)
delete stream;
+6
View File
@@ -79,6 +79,9 @@ struct A8SVXLoader {
assert(_data);
loadData(chunk._stream);
return true;
default:
break;
}
return false;
@@ -94,6 +97,9 @@ struct A8SVXLoader {
// implement other formats here
error("compressed IFF audio is not supported");
break;
default:
break;
}
}
+3
View File
@@ -1552,6 +1552,9 @@ void QDM2Stream::fill_coding_method_array(sb_int8_array tone_level_idx, sb_int8_
case 4:
if (comp < 16)
comp = 16;
break;
default:
break;
}
if (comp <= 5)
tmp = 0;
+73
View File
@@ -28,6 +28,7 @@
#include "common/textconsole.h"
#include "common/translation.h"
#include "common/util.h"
#include "common/file.h"
#include "gui/message.h"
#include "audio/mididrv.h"
#include "audio/musicplugin.h"
@@ -56,6 +57,29 @@ const byte MidiDriver::_gmToMt32[128] = {
101, 103, 100, 120, 117, 113, 99, 128, 128, 128, 128, 124, 123, 128, 128, 128, // 7x
};
// This is the drum map for the Roland Sound Canvas SC-55 v1.xx. It had a fallback mechanism
// to correct invalid drumkit selections. Some games rely on this mechanism to select the
// correct Roland GS drumkit. Use this map to emulate this mechanism.
// E.g. correct invalid drumkit 50: _gsDrumkitFallbackMap[50] == 48
const uint8 MidiDriver::_gsDrumkitFallbackMap[128] = {
0, 0, 0, 0, 0, 0, 0, 0, // STANDARD
8, 8, 8, 8, 8, 8, 8, 8, // ROOM
16, 16, 16, 16, 16, 16, 16, 16, // POWER
24, 25, 24, 24, 24, 24, 24, 24, // ELECTRONIC; TR-808 (25)
32, 32, 32, 32, 32, 32, 32, 32, // JAZZ
40, 40, 40, 40, 40, 40, 40, 40, // BRUSH
48, 48, 48, 48, 48, 48, 48, 48, // ORCHESTRA
56, 56, 56, 56, 56, 56, 56, 56, // SFX
0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined (fall back to STANDARD)
0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
0, 0, 0, 0, 0, 0, 0, 0, // No drumkit defined
0, 0, 0, 0, 0, 0, 0, 127 // No drumkit defined; CM-64/32L (127)
};
static const struct {
uint32 type;
const char *guio;
@@ -418,3 +442,52 @@ void MidiDriver::sendGMReset() {
sysEx(resetSysEx, sizeof(resetSysEx));
g_system->delayMillis(100);
}
void MidiDriver_BASE::midiDumpInit() {
// ResidualVM - not used
}
int MidiDriver_BASE::midiDumpVarLength(const uint32 &delta) {
// ResidualVM - not used
return 0;
}
void MidiDriver_BASE::midiDumpDelta() {
// ResidualVM - not used
}
void MidiDriver_BASE::midiDumpDo(uint32 b) {
// ResidualVM - not used
}
void MidiDriver_BASE::midiDumpSysEx(const byte *msg, uint16 length) {
// ResidualVM - not used
}
void MidiDriver_BASE::midiDumpFinish() {
// ResidualVM - not used
}
MidiDriver_BASE::MidiDriver_BASE() {
// ResidualVM - not used
}
MidiDriver_BASE::~MidiDriver_BASE() {
// ResidualVM - not used
}
void MidiDriver_BASE::send(byte status, byte firstOp, byte secondOp) {
send(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16));
}
void MidiDriver::midiDriverCommonSend(uint32 b) {
// ResidualVM - not used
}
void MidiDriver::midiDriverCommonSysEx(const byte *msg, uint16 length) {
// ResidualVM - not used
}
+50 -5
View File
@@ -26,6 +26,7 @@
#include "common/scummsys.h"
#include "common/str.h"
#include "common/timer.h"
#include "common/array.h"
class MidiChannel;
@@ -86,7 +87,9 @@ enum MidiDriverFlags {
*/
class MidiDriver_BASE {
public:
virtual ~MidiDriver_BASE() { }
MidiDriver_BASE();
virtual ~MidiDriver_BASE();
/**
* Output a packed midi command to the midi stream.
@@ -96,6 +99,7 @@ public:
*/
virtual void send(uint32 b) = 0;
/**
* Output a midi command to the midi stream. Convenience wrapper
* around the usual 'packed' send method.
@@ -103,10 +107,8 @@ public:
* Do NOT use this for sysEx transmission; instead, use the sysEx()
* method below.
*/
void send(byte status, byte firstOp, byte secondOp) {
send(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16));
}
void send(byte status, byte firstOp, byte secondOp);
/**
* Transmit a sysEx to the midi device.
*
@@ -121,6 +123,39 @@ public:
// TODO: Document this.
virtual void metaEvent(byte type, byte *data, uint16 length) { }
protected:
/**
* Enables midi dumping to a 'dump.mid' file and to debug messages on screen
* It's set by '--dump-midi' command line parameter
*/
bool _midiDumpEnable;
/** Used for MIDI dumping delta calculation */
uint32 _prevMillis;
/** Stores all MIDI events, will be written to disk after an engine quits */
Common::Array<byte> _midiDumpCache;
/** Initialize midi dumping mechanism, called only if enabled */
void midiDumpInit();
/** Handles MIDI file variable length dumping */
int midiDumpVarLength(const uint32 &delta);
/** Handles MIDI file time delta dumping */
void midiDumpDelta();
/** Performs dumping of MIDI commands, called only if enabled */
void midiDumpDo(uint32 b);
/** Performs dumping of MIDI SysEx commands, called only if enabled */
void midiDumpSysEx(const byte *msg, uint16 length);
/** Writes the captured MIDI events to disk, called only if enabled */
void midiDumpFinish();
};
/**
@@ -166,6 +201,13 @@ public:
/** Get the device description string matching the given device handle and the given type. */
static Common::String getDeviceString(DeviceHandle handle, DeviceStringType type);
/** Common operations to be done by all drivers on start of send */
void midiDriverCommonSend(uint32 b);
/** Common operations to be done by all drivers on start of sysEx */
void midiDriverCommonSysEx(const byte *msg, uint16 length);
private:
// If detectDevice() detects MT32 and we have a preferred MT32 device
// we use this to force getMusicType() to return MT_MT32 so that we don't
@@ -179,6 +221,8 @@ public:
static const byte _mt32ToGm[128];
static const byte _gmToMt32[128];
// Map for correcting Roland GS drumkit numbers.
static const uint8 _gsDrumkitFallbackMap[128];
/**
* Error codes returned by open.
@@ -274,6 +318,7 @@ public:
virtual void volume(byte value) { controlChange(7, value); }
virtual void panPosition(byte value) { controlChange(10, value); }
virtual void pitchBendFactor(byte value) = 0;
virtual void transpose(int8 value) {}
virtual void detune(byte value) { controlChange(17, value); }
virtual void priority(byte value) { }
virtual void sustain(bool value) { controlChange(64, value ? 1 : 0); }
+4 -4
View File
@@ -42,10 +42,10 @@ class MusicDevice {
public:
MusicDevice(MusicPluginObject const *musicPlugin, Common::String name, MusicType mt);
Common::String &getName() { return _name; }
Common::String &getMusicDriverName() { return _musicDriverName; }
Common::String &getMusicDriverId() { return _musicDriverId; }
MusicType getMusicType() { return _type; }
const Common::String &getName() const { return _name; }
const Common::String &getMusicDriverName() const { return _musicDriverName; }
const Common::String &getMusicDriverId() const { return _musicDriverId; }
MusicType getMusicType() const { return _type; }
/**
* Returns a user readable string that contains the name of the current
+233
View File
@@ -0,0 +1,233 @@
/* 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/cloud/basestorage.h"
#include "backends/cloud/cloudmanager.h"
#include "backends/networking/curl/connectionmanager.h"
#include "backends/networking/curl/curljsonrequest.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/json.h"
namespace Cloud {
BaseStorage::BaseStorage() {}
BaseStorage::BaseStorage(Common::String token, Common::String refreshToken, bool enabled):
_token(token), _refreshToken(refreshToken) {
_isEnabled = enabled;
}
BaseStorage::~BaseStorage() {}
void BaseStorage::getAccessToken(Common::String code, Networking::ErrorCallback callback) {
Networking::JsonCallback innerCallback = new Common::CallbackBridge<BaseStorage, Networking::ErrorResponse, Networking::JsonResponse>(this, &BaseStorage::codeFlowComplete, callback);
Networking::ErrorCallback errorCallback = new Common::CallbackBridge<BaseStorage, Networking::ErrorResponse, Networking::ErrorResponse>(this, &BaseStorage::codeFlowFailed, callback);
Common::String url = Common::String::format("https://cloud.residualvm.org/%s/token/%s", cloudProvider().c_str(), code.c_str());
Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, url);
addRequest(request);
}
void BaseStorage::codeFlowComplete(Networking::ErrorCallback callback, Networking::JsonResponse response) {
bool success = true;
Common::String callbackMessage = "OK";
Common::JSONValue *json = (Common::JSONValue *)response.value;
if (json == nullptr) {
debug(9, "BaseStorage::codeFlowComplete: got NULL instead of JSON!");
success = false;
}
if (success && !json->isObject()) {
debug(9, "BaseStorage::codeFlowComplete: passed JSON is not an object!");
success = false;
}
Common::JSONObject result;
if (success) {
result = json->asObject();
if (!Networking::CurlJsonRequest::jsonContainsAttribute(result, "error", "BaseStorage::codeFlowComplete")) {
warning("BaseStorage: bad response, no 'error' attribute passed");
debug(9, "%s", json->stringify(true).c_str());
success = false;
}
}
if (success && result.getVal("error")->asBool()) {
Common::String errorMessage = "{error: true}, message is missing";
if (Networking::CurlJsonRequest::jsonContainsString(result, "message", "BaseStorage::codeFlowComplete")) {
errorMessage = result.getVal("message")->asString();
}
warning("BaseStorage: response says error occurred: %s", errorMessage.c_str());
success = false;
callbackMessage = errorMessage;
}
if (success && !Networking::CurlJsonRequest::jsonContainsObject(result, "oauth", "BaseStorage::codeFlowComplete")) {
warning("BaseStorage: bad response, no 'oauth' attribute passed");
debug(9, "%s", json->stringify(true).c_str());
success = false;
}
Common::JSONObject oauth;
bool requiresRefreshToken = needsRefreshToken();
if (success) {
oauth = result.getVal("oauth")->asObject();
if (!Networking::CurlJsonRequest::jsonContainsString(oauth, "access_token", "BaseStorage::codeFlowComplete") ||
!Networking::CurlJsonRequest::jsonContainsString(oauth, "refresh_token", "BaseStorage::codeFlowComplete", !requiresRefreshToken)) {
warning("BaseStorage: bad response, no 'access_token' or 'refresh_token' attribute passed");
debug(9, "%s", json->stringify(true).c_str());
success = false;
}
}
if (success) {
debug(9, "%s", json->stringify(true).c_str()); // TODO: remove when done testing against cloud.scummvm.org
_token = oauth.getVal("access_token")->asString();
if (requiresRefreshToken) {
_refreshToken = oauth.getVal("refresh_token")->asString();
}
CloudMan.replaceStorage(this, storageIndex());
ConfMan.flushToDisk();
}
if (!success)
CloudMan.removeStorage(this);
if (callback)
(*callback)(Networking::ErrorResponse(nullptr, false, !success, callbackMessage, -1));
delete json;
delete callback;
}
void BaseStorage::codeFlowFailed(Networking::ErrorCallback callback, Networking::ErrorResponse error) {
debug(9, "BaseStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode);
debug(9, "%s", error.response.c_str());
CloudMan.removeStorage(this);
if (callback)
(*callback)(error);
delete callback;
}
void BaseStorage::refreshAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback) {
if (_refreshToken == "") {
warning("BaseStorage: no refresh token available to get new access token.");
if (callback) (*callback)(BoolResponse(nullptr, false));
return;
}
Networking::JsonCallback innerCallback = new Common::CallbackBridge<BaseStorage, BoolResponse, Networking::JsonResponse>(this, &BaseStorage::tokenRefreshed, callback);
if (errorCallback == nullptr)
errorCallback = getErrorPrintingCallback();
Common::String url = Common::String::format("https://cloud.residulvm.org/%s/refresh", cloudProvider().c_str());
Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, url);
request->addHeader("X-ResidualVM-Refresh-Token: " + _refreshToken);
addRequest(request);
}
void BaseStorage::tokenRefreshed(BoolCallback callback, Networking::JsonResponse response) {
bool success = true;
Common::JSONValue *json = response.value;
if (json == nullptr) {
debug(9, "BaseStorage::tokenRefreshed: got NULL instead of JSON!");
success = false;
}
if (success && !json->isObject()) {
debug(9, "BaseStorage::tokenRefreshed: passed JSON is not an object!");
success = false;
}
Common::JSONObject result;
if (success) {
result = json->asObject();
if (!Networking::CurlJsonRequest::jsonContainsAttribute(result, "error", "BaseStorage::tokenRefreshed")) {
warning("BaseStorage: bad response, no 'error' attribute passed");
debug(9, "%s", json->stringify(true).c_str());
success = false;
}
}
if (success && result.getVal("error")->asBool()) {
Common::String errorMessage = "{error: true}, message is missing";
if (Networking::CurlJsonRequest::jsonContainsString(result, "message", "BaseStorage::tokenRefreshed")) {
errorMessage = result.getVal("message")->asString();
}
warning("BaseStorage: response says error occurred: %s", errorMessage.c_str());
success = false;
}
if (success && !Networking::CurlJsonRequest::jsonContainsObject(result, "oauth", "BaseStorage::tokenRefreshed")) {
warning("BaseStorage: bad response, no 'oauth' attribute passed");
debug(9, "%s", json->stringify(true).c_str());
success = false;
}
Common::JSONObject oauth;
bool requiresRefreshToken = !canReuseRefreshToken();
if (success) {
oauth = result.getVal("oauth")->asObject();
if (!Networking::CurlJsonRequest::jsonContainsString(oauth, "access_token", "BaseStorage::tokenRefreshed") ||
!Networking::CurlJsonRequest::jsonContainsString(oauth, "refresh_token", "BaseStorage::tokenRefreshed", !requiresRefreshToken)) {
warning("BaseStorage: bad response, no 'access_token' or 'refresh_token' attribute passed");
debug(9, "%s", json->stringify(true).c_str());
success = false;
}
}
if (success) {
debug(9, "%s", json->stringify(true).c_str()); // TODO: remove when done testing against cloud.scummvm.org
_token = oauth.getVal("access_token")->asString();
if (requiresRefreshToken) {
_refreshToken = oauth.getVal("refresh_token")->asString();
}
CloudMan.save(); //ask CloudManager to save our new access_token and refresh_token
}
if (callback)
(*callback)(BoolResponse(nullptr, success));
delete json;
delete callback;
}
void BaseStorage::saveIsEnabledFlag(const Common::String &keyPrefix) const {
ConfMan.set(keyPrefix + "enabled", _isEnabled ? "true" : "false", ConfMan.kCloudDomain);
}
bool BaseStorage::loadIsEnabledFlag(const Common::String &keyPrefix) {
if (!ConfMan.hasKey(keyPrefix + "enabled", ConfMan.kCloudDomain))
return false;
Common::String enabled = ConfMan.get(keyPrefix + "enabled", ConfMan.kCloudDomain);
return (enabled == "true");
}
void BaseStorage::removeIsEnabledFlag(const Common::String &keyPrefix) {
ConfMan.removeKey(keyPrefix + "enabled", ConfMan.kCloudDomain);
}
} // End of namespace Cloud
+104
View File
@@ -0,0 +1,104 @@
/* 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_CLOUD_BASE_STORAGE_H
#define BACKENDS_CLOUD_BASE_STORAGE_H
#include "backends/cloud/storage.h"
#include "backends/networking/curl/curljsonrequest.h"
namespace Cloud {
class BaseStorage: public Cloud::Storage {
protected:
/** Storage's access and refresh tokens. */
Common::String _token, _refreshToken;
/**
* Gets token from cloud.scummvm.org using given code.
* Base implementation for storages with common auth procedure.
*/
virtual void getAccessToken(Common::String code, Networking::ErrorCallback callback);
/**
* Handles JSON response which should contain access token requested
* with getAccessToken().
*/
virtual void codeFlowComplete(Networking::ErrorCallback callback, Networking::JsonResponse response);
/**
* Handles network errors occurred while getting access token requested
* with getAccessToken().
*/
virtual void codeFlowFailed(Networking::ErrorCallback callback, Networking::ErrorResponse error);
/**
* Return cloud provider name, used in cloud.scummvm.org endpoints.
* @return cloud provider (for example, "dropbox").
*/
virtual Common::String cloudProvider() = 0;
/**
* Return CloudManager's StorageID for this storage.
* @return StorageID corresponding to this storage (for example,
* kStorageDropboxId).
*/
virtual uint32 storageIndex() = 0;
/**
* Return whether storage needs refresh_token to work.
*/
virtual bool needsRefreshToken() = 0;
/**
* Return whether to expect new refresh_token on refresh.
*/
virtual bool canReuseRefreshToken() = 0;
private:
void tokenRefreshed(BoolCallback callback, Networking::JsonResponse response);
protected:
/** Helper function to save Storage::_isEnabled into config. */
void saveIsEnabledFlag(const Common::String &keyPrefix) const;
/** Helper function to load Storage::_isEnabled value from config. */
static bool loadIsEnabledFlag(const Common::String &keyPrefix);
/** Helper function to remove Storage::_isEnabled from config. */
static void removeIsEnabledFlag(const Common::String &keyPrefix);
public:
BaseStorage();
BaseStorage(Common::String token, Common::String refreshToken, bool enabled = false);
virtual ~BaseStorage();
/**
* Gets new access_token. Pass a callback, so you could
* continue your work when new token is available.
*/
virtual void refreshAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback = nullptr);
};
} // End of namespace Cloud
#endif
+1 -1
View File
@@ -210,7 +210,7 @@ Networking::Request *BoxStorage::info(StorageInfoCallback callback, Networking::
return addRequest(request);
}
Common::String BoxStorage::savesDirectoryPath() { return "scummvm/saves/"; }
Common::String BoxStorage::savesDirectoryPath() { return "residualvm/saves/"; }
BoxStorage *BoxStorage::loadFromConfig(Common::String keyPrefix) {
if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) {
+2
View File
@@ -85,6 +85,8 @@ void CloudIcon::update() {
_lastUpdateTime = currentTime;
switch (_state) {
default:
// fallthrough intended
case kHidden:
return; // Nothing to do
case kShown:
+9 -2
View File
@@ -58,6 +58,8 @@ Common::String CloudManager::getStorageConfigName(uint32 index) const {
case kStorageOneDriveId: return "OneDrive";
case kStorageGoogleDriveId: return "GoogleDrive";
case kStorageBoxId: return "Box";
default:
break;
}
assert(false); // Unhandled StorageID value
return "";
@@ -79,6 +81,7 @@ void CloudManager::loadStorage() {
break;
default:
_activeStorage = nullptr;
break;
}
if (!_activeStorage) {
@@ -91,7 +94,7 @@ void CloudManager::init() {
for (uint32 i = 0; i < kStorageTotal; ++i) {
Common::String name = getStorageConfigName(i);
StorageConfig config;
config.name = _(name);
config.name = name;
config.username = "";
config.lastSyncDate = "";
config.usedBytes = 0;
@@ -271,6 +274,8 @@ void CloudManager::connectStorage(uint32 index, Common::String code, Networking:
case kStorageBoxId:
new Box::BoxStorage(code, cb);
break;
default:
break;
}
// in these constructors Storages request token using the passed code
// when the token is received, they call replaceStorage()
@@ -296,6 +301,8 @@ void CloudManager::disconnectStorage(uint32 index) {
case kStorageBoxId:
Box::BoxStorage::removeFromConfig(kStoragePrefix + name + "_");
break;
default:
break;
}
switchStorage(kStorageNoneId);
@@ -305,7 +312,7 @@ void CloudManager::disconnectStorage(uint32 index) {
ConfMan.removeKey(kStoragePrefix + name + "_usedBytes", ConfMan.kCloudDomain);
StorageConfig config;
config.name = _(name);
config.name = name;
config.username = "";
config.lastSyncDate = "";
config.usedBytes = 0;
@@ -214,7 +214,7 @@ Networking::Request *GoogleDriveStorage::info(StorageInfoCallback callback, Netw
return addRequest(request);
}
Common::String GoogleDriveStorage::savesDirectoryPath() { return "scummvm/saves/"; }
Common::String GoogleDriveStorage::savesDirectoryPath() { return "residualvm/saves/"; }
GoogleDriveStorage *GoogleDriveStorage::loadFromConfig(Common::String keyPrefix) {
if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) {
@@ -53,10 +53,10 @@ void IdCreateDirectoryRequest::start() {
Common::String prefix = _requestedParentPath;
if (prefix.size() > 7)
prefix.erase(7);
if (prefix.equalsIgnoreCase("ScummVM")) {
if (prefix.equalsIgnoreCase("ResidualVM")) {
Storage::BoolCallback callback = new Common::Callback<IdCreateDirectoryRequest, Storage::BoolResponse>(this, &IdCreateDirectoryRequest::createdBaseDirectoryCallback);
Networking::ErrorCallback failureCallback = new Common::Callback<IdCreateDirectoryRequest, Networking::ErrorResponse>(this, &IdCreateDirectoryRequest::createdBaseDirectoryErrorCallback);
_workingRequest = _storage->createDirectory("ScummVM", callback, failureCallback);
_workingRequest = _storage->createDirectory("ResidualVM", callback, failureCallback);
return;
}
+1 -1
View File
@@ -217,7 +217,7 @@ void SavesSyncRequest::directoryListedErrorCallback(Networking::ErrorResponse er
if (error.response.contains("subdirectory not found")) {
irrecoverable = false; //base "/ScummVM/" folder not found
} else if (error.response.contains("no such file found in its parent directory")) {
irrecoverable = false; //"Saves" folder within "/ScummVM/" not found
irrecoverable = false; //"Saves" folder within "/ResidualVM/" not found
} else if (error.response.contains("itemNotFound") && error.response.contains("Item does not exist")) {
irrecoverable = false; //"saves" folder within application folder is not found
}
+98
View File
@@ -0,0 +1,98 @@
/* 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.
*
*/
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
#define FORBIDDEN_SYMBOL_EXCEPTION_unistd_h
#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
#include "common/scummsys.h"
#if defined(POSIX) && defined(USE_SYSDIALOGS) && defined(USE_GTK)
#include "backends/dialogs/gtk/gtk-dialogs.h"
#include "common/config-manager.h"
#include "common/encoding.h"
#include "common/translation.h"
#include <gtk/gtk.h>
Common::DialogManager::DialogResult GtkDialogManager::showFileBrowser(const char *title, Common::FSNode &choice, bool isDirBrowser) {
if (!gtk_init_check(NULL, NULL))
return kDialogError;
DialogResult result = kDialogCancel;
// Get current encoding
Common::String guiEncoding = "ASCII";
#ifdef USE_TRANSLATION
guiEncoding = TransMan.getCurrentCharset();
#endif
Common::Encoding utf8("utf-8", guiEncoding);
// Convert labels to UTF-8
char *utf8Title = utf8.convert(title, strlen(title));
Common::String choose = _("Choose");
char *utf8Choose = utf8.convert(choose.c_str(), choose.size());
Common::String cancel = _("Cancel");
char* utf8Cancel = utf8.convert(cancel.c_str(), cancel.size());
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
if (isDirBrowser) {
action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
}
GtkFileChooserNative *native = gtk_file_chooser_native_new(utf8Title, NULL, action, utf8Choose, utf8Cancel);
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
free(utf8Cancel);
free(utf8Choose);
free(utf8Title);
// Customize dialog
gtk_file_chooser_set_show_hidden(chooser, ConfMan.getBool("gui_browser_show_hidden", Common::ConfigManager::kApplicationDomain));
if (ConfMan.hasKey("browser_lastpath")) {
gtk_file_chooser_set_current_folder(chooser, ConfMan.get("browser_lastpath").c_str());
}
// Show dialog
beginDialog();
int res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
if (res == GTK_RESPONSE_ACCEPT) {
// Get the selection from the user
char *path = gtk_file_chooser_get_filename(chooser);
choice = Common::FSNode(path);
result = kDialogOk;
g_free(path);
// Save last path
char *last = gtk_file_chooser_get_current_folder(chooser);
ConfMan.set("browser_lastpath", last);
g_free(last);
}
g_object_unref(native);
while (gtk_events_pending())
gtk_main_iteration();
endDialog();
return result;
}
#endif
+38
View File
@@ -0,0 +1,38 @@
/* 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 BACKEND_GTK_DIALOGS_H
#define BACKEND_GTK_DIALOGS_H
#if defined(POSIX) && defined(USE_SYSDIALOGS) && defined(USE_GTK)
#include "common/fs.h"
#include "common/dialogs.h"
class GtkDialogManager : public Common::DialogManager {
public:
virtual DialogResult showFileBrowser(const char *title, Common::FSNode &choice, bool isDirBrowser);
};
#endif
#endif // BACKEND_GTK_DIALOGS_H
+2 -23
View File
@@ -29,8 +29,6 @@
#include "backends/dialogs/macosx/macosx-dialogs.h"
#include "common/config-manager.h"
#include "common/system.h"
#include "common/events.h"
#include "common/algorithm.h"
#include "common/translation.h"
@@ -147,13 +145,7 @@ Common::DialogManager::DialogResult MacOSXDialogManager::showFileBrowser(const c
CFStringRef titleRef = CFStringCreateWithCString(0, title, stringEncoding);
CFStringRef chooseRef = CFStringCreateWithCString(0, _("Choose"), stringEncoding);
// If in fullscreen mode, switch to windowed mode
bool wasFullscreen = g_system->getFeatureState(OSystem::kFeatureFullscreenMode);
if (wasFullscreen) {
g_system->beginGFXTransaction();
g_system->setFeatureState(OSystem::kFeatureFullscreenMode, false);
g_system->endGFXTransaction();
}
beginDialog();
// Temporarily show the real mouse
CGDisplayShowCursor(kCGDirectMainDisplay);
@@ -185,20 +177,7 @@ Common::DialogManager::DialogResult MacOSXDialogManager::showFileBrowser(const c
CFRelease(titleRef);
CFRelease(chooseRef);
// While the native macOS file browser is open, any input events (e.g. keypresses) are
// still received by the NSApplication. With SDL backend for example this results in the
// events beeing queued and processed after we return, thus dispatching events that were
// intended for the native file browser. For example: pressing Esc to cancel the native
// macOS file browser would cause the application to quit in addition to closing the
// file browser. To avoid this happening clear all pending events.
g_system->getEventManager()->getEventDispatcher()->clearEvents();
// If we were in fullscreen mode, switch back
if (wasFullscreen) {
g_system->beginGFXTransaction();
g_system->setFeatureState(OSystem::kFeatureFullscreenMode, true);
g_system->endGFXTransaction();
}
endDialog();
return result;
}
+2 -15
View File
@@ -67,8 +67,6 @@
#include "backends/platform/sdl/win32/win32-window.h"
#include "common/config-manager.h"
#include "common/system.h"
#include "common/events.h"
#include "common/translation.h"
Win32DialogManager::Win32DialogManager(SdlWindow_Win32 *window) : _window(window) {
@@ -117,13 +115,7 @@ Common::DialogManager::DialogResult Win32DialogManager::showFileBrowser(const ch
reinterpret_cast<void **> (&(dialog)));
if (SUCCEEDED(hr)) {
// If in fullscreen mode, switch to windowed mode
bool wasFullscreen = g_system->getFeatureState(OSystem::kFeatureFullscreenMode);
if (wasFullscreen) {
g_system->beginGFXTransaction();
g_system->setFeatureState(OSystem::kFeatureFullscreenMode, false);
g_system->endGFXTransaction();
}
beginDialog();
// Customize dialog
bool showHidden = ConfMan.getBool("gui_browser_show_hidden", Common::ConfigManager::kApplicationDomain);
@@ -191,12 +183,7 @@ Common::DialogManager::DialogResult Win32DialogManager::showFileBrowser(const ch
dialog->Release();
// If we were in fullscreen mode, switch back
if (wasFullscreen) {
g_system->beginGFXTransaction();
g_system->setFeatureState(OSystem::kFeatureFullscreenMode, true);
g_system->endGFXTransaction();
}
endDialog();
}
return result;
+97 -71
View File
@@ -28,11 +28,13 @@
#include "common/config-manager.h"
#include "common/translation.h"
#include "backends/events/default/default-events.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymapper.h"
#include "backends/keymapper/remap-dialog.h"
#include "backends/keymapper/virtual-mouse.h"
#include "backends/vkeybd/virtual-keyboard.h"
#include "engines/engine.h"
#include "gui/debugger.h"
#include "gui/message.h"
DefaultEventManager::DefaultEventManager(Common::EventSource *boss) :
@@ -40,8 +42,7 @@ DefaultEventManager::DefaultEventManager(Common::EventSource *boss) :
_modifierState(0),
_shouldQuit(false),
_shouldRTL(false),
_confirmExitDialogActive(false),
_shouldGenerateKeyRepeatEvents(true) {
_confirmExitDialogActive(false) {
assert(boss);
@@ -50,26 +51,22 @@ DefaultEventManager::DefaultEventManager(Common::EventSource *boss) :
_dispatcher.registerObserver(this, kEventManPriority, false);
// Reset key repeat
_keyRepeatTime = 0;
#ifdef ENABLE_VKEYBD
_vk = nullptr;
#endif
#ifdef ENABLE_KEYMAPPER
_virtualMouse = new Common::VirtualMouse(&_dispatcher);
_keymapper = new Common::Keymapper(this);
// EventDispatcher will automatically free the keymapper
_dispatcher.registerMapper(_keymapper);
_remap = false;
#else
_dispatcher.registerMapper(new Common::DefaultEventMapper());
#endif
}
DefaultEventManager::~DefaultEventManager() {
delete _virtualMouse;
#ifdef ENABLE_VKEYBD
delete _vk;
#endif
delete _keymapper;
}
void DefaultEventManager::init() {
@@ -87,9 +84,9 @@ void DefaultEventManager::init() {
bool DefaultEventManager::pollEvent(Common::Event &event) {
_dispatcher.dispatch();
if (_shouldGenerateKeyRepeatEvents) {
handleKeyRepeat();
}
if (g_engine)
// Handle autosaves if enabled
g_engine->handleAutoSave();
if (_eventQueue.empty()) {
return false;
@@ -98,6 +95,10 @@ bool DefaultEventManager::pollEvent(Common::Event &event) {
event = _eventQueue.pop();
bool forwardEvent = true;
// If the backend has the kFeatureNoQuit, replace Quit event with RTL
if (event.type == Common::EVENT_QUIT && g_system->hasFeature(OSystem::kFeatureNoQuit))
event.type = Common::EVENT_RTL;
switch (event.type) {
case Common::EVENT_KEYDOWN:
_modifierState = event.kbd.flags;
@@ -111,7 +112,6 @@ bool DefaultEventManager::pollEvent(Common::Event &event) {
// key pressed. A better fix would be for engines to stop
// making invalid assumptions about ascii values.
event.kbd.ascii = Common::KEYCODE_BACKSPACE;
_currentKeyDown.ascii = Common::KEYCODE_BACKSPACE;
}
break;
@@ -168,20 +168,6 @@ bool DefaultEventManager::pollEvent(Common::Event &event) {
forwardEvent = false;
}
break;
#endif
#ifdef ENABLE_KEYMAPPER
case Common::EVENT_KEYMAPPER_REMAP:
if (!_remap) {
_remap = true;
Common::RemapDialog _remapDialog;
if (g_engine)
g_engine->pauseEngine(true);
_remapDialog.runModal();
if (g_engine)
g_engine->pauseEngine(false);
_remap = false;
}
break;
#endif
case Common::EVENT_RTL:
if (ConfMan.getBool("confirm_exit")) {
@@ -214,11 +200,29 @@ bool DefaultEventManager::pollEvent(Common::Event &event) {
if (g_engine)
g_engine->pauseEngine(false);
_confirmExitDialogActive = false;
} else
} else {
_shouldQuit = true;
}
break;
case Common::EVENT_DEBUGGER: {
GUI::Debugger *debugger = g_engine ? g_engine->getOrCreateDebugger() : nullptr;
if (debugger) {
debugger->attach();
debugger->onFrame();
forwardEvent = false;
}
break;
}
case Common::EVENT_INPUT_CHANGED: {
Common::HardwareInputSet *inputSet = g_system->getHardwareInputSet();
Common::KeymapperDefaultBindings *backendDefaultBindings = g_system->getKeymapperDefaultBindings();
_keymapper->registerHardwareInputSet(inputSet, backendDefaultBindings);
break;
}
default:
break;
}
@@ -226,45 +230,6 @@ bool DefaultEventManager::pollEvent(Common::Event &event) {
return forwardEvent;
}
void DefaultEventManager::handleKeyRepeat() {
uint32 time = g_system->getMillis(true);
if (!_eventQueue.empty()) {
// Peek in the event queue
const Common::Event &nextEvent = _eventQueue.front();
switch (nextEvent.type) {
case Common::EVENT_KEYDOWN:
// init continuous event stream
_currentKeyDown = nextEvent.kbd;
_keyRepeatTime = time + kKeyRepeatInitialDelay;
break;
case Common::EVENT_KEYUP:
if (nextEvent.kbd.keycode == _currentKeyDown.keycode) {
// Only stop firing events if it's the current key
_currentKeyDown.keycode = Common::KEYCODE_INVALID;
}
break;
default:
break;
}
} else {
// Check if event should be sent again (keydown)
if (_currentKeyDown.keycode != Common::KEYCODE_INVALID && _keyRepeatTime <= time) {
// fire event
Common::Event repeatEvent;
repeatEvent.type = Common::EVENT_KEYDOWN;
repeatEvent.kbdRepeat = true;
repeatEvent.kbd = _currentKeyDown;
_keyRepeatTime = time + kKeyRepeatSustainDelay;
_eventQueue.push(repeatEvent);
}
}
}
void DefaultEventManager::pushEvent(const Common::Event &event) {
// If already received an EVENT_QUIT, don't add another one
if (event.type == Common::EVENT_QUIT) {
@@ -306,6 +271,10 @@ void DefaultEventManager::purgeMouseEvents() {
case Common::EVENT_WHEELDOWN:
case Common::EVENT_MBUTTONDOWN:
case Common::EVENT_MBUTTONUP:
case Common::EVENT_X1BUTTONDOWN:
case Common::EVENT_X1BUTTONUP:
case Common::EVENT_X2BUTTONDOWN:
case Common::EVENT_X2BUTTONUP:
case Common::EVENT_MOUSEMOVE:
// do nothing
break;
@@ -317,4 +286,61 @@ void DefaultEventManager::purgeMouseEvents() {
_eventQueue = filteredQueue;
}
Common::Keymap *DefaultEventManager::getGlobalKeymap() {
using namespace Common;
// Now create the global keymap
Keymap *globalKeymap = new Keymap(Keymap::kKeymapTypeGlobal, kGlobalKeymapName, _("Global"));
Action *act;
act = new Action("MENU", _("Global Main Menu"));
act->addDefaultInputMapping("C+F5");
act->addDefaultInputMapping("JOY_START");
act->setEvent(EVENT_MAINMENU);
globalKeymap->addAction(act);
#ifdef ENABLE_VKEYBD
act = new Action("VIRT", _("Display keyboard"));
act->addDefaultInputMapping("C+F7");
act->addDefaultInputMapping("JOY_BACK");
act->setEvent(EVENT_VIRTUAL_KEYBOARD);
globalKeymap->addAction(act);
#endif
act = new Action("MUTE", _("Toggle mute"));
act->addDefaultInputMapping("C+u");
act->setEvent(EVENT_MUTE);
globalKeymap->addAction(act);
act = new Action("QUIT", _("Quit"));
act->setEvent(EVENT_QUIT);
#if defined(MACOSX)
// On Macintosh, Cmd-Q quits
act->addDefaultInputMapping("M+q");
#elif defined(POSIX)
// On other *nix systems, Control-Q quits
act->addDefaultInputMapping("C+q");
#else
// Ctrl-z quits
act->addDefaultInputMapping("C+z");
#ifdef WIN32
// On Windows, also use the default Alt-F4 quit combination
act->addDefaultInputMapping("A+F4");
#endif
#endif
globalKeymap->addAction(act);
act = new Action("DEBUGGER", _("Open Debugger"));
act->addDefaultInputMapping("C+A+d");
act->setEvent(EVENT_DEBUGGER);
globalKeymap->addAction(act);
_virtualMouse->addActionsToKeymap(globalKeymap);
return globalKeymap;
}
#endif // !defined(DISABLE_DEFAULT_EVENTMANAGER)
+5 -32
View File
@@ -27,12 +27,11 @@
#include "common/queue.h"
namespace Common {
#ifdef ENABLE_KEYMAPPER
class Keymapper;
#endif
#ifdef ENABLE_VKEYBD
class VirtualKeyboard;
#endif
class VirtualMouse;
}
@@ -41,10 +40,9 @@ class DefaultEventManager : public Common::EventManager, Common::EventObserver {
Common::VirtualKeyboard *_vk;
#endif
#ifdef ENABLE_KEYMAPPER
Common::VirtualMouse *_virtualMouse;
Common::Keymapper *_keymapper;
bool _remap;
#endif
Common::ArtificialEventSource _artificialEventSource;
@@ -61,17 +59,6 @@ class DefaultEventManager : public Common::EventManager, Common::EventObserver {
bool _shouldRTL;
bool _confirmExitDialogActive;
// for continuous events (keyDown)
enum {
kKeyRepeatInitialDelay = 400,
kKeyRepeatSustainDelay = 100
};
bool _shouldGenerateKeyRepeatEvents;
Common::KeyState _currentKeyDown;
uint32 _keyRepeatTime;
void handleKeyRepeat();
public:
DefaultEventManager(Common::EventSource *boss);
~DefaultEventManager();
@@ -91,22 +78,8 @@ public:
virtual void resetQuit() override { _shouldQuit = false; }
#endif
#ifdef ENABLE_KEYMAPPER
// IMPORTANT NOTE: This is part of the WIP Keymapper. If you plan to use
// this, please talk to tsoliman and/or LordHoto.
virtual Common::Keymapper *getKeymapper() override { return _keymapper; }
#endif
/**
* Controls whether repeated key down events are generated while a key is pressed
*
* Backends that generate their own keyboard repeat events should disable this.
*
* @param generateKeyRepeatEvents
*/
void setGenerateKeyRepeatEvents(bool generateKeyRepeatEvents) {
_shouldGenerateKeyRepeatEvents = generateKeyRepeatEvents;
}
Common::Keymapper *getKeymapper() override { return _keymapper; }
Common::Keymap *getGlobalKeymap() override;
};
#endif
+177 -1
View File
@@ -24,15 +24,24 @@
#if defined(SDL_BACKEND)
#include "resvm-sdl-events.h"
#include "backends/events/sdl/resvm-sdl-events.h"
#include "backends/graphics/sdl/resvm-sdl-graphics.h"
#include "engines/engine.h"
#include "gui/gui-manager.h"
#include "common/config-manager.h"
#if defined(ENABLE_VKEYBD) && SDL_VERSION_ATLEAST(2, 0, 0)
#define CONTROLLER_BUT_VKEYBOARD SDL_CONTROLLER_BUTTON_BACK
#endif
ResVmSdlEventSource::ResVmSdlEventSource() {
// Reset mouse state
memset(&_km, 0, sizeof(_km));
ConfMan.registerDefault("kbdmouse_speed", 3);
ConfMan.registerDefault("joystick_deadzone", 3);
}
bool ResVmSdlEventSource::handleJoyButtonDown(SDL_Event &ev, Common::Event &event) {
if (shouldGenerateMouseEvents()) {
return SdlEventSource::handleJoyButtonDown(ev, event);
@@ -202,4 +211,171 @@ bool ResVmSdlEventSource::handleKbdMouse(Common::Event &event) {
return false;
}
bool ResVmSdlEventSource::handleAxisToMouseMotion(int16 xAxis, int16 yAxis) {
#ifdef JOY_INVERT_Y
yAxis = -yAxis;
#endif
// conversion factor between keyboard mouse and joy axis value
int vel_to_axis = (1500 / MULTIPLIER);
// radial and scaled deadzone
float analogX = (float)xAxis;
float analogY = (float)yAxis;
float deadZone = (float)ConfMan.getInt("joystick_deadzone") * 1000.0f;
float magnitude = sqrt(analogX * analogX + analogY * analogY);
if (magnitude >= deadZone) {
_km.x_down_count = 0;
_km.y_down_count = 0;
float scalingFactor = 1.0f / magnitude * (magnitude - deadZone) / (32769.0f - deadZone);
_km.x_vel = (int16)(analogX * scalingFactor * 32768.0f / vel_to_axis);
_km.y_vel = (int16)(analogY * scalingFactor * 32768.0f / vel_to_axis);
} else {
_km.x_vel = 0;
_km.y_vel = 0;
}
return false;
}
void ResVmSdlEventSource::resetKeyboardEmulation(int16 x_max, int16 y_max) {
_km.x_max = x_max;
_km.y_max = y_max;
_km.delay_time = 12;
_km.last_time = 0;
_km.modifier = false;
_km.joy_x = 0;
_km.joy_y = 0;
}
void ResVmSdlEventSource::updateKbdMouse() {
uint32 curTime = g_system->getMillis(true);
if (curTime < _km.last_time + _km.delay_time) {
return;
}
_km.last_time = curTime;
if (_km.x_down_count == 1) {
_km.x_down_time = curTime;
_km.x_down_count = 2;
}
if (_km.y_down_count == 1) {
_km.y_down_time = curTime;
_km.y_down_count = 2;
}
if (_km.x_vel || _km.y_vel) {
if (_km.x_down_count) {
if (curTime > _km.x_down_time + 300) {
if (_km.x_vel > 0)
_km.x_vel += MULTIPLIER;
else
_km.x_vel -= MULTIPLIER;
} else if (curTime > _km.x_down_time + 200) {
if (_km.x_vel > 0)
_km.x_vel = 5 * MULTIPLIER;
else
_km.x_vel = -5 * MULTIPLIER;
}
}
if (_km.y_down_count) {
if (curTime > _km.y_down_time + 300) {
if (_km.y_vel > 0)
_km.y_vel += MULTIPLIER;
else
_km.y_vel -= MULTIPLIER;
} else if (curTime > _km.y_down_time + 200) {
if (_km.y_vel > 0)
_km.y_vel = 5 * MULTIPLIER;
else
_km.y_vel = -5 * MULTIPLIER;
}
}
int16 speedFactor = computeJoystickMouseSpeedFactor();
// - The modifier key makes the mouse movement slower
// - The extra factor "delay/speedFactor" ensures velocities
// are independent of the kbdMouse update rate
// - all velocities were originally chosen
// at a delay of 25, so that is the reference used here
// - note: operator order is important to avoid overflow
if (_km.modifier) {
_km.x += ((_km.x_vel / 10) * ((int16)_km.delay_time)) / speedFactor;
_km.y += ((_km.y_vel / 10) * ((int16)_km.delay_time)) / speedFactor;
} else {
_km.x += (_km.x_vel * ((int16)_km.delay_time)) / speedFactor;
_km.y += (_km.y_vel * ((int16)_km.delay_time)) / speedFactor;
}
if (_km.x < 0) {
_km.x = 0;
_km.x_vel = -1 * MULTIPLIER;
_km.x_down_count = 1;
} else if (_km.x > _km.x_max * MULTIPLIER) {
_km.x = _km.x_max * MULTIPLIER;
_km.x_vel = 1 * MULTIPLIER;
_km.x_down_count = 1;
}
if (_km.y < 0) {
_km.y = 0;
_km.y_vel = -1 * MULTIPLIER;
_km.y_down_count = 1;
} else if (_km.y > _km.y_max * MULTIPLIER) {
_km.y = _km.y_max * MULTIPLIER;
_km.y_vel = 1 * MULTIPLIER;
_km.y_down_count = 1;
}
}
}
int16 ResVmSdlEventSource::computeJoystickMouseSpeedFactor() const {
int16 speedFactor;
switch (ConfMan.getInt("kbdmouse_speed")) {
// 0.25 keyboard pointer speed
case 0:
speedFactor = 100;
break;
// 0.5 speed
case 1:
speedFactor = 50;
break;
// 0.75 speed
case 2:
speedFactor = 33;
break;
// 1.0 speed
case 3:
speedFactor = 25;
break;
// 1.25 speed
case 4:
speedFactor = 20;
break;
// 1.5 speed
case 5:
speedFactor = 17;
break;
// 1.75 speed
case 6:
speedFactor = 14;
break;
// 2.0 speed
case 7:
speedFactor = 12;
break;
default:
speedFactor = 25;
}
// Scale the mouse cursor speed with the display size so moving across
// the screen takes a reasonable amount of time at higher resolutions.
return speedFactor * 480 / _km.y_max;
}
#endif
+33 -1
View File
@@ -23,18 +23,50 @@
#ifndef BACKEND_EVENTS_RESVM_SDL
#define BACKEND_EVENTS_RESVM_SDL
#include "sdl-events.h"
#include "backends/events/sdl/sdl-events.h"
// multiplier used to increase resolution for keyboard/joystick mouse
#define MULTIPLIER 16
/**
* Custom event source for ResidualVM with true joystick support.
*/
class ResVmSdlEventSource : public SdlEventSource {
public:
ResVmSdlEventSource();
/**
* Resets keyboard emulation after a video screen change
*/
void resetKeyboardEmulation(int16 x_max, int16 y_max);
protected:
struct KbdMouse {
int32 x, y;
int16 x_vel, y_vel, x_max, y_max, x_down_count, y_down_count, joy_x, joy_y;
uint32 last_time, delay_time, x_down_time, y_down_time;
bool modifier;
};
KbdMouse _km;
virtual void updateKbdMouse();
bool handleJoyButtonDown(SDL_Event &ev, Common::Event &event) override;
bool handleJoyButtonUp(SDL_Event &ev, Common::Event &event) override;
bool handleJoyAxisMotion(SDL_Event &ev, Common::Event &event) override;
bool handleKbdMouse(Common::Event &event) override;
/**
* Update the virtual mouse according to a joystick or game controller axis position change
*/
virtual bool handleAxisToMouseMotion(int16 xAxis, int16 yAxis);
/**
* Compute the virtual mouse movement speed factor according to the 'kbdmouse_speed' setting.
* The speed factor is scaled with the display size.
*/
int16 computeJoystickMouseSpeedFactor() const;
#if SDL_VERSION_ATLEAST(2, 0, 0)
bool handleControllerButton(const SDL_Event &ev, Common::Event &event, bool buttonUp) override;
bool handleControllerAxisMotion(const SDL_Event &ev, Common::Event &event) override;
File diff suppressed because it is too large Load Diff
+25 -45
View File
@@ -28,8 +28,12 @@
#include "common/events.h"
// multiplier used to increase resolution for keyboard/joystick mouse
#define MULTIPLIER 16
// Type names which changed between SDL 1.2 and SDL 2.
#if !SDL_VERSION_ATLEAST(2, 0, 0)
typedef SDLKey SDL_Keycode;
typedef SDLMod SDL_Keymod;
typedef SDL_keysym SDL_Keysym;
#endif
/**
* The SDL event source.
@@ -46,38 +50,22 @@ public:
*/
virtual bool pollEvent(Common::Event &event);
/**
* Resets keyboard emulation after a video screen change
*/
virtual void resetKeyboardEmulation(int16 x_max, int16 y_max);
/**
* Emulates a mouse movement that would normally be caused by a mouse warp
* of the system mouse.
*/
void fakeWarpMouse(const int x, const int y);
/** Returns whether a joystick is currently connected */
bool isJoystickConnected() const;
protected:
/** @name Keyboard mouse emulation
* Disabled by fingolfin 2004-12-18.
* I am keeping the rest of the code in for now, since the joystick
* code (or rather, "hack") uses it, too.
*/
//@{
struct KbdMouse {
int32 x, y;
int16 x_vel, y_vel, x_max, y_max, x_down_count, y_down_count, joy_x, joy_y;
uint32 last_time, delay_time, x_down_time, y_down_time;
bool modifier;
};
KbdMouse _km;
//@}
/** Scroll lock state - since SDL doesn't track it */
bool _scrollLock;
int _mouseX;
int _mouseY;
/** Joystick */
SDL_Joystick *_joystick;
@@ -139,37 +127,27 @@ protected:
virtual bool handleMouseButtonDown(SDL_Event &ev, Common::Event &event);
virtual bool handleMouseButtonUp(SDL_Event &ev, Common::Event &event);
virtual bool handleSysWMEvent(SDL_Event &ev, Common::Event &event);
virtual int mapSDLJoystickButtonToOSystem(Uint8 sdlButton);
virtual bool handleJoyButtonDown(SDL_Event &ev, Common::Event &event);
virtual bool handleJoyButtonUp(SDL_Event &ev, Common::Event &event);
virtual bool handleJoyAxisMotion(SDL_Event &ev, Common::Event &event);
virtual void updateKbdMouse();
virtual bool handleKbdMouse(Common::Event &event);
virtual bool handleJoyHatMotion(SDL_Event &ev, Common::Event &event);
#if SDL_VERSION_ATLEAST(2, 0, 0)
virtual bool handleJoystickAdded(const SDL_JoyDeviceEvent &event);
virtual bool handleJoystickRemoved(const SDL_JoyDeviceEvent &device);
virtual bool handleJoystickAdded(const SDL_JoyDeviceEvent &device, Common::Event &event);
virtual bool handleJoystickRemoved(const SDL_JoyDeviceEvent &device, Common::Event &event);
virtual int mapSDLControllerButtonToOSystem(Uint8 sdlButton);
virtual bool handleControllerButton(const SDL_Event &ev, Common::Event &event, bool buttonUp);
virtual bool handleControllerAxisMotion(const SDL_Event &ev, Common::Event &event);
#endif
//@}
/**
* Update the virtual mouse according to a joystick or game controller axis position change
*/
virtual bool handleAxisToMouseMotion(int16 xAxis, int16 yAxis);
/**
* Compute the virtual mouse movement speed factor according to the 'kbdmouse_speed' setting.
* The speed factor is scaled with the display size.
*/
int16 computeJoystickMouseSpeedFactor() const;
/**
* Assigns the mouse coords to the mouse event. Furthermore notify the
* graphics manager about the position change.
* ResidualVM addon:
* The parameters relx and rely for relative mouse movement are Residual specific
* The parameters relx and rely for relative mouse movement are ResidualVM specific
*/
virtual bool processMouseEvent(Common::Event &event, int x, int y, int relx = 0, int rely = 0);
@@ -182,17 +160,17 @@ protected:
/**
* Maps the ASCII value of key
*/
virtual int mapKey(SDLKey key, SDLMod mod, Uint16 unicode);
virtual int mapKey(SDL_Keycode key, SDL_Keymod mod, Uint16 unicode);
/**
* Configures the key modifiers flags status
*/
virtual void SDLModToOSystemKeyFlags(SDLMod mod, Common::Event &event);
virtual void SDLModToOSystemKeyFlags(SDL_Keymod mod, Common::Event &event);
/**
* Translates SDL key codes to OSystem key codes
*/
Common::KeyCode SDLToOSystemKeycode(const SDLKey key);
Common::KeyCode SDLToOSystemKeycode(const SDL_Keycode key);
/**
* Notify graphics manager of a resize request.
@@ -203,12 +181,12 @@ protected:
* Extracts unicode information for the specific key sym.
* May only be used for key down events.
*/
uint32 obtainUnicode(const SDL_keysym keySym);
uint32 obtainUnicode(const SDL_Keysym keySym);
/**
* Extracts the keycode for the specified key sym.
*/
SDLKey obtainKeycode(const SDL_keysym keySym);
SDL_Keycode obtainKeycode(const SDL_Keysym keySym);
/**
* Whether _fakeMouseMove contains an event we need to send.
@@ -222,6 +200,8 @@ protected:
*/
Common::Event _fakeMouseMove;
uint8 _lastHatPosition;
#if SDL_VERSION_ATLEAST(2, 0, 0)
/**
* Whether _fakeKeyUp contains an event we need to send.
+79 -46
View File
@@ -40,7 +40,7 @@ const char *lastPathComponent(const Common::String &str) {
int offset = str.size();
if (offset <= 0) {
debug(6, "Bad offset");
debug(6, "Bad offset!");
return 0;
}
@@ -57,12 +57,12 @@ const char *lastPathComponent(const Common::String &str) {
AmigaOSFilesystemNode::AmigaOSFilesystemNode() {
ENTER();
_sDisplayName = "Available Disks";
_sDisplayName = "Available HDDs/Partitions";
_bIsValid = true;
_bIsDirectory = true;
_sPath = "";
_pFileLock = 0;
_nProt = 0; // Protection is ignored for the root volume
_nProt = 0; // Protection is ignored for the root volume.
LEAVE();
}
@@ -71,10 +71,8 @@ AmigaOSFilesystemNode::AmigaOSFilesystemNode(const Common::String &p) {
int offset = p.size();
//assert(offset > 0);
if (offset <= 0) {
debug(6, "Bad offset");
debug(6, "Bad offset!");
return;
}
@@ -84,7 +82,20 @@ AmigaOSFilesystemNode::AmigaOSFilesystemNode(const Common::String &p) {
_bIsDirectory = false;
_bIsValid = false;
// Check whether the node exists and if it's a directory
// WORKAROUND:
// This is a workaround for a bug present in AmigaOS4's
// newlib.library 53.30 and lower.
// It will be removed once a fixed version of said library is
// available to the public.
// DESCRIPTION:
// We need to explicitly open dos.library and it's IDOS interface.
// Otherwise it will hit a NULL pointer with a shared binary build.
// The hit will happen on loading a game from any engine, if
// more than one engine (shared) plugin is available.
DOSBase = IExec->OpenLibrary("dos.library", 0);
IDOS = (struct DOSIFace *)IExec->GetInterface(DOSBase, "main", 1, NULL);
// Check whether the node exists and if it's a directory.
struct ExamineData * pExd = IDOS->ExamineObjectTags(EX_StringNameInput,_sPath.c_str(),TAG_END);
if (pExd) {
_nProt = pExd->Protection;
@@ -93,7 +104,7 @@ AmigaOSFilesystemNode::AmigaOSFilesystemNode(const Common::String &p) {
_pFileLock = IDOS->Lock((CONST_STRPTR)_sPath.c_str(), SHARED_LOCK);
_bIsValid = (_pFileLock != 0);
// Add a trailing slash if needed
// Add a trailing slash, if needed.
const char c = _sPath.lastChar();
if (c != '/' && c != ':')
_sPath += '/';
@@ -105,6 +116,11 @@ AmigaOSFilesystemNode::AmigaOSFilesystemNode(const Common::String &p) {
IDOS->FreeDosObject(DOS_EXAMINEDATA, pExd);
}
// WORKAROUND:
// Close dos.library and its IDOS interface again.
IExec->DropInterface((struct Interface *)IDOS);
IExec->CloseLibrary(DOSBase);
LEAVE();
}
@@ -124,7 +140,7 @@ AmigaOSFilesystemNode::AmigaOSFilesystemNode(BPTR pLock, const char *pDisplayNam
if (IDOS->IoErr() != ERROR_LINE_TOO_LONG) {
_bIsValid = false;
debug(6, "IoErr() != ERROR_LINE_TOO_LONG");
debug(6, "IDOS->IoErr() failed - ERROR_LINE_TOO_LONG not matched!");
LEAVE();
delete[] n;
return;
@@ -134,9 +150,10 @@ AmigaOSFilesystemNode::AmigaOSFilesystemNode(BPTR pLock, const char *pDisplayNam
delete[] n;
}
_bIsValid = false;
_bIsDirectory = false;
_bIsValid = false;
// Check whether the node exists and if it's a directory.
struct ExamineData * pExd = IDOS->ExamineObjectTags(EX_FileLockInput,pLock,TAG_END);
if (pExd) {
_nProt = pExd->Protection;
@@ -145,6 +162,7 @@ AmigaOSFilesystemNode::AmigaOSFilesystemNode(BPTR pLock, const char *pDisplayNam
_pFileLock = IDOS->DupLock(pLock);
_bIsValid = _pFileLock != 0;
// Add a trailing slash, if needed.
const char c = _sPath.lastChar();
if (c != '/' && c != ':')
_sPath += '/';
@@ -155,13 +173,13 @@ AmigaOSFilesystemNode::AmigaOSFilesystemNode(BPTR pLock, const char *pDisplayNam
IDOS->FreeDosObject(DOS_EXAMINEDATA, pExd);
} else {
debug(6, "ExamineObject() returned NULL");
debug(6, "IDOS->ExamineData() failed - ExamineDosObject returned NULL!");
}
LEAVE();
}
// We need the custom copy constructor because of DupLock()
// We need the custom copy constructor because of DupLock().
AmigaOSFilesystemNode::AmigaOSFilesystemNode(const AmigaOSFilesystemNode& node)
: AbstractFSNode() {
ENTER();
@@ -218,7 +236,7 @@ bool AmigaOSFilesystemNode::exists() const {
AbstractFSNode *AmigaOSFilesystemNode::getChild(const Common::String &n) const {
ENTER();
if (!_bIsDirectory) {
debug(6, "Not a directory");
debug(6, "Not a directory!");
return 0;
}
@@ -237,34 +255,32 @@ bool AmigaOSFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, b
ENTER();
bool ret = false;
// TODO: Honor the hidden flag
// There is no such thing as a hidden flag in AmigaOS...
if (!_bIsValid) {
debug(6, "Invalid node");
debug(6, "Invalid node!");
LEAVE();
return false; // Empty list
}
if (!_bIsDirectory) {
debug(6, "Not a directory");
debug(6, "Not a directory!");
LEAVE();
return false; // Empty list
}
if (isRootNode()) {
debug(6, "Root node");
debug(6, "Root node!");
LEAVE();
myList = listVolumes();
return true;
}
APTR context = IDOS->ObtainDirContextTags( EX_FileLockInput, _pFileLock,
EX_DoCurrentDir, TRUE, /* for softlinks */
EX_DataFields, (EXF_NAME|EXF_LINK|EXF_TYPE),
TAG_END);
APTR context = IDOS->ObtainDirContextTags( EX_FileLockInput, _pFileLock,
EX_DoCurrentDir, TRUE, /* for softlinks */
EX_DataFields, (EXF_NAME|EXF_LINK|EXF_TYPE),
TAG_END);
if (context) {
struct ExamineData * pExd = NULL; // NB: No need to free the value after usage, everything will be dealt with by the DirContext release
// No need to free the value after usage, everything will be dealt with by the DirContext release.
struct ExamineData * pExd = NULL;
AmigaOSFilesystemNode *entry;
while ( (pExd = IDOS->ExamineDir(context)) ) {
@@ -286,7 +302,7 @@ bool AmigaOSFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, b
}
if (ERROR_NO_MORE_ENTRIES != IDOS->IoErr() ) {
debug(6, "An error occurred during ExamineDir");
debug(6, "IDOS->IoErr() failed - ERROR_NO_MORE_ENTRIES!");
ret = false;
} else {
ret = true;
@@ -295,7 +311,7 @@ bool AmigaOSFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, b
IDOS->ReleaseDirContext(context);
} else {
debug(6, "Unable to ObtainDirContext");
debug(6, "IDOS->ObtainDirContext() failed!");
ret = false;
}
@@ -308,7 +324,7 @@ AbstractFSNode *AmigaOSFilesystemNode::getParent() const {
ENTER();
if (isRootNode()) {
debug(6, "Root node");
debug(6, "Root node!");
LEAVE();
return new AmigaOSFilesystemNode(*this);
}
@@ -343,9 +359,9 @@ bool AmigaOSFilesystemNode::isReadable() const {
if (!_bIsValid)
return false;
// Regular RWED protection flags are low-active or inverted, thus the negation.
// Moreover, a pseudo root filesystem is readable whatever the
// protection says.
// Regular RWED protection flags are low-active or inverted,
// thus the negation. Moreover, a pseudo root filesystem is
// readable whatever the protection says.
bool readable = !(_nProt & EXDF_OTR_READ) || isRootNode();
return readable;
@@ -355,9 +371,10 @@ bool AmigaOSFilesystemNode::isWritable() const {
if (!_bIsValid)
return false;
// Regular RWED protection flags are low-active or inverted, thus the negation.
// Moreover, a pseudo root filesystem is never writable whatever
// the protection says (Because of it's pseudo nature).
// Regular RWED protection flags are low-active or inverted,
// thus the negation. Moreover, a pseudo root filesystem is
// never writable whatever the protection says.
// (Because of it's pseudo nature).
bool writable = !(_nProt & EXDF_OTR_WRITE) && !isRootNode();
return writable;
@@ -373,7 +390,7 @@ AbstractFSList AmigaOSFilesystemNode::listVolumes() const {
struct DosList *dosList = IDOS->LockDosList(kLockFlags);
if (!dosList) {
debug(6, "Cannot lock the DOS list");
debug(6, "IDOS->LockDOSList() failed!");
LEAVE();
return myList;
}
@@ -385,18 +402,21 @@ AbstractFSList AmigaOSFilesystemNode::listVolumes() const {
dosList->dol_Port) {
// The original line was
//if (dosList->dol_Type == DLT_VOLUME &&
//dosList->dol_Name &&
//dosList->dol_Task) {
// which errored using SDK 53.24 with a 'struct dosList' has no member called 'dol_Task'
// The reason for that was that
// 1) dol_Task wasn't a task pointer, it is a message port instead
// 2) It was redefined to be dol_Port in dos/obsolete.h in afore mentioned SDK
//
// if (dosList->dol_Type == DLT_VOLUME &&
// dosList->dol_Name &&
// dosList->dol_Task) {
//
// which errored using SDK 53.24 with a
// 'struct dosList' has no member called 'dol_Task'
// The reason for that was, that
// 1) dol_Task wasn't a task pointer, it was a message port instead.
// 2) it was redefined to be dol_Port in dos/obsolete.h in aforementioned SDK.
// Copy name to buffer
// Copy name to buffer.
IDOS->CopyStringBSTRToC(dosList->dol_Name, buffer, MAXPATHLEN);
// Volume name + '\0'
// Volume name + '\0'.
char *volName = new char [strlen(buffer) + 1];
strcpy(volName, buffer);
@@ -408,10 +428,10 @@ AbstractFSList AmigaOSFilesystemNode::listVolumes() const {
char *devName = new char [MAXPATHLEN];
// Find device name
// Find device name.
IDOS->DevNameFromLock(volumeLock, devName, MAXPATHLEN, DN_DEVICEONLY);
sprintf(buffer, "%s (%s)", volName, devName);
snprintf(buffer, MAXPATHLEN, "%s (%s)", volName, devName);
delete[] devName;
@@ -436,7 +456,20 @@ AbstractFSList AmigaOSFilesystemNode::listVolumes() const {
}
Common::SeekableReadStream *AmigaOSFilesystemNode::createReadStream() {
return StdioStream::makeFromPath(getPath(), false);
StdioStream *readStream = StdioStream::makeFromPath(getPath(), false);
//
// Work around for possibility that someone uses AmigaOS "newlib" build
// with SmartFileSystem (blocksize 512 bytes), leading to buffer size
// being only 512 bytes. "Clib2" sets the buffer size to 8KB, resulting
// smooth movie playback. This forces the buffer to be enough also when
// using "newlib" compile on SFS.
//
if (readStream) {
readStream->setBufferSize(8192);
}
return readStream;
}
Common::WriteStream *AmigaOSFilesystemNode::createWriteStream() {
@@ -0,0 +1,55 @@
/* 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.
*
*/
#if defined(POSIX)
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "backends/fs/posix-drives/posix-drives-fs-factory.h"
#include "backends/fs/posix-drives/posix-drives-fs.h"
#include <unistd.h>
void DrivesPOSIXFilesystemFactory::addDrive(const Common::String &name) {
_config.drives.push_back(Common::normalizePath(name, '/'));
}
void DrivesPOSIXFilesystemFactory::configureBuffering(DrivePOSIXFilesystemNode::BufferingMode bufferingMode, uint32 bufferSize) {
_config.bufferingMode = bufferingMode;
_config.bufferSize = bufferSize;
}
AbstractFSNode *DrivesPOSIXFilesystemFactory::makeRootFileNode() const {
return new DrivePOSIXFilesystemNode(_config);
}
AbstractFSNode *DrivesPOSIXFilesystemFactory::makeCurrentDirectoryFileNode() const {
char buf[MAXPATHLEN];
return getcwd(buf, MAXPATHLEN) ? new DrivePOSIXFilesystemNode(buf, _config) : nullptr;
}
AbstractFSNode *DrivesPOSIXFilesystemFactory::makeFileNodePath(const Common::String &path) const {
assert(!path.empty());
return new DrivePOSIXFilesystemNode(path, _config);
}
#endif
@@ -0,0 +1,63 @@
/* 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 POSIX_DRIVES_FILESYSTEM_FACTORY_H
#define POSIX_DRIVES_FILESYSTEM_FACTORY_H
#include "backends/fs/fs-factory.h"
#include "backends/fs/posix-drives/posix-drives-fs.h"
/**
* A FilesystemFactory implementation for filesystems with a special
* top-level directory with hard-coded entries but that otherwise
* implement the POSIX APIs.
*
* For used with paths like these:
* - 'sdcard:/games/scummvm.ini'
* - 'hdd1:/usr/bin'
*/
class DrivesPOSIXFilesystemFactory : public FilesystemFactory {
public:
/**
* Add a drive to the top-level directory
*/
void addDrive(const Common::String &name);
/**
* Configure file stream buffering
*
* @param bufferingMode select the buffering implementation to use
* @param bufferSize the size of the IO buffer in bytes. A buffer size of 0 means the default size should be used
*/
void configureBuffering(DrivePOSIXFilesystemNode::BufferingMode bufferingMode, uint32 bufferSize);
protected:
// FilesystemFactory API
AbstractFSNode *makeRootFileNode() const override;
AbstractFSNode *makeCurrentDirectoryFileNode() const override;
AbstractFSNode *makeFileNodePath(const Common::String &path) const override;
private:
DrivePOSIXFilesystemNode::Config _config;
};
#endif
@@ -0,0 +1,205 @@
/* 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.
*
*/
#if defined(POSIX)
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "backends/fs/posix-drives/posix-drives-fs.h"
#include "backends/fs/posix/posix-iostream.h"
#include "common/algorithm.h"
#include "common/bufferedstream.h"
#include <dirent.h>
DrivePOSIXFilesystemNode::Config::Config() {
bufferingMode = kBufferingModeStdio;
bufferSize = 0; // Use the default stdio buffer size
}
DrivePOSIXFilesystemNode::DrivePOSIXFilesystemNode(const Common::String &path, const Config &config) :
POSIXFilesystemNode(path),
_isPseudoRoot(false),
_config(config) {
if (isDrive(path)) {
_isDirectory = true;
_isValid = true;
// FIXME: Add a setting for deciding whether drive paths need to end with a slash
if (!_path.hasSuffix("/")) {
_path += "/";
}
return;
}
}
DrivePOSIXFilesystemNode::DrivePOSIXFilesystemNode(const Config &config) :
_isPseudoRoot(true),
_config(config) {
_isDirectory = true;
_isValid = false;
}
void DrivePOSIXFilesystemNode::configureStream(StdioStream *stream) {
if (!stream) {
return;
}
if (_config.bufferingMode != kBufferingModeStdio) {
// Disable stdio buffering
stream->setBufferSize(0);
} else if (_config.bufferSize) {
stream->setBufferSize(_config.bufferSize);
}
}
Common::SeekableReadStream *DrivePOSIXFilesystemNode::createReadStream() {
PosixIoStream *readStream = PosixIoStream::makeFromPath(getPath(), false);
configureStream(readStream);
if (_config.bufferingMode == kBufferingModeScummVM) {
uint32 bufferSize = _config.bufferSize != 0 ? _config.bufferSize : 1024;
return Common::wrapBufferedSeekableReadStream(readStream, bufferSize, DisposeAfterUse::YES);
}
return readStream;
}
Common::WriteStream *DrivePOSIXFilesystemNode::createWriteStream() {
PosixIoStream *writeStream = PosixIoStream::makeFromPath(getPath(), true);
configureStream(writeStream);
if (_config.bufferingMode == kBufferingModeScummVM) {
uint32 bufferSize = _config.bufferSize != 0 ? _config.bufferSize : 1024;
return Common::wrapBufferedWriteStream(writeStream, bufferSize);
}
return writeStream;
}
DrivePOSIXFilesystemNode *DrivePOSIXFilesystemNode::getChildWithKnownType(const Common::String &n, bool isDirectoryFlag) const {
assert(_isDirectory);
// Make sure the string contains no slashes
assert(!n.contains('/'));
Common::String newPath(_path);
if (_path.lastChar() != '/')
newPath += '/';
newPath += n;
DrivePOSIXFilesystemNode *child = new DrivePOSIXFilesystemNode(_config);
child->_path = newPath;
child->_isValid = true;
child->_isPseudoRoot = false;
child->_isDirectory = isDirectoryFlag;
child->_displayName = n;
return child;
}
AbstractFSNode *DrivePOSIXFilesystemNode::getChild(const Common::String &n) const {
DrivePOSIXFilesystemNode *child = getChildWithKnownType(n, false);
child->setFlags();
return child;
}
bool DrivePOSIXFilesystemNode::getChildren(AbstractFSList &list, AbstractFSNode::ListMode mode, bool hidden) const {
assert(_isDirectory);
if (_isPseudoRoot) {
for (uint i = 0; i < _config.drives.size(); i++) {
list.push_back(makeNode(_config.drives[i]));
}
return true;
} else {
DIR *dirp = opendir(_path.c_str());
struct dirent *dp;
if (!dirp)
return false;
while ((dp = readdir(dirp)) != nullptr) {
// Skip 'invisible' files if necessary
if (dp->d_name[0] == '.' && !hidden) {
continue;
}
// Skip '.' and '..' to avoid cycles
if ((dp->d_name[0] == '.' && dp->d_name[1] == 0) || (dp->d_name[0] == '.' && dp->d_name[1] == '.')) {
continue;
}
AbstractFSNode *child = nullptr;
#if !defined(SYSTEM_NOT_SUPPORTING_D_TYPE)
if (dp->d_type == DT_DIR || dp->d_type == DT_REG) {
child = getChildWithKnownType(dp->d_name, dp->d_type == DT_DIR);
} else
#endif
{
child = getChild(dp->d_name);
}
// Honor the chosen mode
if ((mode == Common::FSNode::kListFilesOnly && child->isDirectory()) ||
(mode == Common::FSNode::kListDirectoriesOnly && !child->isDirectory())) {
delete child;
continue;
}
list.push_back(child);
}
closedir(dirp);
return true;
}
}
AbstractFSNode *DrivePOSIXFilesystemNode::getParent() const {
if (_isPseudoRoot) {
return nullptr;
}
if (isDrive(_path)) {
DrivePOSIXFilesystemNode *root = new DrivePOSIXFilesystemNode(_config);
return root;
}
return POSIXFilesystemNode::getParent();
}
bool DrivePOSIXFilesystemNode::isDrive(const Common::String &path) const {
Common::String normalizedPath = Common::normalizePath(path, '/');
DrivesArray::const_iterator drive = Common::find(_config.drives.begin(), _config.drives.end(), normalizedPath);
return drive != _config.drives.end();
}
#endif //#if defined(POSIX)
@@ -0,0 +1,79 @@
/* 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 POSIX_DRIVES_FILESYSTEM_H
#define POSIX_DRIVES_FILESYSTEM_H
#include "backends/fs/posix/posix-fs.h"
class StdioStream;
/**
* POSIX file system node where the top-level directory is a hardcoded
* list of drives.
*/
class DrivePOSIXFilesystemNode : public POSIXFilesystemNode {
protected:
AbstractFSNode *makeNode(const Common::String &path) const override {
return new DrivePOSIXFilesystemNode(path, _config);
}
public:
typedef Common::Array<Common::String> DrivesArray;
enum BufferingMode {
/** IO buffering is fully disabled */
kBufferingModeDisabled,
/** IO buffering is enabled and uses the libc implemenation */
kBufferingModeStdio,
/** IO buffering is enabled and uses ScummVM's buffering stream wraappers */
kBufferingModeScummVM
};
struct Config {
DrivesArray drives;
BufferingMode bufferingMode;
uint32 bufferSize;
Config();
};
DrivePOSIXFilesystemNode(const Common::String &path, const Config &config);
DrivePOSIXFilesystemNode(const Config &config);
// AbstractFSNode API
Common::SeekableReadStream *createReadStream() override;
Common::WriteStream *createWriteStream() override;
AbstractFSNode *getChild(const Common::String &n) const override;
bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const override;
AbstractFSNode *getParent() const override;
private:
bool _isPseudoRoot;
const Config &_config;
DrivePOSIXFilesystemNode *getChildWithKnownType(const Common::String &n, bool isDirectoryFlag) const;
bool isDrive(const Common::String &path) const;
void configureStream(StdioStream *stream);
};
#endif
@@ -0,0 +1,55 @@
/* 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.
*
*/
#if defined(POSIX)
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "backends/fs/posix-drives/posix-drives-fs-factory.h"
#include "backends/fs/posix-drives/posix-drives-fs.h"
#include <unistd.h>
void DrivesPOSIXFilesystemFactory::addDrive(const Common::String &name) {
_config.drives.push_back(Common::normalizePath(name, '/'));
}
void DrivesPOSIXFilesystemFactory::configureBuffering(DrivePOSIXFilesystemNode::BufferingMode bufferingMode, uint32 bufferSize) {
_config.bufferingMode = bufferingMode;
_config.bufferSize = bufferSize;
}
AbstractFSNode *DrivesPOSIXFilesystemFactory::makeRootFileNode() const {
return new DrivePOSIXFilesystemNode(_config);
}
AbstractFSNode *DrivesPOSIXFilesystemFactory::makeCurrentDirectoryFileNode() const {
char buf[MAXPATHLEN];
return getcwd(buf, MAXPATHLEN) ? new DrivePOSIXFilesystemNode(buf, _config) : nullptr;
}
AbstractFSNode *DrivesPOSIXFilesystemFactory::makeFileNodePath(const Common::String &path) const {
assert(!path.empty());
return new DrivePOSIXFilesystemNode(path, _config);
}
#endif
@@ -0,0 +1,63 @@
/* 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 POSIX_DRIVES_FILESYSTEM_FACTORY_H
#define POSIX_DRIVES_FILESYSTEM_FACTORY_H
#include "backends/fs/fs-factory.h"
#include "backends/fs/posix-drives/posix-drives-fs.h"
/**
* A FilesystemFactory implementation for filesystems with a special
* top-level directory with hard-coded entries but that otherwise
* implement the POSIX APIs.
*
* For used with paths like these:
* - 'sdcard:/games/scummvm.ini'
* - 'hdd1:/usr/bin'
*/
class DrivesPOSIXFilesystemFactory : public FilesystemFactory {
public:
/**
* Add a drive to the top-level directory
*/
void addDrive(const Common::String &name);
/**
* Configure file stream buffering
*
* @param bufferingMode select the buffering implementation to use
* @param bufferSize the size of the IO buffer in bytes. A buffer size of 0 means the default size should be used
*/
void configureBuffering(DrivePOSIXFilesystemNode::BufferingMode bufferingMode, uint32 bufferSize);
protected:
// FilesystemFactory API
AbstractFSNode *makeRootFileNode() const override;
AbstractFSNode *makeCurrentDirectoryFileNode() const override;
AbstractFSNode *makeFileNodePath(const Common::String &path) const override;
private:
DrivePOSIXFilesystemNode::Config _config;
};
#endif
@@ -0,0 +1,205 @@
/* 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.
*
*/
#if defined(POSIX)
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "backends/fs/posix-drives/posix-drives-fs.h"
#include "backends/fs/posix/posix-iostream.h"
#include "common/algorithm.h"
#include "common/bufferedstream.h"
#include <dirent.h>
DrivePOSIXFilesystemNode::Config::Config() {
bufferingMode = kBufferingModeStdio;
bufferSize = 0; // Use the default stdio buffer size
}
DrivePOSIXFilesystemNode::DrivePOSIXFilesystemNode(const Common::String &path, const Config &config) :
POSIXFilesystemNode(path),
_isPseudoRoot(false),
_config(config) {
if (isDrive(path)) {
_isDirectory = true;
_isValid = true;
// FIXME: Add a setting for deciding whether drive paths need to end with a slash
if (!_path.hasSuffix("/")) {
_path += "/";
}
return;
}
}
DrivePOSIXFilesystemNode::DrivePOSIXFilesystemNode(const Config &config) :
_isPseudoRoot(true),
_config(config) {
_isDirectory = true;
_isValid = false;
}
void DrivePOSIXFilesystemNode::configureStream(StdioStream *stream) {
if (!stream) {
return;
}
if (_config.bufferingMode != kBufferingModeStdio) {
// Disable stdio buffering
stream->setBufferSize(0);
} else if (_config.bufferSize) {
stream->setBufferSize(_config.bufferSize);
}
}
Common::SeekableReadStream *DrivePOSIXFilesystemNode::createReadStream() {
PosixIoStream *readStream = PosixIoStream::makeFromPath(getPath(), false);
configureStream(readStream);
if (_config.bufferingMode == kBufferingModeScummVM) {
uint32 bufferSize = _config.bufferSize != 0 ? _config.bufferSize : 1024;
return Common::wrapBufferedSeekableReadStream(readStream, bufferSize, DisposeAfterUse::YES);
}
return readStream;
}
Common::WriteStream *DrivePOSIXFilesystemNode::createWriteStream() {
PosixIoStream *writeStream = PosixIoStream::makeFromPath(getPath(), true);
configureStream(writeStream);
if (_config.bufferingMode == kBufferingModeScummVM) {
uint32 bufferSize = _config.bufferSize != 0 ? _config.bufferSize : 1024;
return Common::wrapBufferedWriteStream(writeStream, bufferSize);
}
return writeStream;
}
DrivePOSIXFilesystemNode *DrivePOSIXFilesystemNode::getChildWithKnownType(const Common::String &n, bool isDirectoryFlag) const {
assert(_isDirectory);
// Make sure the string contains no slashes
assert(!n.contains('/'));
Common::String newPath(_path);
if (_path.lastChar() != '/')
newPath += '/';
newPath += n;
DrivePOSIXFilesystemNode *child = new DrivePOSIXFilesystemNode(_config);
child->_path = newPath;
child->_isValid = true;
child->_isPseudoRoot = false;
child->_isDirectory = isDirectoryFlag;
child->_displayName = n;
return child;
}
AbstractFSNode *DrivePOSIXFilesystemNode::getChild(const Common::String &n) const {
DrivePOSIXFilesystemNode *child = getChildWithKnownType(n, false);
child->setFlags();
return child;
}
bool DrivePOSIXFilesystemNode::getChildren(AbstractFSList &list, AbstractFSNode::ListMode mode, bool hidden) const {
assert(_isDirectory);
if (_isPseudoRoot) {
for (uint i = 0; i < _config.drives.size(); i++) {
list.push_back(makeNode(_config.drives[i]));
}
return true;
} else {
DIR *dirp = opendir(_path.c_str());
struct dirent *dp;
if (!dirp)
return false;
while ((dp = readdir(dirp)) != nullptr) {
// Skip 'invisible' files if necessary
if (dp->d_name[0] == '.' && !hidden) {
continue;
}
// Skip '.' and '..' to avoid cycles
if ((dp->d_name[0] == '.' && dp->d_name[1] == 0) || (dp->d_name[0] == '.' && dp->d_name[1] == '.')) {
continue;
}
AbstractFSNode *child = nullptr;
#if !defined(SYSTEM_NOT_SUPPORTING_D_TYPE)
if (dp->d_type == DT_DIR || dp->d_type == DT_REG) {
child = getChildWithKnownType(dp->d_name, dp->d_type == DT_DIR);
} else
#endif
{
child = getChild(dp->d_name);
}
// Honor the chosen mode
if ((mode == Common::FSNode::kListFilesOnly && child->isDirectory()) ||
(mode == Common::FSNode::kListDirectoriesOnly && !child->isDirectory())) {
delete child;
continue;
}
list.push_back(child);
}
closedir(dirp);
return true;
}
}
AbstractFSNode *DrivePOSIXFilesystemNode::getParent() const {
if (_isPseudoRoot) {
return nullptr;
}
if (isDrive(_path)) {
DrivePOSIXFilesystemNode *root = new DrivePOSIXFilesystemNode(_config);
return root;
}
return POSIXFilesystemNode::getParent();
}
bool DrivePOSIXFilesystemNode::isDrive(const Common::String &path) const {
Common::String normalizedPath = Common::normalizePath(path, '/');
DrivesArray::const_iterator drive = Common::find(_config.drives.begin(), _config.drives.end(), normalizedPath);
return drive != _config.drives.end();
}
#endif //#if defined(POSIX)
@@ -0,0 +1,79 @@
/* 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 POSIX_DRIVES_FILESYSTEM_H
#define POSIX_DRIVES_FILESYSTEM_H
#include "backends/fs/posix/posix-fs.h"
class StdioStream;
/**
* POSIX file system node where the top-level directory is a hardcoded
* list of drives.
*/
class DrivePOSIXFilesystemNode : public POSIXFilesystemNode {
protected:
AbstractFSNode *makeNode(const Common::String &path) const override {
return new DrivePOSIXFilesystemNode(path, _config);
}
public:
typedef Common::Array<Common::String> DrivesArray;
enum BufferingMode {
/** IO buffering is fully disabled */
kBufferingModeDisabled,
/** IO buffering is enabled and uses the libc implemenation */
kBufferingModeStdio,
/** IO buffering is enabled and uses ScummVM's buffering stream wraappers */
kBufferingModeScummVM
};
struct Config {
DrivesArray drives;
BufferingMode bufferingMode;
uint32 bufferSize;
Config();
};
DrivePOSIXFilesystemNode(const Common::String &path, const Config &config);
DrivePOSIXFilesystemNode(const Config &config);
// AbstractFSNode API
Common::SeekableReadStream *createReadStream() override;
Common::WriteStream *createWriteStream() override;
AbstractFSNode *getChild(const Common::String &n) const override;
bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const override;
AbstractFSNode *getParent() const override;
private:
bool _isPseudoRoot;
const Config &_config;
DrivePOSIXFilesystemNode *getChildWithKnownType(const Common::String &n, bool isDirectoryFlag) const;
bool isDrive(const Common::String &path) const;
void configureStream(StdioStream *stream);
};
#endif
+14
View File
@@ -41,8 +41,22 @@ AbstractFSNode *POSIXFilesystemFactory::makeRootFileNode() const {
}
AbstractFSNode *POSIXFilesystemFactory::makeCurrentDirectoryFileNode() const {
#if defined(__ANDROID__)
// For Android it does not make sense to have "." in Search Manager as a current directory file node, so we skip it here
// Otherwise this can potentially lead to a crash since, in Android getcwd() returns the root path "/"
// and when SearchMan is used (eg. SearchSet::createReadStreamForMember) and it tries to search root path (and calls POSIXFilesystemNode::getChildren())
// then a JNI call is made (JNI::getAllStorageLocations()) which leads to a crash if done from the wrong context -- and is also useless overhead as a call in that case.
// This fixes the error case: Loading "Beneath A Steel Sky" with Adlib or FluidSynth audio once, exiting to Launcher via in-game ScummVM menu and re-launching the game.
// Don't return NULL here, since it causes crash with other engines (eg. Blade Runner)
// Returning '.' here will cause POSIXFilesystemNode::getChildren() to ignore it
//
// We also skip adding the '.' directory to SearchManager (in archive.cpp, SearchManager::clear())
char buf[MAXPATHLEN] = {'.', '\0'};
return new POSIXFilesystemNode(buf);
#else
char buf[MAXPATHLEN];
return getcwd(buf, MAXPATHLEN) ? new POSIXFilesystemNode(buf) : NULL;
#endif
}
AbstractFSNode *POSIXFilesystemFactory::makeFileNodePath(const Common::String &path) const {
+20
View File
@@ -57,6 +57,9 @@
#include <os2.h>
#endif
#if defined(__ANDROID__) && !defined(ANDROIDSDL)
#include "backends/platform/android/jni-android.h"
#endif
bool POSIXFilesystemNode::exists() const {
return access(_path.c_str(), F_OK) == 0;
@@ -190,6 +193,23 @@ bool POSIXFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, boo
}
#endif
#if defined(__ANDROID__) && !defined(ANDROIDSDL)
if (_path == "/") {
Common::Array<Common::String> list = JNI::getAllStorageLocations();
for (Common::Array<Common::String>::const_iterator it = list.begin(), end = list.end(); it != end; ++it) {
POSIXFilesystemNode *entry = new POSIXFilesystemNode();
entry->_isDirectory = true;
entry->_isValid = true;
entry->_displayName = *it;
++it;
entry->_path = *it;
myList.push_back(entry);
}
return true;
}
#endif
DIR *dirp = opendir(_path.c_str());
struct dirent *dp;
+1 -1
View File
@@ -70,7 +70,7 @@ public:
virtual Common::WriteStream *createWriteStream();
virtual bool createDirectory();
private:
protected:
/**
* Tests and sets the _isValid and _isDirectory flags, using the stat() function.
*/
+8 -22
View File
@@ -26,9 +26,6 @@
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "backends/fs/stdiostream.h"
#ifdef _WIN32_WCE
#include "backends/platform/wince/missing/fopen.h"
#endif
StdioStream::StdioStream(void *handle) : _handle(handle) {
assert(handle);
@@ -71,6 +68,14 @@ uint32 StdioStream::read(void *ptr, uint32 len) {
return fread((byte *)ptr, 1, len, (FILE *)_handle);
}
bool StdioStream::setBufferSize(uint32 bufferSize) {
if (bufferSize != 0) {
return setvbuf((FILE *)_handle, nullptr, _IOFBF, bufferSize) == 0;
} else {
return setvbuf((FILE *)_handle, nullptr, _IONBF, 0) == 0;
}
}
uint32 StdioStream::write(const void *ptr, uint32 len) {
return fwrite(ptr, 1, len, (FILE *)_handle);
}
@@ -82,25 +87,6 @@ bool StdioStream::flush() {
StdioStream *StdioStream::makeFromPath(const Common::String &path, bool writeMode) {
FILE *handle = fopen(path.c_str(), writeMode ? "wb" : "rb");
#ifdef __amigaos4__
//
// Work around for possibility that someone uses AmigaOS "newlib" build
// with SmartFileSystem (blocksize 512 bytes), leading to buffer size
// being only 512 bytes. "Clib2" sets the buffer size to 8KB, resulting
// smooth movie playback. This forces the buffer to be enough also when
// using "newlib" compile on SFS.
//
if (handle && !writeMode) {
setvbuf(handle, NULL, _IOFBF, 8192);
}
#endif
#if defined(__WII__)
// disable newlib's buffering, the device libraries handle caching
if (handle)
setvbuf(handle, NULL, _IONBF, 0);
#endif
if (handle)
return new StdioStream(handle);
return 0;
+21 -10
View File
@@ -41,19 +41,30 @@ public:
static StdioStream *makeFromPath(const Common::String &path, bool writeMode);
StdioStream(void *handle);
virtual ~StdioStream();
~StdioStream() override;
virtual bool err() const override;
virtual void clearErr() override;
virtual bool eos() const override;
bool err() const override;
void clearErr() override;
bool eos() const override;
virtual uint32 write(const void *dataPtr, uint32 dataSize) override;
virtual bool flush() override;
uint32 write(const void *dataPtr, uint32 dataSize) override;
bool flush() override;
virtual int32 pos() const override;
virtual int32 size() const override;
virtual bool seek(int32 offs, int whence = SEEK_SET) override;
virtual uint32 read(void *dataPtr, uint32 dataSize) override;
int32 pos() const override;
int32 size() const override;
bool seek(int32 offs, int whence = SEEK_SET) override;
uint32 read(void *dataPtr, uint32 dataSize) override;
/**
* Configure buffered IO
*
* Must be called immediately after opening the file.
* A buffer size of 0 disables buffering.
*
* @param bufferSize the size of the Stdio read / write buffer
* @return success or failure
*/
bool setBufferSize(uint32 bufferSize);
};
#endif
+2 -13
View File
@@ -107,19 +107,11 @@ const TCHAR* WindowsFilesystemNode::toUnicode(const char *str) {
}
WindowsFilesystemNode::WindowsFilesystemNode() {
_isDirectory = true;
#ifndef _WIN32_WCE
// Create a virtual root directory for standard Windows system
_isDirectory = true;
_isValid = false;
_path = "";
_isPseudoRoot = true;
#else
_displayName = "Root";
// No need to create a pseudo root directory on Windows CE
_isValid = true;
_path = "\\";
_isPseudoRoot = false;
#endif
}
WindowsFilesystemNode::WindowsFilesystemNode(const Common::String &p, const bool currentDir) {
@@ -174,7 +166,6 @@ bool WindowsFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, b
assert(_isDirectory);
if (_isPseudoRoot) {
#ifndef _WIN32_WCE
// Drives enumeration
TCHAR drive_buffer[100];
GetLogicalDriveStrings(sizeof(drive_buffer) / sizeof(TCHAR), drive_buffer);
@@ -193,9 +184,7 @@ bool WindowsFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, b
entry._path = toAscii(current_drive);
myList.push_back(new WindowsFilesystemNode(entry));
}
#endif
}
else {
} else {
// Files enumeration
WIN32_FIND_DATA desc;
HANDLE handle;
-4
View File
@@ -24,9 +24,6 @@
#define WINDOWS_FILESYSTEM_H
#include <windows.h>
#ifdef _WIN32_WCE
#undef GetCurrentDirectory
#endif
#include "backends/fs/abstract-fs.h"
@@ -53,7 +50,6 @@ public:
* Creates a WindowsFilesystemNode with the root node as path.
*
* In regular windows systems, a virtual root path is used "".
* In windows CE, the "\" root is used instead.
*/
WindowsFilesystemNode();
+10 -8
View File
@@ -42,15 +42,19 @@ public:
virtual void setFeatureState(OSystem::Feature f, bool enable) = 0;
virtual bool getFeatureState(OSystem::Feature f) const = 0;
virtual const OSystem::GraphicsMode *getSupportedGraphicsModes() const = 0;
virtual int getDefaultGraphicsMode() const = 0;
virtual bool setGraphicsMode(int mode) = 0;
virtual void resetGraphicsScale() = 0;
virtual int getGraphicsMode() const = 0;
virtual const OSystem::GraphicsMode *getSupportedGraphicsModes() const {
static const OSystem::GraphicsMode noGraphicsModes[] = {{"NONE", "Normal", 0}, {nullptr, nullptr, 0 }};
return noGraphicsModes;
};
virtual int getDefaultGraphicsMode() const { return 0; }
virtual bool setGraphicsMode(int mode) { return (mode == 0); }
virtual void resetGraphicsScale() {}
virtual int getGraphicsMode() const { return 0; }
virtual const OSystem::GraphicsMode *getSupportedShaders() const {
static const OSystem::GraphicsMode no_shader[2] = {{"NONE", "Normal (no shader)", 0}, {0, 0, 0}};
return no_shader;
};
virtual int getDefaultShader() const { return 0; }
virtual bool setShader(int id) { return false; }
virtual int getShader() const { return 0; }
virtual const OSystem::GraphicsMode *getSupportedStretchModes() const {
@@ -78,8 +82,6 @@ public:
virtual Graphics::PixelBuffer getScreenPixelBuffer() = 0;
// ResidualVM specific method
virtual void suggestSideTextures(Graphics::Surface *left, Graphics::Surface *right) = 0;
// ResidualVM specific method
virtual void saveScreenshot() {}
virtual int16 getHeight() const = 0;
virtual int16 getWidth() const = 0;
@@ -90,7 +92,7 @@ public:
virtual void unlockScreen() = 0;
virtual void fillScreen(uint32 col) = 0;
virtual void updateScreen() = 0;
virtual void setShakePos(int shakeOffset) = 0;
virtual void setShakePos(int shakeXOffset, int shakeYOffset) = 0;
virtual void setFocusRectangle(const Common::Rect& rect) = 0;
virtual void clearFocusRectangle() = 0;
@@ -26,7 +26,7 @@
#include "backends/graphics/openglsdl/openglsdl-graphics.h"
#include "backends/events/sdl/sdl-events.h"
#include "backends/events/sdl/resvm-sdl-events.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "engines/engine.h"
@@ -237,7 +237,7 @@ void OpenGLSdlGraphicsManager::createOrUpdateScreen() {
_screenChangeCount++;
_eventSource->resetKeyboardEmulation(obtainedWidth - 1, obtainedHeight - 1);
dynamic_cast<ResVmSdlEventSource *>(_eventSource)->resetKeyboardEmulation(obtainedWidth - 1, obtainedHeight - 1);
#if !defined(AMIGAOS)
if (renderToFrameBuffer) {
@@ -269,7 +269,7 @@ Math::Rect2d OpenGLSdlGraphicsManager::computeGameRect(bool renderToFrameBuffer,
}
}
void OpenGLSdlGraphicsManager::notifyResize(const uint width, const uint height) {
void OpenGLSdlGraphicsManager::notifyResize(const int width, const int height) {
#if SDL_VERSION_ATLEAST(2, 0, 0)
// Get the updated size directly from SDL, in case there are multiple
// resize events in the message queue.
@@ -295,7 +295,7 @@ void OpenGLSdlGraphicsManager::notifyResize(const uint width, const uint height)
_screenChangeCount++;
_eventSource->resetKeyboardEmulation(newWidth - 1, newHeight- 1);
dynamic_cast<ResVmSdlEventSource *>(_eventSource)->resetKeyboardEmulation(newWidth - 1, newHeight- 1);
#endif
}
@@ -699,7 +699,7 @@ void OpenGLSdlGraphicsManager::deinitializeRenderer() {
}
#endif // SDL_VERSION_ATLEAST(2, 0, 0)
bool OpenGLSdlGraphicsManager::saveScreenshot(const Common::String &file) const {
bool OpenGLSdlGraphicsManager::saveScreenshot(const Common::String &filename) const {
// Largely based on the implementation from ScummVM
uint width = _overlayScreen->getWidth();
uint height = _overlayScreen->getHeight();
@@ -708,7 +708,7 @@ bool OpenGLSdlGraphicsManager::saveScreenshot(const Common::String &file) const
uint lineSize = width * 3 + linePaddingSize;
Common::DumpFile out;
if (!out.open(file)) {
if (!out.open(filename)) {
return false;
}
@@ -77,7 +77,7 @@ public:
// SdlGraphicsManager API
virtual void transformMouseCoordinates(Common::Point &point) override;
void notifyResize(const uint width, const uint height) override;
void notifyResize(const int width, const int height) override;
protected:
#if SDL_VERSION_ATLEAST(2, 0, 0)
@@ -114,7 +114,9 @@ protected:
uint screenWidth, uint screenHeight);
// ResVmSdlGraphicsManager API
bool saveScreenshot(const Common::String &file) const override;
virtual bool saveScreenshot(const Common::String &filename) const override;
virtual int getGraphicsModeScale(int mode) const override { return 1; }
int _antialiasing;
bool _vsync;
+39 -28
View File
@@ -23,7 +23,7 @@
#include "backends/graphics/sdl/resvm-sdl-graphics.h"
#include "backends/platform/sdl/sdl-sys.h"
#include "backends/events/sdl/sdl-events.h"
#include "backends/events/sdl/resvm-sdl-events.h"
#include "backends/platform/sdl/sdl.h"
#include "common/config-manager.h"
@@ -197,7 +197,7 @@ void ResVmSdlGraphicsManager::setCursorPalette(const byte *colors, uint start, u
// ResidualVM: not use it
}
void ResVmSdlGraphicsManager::setShakePos(int shake_pos) {
void ResVmSdlGraphicsManager::setShakePos(int shakeXOffset, int shakeYOffset) {
// ResidualVM: not use it
}
@@ -286,39 +286,50 @@ bool ResVmSdlGraphicsManager::notifyMousePosition(Common::Point &mouse) {
}
void ResVmSdlGraphicsManager::saveScreenshot() {
OSystem_SDL *g_systemSdl = dynamic_cast<OSystem_SDL*>(g_system);
Common::String filename;
if (g_systemSdl) {
Common::String filename;
Common::String path = g_systemSdl->getScreenshotsPath();
Common::String screenshotsPath;
OSystem_SDL *sdl_g_system = dynamic_cast<OSystem_SDL*>(g_system);
if (sdl_g_system)
screenshotsPath = sdl_g_system->getScreenshotsPath();
// Use the name of the running target as a base for screenshot file names
Common::String currentTarget = ConfMan.getActiveDomainName();
// Find unused filename
int n = 0;
while (true) {
#ifdef USE_PNG
filename = Common::String::format("residualvm%05d.png", n);
const char *extension = "png";
#else
filename = Common::String::format("residualvm%05d.bmp", n);
const char *extension = "bmp";
#endif
SDL_RWops *file = SDL_RWFromFile((path + filename).c_str(), "r");
if (!file) {
break;
}
SDL_RWclose(file);
++n;
}
for (int n = 0;; n++) {
filename = Common::String::format("resiudalvm%s%s-%05d.%s", currentTarget.empty() ? "" : "-",
currentTarget.c_str(), n, extension);
if (saveScreenshot(path + filename)) {
if (path.empty())
debug("Saved screenshot '%s' in current directory", filename.c_str());
else
debug("Saved screenshot '%s' in directory '%s'", filename.c_str(), path.c_str());
} else {
if (path.empty())
warning("Could not save screenshot in current directory");
else
warning("Could not save screenshot in directory '%s'", path.c_str());
Common::FSNode file = Common::FSNode(screenshotsPath + filename);
if (!file.exists()) {
break;
}
}
if (saveScreenshot(screenshotsPath + filename)) {
if (screenshotsPath.empty())
debug("Saved screenshot '%s' in current directory", filename.c_str());
else
debug("Saved screenshot '%s' in directory '%s'", filename.c_str(), screenshotsPath.c_str());
} else {
if (screenshotsPath.empty())
warning("Could not save screenshot in current directory");
else
warning("Could not save screenshot in directory '%s'", screenshotsPath.c_str());
}
}
bool ResVmSdlGraphicsManager::gameNeedsAspectRatioCorrection() const {
// ResidualVM: not use it
return false;
}
int ResVmSdlGraphicsManager::getGraphicsModeScale(int mode) const {
return -1;
}
+6 -2
View File
@@ -37,7 +37,7 @@ class SdlEventSource;
*
* Used to share reusable methods between SDL graphics managers
*/
class ResVmSdlGraphicsManager : public SdlGraphicsManager, public Common::EventObserver {
class ResVmSdlGraphicsManager : public SdlGraphicsManager {
public:
/**
* Capabilities of the current device
@@ -101,7 +101,7 @@ public:
Graphics::Surface *lockScreen() override;
void unlockScreen() override;
void fillScreen(uint32 col) override;
void setShakePos(int shakeOffset) override;
void setShakePos(int shakeXOffset, int shakeYOffset) override;
void saveScreenshot() override;
// GraphicsManager API - Focus Rectangle
@@ -150,6 +150,10 @@ protected:
/** Obtain the user configured fullscreen resolution, or default to the desktop resolution */
Common::Rect getPreferredFullscreenResolution();
virtual int getGraphicsModeScale(int mode) const = 0;
virtual bool gameNeedsAspectRatioCorrection() const override;
/** Save a screenshot to the specified file */
virtual bool saveScreenshot(const Common::String &file) const = 0;
};
+33 -2
View File
@@ -21,10 +21,16 @@
*/
#include "backends/graphics/sdl/sdl-graphics.h"
#include "backends/platform/sdl/sdl-sys.h"
#include "backends/events/sdl/sdl-events.h"
#include "backends/events/sdl/resvm-sdl-events.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymap.h"
#include "common/textconsole.h"
#include "backends/platform/sdl/sdl.h"
#include "common/config-manager.h"
#include "common/fs.h"
#include "common/textconsole.h"
#include "common/translation.h"
SdlGraphicsManager::SdlGraphicsManager(SdlEventSource *source, SdlWindow *window)
: _eventSource(source), _window(window) {
@@ -72,4 +78,29 @@ bool SdlGraphicsManager::setState(const State &state) {
return true;
}
}
Common::Keymap *SdlGraphicsManager::getKeymap() {
using namespace Common;
Keymap *keymap = new Keymap(Keymap::kKeymapTypeGlobal, "sdl-graphics", _("Graphics"));
Action *act;
if (g_system->hasFeature(OSystem::kFeatureFullscreenMode)) {
act = new Action("FULS", _("Toggle fullscreen"));
act->addDefaultInputMapping("A+RETURN");
act->addDefaultInputMapping("A+KP_ENTER");
act->setCustomBackendActionEvent(kActionToggleFullscreen);
keymap->addAction(act);
}
act = new Action("CAPT", _("Toggle mouse capture"));
act->addDefaultInputMapping("C+m");
act->setCustomBackendActionEvent(kActionToggleMouseCapture);
keymap->addAction(act);
act = new Action("SCRS", _("Save screenshot"));
act->addDefaultInputMapping("A+s");
act->setCustomBackendActionEvent(kActionSaveScreenshot);
keymap->addAction(act);
return keymap;
}
+21 -2
View File
@@ -26,6 +26,7 @@
#include "backends/graphics/graphics.h"
#include "backends/platform/sdl/sdl-window.h"
#include "common/events.h"
#include "common/rect.h"
class SdlEventSource;
@@ -35,7 +36,7 @@ class SdlEventSource;
*
* It features a few extra a few extra features required by SdlEventSource.
*/
class SdlGraphicsManager : virtual public GraphicsManager {
class SdlGraphicsManager : virtual public GraphicsManager, public Common::EventObserver {
public:
SdlGraphicsManager(SdlEventSource *source, SdlWindow *window);
virtual ~SdlGraphicsManager();
@@ -74,7 +75,7 @@ public:
* @param width Requested window width.
* @param height Requested window height.
*/
virtual void notifyResize(const uint width, const uint height) {}
virtual void notifyResize(const int width, const int height) {}
/**
* Transforms real screen coordinates into the current active screen
@@ -99,6 +100,9 @@ public:
*/
virtual bool notifyMousePosition(Common::Point &mouse) = 0;
virtual bool saveScreenshot(const Common::String &filename) const { return false; }
void saveScreenshot();
/**
* A (subset) of the graphic manager's state. This is used when switching
* between different SDL graphic managers at runtime.
@@ -129,6 +133,21 @@ public:
*/
SdlWindow *getWindow() const { return _window; }
Common::Keymap *getKeymap();
protected:
enum CustomEventAction {
kActionToggleFullscreen = 100,
kActionToggleMouseCapture,
kActionSaveScreenshot,
kActionToggleAspectRatioCorrection
};
#if SDL_VERSION_ATLEAST(2, 0, 0)
public:
void unlockWindowSize() {}
#endif
protected:
SdlEventSource *_eventSource;
SdlWindow *_window;
@@ -25,7 +25,7 @@
#if defined(SDL_BACKEND)
#include "backends/graphics/surfacesdl/surfacesdl-graphics.h"
#include "backends/events/sdl/sdl-events.h"
#include "backends/events/sdl/resvm-sdl-events.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "engines/engine.h"
@@ -35,6 +35,11 @@
#include "image/png.h"
#endif
// SDL surface flags which got removed in SDL2.
#if SDL_VERSION_ATLEAST(2, 0, 0)
#define SDL_FULLSCREEN 0x40000000
#endif
SurfaceSdlGraphicsManager::SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window, const Capabilities &capabilities)
:
ResVmSdlGraphicsManager(sdlEventSource, window, capabilities),
@@ -163,7 +168,7 @@ void SurfaceSdlGraphicsManager::createOrUpdateScreen() {
_screenChangeCount++;
_eventSource->resetKeyboardEmulation(_gameRect.getWidth() - 1, _gameRect.getHeight() - 1);
dynamic_cast<ResVmSdlEventSource *>(_eventSource)->resetKeyboardEmulation(_gameRect.getWidth() - 1, _gameRect.getHeight() - 1);
}
Graphics::PixelBuffer SurfaceSdlGraphicsManager::getScreenPixelBuffer() {
@@ -271,7 +276,7 @@ void SurfaceSdlGraphicsManager::showOverlay() {
clearOverlay();
_eventSource->resetKeyboardEmulation(getOverlayWidth() - 1, getOverlayHeight() - 1);
dynamic_cast<ResVmSdlEventSource *>(_eventSource)->resetKeyboardEmulation(getOverlayWidth() - 1, getOverlayHeight() - 1);
}
void SurfaceSdlGraphicsManager::hideOverlay() {
@@ -282,7 +287,7 @@ void SurfaceSdlGraphicsManager::hideOverlay() {
clearOverlay();
_eventSource->resetKeyboardEmulation(_gameRect.getWidth() - 1, _gameRect.getHeight() - 1);
dynamic_cast<ResVmSdlEventSource *>(_eventSource)->resetKeyboardEmulation(_gameRect.getWidth() - 1, _gameRect.getHeight() - 1);
}
void SurfaceSdlGraphicsManager::grabOverlay(void *buf, int pitch) const {
@@ -472,7 +477,7 @@ SDL_Surface *SurfaceSdlGraphicsManager::SDL_SetVideoMode(int width, int height,
}
#endif // SDL_VERSION_ATLEAST(2, 0, 0)
bool SurfaceSdlGraphicsManager::saveScreenshot(const Common::String &file) const {
bool SurfaceSdlGraphicsManager::saveScreenshot(const Common::String &filename) const {
// Based on the implementation from ScummVM
bool success;
SDL_Surface *screen = nullptr;
@@ -499,7 +504,7 @@ bool SurfaceSdlGraphicsManager::saveScreenshot(const Common::String &file) const
#ifdef USE_PNG
Common::DumpFile out;
if (!out.open(file)) {
if (!out.open(filename)) {
success = false;
} else {
if (SDL_LockSurface(screen) < 0) {
@@ -519,7 +524,7 @@ bool SurfaceSdlGraphicsManager::saveScreenshot(const Common::String &file) const
SDL_UnlockSurface(screen);
}
#else
success = SDL_SaveBMP(screen, file.c_str()) == 0;
success = SDL_SaveBMP(screen, filename.c_str()) == 0;
#endif
if (screen && screen != _screen) {
@@ -529,4 +534,8 @@ bool SurfaceSdlGraphicsManager::saveScreenshot(const Common::String &file) const
return success;
}
int SurfaceSdlGraphicsManager::getGraphicsModeScale(int mode) const {
return 1;
}
#endif
@@ -93,7 +93,9 @@ protected:
void closeOverlay();
// ResVmSdlGraphicsManager API
virtual bool saveScreenshot(const Common::String &file) const override;
virtual bool saveScreenshot(const Common::String &filename) const override;
virtual int getGraphicsModeScale(int mode) const override;
};
#endif
+419
View File
@@ -0,0 +1,419 @@
/* 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_GRAPHICS_WINDOWED_H
#define BACKENDS_GRAPHICS_WINDOWED_H
#include "backends/graphics/graphics.h"
#include "common/frac.h"
#include "common/rect.h"
#include "common/config-manager.h"
#include "common/textconsole.h"
//#include "graphics/scaler/aspect.h" // ResidualVM: not use
enum {
STRETCH_CENTER = 0,
STRETCH_INTEGRAL = 1,
STRETCH_FIT = 2,
STRETCH_STRETCH = 3,
STRETCH_FIT_FORCE_ASPECT = 4
};
class WindowedGraphicsManager : virtual public GraphicsManager {
public:
WindowedGraphicsManager() :
_windowWidth(0),
_windowHeight(0),
_overlayVisible(false),
_gameScreenShakeXOffset(0),
_gameScreenShakeYOffset(0),
_forceRedraw(false),
_cursorVisible(false),
_cursorX(0),
_cursorY(0),
_xdpi(90),
_ydpi(90),
_cursorNeedsRedraw(false),
_cursorLastInActiveArea(true) {}
virtual void showOverlay() override {
if (_overlayVisible)
return;
_activeArea.drawRect = _overlayDrawRect;
_activeArea.width = getOverlayWidth();
_activeArea.height = getOverlayHeight();
_overlayVisible = true;
_forceRedraw = true;
}
virtual void hideOverlay() override {
if (!_overlayVisible)
return;
_activeArea.drawRect = _gameDrawRect;
_activeArea.width = getWidth();
_activeArea.height = getHeight();
_overlayVisible = false;
_forceRedraw = true;
}
virtual void setShakePos(int shakeXOffset, int shakeYOffset) override {
if (_gameScreenShakeXOffset != shakeXOffset || _gameScreenShakeYOffset != shakeYOffset) {
_gameScreenShakeXOffset = shakeXOffset;
_gameScreenShakeYOffset = shakeYOffset;
recalculateDisplayAreas();
_cursorNeedsRedraw = true;
}
}
int getWindowWidth() const { return _windowWidth; }
int getWindowHeight() const { return _windowHeight; }
protected:
/**
* @returns whether or not the game screen must have aspect ratio correction
* applied for correct rendering.
*/
virtual bool gameNeedsAspectRatioCorrection() const = 0;
/**
* Backend-specific implementation for updating internal surfaces that need
* to reflect the new window size.
*/
virtual void handleResizeImpl(const int width, const int height, const int xdpi, const int ydpi) = 0;
/**
* Converts the given point from the active virtual screen's coordinate
* space to the window's coordinate space (i.e. game-to-window or
* overlay-to-window).
*/
Common::Point convertVirtualToWindow(const int x, const int y) const {
const int targetX = _activeArea.drawRect.left;
const int targetY = _activeArea.drawRect.top;
const int targetWidth = _activeArea.drawRect.width();
const int targetHeight = _activeArea.drawRect.height();
const int sourceWidth = _activeArea.width;
const int sourceHeight = _activeArea.height;
if (sourceWidth == 0 || sourceHeight == 0) {
error("convertVirtualToWindow called without a valid draw rect");
}
int windowX = targetX + (x * targetWidth + sourceWidth / 2) / sourceWidth;
int windowY = targetY + (y * targetHeight + sourceHeight / 2) / sourceHeight;
return Common::Point(CLIP<int>(windowX, targetX, targetX + targetWidth - 1),
CLIP<int>(windowY, targetY, targetY + targetHeight - 1));
}
/**
* Converts the given point from the window's coordinate space to the
* active virtual screen's coordinate space (i.e. window-to-game or
* window-to-overlay).
*/
Common::Point convertWindowToVirtual(int x, int y) const {
const int sourceX = _activeArea.drawRect.left;
const int sourceY = _activeArea.drawRect.top;
const int sourceMaxX = _activeArea.drawRect.right - 1;
const int sourceMaxY = _activeArea.drawRect.bottom - 1;
const int sourceWidth = _activeArea.drawRect.width();
const int sourceHeight = _activeArea.drawRect.height();
const int targetWidth = _activeArea.width;
const int targetHeight = _activeArea.height;
if (sourceWidth == 0 || sourceHeight == 0) {
error("convertWindowToVirtual called without a valid draw rect");
}
x = CLIP<int>(x, sourceX, sourceMaxX);
y = CLIP<int>(y, sourceY, sourceMaxY);
int virtualX = ((x - sourceX) * targetWidth + sourceWidth / 2) / sourceWidth;
int virtualY = ((y - sourceY) * targetHeight + sourceHeight / 2) / sourceHeight;
return Common::Point(CLIP<int>(virtualX, 0, targetWidth - 1),
CLIP<int>(virtualY, 0, targetHeight - 1));
}
/**
* @returns the desired aspect ratio of the game surface.
*/
frac_t getDesiredGameAspectRatio() const {
if (getHeight() == 0 || gameNeedsAspectRatioCorrection()) {
return intToFrac(4) / 3;
}
return intToFrac(getWidth()) / getHeight();
}
/**
* @returns the scale used between the game size and the surface on which it is rendered.
*/
virtual int getGameRenderScale() const {
return 1;
}
/**
* Called after the window has been updated with new dimensions.
*
* @param width The new width of the window, excluding window decoration.
* @param height The new height of the window, excluding window decoration.
*/
void handleResize(const int width, const int height, const int xdpi, const int ydpi) {
_windowWidth = width;
_windowHeight = height;
_xdpi = xdpi;
_ydpi = ydpi;
handleResizeImpl(width, height, xdpi, ydpi);
}
/**
* Recalculates the display areas for the game and overlay surfaces within
* the window.
*/
virtual void recalculateDisplayAreas() {
if (_windowHeight == 0) {
return;
}
populateDisplayAreaDrawRect(getDesiredGameAspectRatio(), getWidth() * getGameRenderScale(), _gameDrawRect);
if (getOverlayHeight()) {
const frac_t overlayAspect = intToFrac(getOverlayWidth()) / getOverlayHeight();
populateDisplayAreaDrawRect(overlayAspect, getOverlayWidth(), _overlayDrawRect);
}
if (_overlayVisible) {
_activeArea.drawRect = _overlayDrawRect;
_activeArea.width = getOverlayWidth();
_activeArea.height = getOverlayHeight();
} else {
_activeArea.drawRect = _gameDrawRect;
_activeArea.width = getWidth();
_activeArea.height = getHeight();
}
}
/**
* Sets the position of the hardware mouse cursor in the host system,
* relative to the window.
*
* @param x X coordinate in window coordinates.
* @param y Y coordinate in window coordinates.
*/
virtual void setSystemMousePosition(const int x, const int y) = 0;
virtual bool showMouse(const bool visible) override {
if (_cursorVisible == visible) {
return visible;
}
const bool last = _cursorVisible;
_cursorVisible = visible;
_cursorNeedsRedraw = true;
return last;
}
/**
* Move ("warp") the mouse cursor to the specified position.
*
* @param x The new X position of the mouse in virtual screen coordinates.
* @param y The new Y position of the mouse in virtual screen coordinates.
*/
void warpMouse(const int x, const int y) override {
// Check active coordinate instead of window coordinate to avoid warping
// the mouse if it is still within the same virtual pixel
const Common::Point virtualCursor = convertWindowToVirtual(_cursorX, _cursorY);
if (virtualCursor.x != x || virtualCursor.y != y) {
// Warping the mouse in SDL generates a mouse movement event, so
// `setMousePosition` would be called eventually through the
// `notifyMousePosition` callback if we *only* set the system mouse
// position here. However, this can cause problems with some games.
// For example, the cannon script in CoMI calls to warp the mouse
// twice each time the cannon is reloaded, and unless we update the
// mouse position immediately, the second call is ignored, which
// causes the cannon to change its aim.
const Common::Point windowCursor = convertVirtualToWindow(x, y);
setMousePosition(windowCursor.x, windowCursor.y);
setSystemMousePosition(windowCursor.x, windowCursor.y);
}
}
/**
* Sets the position of the rendered mouse cursor in the window.
*
* @param x X coordinate in window coordinates.
* @param y Y coordinate in window coordinates.
*/
void setMousePosition(int x, int y) {
if (_cursorX != x || _cursorY != y) {
_cursorNeedsRedraw = true;
}
_cursorX = x;
_cursorY = y;
}
/**
* The width of the window, excluding window decoration.
*/
int _windowWidth;
/**
* The height of the window, excluding window decoration.
*/
int _windowHeight;
/**
* The DPI of the window.
*/
int _xdpi, _ydpi;
/**
* Whether the overlay (i.e. launcher, including the out-of-game launcher)
* is visible or not.
*/
bool _overlayVisible;
/**
* The offset by which the screen is moved horizontally.
*/
int _gameScreenShakeXOffset;
/**
* The offset by which the screen is moved vertically.
*/
int _gameScreenShakeYOffset;
/**
* The scaled draw rectangle for the game surface within the window.
*/
Common::Rect _gameDrawRect;
/**
* The scaled draw rectangle for the overlay (launcher) surface within the
* window.
*/
Common::Rect _overlayDrawRect;
/**
* Data about the display area of a virtual screen.
*/
struct DisplayArea {
/**
* The scaled area where the virtual screen is drawn within the window.
*/
Common::Rect drawRect;
/**
* The width of the virtual screen's unscaled coordinate space.
*/
int width;
/**
* The height of the virtual screen's unscaled coordinate space.
*/
int height;
};
/**
* Display area information about the currently active virtual screen. This
* will be the overlay screen when the overlay is active, and the game
* screen otherwise.
*/
DisplayArea _activeArea;
/**
* Whether the screen must be redrawn on the next frame.
*/
bool _forceRedraw;
/**
* Whether the cursor is actually visible.
*/
bool _cursorVisible;
/**
* Whether the mouse cursor needs to be redrawn on the next frame.
*/
bool _cursorNeedsRedraw;
/**
* Whether the last position of the system cursor was within the active area
* of the window.
*/
bool _cursorLastInActiveArea;
/**
* The position of the mouse cursor, in window coordinates.
*/
int _cursorX, _cursorY;
private:
void populateDisplayAreaDrawRect(const frac_t displayAspect, int originalWidth, Common::Rect &drawRect) const {
int mode = getStretchMode();
// Mode Center = use original size, or divide by an integral amount if window is smaller than game surface
// Mode Integral = scale by an integral amount.
// Mode Fit = scale to fit the window while respecting the aspect ratio
// Mode Stretch = scale and stretch to fit the window without respecting the aspect ratio
// Mode Fit Force Aspect = scale to fit the window while forcing a 4:3 aspect ratio
int width = 0, height = 0;
if (mode == STRETCH_CENTER || mode == STRETCH_INTEGRAL) {
width = originalWidth;
height = intToFrac(width) / displayAspect;
if (width > _windowWidth || height > _windowHeight) {
int fac = 1 + MAX((width - 1) / _windowWidth, (height - 1) / _windowHeight);
width /= fac;
height /= fac;
} else if (mode == STRETCH_INTEGRAL) {
int fac = MIN(_windowWidth / width, _windowHeight / height);
width *= fac;
height *= fac;
}
} else {
frac_t windowAspect = intToFrac(_windowWidth) / _windowHeight;
width = _windowWidth;
height = _windowHeight;
if (mode == STRETCH_FIT_FORCE_ASPECT) {
frac_t ratio = intToFrac(4) / 3;
if (windowAspect < ratio)
height = intToFrac(width) / ratio;
else if (windowAspect > ratio)
width = fracToInt(height * ratio);
} else if (mode != STRETCH_STRETCH) {
if (windowAspect < displayAspect)
height = intToFrac(width) / displayAspect;
else if (windowAspect > displayAspect)
width = fracToInt(height * displayAspect);
}
}
drawRect.left = ((_windowWidth - width) / 2) + _gameScreenShakeXOffset * width / getWidth();
drawRect.top = ((_windowHeight - height) / 2) + _gameScreenShakeYOffset * height / getHeight();
drawRect.setWidth(width);
drawRect.setHeight(height);
}
};
#endif
+13 -22
View File
@@ -22,36 +22,27 @@
#include "backends/keymapper/action.h"
#ifdef ENABLE_KEYMAPPER
#include "backends/keymapper/keymap.h"
namespace Common {
Action::Action(Keymap *boss, const char *i, String des)
: _boss(boss), description(des), _hwInput(0) {
Action::Action(const char *i, const String &des) :
id(i),
description(des),
_shouldTriggerOnKbdRepeats(false) {
assert(i);
assert(_boss);
Common::strlcpy(id, i, ACTION_ID_SIZE);
_boss->addAction(this);
}
void Action::mapInput(const HardwareInput *input) {
if (_hwInput)
_boss->unregisterMapping(this);
void Action::addDefaultInputMapping(const String &hwId) {
if (hwId.empty()) {
return;
}
_hwInput = input;
if (_hwInput)
_boss->registerMapping(this, _hwInput);
}
const HardwareInput *Action::getMappedInput() const {
return _hwInput;
// 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
#endif // #ifdef ENABLE_KEYMAPPER
+86 -42
View File
@@ -25,85 +25,129 @@
#include "common/scummsys.h"
#ifdef ENABLE_KEYMAPPER
#include "common/array.h"
#include "common/events.h"
#include "common/func.h"
#include "common/list.h"
#include "common/str.h"
namespace Common {
struct HardwareInput;
class Keymap;
#define ACTION_ID_SIZE (5)
struct KeyActionEntry {
const KeyState ks;
const char *id;
const KeyState ks;
const char *defaultHwId;
const char *description;
};
struct Action {
/** unique id used for saving/loading to config */
char id[ACTION_ID_SIZE];
const char *id;
/** Human readable description */
String description;
/** Events to be sent when mapped key is pressed */
List<Event> events;
/** Event to be sent when mapped key is pressed */
Event event;
private:
/** Hardware input that is mapped to this Action */
const HardwareInput *_hwInput;
Keymap *_boss;
Array<String> _defaultInputMapping;
bool _shouldTriggerOnKbdRepeats;
public:
Action(Keymap *boss, const char *id, String des = "");
Action(const char *id, const String &description);
void addEvent(const Event &evt) {
events.push_back(evt);
void setEvent(const Event &evt) {
event = evt;
}
void addEvent(const EventType evtType) {
Event evt;
evt.type = evtType;
events.push_back(evt);
void setEvent(const EventType evtType) {
event = Event();
event.type = evtType;
}
void addKeyEvent(const KeyState &ks) {
Event evt;
evt.type = EVENT_KEYDOWN;
evt.kbd = ks;
addEvent(evt);
void setCustomBackendActionEvent(const CustomEventType evtType) {
event = Event();
event.type = EVENT_CUSTOM_BACKEND_ACTION_START;
event.customType = evtType;
}
void addLeftClickEvent() {
addEvent(EVENT_LBUTTONDOWN);
void setCustomBackendActionAxisEvent(const CustomEventType evtType) {
event = Event();
event.type = EVENT_CUSTOM_BACKEND_ACTION_AXIS;
event.customType = evtType;
}
void addMiddleClickEvent() {
addEvent(EVENT_MBUTTONDOWN);
void setCustomEngineActionEvent(const CustomEventType evtType) {
event = Event();
event.type = EVENT_CUSTOM_ENGINE_ACTION_START;
event.customType = evtType;
}
void addRightClickEvent() {
addEvent(EVENT_RBUTTONDOWN);
void setKeyEvent(const KeyState &ks) {
event = Event();
event.type = EVENT_KEYDOWN;
event.kbd = ks;
}
Keymap *getParent() {
return _boss;
void setLeftClickEvent() {
setEvent(EVENT_LBUTTONDOWN);
}
void mapInput(const HardwareInput *input);
const HardwareInput *getMappedInput() const;
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 // #ifdef ENABLE_KEYMAPPER
#endif // #ifndef COMMON_ACTION_H
+549 -228
View File
@@ -22,274 +22,595 @@
#include "backends/keymapper/hardware-input.h"
#ifdef ENABLE_KEYMAPPER
#include "backends/keymapper/keymapper.h"
#include "common/tokenizer.h"
#include "common/translation.h"
namespace Common {
static const KeyTableEntry defaultKeys[] = {
{"BACKSPACE", KEYCODE_BACKSPACE, ASCII_BACKSPACE, "Backspace", false},
{"TAB", KEYCODE_TAB, ASCII_TAB, "Tab", false},
{"CLEAR", KEYCODE_CLEAR, 0, "Clear", false},
{"RETURN", KEYCODE_RETURN, ASCII_RETURN, "Return", false},
{"PAUSE", KEYCODE_PAUSE, 0, "Pause", false},
{"ESCAPE", KEYCODE_ESCAPE, ASCII_ESCAPE, "Esc", false},
{"SPACE", KEYCODE_SPACE, ASCII_SPACE, "Space", false},
{"EXCLAIM", KEYCODE_EXCLAIM, '!', "!", false},
{"QUOTEDBL", KEYCODE_QUOTEDBL, '"', "\"", false},
{"HASH", KEYCODE_HASH, '#', "#", false},
{"DOLLAR", KEYCODE_DOLLAR, '$', "$", false},
{"AMPERSAND", KEYCODE_AMPERSAND, '&', "&", false},
{"QUOTE", KEYCODE_QUOTE, '\'', "'", false},
{"LEFTPAREN", KEYCODE_LEFTPAREN, '(', "(", false},
{"RIGHTPAREN", KEYCODE_RIGHTPAREN, ')', ")", false},
{"ASTERISK", KEYCODE_ASTERISK, '*', "*", false},
{"PLUS", KEYCODE_PLUS, '+', "+", false},
{"COMMA", KEYCODE_COMMA, ',', ",", false},
{"MINUS", KEYCODE_MINUS, '-', "-", false},
{"PERIOD", KEYCODE_PERIOD, '.', ".", false},
{"SLASH", KEYCODE_SLASH, '/', "/", false},
{"0", KEYCODE_0, '0', "0", false},
{"1", KEYCODE_1, '1', "1", false},
{"2", KEYCODE_2, '2', "2", false},
{"3", KEYCODE_3, '3', "3", false},
{"4", KEYCODE_4, '4', "4", false},
{"5", KEYCODE_5, '5', "5", false},
{"6", KEYCODE_6, '6', "6", false},
{"7", KEYCODE_7, '7', "7", false},
{"8", KEYCODE_8, '8', "8", false},
{"9", KEYCODE_9, '9', "9", false},
{"COLON", KEYCODE_COLON, ':', ":", false},
{"SEMICOLON", KEYCODE_SEMICOLON, ';', ";", false},
{"LESS", KEYCODE_LESS, '<', "<", false},
{"EQUALS", KEYCODE_EQUALS, '=', "=", false},
{"GREATER", KEYCODE_GREATER, '>', ">", false},
{"QUESTION", KEYCODE_QUESTION, '?', "?", false},
{"AT", KEYCODE_AT, '@', "@", false},
// 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
{"LEFTBRACKET", KEYCODE_LEFTBRACKET, '[', "[", false},
{"BACKSLASH", KEYCODE_BACKSLASH, '\\', "\\", false},
{"RIGHTBRACKET", KEYCODE_RIGHTBRACKET, ']', "]", false},
{"CARET", KEYCODE_CARET, '^', "^", false},
{"UNDERSCORE", KEYCODE_UNDERSCORE, '_', "_", false},
{"BACKQUOTE", KEYCODE_BACKQUOTE, '`', "`", false},
{"a", KEYCODE_a, 'a', "a", true},
{"b", KEYCODE_b, 'b', "b", true},
{"c", KEYCODE_c, 'c', "c", true},
{"d", KEYCODE_d, 'd', "d", true},
{"e", KEYCODE_e, 'e', "e", true},
{"f", KEYCODE_f, 'f', "f", true},
{"g", KEYCODE_g, 'g', "g", true},
{"h", KEYCODE_h, 'h', "h", true},
{"i", KEYCODE_i, 'i', "i", true},
{"j", KEYCODE_j, 'j', "j", true},
{"k", KEYCODE_k, 'k', "k", true},
{"l", KEYCODE_l, 'l', "l", true},
{"m", KEYCODE_m, 'm', "m", true},
{"n", KEYCODE_n, 'n', "n", true},
{"o", KEYCODE_o, 'o', "o", true},
{"p", KEYCODE_p, 'p', "p", true},
{"q", KEYCODE_q, 'q', "q", true},
{"r", KEYCODE_r, 'r', "r", true},
{"s", KEYCODE_s, 's', "s", true},
{"t", KEYCODE_t, 't', "t", true},
{"u", KEYCODE_u, 'u', "u", true},
{"v", KEYCODE_v, 'v', "v", true},
{"w", KEYCODE_w, 'w', "w", true},
{"x", KEYCODE_x, 'x', "x", true},
{"y", KEYCODE_y, 'y', "y", true},
{"z", KEYCODE_z, 'z', "z", true},
{"DELETE", KEYCODE_DELETE, 0, "Del", false},
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, 0, "KP0", false},
{"KP1", KEYCODE_KP1, 0, "KP1", false},
{"KP2", KEYCODE_KP2, 0, "KP2", false},
{"KP3", KEYCODE_KP3, 0, "KP3", false},
{"KP4", KEYCODE_KP4, 0, "KP4", false},
{"KP5", KEYCODE_KP5, 0, "KP5", false},
{"KP6", KEYCODE_KP6, 0, "KP6", false},
{"KP7", KEYCODE_KP7, 0, "KP7", false},
{"KP8", KEYCODE_KP8, 0, "KP8", false},
{"KP9", KEYCODE_KP9, 0, "KP9", false},
{"KP_PERIOD", KEYCODE_KP_PERIOD, 0, "KP.", false},
{"KP_DIVIDE", KEYCODE_KP_DIVIDE, 0, "KP/", false},
{"KP_MULTIPLY", KEYCODE_KP_MULTIPLY, 0, "KP*", false},
{"KP_MINUS", KEYCODE_KP_MINUS, 0, "KP-", false},
{"KP_PLUS", KEYCODE_KP_PLUS, 0, "KP+", false},
{"KP_ENTER", KEYCODE_KP_ENTER, 0, "KP Enter", false},
{"KP_EQUALS", KEYCODE_KP_EQUALS, 0, "KP=", false},
{"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, 0, "Up", false},
{"DOWN", KEYCODE_DOWN, 0, "Down", false},
{"RIGHT", KEYCODE_RIGHT, 0, "Right", false},
{"LEFT", KEYCODE_LEFT, 0, "Left", false},
{"INSERT", KEYCODE_INSERT, 0, "Insert", false},
{"HOME", KEYCODE_HOME, 0, "Home", false},
{"END", KEYCODE_END, 0, "End", false},
{"PAGEUP", KEYCODE_PAGEUP, 0, "PgUp", false},
{"PAGEDOWN", KEYCODE_PAGEDOWN, 0, "PgDn", false},
{"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, ASCII_F1, "F1", false},
{"F2", KEYCODE_F2, ASCII_F2, "F2", false},
{"F3", KEYCODE_F3, ASCII_F3, "F3", false},
{"F4", KEYCODE_F4, ASCII_F4, "F4", false},
{"F5", KEYCODE_F5, ASCII_F5, "F5", false},
{"F6", KEYCODE_F6, ASCII_F6, "F6", false},
{"F7", KEYCODE_F7, ASCII_F7, "F7", false},
{"F8", KEYCODE_F8, ASCII_F8, "F8", false},
{"F9", KEYCODE_F9, ASCII_F9, "F9", false},
{"F10", KEYCODE_F10, ASCII_F10, "F10", false},
{"F11", KEYCODE_F11, ASCII_F11, "F11", false},
{"F12", KEYCODE_F12, ASCII_F12, "F12", false},
{"F13", KEYCODE_F13, 0, "F13", false},
{"F14", KEYCODE_F14, 0, "F14", false},
{"F15", KEYCODE_F15, 0, "F15", false},
{"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, 0, "Help", false},
{"PRINT", KEYCODE_PRINT, 0, "Print", false},
{"SYSREQ", KEYCODE_SYSREQ, 0, "SysRq", false},
{"BREAK", KEYCODE_BREAK, 0, "Break", false},
{"MENU", KEYCODE_MENU, 0, "Menu", false},
{"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, 0, "Power", false},
{"POWER", KEYCODE_POWER, "Power"},
// Some european keyboards
{"EURO", KEYCODE_EURO, 0, "Euro", false},
{"EURO", KEYCODE_EURO, "Euro"},
// Atari keyboard has Undo
{"UNDO", KEYCODE_UNDO, 0, "Undo", false},
{0, KEYCODE_INVALID, 0, 0, false}
{"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 },
{0, KEYCODE_INVALID, 0}
};
static const ModifierTableEntry defaultModifiers[] = {
{ 0, "", "", false },
{ KBD_CTRL, "C+", "Ctrl+", false },
{ KBD_ALT, "A+", "Alt+", false },
{ KBD_SHIFT, "", "", true },
{ KBD_CTRL | KBD_ALT, "C+A+", "Ctrl+Alt+", false },
{ KBD_SHIFT | KBD_CTRL, "S+C+", "Shift+Ctrl+", true },
{ KBD_SHIFT | KBD_CTRL | KBD_ALT, "C+A+", "Ctrl+Alt+", true },
{ 0, 0, 0, false }
// 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 }
};
HardwareInputSet::HardwareInputSet(bool useDefault, const KeyTableEntry *keys, const ModifierTableEntry *modifiers) {
if (useDefault)
addHardwareInputs(defaultKeys, defaultModifiers);
if (keys)
addHardwareInputs(keys, modifiers ? modifiers : defaultModifiers);
}
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") },
{ 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") },
{ nullptr, 0, kAxisTypeFull, nullptr }
};
HardwareInputSet::~HardwareInputSet() {
List<const HardwareInput *>::const_iterator it;
for (it = _inputs.begin(); it != _inputs.end(); ++it)
delete *it;
}
void HardwareInputSet::addHardwareInput(const HardwareInput *input) {
assert(input);
debug(8, "Adding hardware input [%s][%s]", input->id.c_str(), input->description.c_str());
removeHardwareInput(input);
_inputs.push_back(input);
KeyboardHardwareInputSet::KeyboardHardwareInputSet(const KeyTableEntry *keys, const ModifierTableEntry *modifiers) :
_keys(keys),
_modifiers(modifiers) {
assert(_keys);
assert(_modifiers);
}
const HardwareInput *HardwareInputSet::findHardwareInput(String id) const {
List<const HardwareInput *>::const_iterator it;
HardwareInput KeyboardHardwareInputSet::findHardwareInput(const String &id) const {
StringTokenizer tokenizer(id, "+");
for (it = _inputs.begin(); it != _inputs.end(); ++it) {
if ((*it)->id == id)
return (*it);
}
return 0;
}
byte modifierFlags = 0;
const HardwareInput *HardwareInputSet::findHardwareInput(const HardwareInputCode code) const {
List<const HardwareInput *>::const_iterator it;
// TODO: Normalize modifier order
String fullKeyDesc;
for (it = _inputs.begin(); it != _inputs.end(); ++it) {
const HardwareInput *entry = *it;
if (entry->type == kHardwareInputTypeGeneric && entry->inputCode == code)
return entry;
}
return 0;
}
String token;
while (!tokenizer.empty()) {
token = tokenizer.nextToken();
const HardwareInput *HardwareInputSet::findHardwareInput(const KeyState& keystate) const {
List<const HardwareInput *>::const_iterator it;
for (it = _inputs.begin(); it != _inputs.end(); ++it) {
const HardwareInput *entry = *it;
if (entry->type == kHardwareInputTypeKeyboard && entry->key == keystate)
return entry;
}
return 0;
}
void HardwareInputSet::addHardwareInputs(const HardwareInputTableEntry inputs[]) {
for (const HardwareInputTableEntry *entry = inputs; entry->hwId; ++entry)
addHardwareInput(new HardwareInput(entry->hwId, entry->code, entry->desc));
}
void HardwareInputSet::addHardwareInputs(const KeyTableEntry keys[], const ModifierTableEntry modifiers[]) {
const KeyTableEntry *key;
const ModifierTableEntry *mod;
char fullKeyId[50];
char fullKeyDesc[100];
uint16 ascii;
for (mod = modifiers; mod->id; mod++) {
for (key = keys; key->hwId; key++) {
ascii = key->ascii;
if (mod->shiftable && key->shiftable) {
snprintf(fullKeyId, 50, "%s%c", mod->id, toupper(key->hwId[0]));
snprintf(fullKeyDesc, 100, "%s%c", mod->desc, toupper(key->desc[0]));
ascii = toupper(key->ascii);
} else if (mod->shiftable) {
snprintf(fullKeyId, 50, "S+%s%s", mod->id, key->hwId);
snprintf(fullKeyDesc, 100, "Shift+%s%s", mod->desc, key->desc);
} else {
snprintf(fullKeyId, 50, "%s%s", mod->id, key->hwId);
snprintf(fullKeyDesc, 100, "%s%s", mod->desc, key->desc);
const ModifierTableEntry *modifier = nullptr;
for (modifier = _modifiers; modifier->id; modifier++) {
if (token == modifier->id) {
break;
}
addHardwareInput(new HardwareInput(fullKeyId, KeyState(key->keycode, ascii, mod->flag), fullKeyDesc));
}
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;
String 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();
}
}
void HardwareInputSet::removeHardwareInput(const HardwareInput *input) {
if (!input)
return;
KeyState KeyboardHardwareInputSet::normalizeKeyState(const KeyState &keystate) {
KeyState normalizedKeystate = keystate;
List<const HardwareInput *>::iterator it;
// 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;
for (it = _inputs.begin(); it != _inputs.end(); ++it) {
const HardwareInput *entry = (*it);
bool match = false;
if (entry->id == input->id)
match = true;
else if (input->type == entry->type) {
if (input->type == kHardwareInputTypeGeneric && input->inputCode == entry->inputCode)
match = true;
else if (input->type == kHardwareInputTypeKeyboard && input->key == entry->key)
match = true;
// 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 (match) {
debug(7, "Removing hardware input [%s] (%s) because it matches [%s] (%s)", entry->id.c_str(), entry->description.c_str(), input->id.c_str(), input->description.c_str());
delete entry;
_inputs.erase(it);
if (axis->type == kAxisTypeHalf) {
return HardwareInput::createJoystickHalfAxis(axis->hwId, axis->code, true, axis->desc);
} else {
bool positiveHalf = id.lastChar() == '+';
Common::String desc = String::format("%s%c", axis->desc, 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::String desc = String::format("%s%c", hw->desc, 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
#endif // #ifdef ENABLE_KEYMAPPER
+226 -52
View File
@@ -25,22 +25,28 @@
#include "common/scummsys.h"
#ifdef ENABLE_KEYMAPPER
#include "common/array.h"
#include "common/events.h"
#include "common/keyboard.h"
#include "common/list.h"
#include "common/str.h"
#include "common/textconsole.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 */
kHardwareInputTypeGeneric,
/** Input that usually send -up and -down events */
kHardwareInputTypeKeyboard
kHardwareInputTypeCustom
};
/**
@@ -53,7 +59,8 @@ struct HardwareInput {
/** Human readable description */
String description;
const HardwareInputType type;
/** Type tag */
HardwareInputType type;
/**
* A platform specific unique identifier for an input event
@@ -69,17 +76,71 @@ struct HardwareInput {
*/
KeyState key;
HardwareInput(String i, HardwareInputCode ic = 0, String desc = "")
: id(i), inputCode(ic), description(desc), type(kHardwareInputTypeGeneric) { }
HardwareInput()
: inputCode(0), type(kHardwareInputTypeInvalid) { }
HardwareInput(String i, KeyState ky, String desc = "")
: id(i), key(ky), description(desc), type(kHardwareInputTypeKeyboard) { }
static HardwareInput createCustom(const String &i, HardwareInputCode ic, const String &desc) {
return createSimple(kHardwareInputTypeCustom, i, ic, desc);
}
static HardwareInput createKeyboard(const String &i, KeyState ky, const String &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 String &desc) {
return createSimple(kHardwareInputTypeJoystickButton, i, button, desc);
}
static HardwareInput createJoystickHalfAxis(const String &i, uint8 axis, bool positiveHalf, const String &desc) {
return createSimple(kHardwareInputTypeJoystickHalfAxis, i, axis * 2 + (positiveHalf ? 1 : 0), desc);
}
static HardwareInput createMouse(const String &i, uint8 button, const String &desc) {
return createSimple(kHardwareInputTypeMouse, i, button, desc);
}
private:
static HardwareInput createSimple(HardwareInputType type, const String &i, HardwareInputCode ic, const String &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;
}
};
/**
@@ -88,9 +149,7 @@ struct HardwareInputTableEntry {
struct KeyTableEntry {
const char *hwId;
KeyCode keycode;
uint16 ascii;
const char *desc;
bool shiftable;
};
/**
@@ -100,61 +159,176 @@ struct ModifierTableEntry {
byte flag;
const char *id;
const char *desc;
bool shiftable;
};
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;
}
};
/**
* Simple class to encapsulate a device's set of HardwareInputs.
* Each device should instantiate this and call addHardwareInput a number of times
* in its constructor to define the device's available keys.
* Interface for querying information about a hardware input device
*/
class HardwareInputSet {
public:
/**
* Add hardware input keys to the set out of key and modifier tables.
* @param useDefault auto-add the built-in default inputs
* @param keys table of available keys
* @param modifiers table of available modifiers
*/
HardwareInputSet(bool useDefault = false, const KeyTableEntry keys[] = 0, const ModifierTableEntry modifiers[] = 0);
virtual ~HardwareInputSet();
void addHardwareInput(const HardwareInput *input);
const HardwareInput *findHardwareInput(String id) const;
const HardwareInput *findHardwareInput(const HardwareInputCode code) const;
const HardwareInput *findHardwareInput(const KeyState& keystate) const;
const List<const HardwareInput *> &getHardwareInputs() const { return _inputs; }
uint size() const { return _inputs.size(); }
/**
* 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;
/**
* Add hardware inputs to the set out of a table.
* @param inputs table of available inputs
* 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.
*/
void addHardwareInputs(const HardwareInputTableEntry inputs[]);
virtual HardwareInput findHardwareInput(const Event &event) const = 0;
};
/**
* Add hardware inputs to the set out of key and modifier tables.
* @param keys table of available keys
* @param modifiers table of available modifiers
*/
void addHardwareInputs(const KeyTableEntry keys[], const ModifierTableEntry modifiers[]);
/**
* 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);
void removeHardwareInput(const HardwareInput *input);
// 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:
List<const HardwareInput *> _inputs;
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 // #ifdef ENABLE_KEYMAPPER
#endif // #ifndef COMMON_HARDWARE_KEY_H
+98
View File
@@ -0,0 +1,98 @@
/* 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/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
+66
View File
@@ -0,0 +1,66 @@
/* 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 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 an 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
+241 -145
View File
@@ -22,10 +22,10 @@
#include "backends/keymapper/keymap.h"
#ifdef ENABLE_KEYMAPPER
#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"
@@ -33,25 +33,19 @@
namespace Common {
Keymap::Keymap(const Keymap& km) : _actions(km._actions), _keymap(), _nonkeymap(), _configDomain(0) {
List<Action *>::iterator it;
Keymap::Keymap(KeymapType type, const String &id, const String &description) :
_type(type),
_id(id),
_description(description),
_enabled(true),
_configDomain(nullptr),
_hardwareInputSet(nullptr),
_backendDefaultBindings(nullptr) {
for (it = _actions.begin(); it != _actions.end(); ++it) {
const HardwareInput *hwInput = (*it)->getMappedInput();
if (hwInput) {
if (hwInput->type == kHardwareInputTypeKeyboard)
_keymap[hwInput->key] = *it;
else if (hwInput->type == kHardwareInputTypeGeneric)
_nonkeymap[hwInput->inputCode] = *it;
}
}
}
Keymap::~Keymap() {
List<Action *>::iterator it;
for (it = _actions.begin(); it != _actions.end(); ++it)
for (ActionArray::iterator it = _actions.begin(); it != _actions.end(); ++it)
delete *it;
}
@@ -62,176 +56,278 @@ void Keymap::addAction(Action *action) {
_actions.push_back(action);
}
void Keymap::registerMapping(Action *action, const HardwareInput *hwInput) {
if (hwInput->type == kHardwareInputTypeKeyboard) {
HashMap<KeyState, Action *>::iterator it = _keymap.find(hwInput->key);
// if input is already mapped to a different action then unmap it from there
if (it != _keymap.end() && action != it->_value)
it->_value->mapInput(0);
// now map it
_keymap[hwInput->key] = action;
} else if (hwInput->type == kHardwareInputTypeGeneric) {
HashMap<HardwareInputCode, Action *>::iterator it = _nonkeymap.find(hwInput->inputCode);
// if input is already mapped to a different action then unmap it from there
if (it != _nonkeymap.end() && action != it->_value)
it->_value->mapInput(0);
// now map it
_nonkeymap[hwInput->inputCode] = action;
void Keymap::registerMapping(Action *action, const HardwareInput &hwInput) {
ActionArray &actionArray = _hwActionMap.getVal(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) {
const HardwareInput *hwInput = action->getMappedInput();
if (hwInput) {
if (hwInput->type == kHardwareInputTypeKeyboard)
_keymap.erase(hwInput->key);
else if (hwInput->type == kHardwareInputTypeGeneric)
_nonkeymap.erase(hwInput->inputCode);
// Remove the action from all the input mappings
for (HardwareActionMap::iterator itInput = _hwActionMap.begin(); itInput != _hwActionMap.end(); itInput++) {
for (ActionArray::iterator itAction = itInput->_value.begin(); itAction != itInput->_value.end(); itAction++) {
if (*itAction == action) {
itInput->_value.erase(itAction);
break;
}
}
if (itInput->_value.empty()) {
_hwActionMap.erase(itInput);
}
}
}
Action *Keymap::getAction(const char *id) {
return findAction(id);
void Keymap::resetMapping(Action *action) {
unregisterMapping(action);
StringArray hwInputIds = getActionDefaultMappings(action);
registerMappings(action, hwInputIds);
}
Action *Keymap::findAction(const char *id) {
List<Action *>::iterator it;
for (it = _actions.begin(); it != _actions.end(); ++it) {
if (strncmp((*it)->id, id, ACTION_ID_SIZE) == 0)
return *it;
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);
}
return 0;
};
Array<HardwareInput> Keymap::getActionMapping(Action *action) const {
Array<HardwareInput> inputs;
for (HardwareActionMap::iterator itInput = _hwActionMap.begin(); itInput != _hwActionMap.end(); itInput++) {
for (ActionArray::iterator itAction = itInput->_value.begin(); itAction != itInput->_value.end(); itAction++) {
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 {
List<Action *>::const_iterator it;
for (it = _actions.begin(); it != _actions.end(); ++it) {
if (strncmp((*it)->id, id, ACTION_ID_SIZE) == 0)
for (ActionArray::const_iterator it = _actions.begin(); it != _actions.end(); ++it) {
if (strcmp((*it)->id, id) == 0)
return *it;
}
return 0;
return nullptr;
}
Action *Keymap::getMappedAction(const KeyState& ks) const {
HashMap<KeyState, Action *>::iterator it;
it = _keymap.find(ks);
if (it == _keymap.end())
return 0;
else
return it->_value;
}
Action *Keymap::getMappedAction(const HardwareInputCode code) const {
HashMap<HardwareInputCode, Action *>::iterator it;
it = _nonkeymap.find(code);
if (it == _nonkeymap.end())
return 0;
else
return it->_value;
}
void Keymap::setConfigDomain(ConfigManager::Domain *dom) {
_configDomain = dom;
}
void Keymap::loadMappings(const HardwareInputSet *hwKeys) {
if (!_configDomain)
return;
if (_actions.empty())
return;
Common::KeymapperDefaultBindings *defaults = g_system->getKeymapperDefaultBindings();
HashMap<String, const HardwareInput *> mappedInputs;
List<Action*>::iterator it;
String prefix = KEYMAP_KEY_PREFIX + _name + "_";
for (it = _actions.begin(); it != _actions.end(); ++it) {
Action* ua = *it;
String actionId(ua->id);
String confKey = prefix + actionId;
String hwInputId = _configDomain->getVal(confKey);
bool defaulted = false;
// fall back to the platform-specific defaults
if (hwInputId.empty() && defaults) {
hwInputId = defaults->getDefaultBinding(_name, actionId);
if (!hwInputId.empty())
defaulted = true;
Keymap::ActionArray Keymap::getMappedActions(const Event &event) const {
switch (event.type) {
case EVENT_KEYDOWN:
case EVENT_KEYUP: {
KeyState normalizedKeystate = KeyboardHardwareInputSet::normalizeKeyState(event.kbd);
HardwareInput hardwareInput = HardwareInput::createKeyboard("", normalizedKeystate, "");
return _hwActionMap[hardwareInput];
}
case EVENT_LBUTTONDOWN:
case EVENT_LBUTTONUP: {
HardwareInput hardwareInput = HardwareInput::createMouse("", MOUSE_BUTTON_LEFT, "");
return _hwActionMap[hardwareInput];
}
case EVENT_RBUTTONDOWN:
case EVENT_RBUTTONUP: {
HardwareInput hardwareInput = HardwareInput::createMouse("", MOUSE_BUTTON_RIGHT, "");
return _hwActionMap[hardwareInput];
}
case EVENT_MBUTTONDOWN:
case EVENT_MBUTTONUP: {
HardwareInput hardwareInput = HardwareInput::createMouse("", MOUSE_BUTTON_MIDDLE, "");
return _hwActionMap[hardwareInput];
}
case Common::EVENT_WHEELUP: {
HardwareInput hardwareInput = HardwareInput::createMouse("", MOUSE_WHEEL_UP, "");
return _hwActionMap[hardwareInput];
}
case Common::EVENT_WHEELDOWN: {
HardwareInput hardwareInput = HardwareInput::createMouse("", MOUSE_WHEEL_DOWN, "");
return _hwActionMap[hardwareInput];
}
case EVENT_X1BUTTONDOWN:
case EVENT_X1BUTTONUP: {
HardwareInput hardwareInput = HardwareInput::createMouse("", MOUSE_BUTTON_X1, "");
return _hwActionMap[hardwareInput];
}
case EVENT_X2BUTTONDOWN:
case EVENT_X2BUTTONUP: {
HardwareInput hardwareInput = HardwareInput::createMouse("", MOUSE_BUTTON_X2, "");
return _hwActionMap[hardwareInput];
}
case EVENT_JOYBUTTON_DOWN:
case EVENT_JOYBUTTON_UP: {
HardwareInput hardwareInput = HardwareInput::createJoystickButton("", event.joystick.button, "");
return _hwActionMap[hardwareInput];
}
case EVENT_JOYAXIS_MOTION: {
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;
}
// there's no mapping
if (hwInputId.empty())
continue;
}
case EVENT_CUSTOM_BACKEND_HARDWARE: {
HardwareInput hardwareInput = HardwareInput::createCustom("", event.customType, "");
return _hwActionMap[hardwareInput];
}
default:
return ActionArray();
}
}
const HardwareInput *hwInput = hwKeys->findHardwareInput(hwInputId.c_str());
void Keymap::setConfigDomain(ConfigManager::Domain *configDomain) {
_configDomain = configDomain;
}
if (!hwInput) {
warning("HardwareInput with ID '%s' not known", hwInputId.c_str());
continue;
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()) {
if (it->_value.empty()) {
return StringArray();
}
return StringArray(1, it->_value);
}
if (defaulted) {
if (mappedInputs.contains(hwInputId)) {
debug(1, "Action [%s] not falling back to hardcoded default value [%s] because the hardware input is in use", confKey.c_str(), hwInputId.c_str());
// If no keymap-specific default mapping was found, look for a standard action binding
it = _backendDefaultBindings->findDefaultBinding(kStandardActionsKeymapName, action->id);
if (it != _backendDefaultBindings->end()) {
if (it->_value.empty()) {
return StringArray();
}
return StringArray(1, 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 (ActionArray::const_iterator it = _actions.begin(); it != _actions.end(); ++it) {
Action *action = *it;
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;
}
warning("Action [%s] fell back to hardcoded default value [%s]", confKey.c_str(), hwInputId.c_str());
}
mappedInputs.setVal(hwInputId, hwInput);
// map the key
ua->mapInput(hwInput);
}
// map the key
registerMapping(action, hwInput);
}
}
void Keymap::saveMappings() {
if (!_configDomain)
return;
List<Action *>::const_iterator it;
String prefix = KEYMAP_KEY_PREFIX + _name + "_";
String prefix = KEYMAP_KEY_PREFIX + _id + "_";
for (it = _actions.begin(); it != _actions.end(); ++it) {
uint actIdLen = strlen((*it)->id);
for (ActionArray::const_iterator it = _actions.begin(); it != _actions.end(); it++) {
Action *action = *it;
Array<HardwareInput> mappedInputs = getActionMapping(action);
actIdLen = (actIdLen > ACTION_ID_SIZE) ? ACTION_ID_SIZE : actIdLen;
String actId((*it)->id, (*it)->id + actIdLen);
String hwId = "";
if ((*it)->getMappedInput()) {
hwId = (*it)->getMappedInput()->id;
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;
}
_configDomain->setVal(prefix + actId, hwId);
// 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::isComplete(const HardwareInputSet *hwInputs) {
List<Action *>::iterator it;
bool allMapped = true;
uint numberMapped = 0;
bool Keymap::areMappingsIdentical(const Array<HardwareInput> &mappingsA, const StringArray &mappingsB) {
// Assumes array values are not duplicated, but registerMapping and addDefaultInputMapping ensure that
for (it = _actions.begin(); it != _actions.end(); ++it) {
if ((*it)->getMappedInput()) {
++numberMapped;
} else {
allMapped = false;
uint foundCount = 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;
for (uint j = 0; j < mappingsA.size(); j++) {
if (mappingsA[j].id == mappingB.id) {
foundCount++;
break;
}
}
}
return allMapped || (numberMapped == hwInputs->size());
return foundCount == mappingsA.size();
}
} // End of namespace Common
#endif // #ifdef ENABLE_KEYMAPPER
+126 -82
View File
@@ -25,94 +25,60 @@
#include "common/scummsys.h"
#ifdef ENABLE_KEYMAPPER
#include "backends/keymapper/hardware-input.h"
#include "common/config-manager.h"
#include "common/func.h"
#include "common/hashmap.h"
#include "common/keyboard.h"
#include "common/hash-ptr.h"
#include "common/list.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/hardware-input.h"
#include "common/str-array.h"
namespace Common {
/**
* Hash function for KeyState
*/
template<> struct Hash<KeyState>
: public UnaryFunction<KeyState, uint> {
const char *const kStandardActionsKeymapName = "standard-actions";
uint operator()(const KeyState &val) const {
return (uint)val.keycode | ((uint)val.flags << 24);
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:
Keymap(const String& name) : _name(name) {}
Keymap(const Keymap& km);
enum KeymapType {
kKeymapTypeGlobal,
kKeymapTypeGui,
kKeymapTypeGame
};
typedef Array<Action *> ActionArray;
Keymap(KeymapType type, const String &id, const String &description);
~Keymap();
public:
/**
* Retrieves the Action with the given id
* @param id id of Action to retrieve
* @return Pointer to the Action or 0 if not found
*/
Action *getAction(const char *id);
/**
* Get the list of all the Actions contained in this Keymap
*/
List<Action *>& getActions() { return _actions; }
/**
* Find the Action that a key is mapped to
* @param key the key that is mapped to the required Action
* @return a pointer to the Action or 0 if no
*/
Action *getMappedAction(const KeyState& ks) const;
/**
* Find the Action that a generic input is mapped to
* @param code the input code that is mapped to the required Action
* @return a pointer to the Action or 0 if no
*/
Action *getMappedAction(const HardwareInputCode code) const;
void setConfigDomain(ConfigManager::Domain *dom);
/**
* Load this keymap's mappings from the config manager.
* @param hwInputs the set to retrieve hardware input pointers from
*/
void loadMappings(const HardwareInputSet *hwInputs);
/**
* Save this keymap's mappings to the config manager
* @note Changes are *not* flushed to disk, to do so call ConfMan.flushToDisk()
* @note Changes are *not* flushed to disk, to do so call ConfMan.flushToDisk()
*/
void saveMappings();
/**
* Returns true if all UserAction's in Keymap are mapped, or,
* all HardwareInputs from the given set have been used up.
*/
bool isComplete(const HardwareInputSet *hwInputs);
const String& getName() { return _name; }
private:
friend struct Action;
/**
* Adds a new Action to this Map,
* adding it at the back of the internal array
* @param action the Action to add
*/
void addAction(Action *action);
void setConfigDomain(ConfigManager::Domain *configDomain);
void setHardwareInputs(HardwareInputSet *hardwareInputSet);
void setBackendDefaultBindings(const KeymapperDefaultBindings *backendDefaultBindings);
/**
* Registers a HardwareInput to the given Action
@@ -120,7 +86,7 @@ private:
* @param key pointer to HardwareInput to map
* @see Action::mapKey
*/
void registerMapping(Action *action, const HardwareInput *input);
void registerMapping(Action *action, const HardwareInput &input);
/**
* Unregisters a HardwareInput from the given Action (if one is mapped)
@@ -129,20 +95,98 @@ private:
*/
void unregisterMapping(Action *action);
Action *findAction(const char *id);
/**
* 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(Action *action) const;
/**
* Find the Actions that a hardware input is mapped to
* @param hardwareInput the input that is mapped to the required Action
* @return an array containing pointers to the actions
*/
ActionArray getMappedActions(const Event &event) 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 String &getDescription() const { return _description; }
KeymapType getType() const { return _type; }
/**
* 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;
String _name;
List<Action *> _actions;
HashMap<KeyState, Action *> _keymap;
HashMap<HardwareInputCode, Action *> _nonkeymap;
ConfigManager::Domain *_configDomain;
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;
String _description;
bool _enabled;
ActionArray _actions;
HardwareActionMap _hwActionMap;
ConfigManager::Domain *_configDomain;
HardwareInputSet *_hardwareInputSet;
const KeymapperDefaultBindings *_backendDefaultBindings;
};
typedef Array<Keymap *> KeymapArray;
} // End of namespace Common
#endif // #ifdef ENABLE_KEYMAPPER
#endif // #ifndef COMMON_KEYMAP_H
+4 -5
View File
@@ -20,8 +20,6 @@
*
*/
#ifdef ENABLE_KEYMAPPER
#ifndef KEYMAPPER_DEFAULTS_H
#define KEYMAPPER_DEFAULTS_H
@@ -32,7 +30,7 @@
namespace Common {
class KeymapperDefaultBindings : HashMap<String, String> {
class KeymapperDefaultBindings : public HashMap<String, String> {
public:
/**
* This sets a default hwInput for a given Keymap Action
@@ -47,10 +45,11 @@ public:
* @param actionId String representing Action id (Action.id)
* @return String representing the HardwareInput id (HardwareInput.id)
*/
String getDefaultBinding(String keymapId, String actionId) { return getVal(keymapId + "_" + actionId); }
const_iterator findDefaultBinding(String keymapId, String actionId) const {
return find(keymapId + "_" + actionId);
}
};
} //namespace Common
#endif // #ifndef KEYMAPPER_DEFAULTS_H
#endif // #ifdef ENABLE_KEYMAPPER
+329 -280
View File
@@ -22,9 +22,10 @@
#include "backends/keymapper/keymapper.h"
#ifdef ENABLE_KEYMAPPER
#include "backends/keymapper/action.h"
#include "backends/keymapper/hardware-input.h"
#include "backends/keymapper/keymapper-defaults.h"
#include "common/config-manager.h"
#include "common/system.h"
namespace Common {
@@ -33,295 +34,304 @@ namespace Common {
static const uint32 kDelayKeyboardEventMillis = 250;
static const uint32 kDelayMouseEventMillis = 50;
void Keymapper::Domain::addKeymap(Keymap *map) {
iterator it = find(map->getName());
if (it != end())
delete it->_value;
setVal(map->getName(), map);
}
void Keymapper::Domain::deleteAllKeyMaps() {
for (iterator it = begin(); it != end(); ++it)
delete it->_value;
clear();
}
Keymap *Keymapper::Domain::getKeymap(const String& name) {
iterator it = find(name);
if (it != end())
return it->_value;
else
return 0;
}
Keymapper::Keymapper(EventManager *evtMgr)
: _eventMan(evtMgr), _enabled(true), _remapping(false), _hardwareInputs(0), _actionToRemap(0) {
ConfigManager::Domain *confDom = ConfMan.getDomain(ConfigManager::kKeymapperDomain);
_globalDomain.setConfigDomain(confDom);
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() {
delete _hardwareInputs;
clear();
}
void Keymapper::registerHardwareInputSet(HardwareInputSet *inputs) {
if (_hardwareInputs)
error("Hardware input set already registered");
void Keymapper::clear() {
for (KeymapArray::iterator it = _keymaps.begin(); it != _keymaps.end(); it++) {
delete *it;
}
_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");
inputs = new HardwareInputSet(true);
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) {
initKeymap(_globalDomain, 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) {
if (ConfMan.getActiveDomain() == 0)
error("Call to Keymapper::addGameKeymap when no game loaded");
assert(keymap->getType() == Keymap::kKeymapTypeGame);
// Detect whether the active game changed since last call.
// If so, flush the game key configuration.
if (_gameDomain.getConfigDomain() != ConfMan.getActiveDomain()) {
cleanupGameKeymaps();
_gameDomain.setConfigDomain(ConfMan.getActiveDomain());
ConfigManager::Domain *gameDomain = ConfMan.getActiveDomain();
if (!gameDomain) {
error("Call to Keymapper::addGameKeymap when no game loaded");
}
initKeymap(_gameDomain, keymap);
initKeymap(keymap, gameDomain);
_keymaps.push_back(keymap);
}
void Keymapper::initKeymap(Domain &domain, Keymap *map) {
void Keymapper::initKeymap(Keymap *keymap, ConfigManager::Domain *domain) {
if (!_hardwareInputs) {
warning("No hardware inputs were registered yet (%s)", map->getName().c_str());
warning("No hardware inputs were registered yet (%s)", keymap->getId().c_str());
return;
}
map->setConfigDomain(domain.getConfigDomain());
map->loadMappings(_hardwareInputs);
keymap->setConfigDomain(domain);
reloadKeymapMappings(keymap);
}
if (map->isComplete(_hardwareInputs) == false) {
map->saveMappings();
ConfMan.flushToDisk();
}
domain.addKeymap(map);
void Keymapper::reloadKeymapMappings(Keymap *keymap) {
keymap->setHardwareInputs(_hardwareInputs);
keymap->setBackendDefaultBindings(_backendDefaultBindings);
keymap->loadMappings();
}
void Keymapper::cleanupGameKeymaps() {
// Flush all game specific keymaps
_gameDomain.deleteAllKeyMaps();
// Now restore the stack of active maps. Re-add all global keymaps, drop
// the game specific (=deleted) ones.
Stack<MapRecord> newStack;
for (Stack<MapRecord>::size_type i = 0; i < _activeMaps.size(); i++) {
if (_activeMaps[i].global)
newStack.push(_activeMaps[i]);
}
_activeMaps = newStack;
}
Keymap *Keymapper::getKeymap(const String& name, bool *globalReturn) {
Keymap *keymap = _gameDomain.getKeymap(name);
bool global = false;
if (!keymap) {
keymap = _globalDomain.getKeymap(name);
global = true;
}
if (globalReturn)
*globalReturn = global;
return keymap;
}
bool Keymapper::pushKeymap(const String& name, bool transparent) {
bool global;
assert(!name.empty());
Keymap *newMap = getKeymap(name, &global);
if (!newMap) {
warning("Keymap '%s' not registered", name.c_str());
return false;
}
pushKeymap(newMap, transparent, global);
return true;
}
void Keymapper::pushKeymap(Keymap *newMap, bool transparent, bool global) {
MapRecord mr = {newMap, transparent, global};
_activeMaps.push(mr);
}
void Keymapper::popKeymap(const char *name) {
if (!_activeMaps.empty()) {
if (name) {
String topKeymapName = _activeMaps.top().keymap->getName();
if (topKeymapName.equals(name))
_activeMaps.pop();
else
warning("An attempt to pop wrong keymap was blocked (expected %s but was %s)", name, topKeymapName.c_str());
KeymapArray::iterator it = _keymaps.begin();
while (it != _keymaps.end()) {
if ((*it)->getType() == Keymap::kKeymapTypeGame) {
delete *it;
it = _keymaps.erase(it);
} else {
_activeMaps.pop();
it++;
}
}
}
Keymap *Keymapper::getKeymap(const String &id) const {
for (KeymapArray::const_iterator it = _keymaps.begin(); it != _keymaps.end(); it++) {
if ((*it)->getId() == id) {
return *it;
}
}
return nullptr;
}
List<Event> Keymapper::mapEvent(const Event &ev, EventSource *source) {
if (source && !source->allowMapping()) {
return DefaultEventMapper::mapEvent(ev, source);
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;
}
List<Event> Keymapper::mapEvent(const Event &ev) {
if (!_enabled) {
List<Event> originalEvent;
originalEvent.push_back(ev);
return originalEvent;
}
hardcodedEventMapping(ev);
List<Event> mappedEvents;
bool matchedAction = mapEvent(ev, _enabledKeymapType, mappedEvents);
if (!matchedAction) {
// If we found actions matching 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.
matchedAction = mapEvent(ev, Keymap::kKeymapTypeGlobal, mappedEvents);
}
if (_remapping)
mappedEvents = remap(ev);
else if (ev.type == Common::EVENT_KEYDOWN)
mappedEvents = mapKeyDown(ev.kbd);
else if (ev.type == Common::EVENT_KEYUP)
mappedEvents = mapKeyUp(ev.kbd);
else if (ev.type == Common::EVENT_CUSTOM_BACKEND_HARDWARE)
mappedEvents = mapNonKey(ev.customType);
if (!mappedEvents.empty())
return mappedEvents;
else
return DefaultEventMapper::mapEvent(ev, source);
}
void Keymapper::startRemappingMode(Action *actionToRemap) {
assert(!_remapping);
_remapping = true;
_actionToRemap = actionToRemap;
}
List<Event> Keymapper::mapKeyDown(const KeyState& key) {
return mapKey(key, true);
}
List<Event> Keymapper::mapKeyUp(const KeyState& key) {
return mapKey(key, false);
}
List<Event> Keymapper::mapKey(const KeyState& key, bool keyDown) {
if (!_enabled || _activeMaps.empty())
return List<Event>();
Action *action = 0;
if (keyDown) {
// Search for key in active keymap stack
for (int i = _activeMaps.size() - 1; i >= 0; --i) {
MapRecord mr = _activeMaps[i];
debug(5, "Keymapper::mapKey keymap: %s", mr.keymap->getName().c_str());
action = mr.keymap->getMappedAction(key);
if (action || !mr.transparent)
break;
}
if (action)
_keysDown[key] = action;
} else {
HashMap<KeyState, Action *>::iterator it = _keysDown.find(key);
if (it != _keysDown.end()) {
action = it->_value;
_keysDown.erase(key);
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;
}
}
if (!action)
return List<Event>();
return executeAction(action, keyDown ? kIncomingKeyDown : kIncomingKeyUp);
}
List<Event> Keymapper::mapNonKey(const HardwareInputCode code) {
if (!_enabled || _activeMaps.empty())
return List<Event>();
Action *action = 0;
// Search for nonkey in active keymap stack
for (int i = _activeMaps.size() - 1; i >= 0; --i) {
MapRecord mr = _activeMaps[i];
debug(5, "Keymapper::mapKey keymap: %s", mr.keymap->getName().c_str());
action = mr.keymap->getMappedAction(code);
if (action || !mr.transparent)
break;
if (!matchedAction) {
// if it didn't get mapped, just pass it through
mappedEvents.push_back(ev);
}
if (!action)
return List<Event>();
return executeAction(action);
}
Action *Keymapper::getAction(const KeyState& key) {
Action *action = 0;
return action;
}
List<Event> Keymapper::executeAction(const Action *action, IncomingEventType incomingType) {
List<Event> mappedEvents;
List<Event>::const_iterator it;
Event evt;
for (it = action->events.begin(); it != action->events.end(); ++it) {
evt = Event(*it);
EventType convertedType = convertDownToUp(evt.type);
// hardware keys need to send up instead when they are up
if (incomingType == kIncomingKeyUp) {
if (convertedType == EVENT_INVALID)
continue; // don't send any non-down-converted events on up they were already sent on down
evt.type = convertedType;
}
evt.mouse = _eventMan->getMousePos();
// Check if the event is coming from a non-key hardware event
// that is mapped to a key event
if (incomingType == kIncomingNonKey && 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.
addDelayedEvent(0, evt);
else
mappedEvents.push_back(evt);
// non-keys need to send up as well
if (incomingType == kIncomingNonKey && convertedType != EVENT_INVALID) {
// WORKAROUND: Delay the up events coming from non-key hardware events
// This is for engines that run scripts that check on key being down
evt.type = convertedType;
const uint32 delay = (convertedType == EVENT_KEYUP ? kDelayKeyboardEventMillis : kDelayMouseEventMillis);
addDelayedEvent(delay, evt);
}
}
return mappedEvents;
}
EventType Keymapper::convertDownToUp(EventType type) {
bool Keymapper::mapEvent(const Event &ev, Keymap::KeymapType keymapType, List<Event> &mappedEvents) {
bool matchedAction = false;
for (uint i = 0; i < _keymaps.size(); i++) {
if (!_keymaps[i]->isEnabled() || _keymaps[i]->getType() != keymapType) {
continue;
}
Keymap::ActionArray actions = _keymaps[i]->getMappedActions(ev);
if (!actions.empty()) {
matchedAction = true;
}
for (Keymap::ActionArray::const_iterator it = actions.begin(); it != actions.end(); it++) {
Event mappedEvent = executeAction(*it, 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);
}
}
return matchedAction;
}
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:
@@ -336,55 +346,94 @@ EventType Keymapper::convertDownToUp(EventType type) {
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;
}
const HardwareInput *Keymapper::findHardwareInput(const KeyState& key) {
return (_hardwareInputs) ? _hardwareInputs->findHardwareInput(key) : 0;
HardwareInput Keymapper::findHardwareInput(const Event &event) {
return _hardwareInputs->findHardwareInput(event);
}
const HardwareInput *Keymapper::findHardwareInput(const HardwareInputCode code) {
return (_hardwareInputs) ? _hardwareInputs->findHardwareInput(code) : 0;
void Keymapper::hardcodedEventMapping(Event ev) {
// TODO: Either add support for long presses to the keymapper
// or move this elsewhere as an event observer + source
#ifdef ENABLE_VKEYBD
// 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);
}
}
#endif
}
List<Event> Keymapper::remap(const Event &ev) {
assert(_remapping);
assert(_actionToRemap);
List<Event> list;
const HardwareInput *hwInput = 0;
Event mappedEvent;
switch (ev.type) {
case EVENT_KEYDOWN:
// eat the event by returning an event invalid
mappedEvent.type = EVENT_INVALID;
list.push_back(mappedEvent);
break;
case EVENT_KEYUP:
hwInput = findHardwareInput(ev.kbd);
break;
case EVENT_CUSTOM_BACKEND_HARDWARE:
hwInput = findHardwareInput(ev.customType);
break;
default:
break;
void Keymapper::resetInputState() {
for (uint i = 0; i < ARRAYSIZE(_joystickAxisPreviouslyPressed); i++) {
_joystickAxisPreviouslyPressed[i] = false;
}
if (hwInput) {
_actionToRemap->mapInput(hwInput);
_actionToRemap->getParent()->saveMappings();
_remapping = false;
_actionToRemap = 0;
mappedEvent.type = EVENT_GUI_REMAP_COMPLETE_ACTION;
list.push_back(mappedEvent);
}
void DelayedEventSource::scheduleEvent(const Event &ev, uint32 delayMillis) {
if (_delayedEvents.empty()) {
_delayedEffectiveTime = g_system->getMillis() + delayMillis;
delayMillis = 0;
}
return list;
DelayedEventsEntry entry = DelayedEventsEntry(delayMillis, ev);
_delayedEvents.push(entry);
}
bool DelayedEventSource::pollEvent(Event &event) {
if (_delayedEvents.empty()) {
return false;
}
uint32 now = g_system->getMillis();
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
#endif // #ifdef ENABLE_KEYMAPPER
+82 -136
View File
@@ -25,84 +25,53 @@
#include "common/scummsys.h"
#ifdef ENABLE_KEYMAPPER
#include "common/events.h"
#include "common/list.h"
#include "common/hashmap.h"
#include "common/stack.h"
#include "backends/keymapper/hardware-input.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";
class Keymapper : public Common::DefaultEventMapper {
struct Action;
class DelayedEventSource;
struct HardwareInput;
class HardwareInputSet;
class KeymapperDefaultBindings;
class Keymapper : public Common::EventMapper {
public:
struct MapRecord {
Keymap* keymap;
bool transparent;
bool global;
};
/* Nested class that represents a set of keymaps */
class Domain : public HashMap<String, Keymap*,
IgnoreCase_Hash, IgnoreCase_EqualTo> {
public:
Domain() : _configDomain(0) {}
~Domain() {
deleteAllKeyMaps();
}
void setConfigDomain(ConfigManager::Domain *confDom) {
_configDomain = confDom;
}
ConfigManager::Domain *getConfigDomain() {
return _configDomain;
}
void addKeymap(Keymap *map);
void deleteAllKeyMaps();
Keymap *getKeymap(const String& name);
private:
ConfigManager::Domain *_configDomain;
};
Keymapper(EventManager *eventMan);
~Keymapper();
// EventMapper interface
virtual List<Event> mapEvent(const Event &ev, EventSource *source);
virtual List<Event> mapEvent(const Event &ev);
/**
* Registers a HardwareInputSet with the Keymapper
* @note should only be called once (during backend initialisation)
* Registers a HardwareInputSet and platform-specific default mappings with the Keymapper
*
* Transfers ownership to the Keymapper
*/
void registerHardwareInputSet(HardwareInputSet *inputs);
/**
* Get a list of all registered HardwareInputs
*/
const List<const HardwareInput *> &getHardwareInputs() const {
assert(_hardwareInputs);
return _hardwareInputs->getHardwareInputs();
}
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.
*/
@@ -117,51 +86,26 @@ public:
/**
* Obtain a keymap of the given name from the keymapper.
* Game keymaps have priority over global keymaps
* @param name name of the keymap to return
* @param global set to true if returned keymap is global, false if game
* @param id name of the keymap to return
*/
Keymap *getKeymap(const String& name, bool *global = 0);
Keymap *getKeymap(const String &id) const;
/**
* Push a new keymap to the top of the active stack, activating
* it for use.
* @param name name of the keymap to push
* @param transparent if true keymapper will iterate down the
* stack if it cannot find a key in the new map
* @return true if successful
* Obtain a list of all the keymaps registered with the keymapper
*/
bool pushKeymap(const String& name, bool transparent = false);
const KeymapArray &getKeymaps() const { return _keymaps; }
/**
* Pop the top keymap off the active stack.
* @param name (optional) name of keymap expected to be popped
* if provided, will not pop unless name is the same
* as the top keymap
* reload the mappings for all the keymaps from the configuration manager
*/
void popKeymap(const char *name = 0);
void reloadAllMappings();
/**
* @brief Map a key press event.
* If the active keymap contains a Action mapped to the given key, then
* the Action's events are pushed into the EventManager's event queue.
* @param key key that was pressed
* @param keyDown true for key down, false for key up
* @return mapped events
* Set which kind of keymap is currently used to map events
*
* Keymaps with the global type are always enabled
*/
List<Event> mapKey(const KeyState& key, bool keyDown);
List<Event> mapNonKey(const HardwareInputCode code);
/**
* @brief Map a key down event.
* @see mapKey
*/
List<Event> mapKeyDown(const KeyState& key);
/**
* @brief Map a key up event.
* @see mapKey
*/
List<Event> mapKeyUp(const KeyState& key);
void setEnabledKeymapType(Keymap::KeymapType type);
/**
* Enable/disable the keymapper
@@ -169,72 +113,74 @@ public:
void setEnabled(bool enabled) { _enabled = enabled; }
/**
* @brief Activate remapping mode
* While this mode is active, any mappable event will be bound to the action
* provided.
* @param actionToRemap Action that is the target of the remap
* Clear all the keymaps and hardware input sets
*/
void startRemappingMode(Action *actionToRemap);
void clear();
/**
* @brief Force-stop the remapping mode
* Return a HardwareInput pointer for the given event
*/
void stopRemappingMode() { _remapping = false; }
HardwareInput findHardwareInput(const Event &event);
/**
* Query whether the keymapper is currently in the remapping mode
*/
bool isRemapping() const { return _remapping; }
/**
* Return a HardwareInput pointer for the given key state
*/
const HardwareInput *findHardwareInput(const KeyState& key);
/**
* Return a HardwareInput pointer for the given input code
*/
const HardwareInput *findHardwareInput(const HardwareInputCode code);
Domain& getGlobalDomain() { return _globalDomain; }
Domain& getGameDomain() { return _gameDomain; }
const Stack<MapRecord>& getActiveStack() const { return _activeMaps; }
void initKeymap(Keymap *keymap, ConfigManager::Domain *domain);
void reloadKeymapMappings(Keymap *keymap);
private:
EventManager *_eventMan;
HardwareInputSet *_hardwareInputs;
KeymapperDefaultBindings *_backendDefaultBindings;
DelayedEventSource *_delayedEventSource;
enum IncomingEventType {
kIncomingKeyDown,
kIncomingKeyUp,
kIncomingNonKey
kIncomingEventIgnored,
kIncomingEventStart,
kIncomingEventEnd,
kIncomingEventInstant
};
void initKeymap(Domain &domain, Keymap *keymap);
Domain _globalDomain;
Domain _gameDomain;
HardwareInputSet *_hardwareInputs;
void pushKeymap(Keymap *newMap, bool transparent, bool global);
Action *getAction(const KeyState& key);
List<Event> executeAction(const Action *act, IncomingEventType incomingType = kIncomingNonKey);
EventType convertDownToUp(EventType eventType);
List<Event> remap(const Event &ev);
EventManager *_eventMan;
enum {
kJoyAxisPressedTreshold = Common::JOYAXIS_MAX / 2,
kJoyAxisUnpressedTreshold = Common::JOYAXIS_MAX / 4
};
bool _enabled;
bool _remapping;
Keymap::KeymapType _enabledKeymapType;
Action *_actionToRemap;
Stack<MapRecord> _activeMaps;
HashMap<KeyState, Action *> _keysDown;
KeymapArray _keymaps;
bool _joystickAxisPreviouslyPressed[6];
bool mapEvent(const Event &ev, Keymap::KeymapType keymapType, List<Event> &mappedEvents);
Event executeAction(const Action *act, const Event &incomingEvent);
EventType convertStartToEnd(EventType eventType);
IncomingEventType convertToIncomingEventType(const Event &ev) const;
void hardcodedEventMapping(Event ev);
void resetInputState();
};
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 // #ifdef ENABLE_KEYMAPPER
#endif // #ifndef COMMON_KEYMAPPER_H
-460
View File
@@ -1,460 +0,0 @@
/* 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/remap-dialog.h"
#ifdef ENABLE_KEYMAPPER
#include "common/system.h"
#include "gui/gui-manager.h"
#include "gui/widgets/popup.h"
#include "gui/widgets/scrollbar.h"
#include "gui/ThemeEval.h"
#include "common/translation.h"
namespace Common {
enum {
kRemapCmd = 'REMP',
kClearCmd = 'CLER',
kCloseCmd = 'CLOS'
};
RemapDialog::RemapDialog()
: Dialog("KeyMapper"), _keymapTable(0), _topAction(0), _remapTimeout(0), _topKeymapIsGui(false) {
_keymapper = g_system->getEventManager()->getKeymapper();
assert(_keymapper);
_kmPopUpDesc = new GUI::StaticTextWidget(this, "KeyMapper.PopupDesc", _("Keymap:"));
_kmPopUp = new GUI::PopUpWidget(this, "KeyMapper.Popup");
_scrollBar = new GUI::ScrollBarWidget(this, 0, 0, 0, 0);
new GUI::ButtonWidget(this, "KeyMapper.Close", _("Close"), 0, kCloseCmd);
}
RemapDialog::~RemapDialog() {
free(_keymapTable);
}
void RemapDialog::open() {
const Stack<Keymapper::MapRecord> &activeKeymaps = _keymapper->getActiveStack();
if (activeKeymaps.size() > 0) {
if (activeKeymaps.top().keymap->getName() == Common::kGuiKeymapName)
_topKeymapIsGui = true;
// Add the entry for the "effective" special view. See RemapDialog::loadKeymap()
_kmPopUp->appendEntry(activeKeymaps.top().keymap->getName() + _(" (Effective)"));
}
Keymapper::Domain *_globalKeymaps = &_keymapper->getGlobalDomain();
Keymapper::Domain *_gameKeymaps = 0;
int keymapCount = 0;
if (_globalKeymaps->empty())
_globalKeymaps = 0;
else
keymapCount += _globalKeymaps->size();
if (ConfMan.getActiveDomain() != 0) {
_gameKeymaps = &_keymapper->getGameDomain();
if (_gameKeymaps->empty())
_gameKeymaps = 0;
else
keymapCount += _gameKeymaps->size();
}
if (activeKeymaps.size() > 1) {
keymapCount += activeKeymaps.size() - 1;
}
debug(3, "RemapDialog::open keymaps: %d", keymapCount);
_keymapTable = (Keymap **)malloc(sizeof(Keymap *) * keymapCount);
Keymapper::Domain::iterator it;
uint32 idx = 0;
if (activeKeymaps.size() > 1) {
int topIndex = activeKeymaps.size() - 1;
bool active = activeKeymaps[topIndex].transparent;
for (int i = topIndex - 1; i >= 0; --i) {
Keymapper::MapRecord mr = activeKeymaps[i];
// Add an entry for each keymap in the stack after the top keymap. Mark it Active if it is
// reachable or Blocked if an opaque keymap is on top of it thus blocking access to it.
_kmPopUp->appendEntry(mr.keymap->getName() + (active ? _(" (Active)") : _(" (Blocked)")), idx);
_keymapTable[idx++] = mr.keymap;
active &= mr.transparent;
}
}
_kmPopUp->appendEntry("");
// Now add entries for all known keymaps. Note that there will be duplicates with the stack entries.
if (_globalKeymaps) {
for (it = _globalKeymaps->begin(); it != _globalKeymaps->end(); ++it) {
// "global" means its keybindings apply to all games; saved in a global conf domain
_kmPopUp->appendEntry(it->_value->getName() + _(" (Global)"), idx);
_keymapTable[idx++] = it->_value;
}
}
if (_gameKeymaps) {
for (it = _gameKeymaps->begin(); it != _gameKeymaps->end(); ++it) {
// "game" means its keybindings are saved per-target
_kmPopUp->appendEntry(it->_value->getName() + _(" (Game)"), idx);
_keymapTable[idx++] = it->_value;
}
}
_changes = false;
Dialog::open();
_kmPopUp->setSelected(0);
loadKeymap();
}
void RemapDialog::close() {
_kmPopUp->clearEntries();
free(_keymapTable);
_keymapTable = 0;
if (_changes)
ConfMan.flushToDisk();
Dialog::close();
}
void RemapDialog::reflowLayout() {
Dialog::reflowLayout();
int buttonHeight = g_gui.xmlEval()->getVar("Globals.Button.Height", 0);
int scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
int16 areaX, areaY;
uint16 areaW, areaH;
g_gui.xmlEval()->getWidgetData((const String&)String("KeyMapper.KeymapArea"), areaX, areaY, areaW, areaH);
int spacing = g_gui.xmlEval()->getVar("Globals.KeyMapper.Spacing");
int keyButtonWidth = g_gui.xmlEval()->getVar("Globals.KeyMapper.ButtonWidth");
int clearButtonWidth = g_gui.xmlEval()->getVar("Globals.Line.Height");
int clearButtonHeight = g_gui.xmlEval()->getVar("Globals.Line.Height");
int colWidth = areaW - scrollbarWidth;
int labelWidth = colWidth - (keyButtonWidth + spacing + clearButtonWidth + spacing);
_rowCount = (areaH + spacing) / (buttonHeight + spacing);
debug(7, "rowCount = %d" , _rowCount);
if (colWidth <= 0 || _rowCount <= 0)
error("Remap dialog too small to display any keymaps");
_scrollBar->resize(areaX + areaW - scrollbarWidth, areaY, scrollbarWidth, areaH);
_scrollBar->_entriesPerPage = _rowCount;
_scrollBar->_numEntries = 1;
_scrollBar->recalc();
uint textYOff = (buttonHeight - kLineHeight) / 2;
uint clearButtonYOff = (buttonHeight - clearButtonHeight) / 2;
uint oldSize = _keymapWidgets.size();
uint newSize = _rowCount;
_keymapWidgets.reserve(newSize);
for (uint i = 0; i < newSize; i++) {
ActionWidgets widg;
if (i >= _keymapWidgets.size()) {
widg.actionText =
new GUI::StaticTextWidget(this, 0, 0, 0, 0, "", Graphics::kTextAlignLeft);
widg.keyButton =
new GUI::ButtonWidget(this, 0, 0, 0, 0, "", 0, kRemapCmd + i);
widg.clearButton = addClearButton(this, "", kClearCmd + i, 0, 0, clearButtonWidth, clearButtonHeight);
_keymapWidgets.push_back(widg);
} else {
widg = _keymapWidgets[i];
}
uint x = areaX;
uint y = areaY + (i) * (buttonHeight + spacing);
widg.keyButton->resize(x, y, keyButtonWidth, buttonHeight);
widg.clearButton->resize(x + keyButtonWidth + spacing, y + clearButtonYOff, clearButtonWidth, clearButtonHeight);
widg.actionText->resize(x + keyButtonWidth + spacing + clearButtonWidth + spacing, y + textYOff, labelWidth, kLineHeight);
}
while (oldSize > newSize) {
ActionWidgets widg = _keymapWidgets.remove_at(--oldSize);
removeWidget(widg.actionText);
delete widg.actionText;
removeWidget(widg.keyButton);
delete widg.keyButton;
removeWidget(widg.clearButton);
delete widg.clearButton;
}
}
void RemapDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
debug(3, "RemapDialog::handleCommand %u %u", cmd, data);
if (cmd >= kRemapCmd && cmd < kRemapCmd + _keymapWidgets.size()) {
startRemapping(cmd - kRemapCmd);
} else if (cmd >= kClearCmd && cmd < kClearCmd + _keymapWidgets.size()) {
clearMapping(cmd - kClearCmd);
} else if (cmd == GUI::kPopUpItemSelectedCmd) {
loadKeymap();
} else if (cmd == GUI::kSetPositionCmd) {
refreshKeymap();
} else if (cmd == kCloseCmd) {
close();
} else {
GUI::Dialog::handleCommand(sender, cmd, data);
}
}
void RemapDialog::clearMapping(uint i) {
if (_topAction + i >= _currentActions.size())
return;
debug(3, "clear the mapping %u", i);
Action *activeRemapAction = _currentActions[_topAction + i].action;
activeRemapAction->mapInput(0);
activeRemapAction->getParent()->saveMappings();
_changes = true;
// force refresh
stopRemapping(true);
refreshKeymap();
}
void RemapDialog::startRemapping(uint i) {
if (_topAction + i >= _currentActions.size())
return;
if (_keymapper->isRemapping()) {
// Handle a second click on the button as a stop to remapping
stopRemapping(true);
return;
}
_remapTimeout = g_system->getMillis() + kRemapTimeoutDelay;
Action *activeRemapAction = _currentActions[_topAction + i].action;
_keymapWidgets[i].keyButton->setLabel("...");
_keymapWidgets[i].keyButton->markAsDirty();
_keymapper->startRemappingMode(activeRemapAction);
}
void RemapDialog::stopRemapping(bool force) {
_topAction = -1;
refreshKeymap();
if (force)
_keymapper->stopRemappingMode();
}
void RemapDialog::handleKeyDown(Common::KeyState state) {
if (_keymapper->isRemapping())
return;
GUI::Dialog::handleKeyDown(state);
}
void RemapDialog::handleKeyUp(Common::KeyState state) {
if (_keymapper->isRemapping())
return;
GUI::Dialog::handleKeyUp(state);
}
void RemapDialog::handleOtherEvent(Event ev) {
if (ev.type == EVENT_GUI_REMAP_COMPLETE_ACTION) {
// _keymapper is telling us that something changed
_changes = true;
stopRemapping();
} else {
GUI::Dialog::handleOtherEvent(ev);
}
}
void RemapDialog::handleMouseDown(int x, int y, int button, int clickCount) {
if (_keymapper->isRemapping())
stopRemapping();
else
Dialog::handleMouseDown(x, y, button, clickCount);
}
void RemapDialog::handleTickle() {
if (_keymapper->isRemapping() && g_system->getMillis() > _remapTimeout)
stopRemapping(true);
Dialog::handleTickle();
}
void RemapDialog::loadKeymap() {
_currentActions.clear();
const Stack<Keymapper::MapRecord> &activeKeymaps = _keymapper->getActiveStack();
debug(3, "RemapDialog::loadKeymap active keymaps: %u", activeKeymaps.size());
if (!activeKeymaps.empty() && _kmPopUp->getSelected() == 0) {
// This is the "effective" view which shows all effective actions:
// - all of the topmost keymap action
// - all mapped actions that are reachable
List<const HardwareInput *> freeInputs(_keymapper->getHardwareInputs());
int topIndex = activeKeymaps.size() - 1;
// This is a WORKAROUND for changing the popup list selected item and changing it back
// to the top entry. Upon changing it back, the top keymap is always "gui".
if (!_topKeymapIsGui && activeKeymaps[topIndex].keymap->getName() == kGuiKeymapName)
--topIndex;
// add most active keymap's keys
Keymapper::MapRecord top = activeKeymaps[topIndex];
List<Action *>::iterator actIt;
debug(3, "RemapDialog::loadKeymap top keymap: %s", top.keymap->getName().c_str());
for (actIt = top.keymap->getActions().begin(); actIt != top.keymap->getActions().end(); ++actIt) {
Action *act = *actIt;
ActionInfo info = {act, false, act->description};
_currentActions.push_back(info);
if (act->getMappedInput())
freeInputs.remove(act->getMappedInput());
}
// loop through remaining finding mappings for unmapped keys
if (top.transparent && topIndex >= 0) {
for (int i = topIndex - 1; i >= 0; --i) {
Keymapper::MapRecord mr = activeKeymaps[i];
debug(3, "RemapDialog::loadKeymap keymap: %s", mr.keymap->getName().c_str());
List<const HardwareInput *>::iterator inputIt = freeInputs.begin();
const HardwareInput *input = *inputIt;
while (inputIt != freeInputs.end()) {
Action *act = 0;
if (input->type == kHardwareInputTypeKeyboard)
act = mr.keymap->getMappedAction(input->key);
else if (input->type == kHardwareInputTypeGeneric)
act = mr.keymap->getMappedAction(input->inputCode);
if (act) {
ActionInfo info = {act, true, act->description + " (" + mr.keymap->getName() + ")"};
_currentActions.push_back(info);
freeInputs.erase(inputIt);
} else {
++inputIt;
}
}
if (mr.transparent == false || freeInputs.empty())
break;
}
}
} else if (_kmPopUp->getSelected() != -1) {
// This is the regular view of a keymap that isn't the topmost one.
// It shows all of that keymap's actions
Keymap *km = _keymapTable[_kmPopUp->getSelectedTag()];
List<Action *>::iterator it;
for (it = km->getActions().begin(); it != km->getActions().end(); ++it) {
ActionInfo info = {*it, false, (*it)->description};
_currentActions.push_back(info);
}
}
// refresh scroll bar
_scrollBar->_currentPos = 0;
_scrollBar->_numEntries = _currentActions.size();
_scrollBar->recalc();
// force refresh
_topAction = -1;
refreshKeymap();
}
void RemapDialog::refreshKeymap() {
int newTopAction = _scrollBar->_currentPos;
if (newTopAction == _topAction)
return;
_topAction = newTopAction;
//_container->markAsDirty();
_scrollBar->markAsDirty();
uint actionI = _topAction;
for (uint widgetI = 0; widgetI < _keymapWidgets.size(); widgetI++) {
ActionWidgets& widg = _keymapWidgets[widgetI];
if (actionI < _currentActions.size()) {
debug(8, "RemapDialog::refreshKeymap actionI=%u", actionI);
ActionInfo& info = _currentActions[actionI];
widg.actionText->setLabel(info.description);
widg.actionText->setEnabled(!info.inherited);
const HardwareInput *mappedInput = info.action->getMappedInput();
if (mappedInput)
widg.keyButton->setLabel(mappedInput->description);
else
widg.keyButton->setLabel("-");
widg.actionText->setVisible(true);
widg.keyButton->setVisible(true);
widg.clearButton->setVisible(true);
actionI++;
} else {
widg.actionText->setVisible(false);
widg.keyButton->setVisible(false);
widg.clearButton->setVisible(false);
}
//widg.actionText->markAsDirty();
//widg.keyButton->markAsDirty();
}
// need to redraw entire Dialog so that invisible
// widgets disappear
g_gui.scheduleTopDialogRedraw();
}
} // End of namespace Common
#endif // #ifdef ENABLE_KEYMAPPER
-103
View File
@@ -1,103 +0,0 @@
/* 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 REMAP_DIALOG_H
#define REMAP_DIALOG_H
#include "common/scummsys.h"
#ifdef ENABLE_KEYMAPPER
#include "backends/keymapper/keymapper.h"
#include "gui/dialog.h"
namespace GUI {
class ButtonWidget;
class PopUpWidget;
class ScrollBarWidget;
class StaticTextWidget;
}
namespace Common {
class RemapDialog : public GUI::Dialog {
public:
RemapDialog();
virtual ~RemapDialog();
virtual void open();
virtual void close();
virtual void reflowLayout();
virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data);
virtual void handleKeyDown(Common::KeyState state);
virtual void handleKeyUp(Common::KeyState state);
virtual void handleMouseDown(int x, int y, int button, int clickCount);
virtual void handleTickle();
virtual void handleOtherEvent(Common::Event ev);
protected:
struct ActionWidgets {
GUI::StaticTextWidget *actionText;
GUI::ButtonWidget *keyButton;
GUI::ButtonWidget *clearButton;
};
struct ActionInfo {
Action *action;
bool inherited;
String description;
};
void loadKeymap();
void refreshKeymap();
void clearMapping(uint i);
void startRemapping(uint i);
void stopRemapping(bool force = false);
Keymapper *_keymapper;
Keymap** _keymapTable;
Array<ActionInfo> _currentActions;
int _topAction;
Rect _keymapArea;
GUI::StaticTextWidget *_kmPopUpDesc;
GUI::PopUpWidget *_kmPopUp;
//GUI::ContainerWidget *_container;
GUI::ScrollBarWidget *_scrollBar;
uint _rowCount;
Array<ActionWidgets> _keymapWidgets;
uint32 _remapTimeout;
static const uint32 kRemapTimeoutDelay = 3000;
bool _changes;
bool _topKeymapIsGui;
};
} // End of namespace Common
#endif // #ifdef ENABLE_KEYMAPPER
#endif // #ifndef REMAP_DIALOG_H
+316
View File
@@ -0,0 +1,316 @@
/* 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/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, "", true, ""),
_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::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 = getWidth() - (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++) {
uint x;
ActionRow &row = _actions[i];
if (previousKeymap != row.keymap) {
previousKeymap = row.keymap;
// Insert a keymap separator
x = 2 * spacing + keyButtonWidth;
KeymapTitleRow keymapTitle = _keymapSeparators[row.keymap];
if (keymapTitle.descriptionText) {
int descriptionWidth = getWidth() - x - spacing - resetButtonWidth - spacing;
descriptionWidth = MAX(0, descriptionWidth);
keymapTitle.descriptionText->resize(x, y + textYOff, descriptionWidth, kLineHeight);
keymapTitle.resetButton->resize(x + descriptionWidth, y, resetButtonWidth, buttonHeight);
}
y += buttonHeight + spacing;
}
x = spacing;
row.keyButton->resize(x, y, keyButtonWidth, buttonHeight);
x += keyButtonWidth + spacing;
row.actionText->resize(x, y + textYOff, labelWidth, kLineHeight);
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 if (cmd == kReflowCmd) {
reflowActionWidgets();
} 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;
_remapTimeout = g_system->getMillis() + kRemapTimeoutDelay;
_remapInputWatcher->startWatching();
_actions[actionIndex].keyButton->setLabel("...");
_actions[actionIndex].keyButton->setTooltip("");
_actions[actionIndex].keyButton->markAsDirty();
}
void RemapWidget::stopRemapping() {
_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 (KeymapArray::const_iterator km = _keymapTable.begin(); km != _keymapTable.end(); km++) {
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, "", Graphics::kTextAlignLeft, nullptr, GUI::ThemeEngine::kFontStyleNormal);
row.actionText->setLabel(row.action->description);
row.keyButton = new GUI::DropdownButtonWidget(widgetsBoss(), 0, 0, 0, 0, "", nullptr, 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);
String keysLabel;
for (uint j = 0; j < mappedInputs.size(); j++) {
if (!keysLabel.empty()) {
keysLabel += ", ";
}
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::kTextAlignLeft);
keymapTitle.resetButton = new GUI::ButtonWidget(widgetsBoss(), 0, 0, 0, 0, "", nullptr, kResetKeymapCmd + i);
// I18N: Button to reset keymap mappings to defaults
keymapTitle.resetButton->setLabel(_("Reset"));
keymapTitle.resetButton->setTooltip(_("Reset to defaults"));
}
}
}
} // End of namespace Common
+103
View File
@@ -0,0 +1,103 @@
/* 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 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 ScrollContainerWidget;
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;
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 kRemapTimeoutDelay = 3000;
bool _changes;
Array<ActionRow> _actions;
HashMap<Keymap *, KeymapTitleRow> _keymapSeparators;
};
} // End of namespace Common
#endif // #ifndef REMAP_WIDGET_H
+39
View File
@@ -0,0 +1,39 @@
/* 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/standard-actions.h"
namespace Common {
const char *kStandardActionInteract = "INTRCT";
const char *kStandardActionSkip = "SKIP";
const char *kStandardActionPause = "PAUSE";
const char *kStandardActionMoveUp = "UP";
const char *kStandardActionMoveDown = "DOWN";
const char *kStandardActionMoveLeft = "LEFT";
const char *kStandardActionMoveRight = "RIGHT";
const char *kStandardActionOpenMainMenu = "MENU";
const char *kStandardActionLoad = "LOAD";
const char *kStandardActionSave = "SAVE";
const char *kStandardActionOpenSettings = "OPTS";
} //namespace Common
+53
View File
@@ -0,0 +1,53 @@
/* 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_STANDARD_ACTIONS_H
#define BACKENDS_KEYMAPPER_STANDARD_ACTIONS_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 *kStandardActionInteract;
extern const char *kStandardActionSkip;
extern const char *kStandardActionPause;
extern const char *kStandardActionMoveUp;
extern const char *kStandardActionMoveDown;
extern const char *kStandardActionMoveLeft;
extern const char *kStandardActionMoveRight;
extern const char *kStandardActionOpenMainMenu;
extern const char *kStandardActionLoad;
extern const char *kStandardActionSave;
extern const char *kStandardActionOpenSettings;
} //namespace Common
#endif // BACKENDS_KEYMAPPER_STANDARD_ACTIONS_H
+224
View File
@@ -0,0 +1,224 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 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) {
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_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;
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 = 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
+91
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
+1 -1
View File
@@ -47,7 +47,7 @@ public:
*/
Audio::Mixer *getMixer() { return (Audio::Mixer *)_mixer; }
// Used by LinuxMoto Port
// Used by Event recorder
/**
* Pauses the audio system
+6 -6
View File
@@ -84,6 +84,10 @@ const OSystem::GraphicsMode *ModularBackend::getSupportedShaders() const {
return _graphicsManager->getSupportedShaders();
}
int ModularBackend::getDefaultShader() const {
return _graphicsManager->getDefaultShader();
}
bool ModularBackend::setShader(int id) {
return _graphicsManager->setShader(id);
}
@@ -199,8 +203,8 @@ void ModularBackend::updateScreen() {
#endif
}
void ModularBackend::setShakePos(int shakeOffset) {
_graphicsManager->setShakePos(shakeOffset);
void ModularBackend::setShakePos(int shakeXOffset, int shakeYOffset) {
_graphicsManager->setShakePos(shakeXOffset, shakeYOffset);
}
void ModularBackend::setFocusRectangle(const Common::Rect& rect) {
_graphicsManager->setFocusRectangle(rect);
@@ -264,10 +268,6 @@ void ModularBackend::setCursorPalette(const byte *colors, uint start, uint num)
_graphicsManager->setCursorPalette(colors, start, num);
}
void ModularBackend::saveScreenshot() {
_graphicsManager->saveScreenshot();
}
OSystem::MutexRef ModularBackend::createMutex() {
assert(_mutexManager);
return _mutexManager->createMutex();
+2 -2
View File
@@ -68,6 +68,7 @@ public:
virtual bool setGraphicsMode(int mode) override;
virtual int getGraphicsMode() const override;
virtual const GraphicsMode *getSupportedShaders() const override;
virtual int getDefaultShader() const override;
virtual int getShader() const override;
virtual bool setShader(int id) override;
virtual const GraphicsMode *getSupportedStretchModes() const override;
@@ -85,7 +86,6 @@ public:
virtual void suggestSideTextures(Graphics::Surface *left, Graphics::Surface *right); // ResidualVM specific method
virtual void initSizeHint(const Graphics::ModeList &modes) override;
virtual int getScreenChangeID() const override;
virtual void saveScreenshot() override; // ResidualVM specific method
virtual void beginGFXTransaction() override;
virtual OSystem::TransactionError endGFXTransaction() override;
@@ -98,7 +98,7 @@ public:
virtual void unlockScreen() override;
virtual void fillScreen(uint32 col) override;
virtual void updateScreen() override;
virtual void setShakePos(int shakeOffset) override;
virtual void setShakePos(int shakeXOffset, int shakeYOffset) override;
virtual void setFocusRectangle(const Common::Rect& rect) override;
virtual void clearFocusRectangle() override;
+47 -31
View File
@@ -8,6 +8,14 @@ MODULE_OBJS := \
events/default/default-events.o \
fs/abstract-fs.o \
fs/stdiostream.o \
keymapper/action.o \
keymapper/hardware-input.o \
keymapper/input-watcher.o \
keymapper/keymap.o \
keymapper/keymapper.o \
keymapper/remap-widget.o \
keymapper/standard-actions.o \
keymapper/virtual-mouse.o \
log/log.o \
saves/savefile.o \
saves/default/default-saves.o \
@@ -60,6 +68,7 @@ MODULE_OBJS += \
networking/curl/curlrequest.o \
networking/curl/curljsonrequest.o \
networking/curl/postrequest.o \
networking/curl/sessionrequest.o \
networking/curl/request.o
endif
@@ -94,15 +103,6 @@ MODULE_OBJS += \
plugins/elf/version.o
endif
ifdef ENABLE_KEYMAPPER
MODULE_OBJS += \
keymapper/action.o \
keymapper/hardware-input.o \
keymapper/keymap.o \
keymapper/keymapper.o \
keymapper/remap-dialog.o
endif
ifdef ENABLE_VKEYBD
MODULE_OBJS += \
vkeybd/image-map.o \
@@ -144,11 +144,22 @@ MODULE_OBJS += \
fs/posix/posix-fs.o \
fs/posix/posix-fs-factory.o \
fs/posix/posix-iostream.o \
fs/posix-drives/posix-drives-fs.o \
fs/posix-drives/posix-drives-fs-factory.o \
fs/chroot/chroot-fs-factory.o \
fs/chroot/chroot-fs.o \
plugins/posix/posix-provider.o \
saves/posix/posix-saves.o \
taskbar/unity/unity-taskbar.o
taskbar/unity/unity-taskbar.o \
dialogs/gtk/gtk-dialogs.o
ifdef USE_SPEECH_DISPATCHER
ifdef USE_TTS
MODULE_OBJS += \
text-to-speech/linux/linux-text-to-speech.o
endif
endif
endif
ifdef MACOSX
@@ -157,6 +168,12 @@ MODULE_OBJS += \
dialogs/macosx/macosx-dialogs.o \
updates/macosx/macosx-updates.o \
taskbar/macosx/macosx-taskbar.o
ifdef USE_TTS
MODULE_OBJS += \
text-to-speech/macosx/macosx-text-to-speech.o
endif
endif
ifdef WIN32
@@ -169,6 +186,12 @@ MODULE_OBJS += \
saves/windows/windows-saves.o \
updates/win32/win32-updates.o \
taskbar/win32/win32-taskbar.o
ifdef USE_TTS
MODULE_OBJS += \
text-to-speech/windows/windows-text-to-speech.o
endif
endif
ifeq ($(BACKEND),android)
@@ -176,6 +199,11 @@ MODULE_OBJS += \
mutex/pthread/pthread-mutex.o
endif
ifeq ($(BACKEND),androidsdl)
MODULE_OBJS += \
events/androidsdl/androidsdl-events.o
endif
ifdef AMIGAOS
MODULE_OBJS += \
fs/amigaos4/amigaos4-fs.o \
@@ -209,6 +237,11 @@ MODULE_OBJS += \
timer/tizen/timer.o
endif
ifeq ($(BACKEND),3ds)
MODULE_OBJS += \
plugins/3ds/3ds-provider.o
endif
ifeq ($(BACKEND),ds)
MODULE_OBJS += \
fs/ds/ds-fs.o \
@@ -219,19 +252,14 @@ endif
ifeq ($(BACKEND),dingux)
MODULE_OBJS += \
events/dinguxsdl/dinguxsdl-events.o \
graphics/dinguxsdl/dinguxsdl-graphics.o
graphics/downscalesdl/downscalesdl-graphics.o
endif
ifeq ($(BACKEND),gph)
MODULE_OBJS += \
events/gph/gph-events.o \
graphics/gph/gph-graphics.o
endif
ifeq ($(BACKEND),linuxmoto)
MODULE_OBJS += \
events/linuxmotosdl/linuxmotosdl-events.o \
graphics/linuxmotosdl/linuxmotosdl-graphics.o
graphics/gph/gph-graphics.o \
graphics/downscalesdl/downscalesdl-graphics.o
endif
ifeq ($(BACKEND),maemo)
@@ -266,7 +294,6 @@ MODULE_OBJS += \
fs/psp/psp-fs-factory.o \
fs/psp/psp-stream.o \
plugins/psp/psp-provider.o \
saves/psp/psp-saves.o \
timer/psp/timer.o
endif
@@ -282,8 +309,7 @@ endif
ifeq ($(BACKEND),samsungtv)
MODULE_OBJS += \
events/samsungtvsdl/samsungtvsdl-events.o \
graphics/samsungtvsdl/samsungtvsdl-graphics.o
events/samsungtvsdl/samsungtvsdl-events.o
endif
ifeq ($(BACKEND),webos)
@@ -291,16 +317,6 @@ MODULE_OBJS += \
events/webossdl/webossdl-events.o
endif
ifeq ($(BACKEND),wince)
MODULE_OBJS += \
events/wincesdl/wincesdl-events.o \
fs/windows/windows-fs.o \
fs/windows/windows-fs-factory.o \
graphics/wincesdl/wincesdl-graphics.o \
mixer/wincesdl/wincesdl-mixer.o \
plugins/win32/win32-provider.o
endif
ifeq ($(BACKEND),wii)
MODULE_OBJS += \
fs/wii/wii-fs.o \
+20 -4
View File
@@ -32,7 +32,7 @@ namespace Networking {
CurlRequest::CurlRequest(DataCallback cb, ErrorCallback ecb, Common::String url):
Request(cb, ecb), _url(url), _stream(nullptr), _headersList(nullptr), _bytesBuffer(nullptr),
_bytesBufferSize(0), _uploading(false), _usingPatch(false) {}
_bytesBufferSize(0), _uploading(false), _usingPatch(false), _keepAlive(false), _keepAliveIdle(120), _keepAliveInterval(60) {}
CurlRequest::~CurlRequest() {
delete _stream;
@@ -41,10 +41,10 @@ CurlRequest::~CurlRequest() {
NetworkReadStream *CurlRequest::makeStream() {
if (_bytesBuffer)
return new NetworkReadStream(_url.c_str(), _headersList, _bytesBuffer, _bytesBufferSize, _uploading, _usingPatch, true);
return new NetworkReadStream(_url.c_str(), _headersList, _bytesBuffer, _bytesBufferSize, _uploading, _usingPatch, true, _keepAlive, _keepAliveIdle, _keepAliveInterval);
if (!_formFields.empty() || !_formFiles.empty())
return new NetworkReadStream(_url.c_str(), _headersList, _formFields, _formFiles);
return new NetworkReadStream(_url.c_str(), _headersList, _postFields, _uploading, _usingPatch);
return new NetworkReadStream(_url.c_str(), _headersList, _formFields, _formFiles, _keepAlive, _keepAliveIdle, _keepAliveInterval);
return new NetworkReadStream(_url.c_str(), _headersList, _postFields, _uploading, _usingPatch, _keepAlive, _keepAliveIdle, _keepAliveInterval);
}
void CurlRequest::handle() {
@@ -137,6 +137,16 @@ void CurlRequest::usePut() { _uploading = true; }
void CurlRequest::usePatch() { _usingPatch = true; }
void CurlRequest::connectionKeepAlive(long idle, long interval) {
_keepAlive = true;
_keepAliveIdle = idle;
_keepAliveInterval = interval;
}
void CurlRequest::connectionClose() {
_keepAlive = false;
}
NetworkReadStreamResponse CurlRequest::execute() {
if (!_stream) {
_stream = makeStream();
@@ -148,4 +158,10 @@ NetworkReadStreamResponse CurlRequest::execute() {
const NetworkReadStream *CurlRequest::getNetworkReadStream() const { return _stream; }
void CurlRequest::wait(int spinlockDelay) {
while (state() == Networking::PROCESSING) {
g_system->delayMillis(spinlockDelay);
}
}
} // End of namespace Networking
+9
View File
@@ -50,6 +50,8 @@ protected:
uint32 _bytesBufferSize;
bool _uploading; //using PUT method
bool _usingPatch; //using PATCH method
bool _keepAlive;
long _keepAliveIdle, _keepAliveInterval;
virtual NetworkReadStream *makeStream();
@@ -85,6 +87,10 @@ public:
/** Remembers to use PATCH method when it would create NetworkReadStream. */
virtual void usePatch();
/** Remembers to use Connection: keep-alive or close. */
virtual void connectionKeepAlive(long idle = 120, long interval = 60);
virtual void connectionClose();
/**
* Starts this Request with ConnMan.
* @return its NetworkReadStream in NetworkReadStreamResponse.
@@ -93,6 +99,9 @@ public:
/** Returns Request's NetworkReadStream. */
const NetworkReadStream *getNetworkReadStream() const;
/** Waits for Request to be processed. Should be called after Request is put into ConnMan. */
void wait(int spinlockDelay = 5);
};
} // End of namespace Networking
+77 -49
View File
@@ -63,13 +63,18 @@ int NetworkReadStream::curlProgressCallbackOlder(void *p, double dltotal, double
return curlProgressCallback(p, (curl_off_t)dltotal, (curl_off_t)dlnow, (curl_off_t)ultotal, (curl_off_t)ulnow);
}
void NetworkReadStream::init(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
void NetworkReadStream::resetStream() {
_eos = _requestComplete = false;
_errorBuffer = (char *)calloc(CURL_ERROR_SIZE, 1);
if (!_errorBuffer)
_errorBuffer = (char *)calloc(CURL_ERROR_SIZE, 1);
_sendingContentsBuffer = nullptr;
_sendingContentsSize = _sendingContentsPos = 0;
_progressDownloaded = _progressTotal = 0;
_bufferCopy = nullptr;
}
void NetworkReadStream::initCurl(const char *url, curl_slist *headersList) {
resetStream();
_easy = curl_easy_init();
curl_easy_setopt(_easy, CURLOPT_WRITEFUNCTION, curlDataCallback);
@@ -87,6 +92,9 @@ void NetworkReadStream::init(const char *url, curl_slist *headersList, const byt
curl_easy_setopt(_easy, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(_easy, CURLOPT_PROGRESSFUNCTION, curlProgressCallbackOlder);
curl_easy_setopt(_easy, CURLOPT_PROGRESSDATA, this);
#if defined NINTENDO_SWITCH || defined ANDROID_PLAIN_PORT || defined PSP2
curl_easy_setopt(_easy, CURLOPT_SSL_VERIFYPEER, 0);
#endif
const char *caCertPath = ConnMan.getCaCertPath();
if (caCertPath) {
@@ -99,6 +107,33 @@ void NetworkReadStream::init(const char *url, curl_slist *headersList, const byt
curl_easy_setopt(_easy, CURLOPT_XFERINFOFUNCTION, curlProgressCallback);
curl_easy_setopt(_easy, CURLOPT_XFERINFODATA, this);
#endif
#if LIBCURL_VERSION_NUM >= 0x071900
// Added in libcurl 7.25.0
if (_keepAlive) {
curl_easy_setopt(_easy, CURLOPT_TCP_KEEPALIVE, 1L);
curl_easy_setopt(_easy, CURLOPT_TCP_KEEPIDLE, _keepAliveIdle);
curl_easy_setopt(_easy, CURLOPT_TCP_KEEPINTVL, _keepAliveInterval);
}
#endif
}
bool NetworkReadStream::reuseCurl(const char *url, curl_slist *headersList) {
if (!_keepAlive) {
warning("NetworkReadStream: Can't reuse curl handle (was not setup as keep-alive)");
return false;
}
resetStream();
curl_easy_setopt(_easy, CURLOPT_URL, url);
curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, headersList);
curl_easy_setopt(_easy, CURLOPT_USERAGENT, gScummVMFullVersion); // in case headersList rewrites it
return true;
}
void NetworkReadStream::setupBufferContents(const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
if (uploading) {
curl_easy_setopt(_easy, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(_easy, CURLOPT_READDATA, this);
@@ -123,43 +158,7 @@ void NetworkReadStream::init(const char *url, curl_slist *headersList, const byt
ConnMan.registerEasyHandle(_easy);
}
void NetworkReadStream::init(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles) {
_eos = _requestComplete = false;
_errorBuffer = (char *)calloc(CURL_ERROR_SIZE, 1);
_sendingContentsBuffer = nullptr;
_sendingContentsSize = _sendingContentsPos = 0;
_progressDownloaded = _progressTotal = 0;
_bufferCopy = nullptr;
_easy = curl_easy_init();
curl_easy_setopt(_easy, CURLOPT_WRITEFUNCTION, curlDataCallback);
curl_easy_setopt(_easy, CURLOPT_WRITEDATA, this); //so callback can call us
curl_easy_setopt(_easy, CURLOPT_PRIVATE, this); //so ConnectionManager can call us when request is complete
curl_easy_setopt(_easy, CURLOPT_HEADER, 0L);
curl_easy_setopt(_easy, CURLOPT_HEADERDATA, this);
curl_easy_setopt(_easy, CURLOPT_HEADERFUNCTION, curlHeadersCallback);
curl_easy_setopt(_easy, CURLOPT_URL, url);
curl_easy_setopt(_easy, CURLOPT_ERRORBUFFER, _errorBuffer);
curl_easy_setopt(_easy, CURLOPT_VERBOSE, 0L);
curl_easy_setopt(_easy, CURLOPT_FOLLOWLOCATION, 1L); //probably it's OK to have it always on
curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, headersList);
curl_easy_setopt(_easy, CURLOPT_USERAGENT, gScummVMFullVersion);
curl_easy_setopt(_easy, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(_easy, CURLOPT_PROGRESSFUNCTION, curlProgressCallbackOlder);
curl_easy_setopt(_easy, CURLOPT_PROGRESSDATA, this);
const char *caCertPath = ConnMan.getCaCertPath();
if (caCertPath) {
curl_easy_setopt(_easy, CURLOPT_CAINFO, caCertPath);
}
#if LIBCURL_VERSION_NUM >= 0x072000
// CURLOPT_XFERINFOFUNCTION introduced in libcurl 7.32.0
// CURLOPT_PROGRESSFUNCTION is used as a backup plan in case older version is used
curl_easy_setopt(_easy, CURLOPT_XFERINFOFUNCTION, curlProgressCallback);
curl_easy_setopt(_easy, CURLOPT_XFERINFODATA, this);
#endif
void NetworkReadStream::setupFormMultipart(Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles) {
// set POST multipart upload form fields/files
struct curl_httppost *formpost = nullptr;
struct curl_httppost *lastptr = nullptr;
@@ -191,23 +190,52 @@ void NetworkReadStream::init(const char *url, curl_slist *headersList, Common::H
}
curl_easy_setopt(_easy, CURLOPT_HTTPPOST, formpost);
ConnMan.registerEasyHandle(_easy);
}
NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, Common::String postFields, bool uploading, bool usingPatch) :
_backingStream(DisposeAfterUse::YES) {
init(url, headersList, (const byte *)postFields.c_str(), postFields.size(), uploading, usingPatch, false);
NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, Common::String postFields, bool uploading, bool usingPatch, bool keepAlive, long keepAliveIdle, long keepAliveInterval):
_backingStream(DisposeAfterUse::YES), _keepAlive(keepAlive), _keepAliveIdle(keepAliveIdle), _keepAliveInterval(keepAliveInterval), _errorBuffer(nullptr) {
initCurl(url, headersList);
setupBufferContents((const byte *)postFields.c_str(), postFields.size(), uploading, usingPatch, false);
}
NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles) :
_backingStream(DisposeAfterUse::YES) {
init(url, headersList, formFields, formFiles);
NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles, bool keepAlive, long keepAliveIdle, long keepAliveInterval):
_backingStream(DisposeAfterUse::YES), _keepAlive(keepAlive), _keepAliveIdle(keepAliveIdle), _keepAliveInterval(keepAliveInterval), _errorBuffer(nullptr) {
initCurl(url, headersList);
setupFormMultipart(formFields, formFiles);
}
NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) :
_backingStream(DisposeAfterUse::YES) {
init(url, headersList, buffer, bufferSize, uploading, usingPatch, post);
NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post, bool keepAlive, long keepAliveIdle, long keepAliveInterval):
_backingStream(DisposeAfterUse::YES), _keepAlive(keepAlive), _keepAliveIdle(keepAliveIdle), _keepAliveInterval(keepAliveInterval), _errorBuffer(nullptr) {
initCurl(url, headersList);
setupBufferContents(buffer, bufferSize, uploading, usingPatch, post);
}
bool NetworkReadStream::reuse(const char *url, curl_slist *headersList, Common::String postFields, bool uploading, bool usingPatch) {
if (!reuseCurl(url, headersList))
return false;
_backingStream = Common::MemoryReadWriteStream(DisposeAfterUse::YES);
setupBufferContents((const byte *)postFields.c_str(), postFields.size(), uploading, usingPatch, false);
return true;
}
bool NetworkReadStream::reuse(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles) {
if (!reuseCurl(url, headersList))
return false;
_backingStream = Common::MemoryReadWriteStream(DisposeAfterUse::YES);
setupFormMultipart(formFields, formFiles);
return true;
}
bool NetworkReadStream::reuse(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
if (!reuseCurl(url, headersList))
return false;
_backingStream = Common::MemoryReadWriteStream(DisposeAfterUse::YES);
setupBufferContents(buffer, bufferSize, uploading, usingPatch, post);
return true;
}
NetworkReadStream::~NetworkReadStream() {
+26 -7
View File
@@ -37,16 +37,22 @@ namespace Networking {
class NetworkReadStream: public Common::ReadStream {
CURL *_easy;
Common::MemoryReadWriteStream _backingStream;
bool _keepAlive;
long _keepAliveIdle, _keepAliveInterval;
bool _eos, _requestComplete;
char *_errorBuffer;
const byte *_sendingContentsBuffer;
uint32 _sendingContentsSize;
uint32 _sendingContentsPos;
byte* _bufferCopy; // To use with old curl version where CURLOPT_COPYPOSTFIELDS is not available
byte *_bufferCopy; // To use with old curl version where CURLOPT_COPYPOSTFIELDS is not available
Common::String _responseHeaders;
uint64 _progressDownloaded, _progressTotal;
void init(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post);
void init(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles);
void resetStream();
void initCurl(const char *url, curl_slist *headersList);
bool reuseCurl(const char *url, curl_slist *headersList);
void setupBufferContents(const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post);
void setupFormMultipart(Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles);
/**
* Fills the passed buffer with _sendingContentsBuffer contents.
@@ -70,16 +76,27 @@ class NetworkReadStream: public Common::ReadStream {
static int curlProgressCallbackOlder(void *p, double dltotal, double dlnow, double ultotal, double ulnow);
public:
/** Send <postFields>, using POST by default. */
NetworkReadStream(const char *url, curl_slist *headersList, Common::String postFields, bool uploading = false, bool usingPatch = false);
NetworkReadStream(const char *url, curl_slist *headersList, Common::String postFields, bool uploading = false, bool usingPatch = false, bool keepAlive = false, long keepAliveIdle = 120, long keepAliveInterval = 60);
/** Send <formFields>, <formFiles>, using POST multipart/form. */
NetworkReadStream(
const char *url, curl_slist *headersList,
Common::HashMap<Common::String, Common::String> formFields,
Common::HashMap<Common::String, Common::String> formFiles);
/** Send <buffer, using POST by default. */
NetworkReadStream(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true);
Common::HashMap<Common::String, Common::String> formFiles,
bool keepAlive = false, long keepAliveIdle = 120, long keepAliveInterval = 60);
/** Send <buffer>, using POST by default. */
NetworkReadStream(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true, bool keepAlive = false, long keepAliveIdle = 120, long keepAliveInterval = 60);
virtual ~NetworkReadStream();
/** Send <postFields>, using POST by default. */
bool reuse(const char *url, curl_slist *headersList, Common::String postFields, bool uploading = false, bool usingPatch = false);
/** Send <formFields>, <formFiles>, using POST multipart/form. */
bool reuse(
const char *url, curl_slist *headersList,
Common::HashMap<Common::String, Common::String> formFields,
Common::HashMap<Common::String, Common::String> formFiles);
/** Send <buffer>, using POST by default. */
bool reuse(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true);
/**
* Returns true if a read failed because the stream end has been reached.
* This flag is cleared by clearErr().
@@ -150,6 +167,8 @@ public:
/** Used in curl progress callback to pass current downloaded/total values. */
void setProgress(uint64 downloaded, uint64 total);
bool keepAlive() const { return _keepAlive; }
};
} // End of namespace Networking
+129
View File
@@ -0,0 +1,129 @@
/* 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/networking/curl/postrequest.h"
#include "backends/networking/curl/connectionmanager.h"
#include "backends/networking/curl/curljsonrequest.h"
#include "backends/networking/curl/networkreadstream.h"
#include "common/json.h"
namespace Networking {
PostRequest::PostRequest(Common::String url, Networking::JSONValueCallback cb, Networking::ErrorCallback ecb):
Networking::Request(nullptr, ecb), _url(url), _jsonCallback(cb),
_workingRequest(nullptr), _ignoreCallback(false), _postData(nullptr), _postLen(0), _jsonData(nullptr) {
_contentType = "application/octet-stream";
}
PostRequest::~PostRequest() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
delete _jsonCallback;
}
void PostRequest::setPostData(byte *postData, int postLen) {
_postData = postData;
_postLen = postLen;
_contentType = "application/octet-stream";
}
void PostRequest::setJSONData(Common::JSONValue *jsonData) {
_jsonData = jsonData;
_contentType = "application/json";
}
void PostRequest::start() {
_ignoreCallback = true;
if (_workingRequest)
_workingRequest->finish();
_ignoreCallback = false;
Networking::JsonCallback innerCallback = new Common::Callback<PostRequest, Networking::JsonResponse>(this, &PostRequest::responseCallback);
Networking::ErrorCallback errorResponseCallback = new Common::Callback<PostRequest, Networking::ErrorResponse>(this, &PostRequest::errorCallback);
Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorResponseCallback, _url);
if (_postData && _jsonData) {
warning("Error, both data and JSON present while calling %s", _url.c_str());
_jsonData = nullptr;
}
request->addHeader(Common::String::format("Content-Type: %s", _contentType.c_str()));
if (_postData)
request->setBuffer(_postData, _postLen);
if (_jsonData)
request->addPostField(Common::JSON::stringify(_jsonData));
_workingRequest = ConnMan.addRequest(request);
}
void PostRequest::responseCallback(Networking::JsonResponse response) {
Common::JSONValue *json = response.value;
_workingRequest = nullptr;
if (_ignoreCallback) {
delete json;
return;
}
if (response.request) _date = response.request->date();
Networking::ErrorResponse error(this, "PostRequest::responseCallback: unknown error");
Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
if (rq && rq->getNetworkReadStream())
error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
if (json == nullptr) {
error.response = "Failed to parse JSON, null passed!";
finishError(error);
return;
}
finishSuccess();
if (_jsonCallback)
(*_jsonCallback)(json);
delete json;
}
void PostRequest::errorCallback(Networking::ErrorResponse error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
if (error.request)
_date = error.request->date();
finishError(error);
}
void PostRequest::handle() {}
void PostRequest::restart() { start(); }
Common::String PostRequest::date() const { return _date; }
} // End of namespace Networking
+64
View File
@@ -0,0 +1,64 @@
/* 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_NETWORKING_CURL_POSTREQUEST_H
#define BACKENDS_NETWORKING_CURL_POSTREQUEST_H
#include "backends/networking/curl/request.h"
#include "backends/networking/curl/curljsonrequest.h"
namespace Networking {
class PostRequest: public Networking::Request {
Common::String _url;
Networking::JSONValueCallback _jsonCallback;
Request *_workingRequest;
bool _ignoreCallback;
Common::String _date;
byte *_postData;
int _postLen;
Common::JSONValue *_jsonData;
Common::String _contentType;
void responseCallback(Networking::JsonResponse response);
void errorCallback(Networking::ErrorResponse error);
public:
PostRequest(Common::String url, Networking::JSONValueCallback cb, Networking::ErrorCallback ecb);
virtual ~PostRequest();
void start();
void setPostData(byte *postData, int postLen);
void setJSONData(Common::JSONValue *jsonData);
void setContentType(Common::String type) { _contentType = type; }
virtual void handle();
virtual void restart();
virtual Common::String date() const;
};
} // End of namespace Networking
#endif
+77
View File
@@ -0,0 +1,77 @@
/* 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.
*
*/
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "backends/networking/curl/session.h"
namespace Networking {
Session::Session(Common::String prefix):
_prefix(prefix), _request(nullptr) {}
Session::~Session() {
close();
}
SessionRequest *Session::get(Common::String url, DataCallback cb, ErrorCallback ecb) {
// check url prefix
if (!_prefix.empty()) {
if (url.contains("://")) {
if (url.size() < _prefix.size() || url.find(_prefix) != 0) {
warning("Session: given URL does not match the prefix!\n\t%s\n\t%s", url.c_str(), _prefix.c_str());
return nullptr;
}
} else {
// if no schema given, just append <url> to <_prefix>
Common::String newUrl = _prefix;
if (newUrl.lastChar() != '/' && (url.size() > 0 && url.firstChar() != '/'))
newUrl += "/";
newUrl += url;
url = newUrl;
}
}
// check if request has finished (ready to be replaced)
if (_request) {
if (!_request->complete()) {
warning("Session: can't reuse Request that is being processed");
return nullptr;
}
}
if (!_request) {
_request = new Networking::SessionRequest(url, cb, ecb); // automatically added to ConnMan
_request->connectionKeepAlive();
} else {
_request->reuse(url, cb, ecb);
}
return _request;
}
void Session::close() {
if (_request)
_request->close();
}
} // End of namespace Networking
+45
View File
@@ -0,0 +1,45 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 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_NETWORKING_CURL_SESSION_H
#define BACKENDS_NETWORKING_CURL_SESSION_H
#include "backends/networking/curl/sessionrequest.h"
namespace Networking {
class Session {
protected:
Common::String _prefix;
SessionRequest *_request;
public:
Session(Common::String prefix = "");
~Session();
SessionRequest *get(Common::String url, DataCallback cb = nullptr, ErrorCallback ecb = nullptr);
void close();
};
} // End of namespace Networking
#endif
+177
View File
@@ -0,0 +1,177 @@
/* 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.
*
*/
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include <curl/curl.h>
#include "backends/networking/curl/connectionmanager.h"
#include "backends/networking/curl/networkreadstream.h"
#include "backends/networking/curl/sessionrequest.h"
#include "common/debug.h"
#include "common/json.h"
namespace Networking {
SessionRequest::SessionRequest(Common::String url, DataCallback cb, ErrorCallback ecb):
CurlRequest(cb, ecb, url), _contentsStream(DisposeAfterUse::YES),
_buffer(new byte[CURL_SESSION_REQUEST_BUFFER_SIZE]), _text(nullptr),
_started(false), _complete(false), _success(false) {
// automatically go under ConnMan control so nobody would be able to leak the memory
// but, we don't need it to be working just yet
_state = PAUSED;
ConnMan.addRequest(this);
}
SessionRequest::~SessionRequest() {
delete[] _buffer;
}
bool SessionRequest::reuseStream() {
if (!_stream) {
return false;
}
if (_bytesBuffer)
return _stream->reuse(_url.c_str(), _headersList, _bytesBuffer, _bytesBufferSize, _uploading, _usingPatch, true);
if (!_formFields.empty() || !_formFiles.empty())
return _stream->reuse(_url.c_str(), _headersList, _formFields, _formFiles);
return _stream->reuse(_url.c_str(), _headersList, _postFields, _uploading, _usingPatch);
}
char *SessionRequest::getPreparedContents() {
//write one more byte in the end
byte zero[1] = {0};
_contentsStream.write(zero, 1);
//replace all "bad" bytes with '.' character
byte *result = _contentsStream.getData();
uint32 size = _contentsStream.size();
//make it zero-terminated string
result[size - 1] = '\0';
return (char *)result;
}
void SessionRequest::finishError(ErrorResponse error) {
_complete = true;
_success = false;
CurlRequest::finishError(error);
}
void SessionRequest::finishSuccess() {
_state = PAUSED;
_complete = true;
_success = true;
if (_callback)
(*_callback)(DataResponse(this, text()));
}
void SessionRequest::start() {
if (_state != PAUSED || _started) {
warning("Can't start() SessionRequest as it is already started");
return;
}
_state = PROCESSING;
_started = true;
}
void SessionRequest::startAndWait() {
start();
wait();
}
void SessionRequest::reuse(Common::String url, DataCallback cb, ErrorCallback ecb) {
_url = url;
delete _callback;
delete _errorCallback;
_callback = cb;
_errorCallback = ecb;
restart();
}
void SessionRequest::handle() {
if (!_stream) _stream = makeStream();
if (_stream) {
uint32 readBytes = _stream->read(_buffer, CURL_SESSION_REQUEST_BUFFER_SIZE);
if (readBytes != 0)
if (_contentsStream.write(_buffer, readBytes) != readBytes)
warning("SessionRequest: unable to write all the bytes into MemoryWriteStreamDynamic");
if (_stream->eos()) {
finishSuccess();
}
}
}
void SessionRequest::restart() {
if (_stream) {
bool deleteStream = true;
if (_keepAlive && reuseStream()) {
deleteStream = false;
}
if (deleteStream) {
delete _stream;
_stream = nullptr;
}
}
_contentsStream = Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES);
_text = nullptr;
_complete = false;
_success = false;
_started = false;
//with no stream available next handle() will create another one
}
void SessionRequest::close() {
_state = FINISHED;
}
bool SessionRequest::complete() {
return _complete;
}
bool SessionRequest::success() {
return _success;
}
char *SessionRequest::text() {
if (_text == nullptr)
_text = getPreparedContents();
return _text;
}
Common::JSONValue *SessionRequest::json() {
return Common::JSON::parse(text());
}
} // End of namespace Networking
+73
View File
@@ -0,0 +1,73 @@
/* 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_NETWORKING_CURL_SESSIONREQUEST_H
#define BACKENDS_NETWORKING_CURL_SESSIONREQUEST_H
#include "backends/networking/curl/curlrequest.h"
#include "common/memstream.h"
#include "common/json.h"
namespace Networking {
#define CURL_SESSION_REQUEST_BUFFER_SIZE 512 * 1024
class SessionRequest: public CurlRequest {
protected:
Common::MemoryWriteStreamDynamic _contentsStream;
byte *_buffer;
char *_text;
bool _started, _complete, _success;
bool reuseStream();
/** Prepares raw bytes from _contentsStream. */
char *getPreparedContents();
virtual void finishError(ErrorResponse error);
virtual void finishSuccess();
public:
SessionRequest(Common::String url, DataCallback cb = nullptr, ErrorCallback ecb = nullptr);
virtual ~SessionRequest();
void start();
void startAndWait();
void reuse(Common::String url, DataCallback cb = nullptr, ErrorCallback ecb = nullptr);
virtual void handle();
virtual void restart();
/** This request DOES NOT delete automatically after calling callbacks. It gets PAUSED, and in order to make it FINISHED (i.e. delete), this method MUST be called. */
void close();
bool complete();
bool success();
char *text();
Common::JSONValue *json();
};
} // End of namespace Networking
#endif
@@ -56,7 +56,7 @@ Common::String encodeHtmlEntities(Common::String s) {
result += "&gt;";
else if (s[i] == '&')
result += "&amp;";
else if (s[i] > (byte)0x7F)
else if ((byte)s[i] > (byte)0x7F)
result += Common::String::format("&#%d;", (int)s[i]);
else result += s[i];
return result;
+12 -1
View File
@@ -28,6 +28,7 @@
#include "common/file.h"
#include "common/translation.h"
#include "common/unzip.h"
#include "common/encoding.h"
namespace Networking {
@@ -172,7 +173,17 @@ bool HandlerUtils::permittedPath(const Common::String path) {
}
Common::String HandlerUtils::toUtf8(const char *text) {
// FIXME: Convert the GUI to use UTF8
#ifdef USE_TRANSLATION
Common::String guiEncoding = TransMan.getCurrentCharset();
if (guiEncoding != "ASCII") {
char *utf8Text = Common::Encoding::convert("utf-8", guiEncoding, text, strlen(text));
if (utf8Text != nullptr) {
Common::String str(utf8Text);
free(utf8Text);
return str;
}
}
#endif
return Common::String(text);
}
@@ -249,7 +249,7 @@ void LocalWebserver::handleClient(uint32 i) {
// fall through
case BAD_REQUEST:
setClientGetHandler(_client[i], "<html><head><title>ScummVM - Bad Request</title></head><body>BAD REQUEST</body></html>", 400);
setClientGetHandler(_client[i], "<html><head><title>ResidualVM - Bad Request</title></head><body>BAD REQUEST</body></html>", 400);
break;
case BEING_HANDLED:
_client[i].handle();
+13 -13
View File
@@ -31,15 +31,15 @@
int main(int argc, char *argv[]) {
// The following will gather the application name and add the install path
// to a variable in AmigaOS4's ENV(ARC) system. It will be placed in AppPaths
// so that ScummVM can become AmiUpdate aware
// The following will gather the application name and add the binary path
// to a variable in AmigaOS4's ENV(ARC) system. It will then be placed in
// AppPaths, so that ScummVM becomes AmiUpdate aware.
const char *const appname = "ResidualVM";
BPTR lock;
APTR oldwin;
// Obtain a lock to the home directory
// Obtain a lock to the home directory.
if ((lock = IDOS->GetProgramDir())) {
TEXT progpath[2048];
TEXT apppath[1024] = "AppPaths";
@@ -49,36 +49,36 @@ int main(int argc, char *argv[]) {
sizeof(progpath),
DN_FULLPATH)) {
// Stop any "Insert volume..." type requesters
// Stop any "Insert volume..." type requesters.
oldwin = IDOS->SetProcWindow((APTR)-1);
// Finally, set the variable to the path the executable was run from
// Finally, set the variable to the path the executable was run from.
IDOS->AddPart( apppath, appname, 1024);
IDOS->SetVar( apppath, progpath, -1, GVF_GLOBAL_ONLY|GVF_SAVE_VAR );
// Turn system requesters back on
// Turn system requesters back on.
IDOS->SetProcWindow( oldwin );
}
}
// Set up a stack cookie to avoid crashes from a stack set too low
static const char *stack_cookie __attribute__((used)) = "$STACK: 600000";
// Set up a stack cookie to avoid crashes from a stack set too low.
static const char *stack_cookie __attribute__((used)) = "$STACK: 2048000";
// Create our OSystem instance
// Create our OSystem instance.
g_system = new OSystem_AmigaOS();
assert(g_system);
// Pre initialize the backend
// Pre-initialize the backend.
((OSystem_AmigaOS *)g_system)->init();
#ifdef DYNAMIC_MODULES
PluginManager::instance().addPluginProvider(new SDLPluginProvider());
#endif
// Invoke the actual ScummVM main entry point
// Invoke the actual ScummVM main entry point.
int res = scummvm_main(argc, argv);
// Free OSystem
// Free OSystem.
g_system->destroy();
return res;
+3 -2
View File
@@ -1,5 +1,6 @@
# Special target to create an AmigaOS snapshot installation
amigaosdist: $(EXECUTABLE)
# Special target to create an AmigaOS snapshot installation.
# AmigaOS shell doesn't like indented comments.
amigaosdist: $(EXECUTABLE) $(PLUGINS)
mkdir -p $(AMIGAOSPATH)
mkdir -p $(AMIGAOSPATH)/themes
mkdir -p $(AMIGAOSPATH)/extras
+32 -24
View File
@@ -137,6 +137,10 @@ NSString *constructNSStringFromCString(const char *rawCString, CFStringEncoding
}
static NSMenu *addMenu(const char *title, CFStringEncoding encoding, NSString *key, SEL setAs) {
if (setAs && ![NSApp respondsToSelector:setAs]) {
return nil;
}
NSString *str = constructNSStringFromCString(title, encoding);
NSMenu *menu = [[NSMenu alloc] initWithTitle:str];
@@ -178,10 +182,6 @@ void releaseMenu() {
}
void replaceApplicationMenuItems() {
if (!delegate) {
delegate = [[ScummVMMenuHandler alloc] init];
}
// We cannot use [[NSApp mainMenu] removeAllItems] as removeAllItems was added in OS X 10.6
// So remove the SDL generated menus one by one instead.
while ([[NSApp mainMenu] numberOfItems] > 0) {
@@ -200,30 +200,38 @@ void replaceApplicationMenuItems() {
#endif
NSMenu *appleMenu = addMenu("ResidualVM", kCFStringEncodingASCII, @"", @selector(setAppleMenu:));
addMenuItem(_("About ResidualVM"), stringEncoding, nil, @selector(orderFrontStandardAboutPanel:), @"", appleMenu);
[appleMenu addItem:[NSMenuItem separatorItem]];
addMenuItem(_("Hide ResidualVM"), stringEncoding, nil, @selector(hide:), @"h", appleMenu);
addMenuItem(_("Hide Others"), stringEncoding, nil, @selector(hideOtherApplications:), @"h", appleMenu, (NSEventModifierFlagOption|NSEventModifierFlagCommand));
addMenuItem(_("Show All"), stringEncoding, nil, @selector(unhideAllApplications:), @"", appleMenu);
[appleMenu addItem:[NSMenuItem separatorItem]];
addMenuItem(_("Quit ResidualVM"), stringEncoding, nil, @selector(terminate:), @"q", appleMenu);
if (appleMenu) {
addMenuItem(_("About ResidualVM"), stringEncoding, nil, @selector(orderFrontStandardAboutPanel:), @"", appleMenu);
[appleMenu addItem:[NSMenuItem separatorItem]];
addMenuItem(_("Hide ResidualVM"), stringEncoding, nil, @selector(hide:), @"h", appleMenu);
addMenuItem(_("Hide Others"), stringEncoding, nil, @selector(hideOtherApplications:), @"h", appleMenu, (NSEventModifierFlagOption|NSEventModifierFlagCommand));
addMenuItem(_("Show All"), stringEncoding, nil, @selector(unhideAllApplications:), @"", appleMenu);
[appleMenu addItem:[NSMenuItem separatorItem]];
addMenuItem(_("Quit ResidualVM"), stringEncoding, nil, @selector(terminate:), @"q", appleMenu);
}
NSMenu *windowMenu = addMenu(_("Window"), stringEncoding, @"", @selector(setWindowsMenu:));
addMenuItem(_("Minimize"), stringEncoding, nil, @selector(performMiniaturize:), @"m", windowMenu);
if (windowMenu) {
addMenuItem(_("Minimize"), stringEncoding, nil, @selector(performMiniaturize:), @"m", windowMenu);
}
NSMenu *helpMenu = addMenu(_("Help"), stringEncoding, @"", @selector(setHelpMenu:));
addMenuItem(_("User Manual"), stringEncoding, delegate, @selector(openUserManual), @"", helpMenu);
[helpMenu addItem:[NSMenuItem separatorItem]];
addMenuItem(_("General Information"), stringEncoding, delegate, @selector(openReadme), @"", helpMenu);
addMenuItem(_("What's New in ResidualVM"), stringEncoding, delegate, @selector(openNews), @"", helpMenu);
[helpMenu addItem:[NSMenuItem separatorItem]];
addMenuItem(_("Credits"), stringEncoding, delegate, @selector(openCredits), @"", helpMenu);
addMenuItem(_("GPL License"), stringEncoding, delegate, @selector(openLicenseGPL), @"", helpMenu);
addMenuItem(_("LGPL License"), stringEncoding, delegate, @selector(openLicenseLGPL), @"", helpMenu);
addMenuItem(_("Freefont License"), stringEncoding, delegate, @selector(openLicenseFreefont), @"", helpMenu);
addMenuItem(_("OFL License"), stringEncoding, delegate, @selector(openLicenseOFL), @"", helpMenu);
addMenuItem(_("BSD License"), stringEncoding, delegate, @selector(openLicenseBSD), @"", helpMenu);
if (helpMenu) {
if (!delegate) {
delegate = [[ScummVMMenuHandler alloc] init];
}
addMenuItem(_("User Manual"), stringEncoding, delegate, @selector(openUserManual), @"", helpMenu);
[helpMenu addItem:[NSMenuItem separatorItem]];
addMenuItem(_("General Information"), stringEncoding, delegate, @selector(openReadme), @"", helpMenu);
addMenuItem(_("What's New in ResidualVM"), stringEncoding, delegate, @selector(openNews), @"", helpMenu);
[helpMenu addItem:[NSMenuItem separatorItem]];
addMenuItem(_("Credits"), stringEncoding, delegate, @selector(openCredits), @"", helpMenu);
addMenuItem(_("GPL License"), stringEncoding, delegate, @selector(openLicenseGPL), @"", helpMenu);
addMenuItem(_("LGPL License"), stringEncoding, delegate, @selector(openLicenseLGPL), @"", helpMenu);
addMenuItem(_("Freefont License"), stringEncoding, delegate, @selector(openLicenseFreefont), @"", helpMenu);
addMenuItem(_("OFL License"), stringEncoding, delegate, @selector(openLicenseOFL), @"", helpMenu);
addMenuItem(_("BSD License"), stringEncoding, delegate, @selector(openLicenseBSD), @"", helpMenu);
}
[appleMenu release];
[windowMenu release];
+26 -7
View File
@@ -32,6 +32,7 @@
#include "backends/platform/sdl/macosx/macosx.h"
#include "backends/updates/macosx/macosx-updates.h"
#include "backends/taskbar/macosx/macosx-taskbar.h"
#include "backends/text-to-speech/macosx/macosx-text-to-speech.h"
#include "backends/dialogs/macosx/macosx-dialogs.h"
#include "backends/platform/sdl/macosx/macosx_wrapper.h"
#include "backends/fs/posix/posix-fs.h"
@@ -44,11 +45,6 @@
#include "ApplicationServices/ApplicationServices.h" // for LSOpenFSRef
#include "CoreFoundation/CoreFoundation.h" // for CF* stuff
OSystem_MacOSX::OSystem_MacOSX()
:
OSystem_POSIX("Library/Preferences/ResidualVM Preferences") {
}
OSystem_MacOSX::~OSystem_MacOSX() {
releaseMenu();
}
@@ -86,6 +82,11 @@ void OSystem_MacOSX::initBackend() {
_updateManager = new MacOSXUpdateManager();
#endif
#ifdef USE_TTS
// Initialize Text to Speech manager
_textToSpeechManager = new MacOSXTextToSpeechManager();
#endif
// Invoke parent implementation of this method
OSystem_POSIX::initBackend();
}
@@ -147,7 +148,7 @@ bool OSystem_MacOSX::setTextInClipboard(const Common::String &text) {
}
bool OSystem_MacOSX::openUrl(const Common::String &url) {
CFURLRef urlRef = CFURLCreateWithBytes (NULL, (UInt8*)url.c_str(), url.size(), kCFStringEncodingASCII, NULL);
CFURLRef urlRef = CFURLCreateWithBytes (NULL, (const UInt8*)url.c_str(), url.size(), kCFStringEncodingASCII, NULL);
OSStatus err = LSOpenCFURLRef(urlRef, NULL);
CFRelease(urlRef);
return err == noErr;
@@ -200,6 +201,24 @@ Common::String OSystem_MacOSX::getSystemLanguage() const {
#endif // USE_DETECTLANG
}
Common::String OSystem_MacOSX::getDefaultConfigFileName() {
const Common::String baseConfigName = "Library/Preferences/ResidualVM Preferences";
Common::String configFile;
Common::String prefix = getenv("HOME");
if (!prefix.empty() && (prefix.size() + 1 + baseConfigName.size()) < MAXPATHLEN) {
configFile = prefix;
configFile += '/';
configFile += baseConfigName;
} else {
configFile = baseConfigName;
}
return configFile;
}
Common::String OSystem_MacOSX::getDefaultLogFileName() {
const char *prefix = getenv("HOME");
if (prefix == nullptr) {
@@ -210,7 +229,7 @@ Common::String OSystem_MacOSX::getDefaultLogFileName() {
return Common::String();
}
return Common::String(prefix) + "/Library/Logs/scummvm.log";
return Common::String(prefix) + "/Library/Logs/residualvm.log";
}
Common::String OSystem_MacOSX::getScreenshotsPath() {
+1 -1
View File
@@ -27,7 +27,6 @@
class OSystem_MacOSX : public OSystem_POSIX {
public:
OSystem_MacOSX();
~OSystem_MacOSX();
virtual bool hasFeature(Feature f);
@@ -50,6 +49,7 @@ public:
virtual Common::String getScreenshotsPath();
protected:
virtual Common::String getDefaultConfigFileName();
virtual Common::String getDefaultLogFileName();
// Override createAudioCDManager() to get our Mac-specific
+1 -1
View File
@@ -22,7 +22,7 @@
#include "common/scummsys.h"
#if defined(POSIX) && !defined(MACOSX) && !defined(SAMSUNGTV) && !defined(MAEMO) && !defined(WEBOS) && !defined(LINUXMOTO) && !defined(GPH_DEVICE) && !defined(GP2X) && !defined(DINGUX) && !defined(OPENPANDORA) && !defined(PLAYSTATION3) && !defined(PSP2) && !defined(ANDROIDSDL) && !defined(NINTENDO_SWITCH)
#if defined(POSIX) && !defined(MACOSX) && !defined(SAMSUNGTV) && !defined(MAEMO) && !defined(WEBOS) && !defined(GPH_DEVICE) && !defined(GP2X) && !defined(DINGUX) && !defined(OPENPANDORA) && !defined(PLAYSTATION3) && !defined(PSP2) && !defined(ANDROIDSDL) && !defined(NINTENDO_SWITCH)
#include "backends/platform/sdl/posix/posix.h"
#include "backends/plugins/sdl/sdl-provider.h"
+26 -14
View File
@@ -38,6 +38,7 @@
#include "backends/fs/posix/posix-fs-factory.h"
#include "backends/fs/posix/posix-fs.h"
#include "backends/taskbar/unity/unity-taskbar.h"
#include "backends/dialogs/gtk/gtk-dialogs.h"
#ifdef USE_LINUXCD
#include "backends/audiocd/linux/linux-audiocd.h"
@@ -54,12 +55,11 @@
#ifdef HAS_POSIX_SPAWN
#include <spawn.h>
#endif
extern char **environ;
OSystem_POSIX::OSystem_POSIX(Common::String baseConfigName)
:
_baseConfigName(baseConfigName) {
}
#if defined(USE_SPEECH_DISPATCHER) && defined(USE_TTS)
#include "backends/text-to-speech/linux/linux-text-to-speech.h"
#endif
extern char **environ;
void OSystem_POSIX::init() {
// Initialze File System Factory
@@ -70,6 +70,11 @@ void OSystem_POSIX::init() {
_taskbarManager = new UnityTaskbarManager();
#endif
#if defined(USE_SYSDIALOGS) && defined(USE_GTK)
// Initialize dialog manager
_dialogManager = new GtkDialogManager();
#endif
// Invoke parent implementation of this method
OSystem_SDL::init();
}
@@ -79,6 +84,11 @@ void OSystem_POSIX::initBackend() {
if (_savefileManager == 0)
_savefileManager = new POSIXSaveFileManager();
#if defined(USE_SPEECH_DISPATCHER) && defined(USE_TTS)
// Initialize Text to Speech manager
_textToSpeechManager = new SpeechDispatcherManager();
#endif
// Invoke parent implementation of this method
OSystem_SDL::initBackend();
@@ -94,21 +104,24 @@ bool OSystem_POSIX::hasFeature(Feature f) {
#ifdef HAS_POSIX_SPAWN
if (f == kFeatureOpenUrl)
return true;
#endif
#if defined(USE_SYSDIALOGS) && defined(USE_GTK)
if (f == kFeatureSystemBrowserDialog)
return true;
#endif
return OSystem_SDL::hasFeature(f);
}
Common::String OSystem_POSIX::getDefaultConfigFileName() {
const Common::String baseConfigName = "residualvm.ini";
Common::String configFile;
Common::String prefix;
#ifdef MACOSX
prefix = getenv("HOME");
#elif !defined(SAMSUNGTV)
const char *envVar;
// Our old configuration file path for POSIX systems was ~/.residualvmrc.
// If that file exists, we still use it.
envVar = getenv("HOME");
const char *envVar = getenv("HOME");
if (envVar && *envVar) {
configFile = envVar;
configFile += '/';
@@ -143,14 +156,13 @@ Common::String OSystem_POSIX::getDefaultConfigFileName() {
if (!prefix.empty() && Posix::assureDirectoryExists("residualvm", prefix.c_str())) {
prefix += "/residualvm";
}
#endif
if (!prefix.empty() && (prefix.size() + 1 + _baseConfigName.size()) < MAXPATHLEN) {
if (!prefix.empty() && (prefix.size() + 1 + baseConfigName.size()) < MAXPATHLEN) {
configFile = prefix;
configFile += '/';
configFile += _baseConfigName;
configFile += baseConfigName;
} else {
configFile = _baseConfigName;
configFile = baseConfigName;
}
return configFile;
+9 -20
View File
@@ -27,37 +27,26 @@
class OSystem_POSIX : public OSystem_SDL {
public:
// Let the subclasses be able to change _baseConfigName in the constructor
OSystem_POSIX(Common::String baseConfigName = "residualvm.ini");
virtual ~OSystem_POSIX() {}
virtual bool hasFeature(Feature f) override;
virtual bool hasFeature(Feature f);
virtual bool displayLogFile() override;
virtual bool displayLogFile();
virtual bool openUrl(const Common::String &url) override;
virtual bool openUrl(const Common::String &url);
virtual void init() override;
virtual void initBackend() override;
virtual void init();
virtual void initBackend();
virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0);
virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0) override;
Common::String getScreenshotsPath() override;
protected:
/**
* Base string for creating the default path and filename for the
* configuration file. This allows the Mac OS X subclass to override
* the config file path and name.
*/
Common::String _baseConfigName;
virtual Common::String getDefaultConfigFileName();
virtual Common::String getDefaultLogFileName();
virtual Common::String getDefaultConfigFileName() override;
virtual Common::String getDefaultLogFileName() override;
Common::String getXdgUserDir(const char *name);
virtual AudioCDManager *createAudioCDManager();
virtual AudioCDManager *createAudioCDManager() override;
bool launchBrowser(const Common::String& client, const Common::String &url);
};

Some files were not shown because too many files have changed in this diff Show More