mirror of
https://github.com/scummvm/scummvm.git
synced 2026-05-21 05:40:43 +00:00
ALL: synced with ScummVM commit 09bf38c120
This commit is contained in:
@@ -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,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)) {
|
||||
|
||||
@@ -85,6 +85,8 @@ void CloudIcon::update() {
|
||||
_lastUpdateTime = currentTime;
|
||||
|
||||
switch (_state) {
|
||||
default:
|
||||
// fallthrough intended
|
||||
case kHidden:
|
||||
return; // Nothing to do
|
||||
case kShown:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+268
-528
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
*/
|
||||
Audio::Mixer *getMixer() { return (Audio::Mixer *)_mixer; }
|
||||
|
||||
// Used by LinuxMoto Port
|
||||
// Used by Event recorder
|
||||
|
||||
/**
|
||||
* Pauses the audio system
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 += ">";
|
||||
else if (s[i] == '&')
|
||||
result += "&";
|
||||
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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user