mirror of
https://github.com/libretro/scummvm.git
synced 2025-04-12 19:58:29 +00:00

Implement function to trigger the device to set the screen orientation according to the configration in the backend options tab. The implementation to trigger the setting is different for devices running iOS prior version 16 and version 16+. The screen orientation update is triggered when user has applied the setting in the backen options tab, when the GUI launcher is loaded and when starting a game. The orientation is only changed if going from any portrait mode to any landscape mode and the opposite. Based on the setting, an UIInterfaceOrientationMask property is set to hold the allowed interface orientations. If the setting is "Portrait" the property will be set to UIInterfaceOrientationMaskPortrait, allowing to only change the orientation to normal or upside down portrait modes. If the setting is "Landscape" the UIInterfaceOrientationMask will be set to UIInterfaceOrientationMaskLandscape allowing the device to rotate the screen orientation only to either right or left landscape mode. If set to "Auto" all orientations will be allowed. When the device orientation changes, the system calls the instance property method supportedInterfaceOrientations on the root view controller or the topmost modal view controller that fills the window. If the view controller supports the new orientation, the system rotates the window and the view controller. The system only calls this method if the view controller's shouldAutorotate method returns YES, which is the default value. The event handler is refactored to receive the internal screen orientation value instead of the enum value of the UIKit enum UIInterfaceOrientation. The convertion from UIInterfaceOrientation to ScreenOrientation is done in iPhoneView class instead when sending the new orientation value to the event handler. This also makes the convertion aligned with the screen orientation settings in the function setSupportedScreenOrientation.
480 lines
14 KiB
C++
480 lines
14 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
// Disable symbol overrides so that we can use system headers.
|
|
#define FORBIDDEN_SYMBOL_ALLOW_ALL
|
|
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
|
|
#include <sys/time.h>
|
|
#include <QuartzCore/QuartzCore.h>
|
|
#include <dlfcn.h>
|
|
|
|
#include "common/scummsys.h"
|
|
#include "common/util.h"
|
|
#include "common/rect.h"
|
|
#include "common/file.h"
|
|
#include "common/fs.h"
|
|
#include "common/config-manager.h"
|
|
#include "common/translation.h"
|
|
|
|
#include "base/main.h"
|
|
|
|
#include "engines/engine.h"
|
|
#include "engines/metaengine.h"
|
|
|
|
#include "graphics/cursorman.h"
|
|
#include "gui/gui-manager.h"
|
|
|
|
#include "backends/graphics/ios/ios-graphics.h"
|
|
#include "backends/graphics3d/ios/ios-graphics3d.h"
|
|
#include "backends/saves/default/default-saves.h"
|
|
#include "backends/timer/default/default-timer.h"
|
|
#include "backends/mutex/pthread/pthread-mutex.h"
|
|
#include "backends/fs/chroot/chroot-fs-factory.h"
|
|
#include "backends/fs/posix/posix-fs.h"
|
|
#include "audio/mixer.h"
|
|
#include "audio/mixer_intern.h"
|
|
|
|
#include "backends/platform/ios7/ios7_osys_main.h"
|
|
|
|
|
|
AQCallbackStruct OSystem_iOS7::s_AudioQueue;
|
|
SoundProc OSystem_iOS7::s_soundCallback = NULL;
|
|
void *OSystem_iOS7::s_soundParam = NULL;
|
|
|
|
class SandboxedSaveFileManager : public DefaultSaveFileManager {
|
|
Common::String _sandboxRootPath;
|
|
public:
|
|
|
|
SandboxedSaveFileManager(Common::String sandboxRootPath, Common::String defaultSavepath)
|
|
: DefaultSaveFileManager(defaultSavepath), _sandboxRootPath(sandboxRootPath) {
|
|
}
|
|
|
|
bool removeSavefile(const Common::String &filename) override {
|
|
Common::String chrootedFile = getSavePath() + "/" + filename;
|
|
Common::String realFilePath = _sandboxRootPath + chrootedFile;
|
|
|
|
if (remove(realFilePath.c_str()) != 0) {
|
|
if (errno == EACCES)
|
|
setError(Common::kWritePermissionDenied, "Search or write permission denied: "+chrootedFile);
|
|
|
|
if (errno == ENOENT)
|
|
setError(Common::kPathDoesNotExist, "removeSavefile: '"+chrootedFile+"' does not exist or path is invalid");
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
};
|
|
|
|
OSystem_iOS7::OSystem_iOS7() :
|
|
_mixer(NULL), _lastMouseTap(0), _queuedEventTime(0),
|
|
_secondaryTapped(false), _lastSecondaryTap(0),
|
|
_screenOrientation(kScreenOrientationAuto),
|
|
_timeSuspended(0) {
|
|
_queuedInputEvent.type = Common::EVENT_INVALID;
|
|
_touchpadModeEnabled = ConfMan.getBool("touchpad_mode");
|
|
_mouseClickAndDragEnabled = ConfMan.getBool("clickanddrag_mode");
|
|
|
|
_chrootBasePath = iOS7_getDocumentsDir();
|
|
ChRootFilesystemFactory *chFsFactory = new ChRootFilesystemFactory(_chrootBasePath);
|
|
_fsFactory = chFsFactory;
|
|
// Add virtual drive for bundle path
|
|
Common::String appBubdlePath = iOS7_getAppBundleDir();
|
|
if (!appBubdlePath.empty())
|
|
chFsFactory->addVirtualDrive("appbundle:", appBubdlePath);
|
|
}
|
|
|
|
OSystem_iOS7::~OSystem_iOS7() {
|
|
AudioQueueDispose(s_AudioQueue.queue, true);
|
|
|
|
delete _mixer;
|
|
delete _graphicsManager;
|
|
}
|
|
|
|
bool OSystem_iOS7::touchpadModeEnabled() const {
|
|
return _touchpadModeEnabled;
|
|
}
|
|
|
|
#if defined(USE_OPENGL) && defined(USE_GLAD)
|
|
void *OSystem_iOS7::getOpenGLProcAddress(const char *name) const {
|
|
return dlsym(RTLD_SELF, name);
|
|
}
|
|
#endif
|
|
|
|
int OSystem_iOS7::timerHandler(int t) {
|
|
DefaultTimerManager *tm = (DefaultTimerManager *)g_system->getTimerManager();
|
|
tm->handler();
|
|
return t;
|
|
}
|
|
|
|
void OSystem_iOS7::initBackend() {
|
|
_savefileManager = new SandboxedSaveFileManager(_chrootBasePath, "/Savegames");
|
|
|
|
_timerManager = new DefaultTimerManager();
|
|
|
|
_startTime = CACurrentMediaTime();
|
|
|
|
_graphicsManager = new iOSGraphicsManager();
|
|
|
|
setupMixer();
|
|
|
|
setTimerCallback(&OSystem_iOS7::timerHandler, 10);
|
|
|
|
EventsBaseBackend::initBackend();
|
|
}
|
|
|
|
bool OSystem_iOS7::hasFeature(Feature f) {
|
|
switch (f) {
|
|
case kFeatureCursorPalette:
|
|
case kFeatureFilteringMode:
|
|
case kFeatureVirtualKeyboard:
|
|
#if TARGET_OS_IOS
|
|
case kFeatureClipboardSupport:
|
|
#endif
|
|
case kFeatureOpenUrl:
|
|
case kFeatureNoQuit:
|
|
case kFeatureKbdMouseSpeed:
|
|
case kFeatureOpenGLForGame:
|
|
case kFeatureShadersForGame:
|
|
return true;
|
|
|
|
default:
|
|
return ModularGraphicsBackend::hasFeature(f);
|
|
}
|
|
}
|
|
|
|
void OSystem_iOS7::setFeatureState(Feature f, bool enable) {
|
|
switch (f) {
|
|
case kFeatureVirtualKeyboard:
|
|
setShowKeyboard(enable);
|
|
break;
|
|
|
|
default:
|
|
ModularGraphicsBackend::setFeatureState(f, enable);
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool OSystem_iOS7::getFeatureState(Feature f) {
|
|
switch (f) {
|
|
case kFeatureVirtualKeyboard:
|
|
return isKeyboardShown();
|
|
|
|
default:
|
|
return ModularGraphicsBackend::getFeatureState(f);
|
|
}
|
|
}
|
|
|
|
bool OSystem_iOS7::setGraphicsMode(int mode, uint flags) {
|
|
bool render3d = flags & OSystem::kGfxModeRender3d;
|
|
|
|
// Utilize the same way to switch between 2D and 3D graphics manager as
|
|
// in SDL based backends and Android.
|
|
iOSCommonGraphics *commonGraphics = dynamic_cast<iOSCommonGraphics *>(_graphicsManager);
|
|
iOSCommonGraphics::State gfxManagerState = commonGraphics->getState();
|
|
|
|
bool supports3D = _graphicsManager->hasFeature(kFeatureOpenGLForGame);
|
|
bool switchedManager = false;
|
|
|
|
// If the new mode and the current mode are not from the same graphics
|
|
// manager, delete and create the new mode graphics manager
|
|
if (render3d && !supports3D) {
|
|
delete _graphicsManager;
|
|
iOSGraphics3dManager *manager = new iOSGraphics3dManager();
|
|
_graphicsManager = manager;
|
|
commonGraphics = manager;
|
|
switchedManager = true;
|
|
} else if (!render3d && supports3D) {
|
|
delete _graphicsManager;
|
|
iOSGraphicsManager *manager = new iOSGraphicsManager();
|
|
_graphicsManager = manager;
|
|
commonGraphics = manager;
|
|
switchedManager = true;
|
|
}
|
|
|
|
if (switchedManager) {
|
|
// Setup the graphics mode and size first
|
|
// This is needed so that we can check the supported pixel formats when
|
|
// restoring the state.
|
|
_graphicsManager->beginGFXTransaction();
|
|
if (!_graphicsManager->setGraphicsMode(mode, flags))
|
|
return false;
|
|
_graphicsManager->initSize(gfxManagerState.screenWidth, gfxManagerState.screenHeight);
|
|
_graphicsManager->endGFXTransaction();
|
|
|
|
// This failing will probably have bad consequences...
|
|
//if (!androidGraphicsManager->setState(gfxManagerState)) {
|
|
// return false;
|
|
//}
|
|
|
|
// Next setup the cursor again
|
|
CursorMan.pushCursor(0, 0, 0, 0, 0, 0);
|
|
CursorMan.popCursor();
|
|
|
|
// Next setup cursor palette if needed
|
|
if (_graphicsManager->getFeatureState(kFeatureCursorPalette)) {
|
|
CursorMan.pushCursorPalette(0, 0, 0);
|
|
CursorMan.popCursorPalette();
|
|
}
|
|
|
|
_graphicsManager->beginGFXTransaction();
|
|
return true;
|
|
} else {
|
|
return _graphicsManager->setGraphicsMode(mode, flags);
|
|
}
|
|
}
|
|
|
|
void OSystem_iOS7::suspendLoop() {
|
|
bool done = false;
|
|
uint32 startTime = getMillis();
|
|
|
|
PauseToken pt;
|
|
if (g_engine)
|
|
pt = g_engine->pauseEngine();
|
|
|
|
// We also need to stop the audio queue and restart it later in case there
|
|
// is an audio interruption that render it invalid.
|
|
stopSoundsystem();
|
|
|
|
InternalEvent event;
|
|
while (!done) {
|
|
if (iOS7_fetchEvent(&event)) {
|
|
if (event.type == kInputApplicationResumed)
|
|
done = true;
|
|
else if (event.type == kInputApplicationSaveState)
|
|
handleEvent_applicationSaveState();
|
|
}
|
|
usleep(100000);
|
|
}
|
|
|
|
startSoundsystem();
|
|
|
|
_timeSuspended += getMillis() - startTime;
|
|
}
|
|
|
|
void OSystem_iOS7::saveState() {
|
|
// Clear any previous restore state to avoid having and obsolete one if we don't save it again below.
|
|
clearState();
|
|
|
|
// If there is an engine running and it accepts autosave, do an autosave and add the current
|
|
// running target to the config file.
|
|
if (g_engine && g_engine->hasFeature(Engine::kSupportsSavingDuringRuntime) && g_engine->canSaveAutosaveCurrently()) {
|
|
Common::String targetName(ConfMan.getActiveDomainName());
|
|
int saveSlot = g_engine->getAutosaveSlot();
|
|
// Make sure we do not overwrite a user save
|
|
SaveStateDescriptor desc = g_engine->getMetaEngine()->querySaveMetaInfos(targetName.c_str(), saveSlot);
|
|
if (desc.getSaveSlot() != -1 && !desc.isAutosave())
|
|
return;
|
|
|
|
// Do the auto-save, and if successful store this it in the config
|
|
if (g_engine->saveGameState(saveSlot, _("Autosave"), true).getCode() == Common::kNoError) {
|
|
ConfMan.set("restore_target", targetName, Common::ConfigManager::kApplicationDomain);
|
|
ConfMan.setInt("restore_slot", saveSlot, Common::ConfigManager::kApplicationDomain);
|
|
ConfMan.flushToDisk();
|
|
}
|
|
}
|
|
}
|
|
|
|
void OSystem_iOS7::restoreState() {
|
|
Common::String target;
|
|
int slot = -1;
|
|
if (ConfMan.hasKey("restore_target", Common::ConfigManager::kApplicationDomain) &&
|
|
ConfMan.hasKey("restore_slot", Common::ConfigManager::kApplicationDomain)) {
|
|
target = ConfMan.get("restore_target", Common::ConfigManager::kApplicationDomain);
|
|
slot = ConfMan.getInt("restore_slot", Common::ConfigManager::kApplicationDomain);
|
|
clearState();
|
|
}
|
|
|
|
// If the g_engine is still running (i.e. the application was not terminated) we don't need to do anything.
|
|
if (g_engine)
|
|
return;
|
|
|
|
if (!target.empty() && slot != -1) {
|
|
ConfMan.setInt("save_slot", slot, Common::ConfigManager::kTransientDomain);
|
|
ConfMan.setActiveDomain(target);
|
|
if (GUI::GuiManager::hasInstance())
|
|
g_gui.exitLoop();
|
|
}
|
|
}
|
|
|
|
void OSystem_iOS7::clearState() {
|
|
if (ConfMan.hasKey("restore_target", Common::ConfigManager::kApplicationDomain) &&
|
|
ConfMan.hasKey("restore_slot", Common::ConfigManager::kApplicationDomain)) {
|
|
ConfMan.removeKey("restore_target", Common::ConfigManager::kApplicationDomain);
|
|
ConfMan.removeKey("restore_slot", Common::ConfigManager::kApplicationDomain);
|
|
ConfMan.flushToDisk();
|
|
}
|
|
}
|
|
|
|
uint32 OSystem_iOS7::getMillis(bool skipRecord) {
|
|
CFTimeInterval timeInSeconds = CACurrentMediaTime();
|
|
return (uint32) ((timeInSeconds - _startTime) * 1000.0) - _timeSuspended;
|
|
}
|
|
|
|
void OSystem_iOS7::delayMillis(uint msecs) {
|
|
//printf("delayMillis(%d)\n", msecs);
|
|
usleep(msecs * 1000);
|
|
}
|
|
|
|
float OSystem_iOS7::getMouseSpeed() {
|
|
switch (ConfMan.getInt("kbdmouse_speed")) {
|
|
case 0:
|
|
return 0.25;
|
|
case 1:
|
|
return 0.5;
|
|
case 2:
|
|
return 0.75;
|
|
case 3:
|
|
return 1.0;
|
|
case 4:
|
|
return 1.25;
|
|
case 5:
|
|
return 1.5;
|
|
case 6:
|
|
return 1.75;
|
|
case 7:
|
|
return 2.0;
|
|
default:
|
|
return 1.0;
|
|
}
|
|
}
|
|
|
|
void OSystem_iOS7::setTimerCallback(TimerProc callback, int interval) {
|
|
//printf("setTimerCallback()\n");
|
|
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
|
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
|
|
|
|
if (timer)
|
|
{
|
|
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval * NSEC_PER_MSEC, interval * NSEC_PER_MSEC / 10);
|
|
dispatch_source_set_event_handler(timer, ^{ callback(interval); });
|
|
dispatch_resume(timer);
|
|
}
|
|
}
|
|
|
|
Common::MutexInternal *OSystem_iOS7::createMutex() {
|
|
return createPthreadMutexInternal();
|
|
}
|
|
|
|
void OSystem_iOS7::quit() {
|
|
}
|
|
|
|
void OSystem_iOS7::getTimeAndDate(TimeDate &td, bool skipRecord) const {
|
|
time_t curTime = time(0);
|
|
struct tm t = *localtime(&curTime);
|
|
td.tm_sec = t.tm_sec;
|
|
td.tm_min = t.tm_min;
|
|
td.tm_hour = t.tm_hour;
|
|
td.tm_mday = t.tm_mday;
|
|
td.tm_mon = t.tm_mon;
|
|
td.tm_year = t.tm_year;
|
|
td.tm_wday = t.tm_wday;
|
|
}
|
|
|
|
Audio::Mixer *OSystem_iOS7::getMixer() {
|
|
assert(_mixer);
|
|
return _mixer;
|
|
}
|
|
|
|
OSystem_iOS7 *OSystem_iOS7::sharedInstance() {
|
|
static OSystem_iOS7 *instance = new OSystem_iOS7();
|
|
return instance;
|
|
}
|
|
|
|
Common::String OSystem_iOS7::getDefaultConfigFileName() {
|
|
Common::String path = "/Preferences";
|
|
return path;
|
|
}
|
|
|
|
void OSystem_iOS7::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) {
|
|
// Get URL of the Resource directory of the .app bundle
|
|
CFURLRef fileUrl = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
|
|
if (fileUrl) {
|
|
// Try to convert the URL to an absolute path
|
|
UInt8 buf[MAXPATHLEN];
|
|
if (CFURLGetFileSystemRepresentation(fileUrl, true, buf, sizeof(buf))) {
|
|
// Success: Add it to the search path
|
|
Common::String bundlePath((const char *)buf);
|
|
POSIXFilesystemNode *posixNode = new POSIXFilesystemNode(bundlePath);
|
|
s.add("__IOS_BUNDLE__", new Common::FSDirectory(AbstractFSNode::makeFSNode(posixNode)), priority);
|
|
}
|
|
CFRelease(fileUrl);
|
|
}
|
|
}
|
|
|
|
bool iOS7_touchpadModeEnabled() {
|
|
OSystem_iOS7 *sys = dynamic_cast<OSystem_iOS7 *>(g_system);
|
|
return sys && sys->touchpadModeEnabled();
|
|
}
|
|
|
|
void iOS7_buildSharedOSystemInstance() {
|
|
OSystem_iOS7::sharedInstance();
|
|
}
|
|
|
|
void iOS7_main(int argc, char **argv) {
|
|
|
|
//OSystem_iOS7::migrateApp();
|
|
|
|
Common::String logFilePath = iOS7_getDocumentsDir() + "/scummvm.log";
|
|
FILE *logFile = fopen(logFilePath.c_str(), "a");
|
|
if (logFile != nullptr) {
|
|
// We check for log file size; if it's too big, we rewrite it.
|
|
// This happens only upon app launch
|
|
// NOTE: We don't check for file size each time we write a log message.
|
|
long sz = ftell(logFile);
|
|
if (sz > MAX_IOS7_SCUMMVM_LOG_FILESIZE_IN_BYTES) {
|
|
fclose(logFile);
|
|
fprintf(stdout, "Default log file is bigger than %dKB. It will be overwritten!", MAX_IOS7_SCUMMVM_LOG_FILESIZE_IN_BYTES / 1024);
|
|
|
|
// Create the log file from scratch overwriting the previous one
|
|
logFile = fopen(logFilePath.c_str(), "w");
|
|
if (logFile == nullptr)
|
|
fprintf(stdout, "Could not open default log file for rewrite!");
|
|
}
|
|
if (logFile != NULL) {
|
|
fclose(stdout);
|
|
fclose(stderr);
|
|
*stdout = *logFile;
|
|
*stderr = *logFile;
|
|
setbuf(stdout, NULL);
|
|
setbuf(stderr, NULL);
|
|
}
|
|
}
|
|
|
|
chdir(iOS7_getDocumentsDir().c_str());
|
|
|
|
g_system = OSystem_iOS7::sharedInstance();
|
|
assert(g_system);
|
|
|
|
// Invoke the actual ScummVM main entry point:
|
|
scummvm_main(argc, (const char *const *) argv);
|
|
g_system->quit(); // TODO: Consider removing / replacing this!
|
|
|
|
if (logFile != NULL) {
|
|
//*stdout = NULL;
|
|
//*stderr = NULL;
|
|
fclose(logFile);
|
|
}
|
|
}
|