From 3539a20a4d420a0f72e1aca3356ee07876523625 Mon Sep 17 00:00:00 2001 From: James VanDusen Date: Tue, 3 Jan 2023 10:51:53 -0500 Subject: [PATCH 1/4] Added checks for LEDBLINKY Added new parameter check in settings.conf called LEDBlinkyCloseOnExit set to yes or no --- RetroFE/Source/RetroFE.cpp | 3997 ++++++++++++++++++------------------ RetroFE/Source/RetroFE.h | 288 +-- 2 files changed, 2145 insertions(+), 2140 deletions(-) diff --git a/RetroFE/Source/RetroFE.cpp b/RetroFE/Source/RetroFE.cpp index 30468906c..6bef2ba1c 100644 --- a/RetroFE/Source/RetroFE.cpp +++ b/RetroFE/Source/RetroFE.cpp @@ -1,1996 +1,2001 @@ -/* This file is part of RetroFE. - * - * RetroFE 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. - * - * RetroFE 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 RetroFE. If not, see . - */ - - -#include "RetroFE.h" -#include "Collection/CollectionInfoBuilder.h" -#include "Collection/CollectionInfo.h" -#include "Database/Configuration.h" -#include "Collection/Item.h" -#include "Execute/Launcher.h" -#include "Menu/Menu.h" -#include "Utility/Log.h" -#include "Utility/Utils.h" -#include "Collection/MenuParser.h" -#include "SDL.h" -#include "Control/UserInput.h" -#include "Graphics/PageBuilder.h" -#include "Graphics/Page.h" -#include "Graphics/Component/ScrollingList.h" -#include "Graphics/Component/Video.h" -#include -#include "Video/VideoFactory.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(__linux) || defined(__APPLE__) -#include -#include -#include -#include -#endif - -#ifdef WIN32 -#include -#include -#include -#endif - - -RetroFE::RetroFE( Configuration &c ) - : initialized(false) - , initializeError(false) - , initializeThread(NULL) - , config_(c) - , db_(NULL) - , metadb_(NULL) - , input_(config_) - , currentPage_(NULL) - , keyInputDisable_(0) - , currentTime_(0) - , lastLaunchReturnTime_(0) - , keyLastTime_(0) - , keyDelayTime_(.3f) - , reboot_(false) -{ - menuMode_ = false; - attractMode_ = false; - attractModePlaylistCollectionNumber_ = 0; - firstPlaylist_ = "all"; -} - - -RetroFE::~RetroFE( ) -{ - deInitialize( ); -} - - -// Render the current page to the screen -void RetroFE::render( ) -{ - - SDL_LockMutex( SDL::getMutex( ) ); - for ( int i = 0; i < SDL::getNumDisplays( ); ++i ) - { - SDL_SetRenderDrawColor( SDL::getRenderer( i ), 0x0, 0x0, 0x00, 0xFF ); - SDL_RenderClear( SDL::getRenderer( i ) ); - } - - if ( currentPage_ ) - { - currentPage_->draw( ); - } - - for ( int i = 0; i < SDL::getNumDisplays( ); ++i ) - { - SDL_RenderPresent( SDL::getRenderer( i ) ); - } - SDL_UnlockMutex( SDL::getMutex( ) ); - -} - - -// Initialize the configuration and database -int RetroFE::initialize( void *context ) -{ - - RetroFE *instance = static_cast(context); - - Logger::write( Logger::ZONE_INFO, "RetroFE", "Initializing" ); - - if ( !instance->input_.initialize( ) ) - { - Logger::write( Logger::ZONE_ERROR, "RetroFE", "Could not initialize user controls" ); - instance->initializeError = true; - return -1; - } - - instance->db_ = new DB( Utils::combinePath( Configuration::absolutePath, "meta.db" ) ); - - if ( !instance->db_->initialize( ) ) - { - Logger::write( Logger::ZONE_ERROR, "RetroFE", "Could not initialize database" ); - instance->initializeError = true; - return -1; - } - - instance->metadb_ = new MetadataDatabase( *(instance->db_), instance->config_ ); - - if ( !instance->metadb_->initialize( ) ) - { - Logger::write( Logger::ZONE_ERROR, "RetroFE", "Could not initialize meta database" ); - instance->initializeError = true; - return -1; - } - - instance->initialized = true; - return 0; - -} - - -// Launch a game/program -void RetroFE::launchEnter( ) -{ - - // Disable window focus - SDL_SetWindowGrab(SDL::getWindow( 0 ), SDL_FALSE); - - // Free the textures, and optionally take down SDL - freeGraphicsMemory( ); - - bool hideMouse = false; - int mouseX = 5000; - int mouseY = 5000; - config_.getProperty( "hideMouse", hideMouse ); - config_.getProperty( "mouseX", mouseX ); - config_.getProperty( "mouseY", mouseY ); - if ( hideMouse ) - SDL_WarpMouseGlobal( mouseX, mouseY ); -} - - -// Return from the launch of a game/program -void RetroFE::launchExit( ) -{ - - // Optionally set up SDL, and load the textures - allocateGraphicsMemory( ); - - // Restore the SDL settings - SDL_RestoreWindow( SDL::getWindow( 0 ) ); - SDL_RaiseWindow( SDL::getWindow( 0 ) ); - SDL_SetWindowGrab( SDL::getWindow( 0 ), SDL_TRUE ); - - // Empty event queue, but handle joystick add/remove events - SDL_Event e; - while ( SDL_PollEvent( &e ) ) - { - if ( e.type == SDL_JOYDEVICEADDED || e.type == SDL_JOYDEVICEREMOVED ) - { - input_.update( e ); - } - } - input_.resetStates( ); - attract_.reset( ); - - // Restore time settings - currentTime_ = static_cast( SDL_GetTicks( ) ) / 1000; - keyLastTime_ = currentTime_; - lastLaunchReturnTime_ = currentTime_; - - bool hideMouse = false; - int mouseX = 5000; - int mouseY = 5000; - config_.getProperty( "hideMouse", hideMouse ); - config_.getProperty( "mouseX", mouseX ); - config_.getProperty( "mouseY", mouseY ); - if ( hideMouse ) - SDL_WarpMouseGlobal( mouseX, mouseY ); - -} - - -// Free the textures, and optionall take down SDL -void RetroFE::freeGraphicsMemory( ) -{ - - // Free textures - if ( currentPage_ ) - { - currentPage_->freeGraphicsMemory( ); - } - - // Close down SDL - bool unloadSDL = false; - config_.getProperty( "unloadSDL", unloadSDL ); - if ( unloadSDL ) - { - currentPage_->deInitializeFonts( ); - SDL::deInitialize( ); - input_.clearJoysticks( ); - } - -} - - -// Optionally set up SDL, and load the textures -void RetroFE::allocateGraphicsMemory( ) -{ - - // Reopen SDL - bool unloadSDL = false; - config_.getProperty( "unloadSDL", unloadSDL ); - if ( unloadSDL ) - { - SDL::initialize( config_ ); - currentPage_->initializeFonts( ); - } - - // Allocate textures - if ( currentPage_ ) - { - currentPage_->allocateGraphicsMemory( ); - } - -} - - -// Deinitialize RetroFE -bool RetroFE::deInitialize( ) -{ - - bool retVal = true; - - // Free textures - freeGraphicsMemory( ); - - // Delete page - if ( currentPage_ ) - { - currentPage_->deInitialize( ); - delete currentPage_; - currentPage_ = NULL; - } - - // Delete databases - if ( metadb_ ) - { - delete metadb_; - metadb_ = NULL; - } - - if ( db_ ) - { - delete db_; - db_ = NULL; - } - - initialized = false; - - if ( reboot_ ) - { - Logger::write( Logger::ZONE_INFO, "RetroFE", "Rebooting" ); - } - else - { - Logger::write( Logger::ZONE_INFO, "RetroFE", "Exiting" ); - SDL::deInitialize( ); - gst_deinit( ); - } - - return retVal; -} - - -// Run RetroFE -bool RetroFE::run( ) -{ - - // Initialize SDL - if(! SDL::initialize( config_ ) ) return false; - fontcache_.initialize( ); - - // Define control configuration - std::string controlsConfPath = Utils::combinePath( Configuration::absolutePath, "controls.conf" ); - if ( !config_.import( "controls", controlsConfPath ) ) - { - Logger::write( Logger::ZONE_ERROR, "RetroFE", "Could not import \"" + controlsConfPath + "\"" ); - return false; - } - - float preloadTime = 0; - - // Initialize video - bool videoEnable = true; - int videoLoop = 0; - config_.getProperty( "videoEnable", videoEnable ); - config_.getProperty( "videoLoop", videoLoop ); - VideoFactory::setEnabled( videoEnable ); - VideoFactory::setNumLoops( videoLoop ); - Video::setEnabled( videoEnable ); - - initializeThread = SDL_CreateThread( initialize, "RetroFEInit", (void *)this ); - - if ( !initializeThread ) - { - Logger::write( Logger::ZONE_INFO, "RetroFE", "Could not initialize RetroFE" ); - return false; - } - - int attractModeTime = 0; - int attractModeNextTime = 0; - int attractModePlaylistTime = 0; - int attractModeCollectionTime = 0; - int attractModeMinTime = 1000; - int attractModeMaxTime = 5000; - std::string firstCollection = "Main"; - bool running = true; - RETROFE_STATE state = RETROFE_NEW; - - config_.getProperty( "attractModeTime", attractModeTime ); - config_.getProperty( "attractModeNextTime", attractModeNextTime ); - config_.getProperty( "attractModePlaylistTime", attractModePlaylistTime ); - config_.getProperty( "attractModeCollectionTime", attractModeCollectionTime ); - config_.getProperty( "attractModeMinTime", attractModeMinTime ); - config_.getProperty( "attractModeMaxTime", attractModeMaxTime ); - config_.getProperty( "firstCollection", firstCollection ); - - attract_.idleTime = static_cast(attractModeTime); - attract_.idleNextTime = static_cast(attractModeNextTime); - attract_.idlePlaylistTime = static_cast(attractModePlaylistTime); - attract_.idleCollectionTime = static_cast(attractModeCollectionTime); - attract_.minTime = attractModeMinTime; - attract_.maxTime = attractModeMaxTime; - - int fps = 60; - int fpsIdle = 60; - config_.getProperty( "fps", fps ); - config_.getProperty( "fpsIdle", fpsIdle ); - double fpsTime = 1000.0 / static_cast(fps); - double fpsIdleTime = 1000.0 / static_cast(fpsIdle); - - int initializeStatus = 0; - bool inputClear = false; - - // load the initial splash screen, unload it once it is complete - currentPage_ = loadSplashPage( ); - state = RETROFE_ENTER; - bool splashMode = true; - bool exitSplashMode = false; - - Launcher l( config_ ); - Menu m( config_, input_ ); - preloadTime = static_cast( SDL_GetTicks( ) ) / 1000; - l.LEDBlinky( 1 ); - - while ( running ) - { - - float lastTime = 0; - float deltaTime = 0; - - // Exit splash mode when an active key is pressed - SDL_Event e; - if ( splashMode && SDL_PollEvent( &e ) ) - { - if ( input_.update( e ) && input_.keystate(UserInput::KeyCodeSelect) ) - { - exitSplashMode = true; - while ( SDL_PollEvent( &e ) ) - { - if ( e.type == SDL_JOYDEVICEADDED || e.type == SDL_JOYDEVICEREMOVED ) - { - input_.update( e ); - } - } - input_.resetStates( ); - attract_.reset( ); - } - } - - if ( !currentPage_ ) - { - Logger::write( Logger::ZONE_WARNING, "RetroFE", "Could not load page" ); - running = false; - break; - } - - switch(state) - { - - // Idle state; waiting for input - case RETROFE_IDLE: - - currentPage_->cleanup( ); - - // Not in splash mode - if ( currentPage_ && !splashMode ) - { - // account for when returning from a menu and the previous key was still "stuck" - if ( lastLaunchReturnTime_ == 0 || (currentTime_ - lastLaunchReturnTime_ > .3) ) - { - if ( currentPage_->isIdle( ) ) - { - state = processUserInput( currentPage_ ); - } - lastLaunchReturnTime_ = 0; - } - } - - // Handle end of splash mode - if ( (initialized || initializeError) && splashMode && (exitSplashMode || (currentPage_->getMinShowTime( ) <= (currentTime_ - preloadTime) && !(currentPage_->isPlaying( )))) ) - { - SDL_WaitThread( initializeThread, &initializeStatus ); - - if ( initializeError ) - { - state = RETROFE_QUIT_REQUEST; - break; - } - - currentPage_->stop( ); - state = RETROFE_SPLASH_EXIT; - - } - - break; - - // Load art on entering RetroFE - case RETROFE_LOAD_ART: - currentPage_->start( ); - state = RETROFE_ENTER; - break; - - // Wait for onEnter animation to finish - case RETROFE_ENTER: - if ( currentPage_->isIdle( ) ) - { - bool startCollectionEnter = false; - config_.getProperty( "startCollectionEnter", startCollectionEnter ); - nextPageItem_ = currentPage_->getSelectedItem( ); - if ( !splashMode && startCollectionEnter && !nextPageItem_->leaf ) - { - state = RETROFE_NEXT_PAGE_REQUEST; - } - else - { - state = RETROFE_IDLE; - } - } - break; - - // Handle end of splash mode - case RETROFE_SPLASH_EXIT: - if ( currentPage_->isIdle( ) ) - { - // delete the splash screen and use the standard menu - currentPage_->deInitialize( ); - delete currentPage_; - - currentPage_ = loadPage( ); - splashMode = false; - if ( currentPage_ ) - { - std::string firstCollection = "Main"; - - config_.getProperty( "firstCollection", firstCollection ); - config_.setProperty( "currentCollection", firstCollection ); - CollectionInfo *info = getCollection(firstCollection); - - currentPage_->pushCollection(info); - - config_.getProperty( "firstPlaylist", firstPlaylist_ ); - currentPage_->selectPlaylist( firstPlaylist_ ); - if (currentPage_->getPlaylistName() != firstPlaylist_ ) - currentPage_->selectPlaylist( "all" ); - - currentPage_->onNewItemSelected( ); - currentPage_->reallocateMenuSpritePoints( ); - - state = RETROFE_LOAD_ART; - } - else - { - state = RETROFE_QUIT_REQUEST; - } - } - break; - - // Switch playlist; start onHighlightExit animation - case RETROFE_PLAYLIST_REQUEST: - inputClear = false; - config_.getProperty( "playlistInputClear", inputClear ); - if ( inputClear ) - { - // Empty event queue - SDL_Event e; - while ( SDL_PollEvent( &e ) ) - input_.update(e); - input_.resetStates( ); - } - currentPage_->playlistExit( ); - currentPage_->setScrolling(Page::ScrollDirectionIdle); - state = RETROFE_PLAYLIST_EXIT; - break; - - // Switch playlist; wait for onHighlightExit animation to finish; load art - case RETROFE_PLAYLIST_EXIT: - if (currentPage_->isIdle( )) - { - currentPage_->onNewItemSelected( ); - state = RETROFE_PLAYLIST_LOAD_ART; - } - break; - - // Switch playlist; start onHighlightEnter animation - case RETROFE_PLAYLIST_LOAD_ART: - if (currentPage_->isIdle( )) - { - currentPage_->reallocateMenuSpritePoints( ); - currentPage_->playlistEnter( ); - state = RETROFE_PLAYLIST_ENTER; - } - break; - - // Switch playlist; wait for onHighlightEnter animation to finish - case RETROFE_PLAYLIST_ENTER: - if (currentPage_->isIdle( )) - { - state = RETROFE_IDLE; - } - break; - - // Jump in menu; start onMenuJumpExit animation - case RETROFE_MENUJUMP_REQUEST: - inputClear = false; - config_.getProperty( "jumpInputClear", inputClear ); - if ( inputClear ) - { - // Empty event queue - SDL_Event e; - while ( SDL_PollEvent( &e ) ) - input_.update(e); - input_.resetStates( ); - } - currentPage_->menuJumpExit( ); - currentPage_->setScrolling(Page::ScrollDirectionIdle); - state = RETROFE_MENUJUMP_EXIT; - break; - - // Jump in menu; wait for onMenuJumpExit animation to finish; load art - case RETROFE_MENUJUMP_EXIT: - if (currentPage_->isIdle( )) - { - currentPage_->onNewItemSelected( ); - state = RETROFE_MENUJUMP_LOAD_ART; - } - break; - - // Jump in menu; start onMenuJumpEnter animation - case RETROFE_MENUJUMP_LOAD_ART: - if (currentPage_->isIdle( )) - { - currentPage_->reallocateMenuSpritePoints( ); - currentPage_->menuJumpEnter( ); - state = RETROFE_MENUJUMP_ENTER; - } - break; - - // Jump in menu; wait for onMenuJump animation to finish - case RETROFE_MENUJUMP_ENTER: - if (currentPage_->isIdle( )) - { - state = RETROFE_IDLE; - } - break; - - // Start onHighlightExit animation - case RETROFE_HIGHLIGHT_REQUEST: - currentPage_->setScrolling(Page::ScrollDirectionIdle); - currentPage_->highlightExit( ); - state = RETROFE_HIGHLIGHT_EXIT; - break; - - // Wait for onHighlightExit animation to finish; load art - case RETROFE_HIGHLIGHT_EXIT: - if (currentPage_->isIdle( )) - { - currentPage_->highlightLoadArt( ); - state = RETROFE_HIGHLIGHT_LOAD_ART; - } - break; - - // Start onHighlightEnter animation - case RETROFE_HIGHLIGHT_LOAD_ART: - currentPage_->highlightEnter( ); - if ( currentPage_->getSelectedItem( ) ) - l.LEDBlinky( 9, currentPage_->getSelectedItem( )->collectionInfo->name, currentPage_->getSelectedItem( ) ); - state = RETROFE_HIGHLIGHT_ENTER; - break; - - // Wait for onHighlightEnter animation to finish - case RETROFE_HIGHLIGHT_ENTER: - RETROFE_STATE state_tmp; - if (currentPage_->isMenuIdle( ) && - ((state_tmp = processUserInput( currentPage_ )) == RETROFE_HIGHLIGHT_REQUEST || - state_tmp == RETROFE_MENUJUMP_REQUEST || - state_tmp == RETROFE_PLAYLIST_REQUEST) ) - { - state = state_tmp; - } - else if (currentPage_->isIdle( )) - { - state = RETROFE_IDLE; - } - break; - - // Next page; start onMenuExit animation - case RETROFE_NEXT_PAGE_REQUEST: - currentPage_->exitMenu( ); - state = RETROFE_NEXT_PAGE_MENU_EXIT; - break; - - // Wait for onMenuExit animation to finish; load new page if applicable; load art - case RETROFE_NEXT_PAGE_MENU_EXIT: - if ( currentPage_->isIdle( ) ) - { - if ( currentPage_->getSelectedItem( ) ) - l.LEDBlinky( 8, currentPage_->getSelectedItem( )->name, currentPage_->getSelectedItem( ) ); - lastMenuOffsets_[currentPage_->getCollectionName( )] = currentPage_->getScrollOffsetIndex( ); - lastMenuPlaylists_[currentPage_->getCollectionName( )] = currentPage_->getPlaylistName( ); - std::string nextPageName = nextPageItem_->name; - if ( !menuMode_ ) - { - // Load new layout if available - std::string layoutName; - config_.getProperty( "layout", layoutName ); - PageBuilder pb( layoutName, "layout", config_, &fontcache_ ); - Page *page = pb.buildPage( nextPageItem_->name ); - if ( page ) - { - currentPage_->freeGraphicsMemory( ); - pages_.push( currentPage_ ); - currentPage_ = page; - } - } - - config_.setProperty( "currentCollection", nextPageName ); - - CollectionInfo *info; - if ( menuMode_ ) - info = getMenuCollection( nextPageName ); - else - info = getCollection( nextPageName ); - - currentPage_->pushCollection(info); - - bool rememberMenu = false; - config_.getProperty( "rememberMenu", rememberMenu ); - - std::string autoPlaylist = "all"; - config_.getProperty( "autoPlaylist", autoPlaylist ); - - if (rememberMenu && lastMenuPlaylists_.find( nextPageName ) != lastMenuPlaylists_.end( )) - { - currentPage_->selectPlaylist( lastMenuPlaylists_[nextPageName] ); // Switch to last playlist - } - else - { - currentPage_->selectPlaylist( autoPlaylist ); - if (currentPage_->getPlaylistName() != autoPlaylist ) - currentPage_->selectPlaylist( "all" ); - } - - if ( rememberMenu && lastMenuOffsets_.find( nextPageName ) != lastMenuOffsets_.end( ) ) - { - currentPage_->setScrollOffsetIndex( lastMenuOffsets_[nextPageName] ); - } - - currentPage_->onNewItemSelected( ); - currentPage_->reallocateMenuSpritePoints( ); - - state = RETROFE_NEXT_PAGE_MENU_LOAD_ART; - - // Check if we've entered an empty collection and need to go back automatically - if (currentPage_->getCollectionSize() == 0) - { - bool backOnEmpty = false; - config_.getProperty( "backOnEmpty", backOnEmpty ); - if (backOnEmpty) - state = RETROFE_BACK_MENU_EXIT; - } - - - } - break; - - // Start onMenuEnter animation - case RETROFE_NEXT_PAGE_MENU_LOAD_ART: - if (currentPage_->getMenuDepth( ) != 1 ) - { - currentPage_->enterMenu( ); - } - else - { - currentPage_->start( ); - } - if ( currentPage_->getSelectedItem( ) ) - l.LEDBlinky( 9, currentPage_->getSelectedItem( )->collectionInfo->name, currentPage_->getSelectedItem( ) ); - state = RETROFE_NEXT_PAGE_MENU_ENTER; - break; - - // Wait for onMenuEnter animation to finish - case RETROFE_NEXT_PAGE_MENU_ENTER: - if ( currentPage_->isIdle( ) ) - { - inputClear = false; - config_.getProperty( "collectionInputClear", inputClear ); - if ( inputClear ) - { - // Empty event queue - SDL_Event e; - while ( SDL_PollEvent( &e ) ) - input_.update(e); - input_.resetStates( ); - } - state = RETROFE_IDLE; - } - break; - - // Start exit animation - case RETROFE_COLLECTION_DOWN_REQUEST: - if ( !pages_.empty( ) && currentPage_->getMenuDepth( ) == 1) // Inside a collection with a different layout - { - currentPage_->stop( ); - m.clearPage( ); - menuMode_ = false; - state = RETROFE_COLLECTION_DOWN_EXIT; - } - else if ( currentPage_->getMenuDepth( ) > 1 ) // Inside a collection with the same layout - { - currentPage_->exitMenu( ); - state = RETROFE_COLLECTION_DOWN_EXIT; - } - else // Not in a collection - { - state = RETROFE_COLLECTION_DOWN_ENTER; - - if ( attractMode_ ) // Check playlist change in attract mode - { - attractModePlaylistCollectionNumber_ += 1; - int attractModePlaylistCollectionNumber = 0; - config_.getProperty( "attractModePlaylistCollectionNumber", attractModePlaylistCollectionNumber ); - // Check if playlist should be changed - if ( attractModePlaylistCollectionNumber_ > 0 && attractModePlaylistCollectionNumber_ >= attractModePlaylistCollectionNumber ) - { - attractModePlaylistCollectionNumber_ = 0; - currentPage_->nextPlaylist( ); - std::string attractModeSkipPlaylist = ""; - config_.getProperty( "attractModeSkipPlaylist", attractModeSkipPlaylist ); - if (currentPage_->getPlaylistName( ) == attractModeSkipPlaylist) - currentPage_->nextPlaylist( ); - state = RETROFE_PLAYLIST_REQUEST; - } - } - } - break; - - // Wait for the menu exit animation to finish - case RETROFE_COLLECTION_DOWN_EXIT: - if ( currentPage_->isIdle( ) ) - { - lastMenuOffsets_[currentPage_->getCollectionName( )] = currentPage_->getScrollOffsetIndex( ); - lastMenuPlaylists_[currentPage_->getCollectionName( )] = currentPage_->getPlaylistName( ); - if (currentPage_->getMenuDepth( ) == 1) // Inside a collection with a different layout - { - currentPage_->deInitialize( ); - delete currentPage_; - currentPage_ = pages_.top( ); - pages_.pop( ); - currentPage_->allocateGraphicsMemory( ); - } - else // Inside a collection with the same layout - { - currentPage_->popCollection( ); - } - config_.setProperty( "currentCollection", currentPage_->getCollectionName( ) ); - - bool rememberMenu = false; - config_.getProperty( "rememberMenu", rememberMenu ); - - std::string autoPlaylist = "all"; - config_.getProperty( "autoPlaylist", autoPlaylist ); - - if (rememberMenu && lastMenuPlaylists_.find( currentPage_->getCollectionName( ) ) != lastMenuPlaylists_.end( )) - { - currentPage_->selectPlaylist( lastMenuPlaylists_[currentPage_->getCollectionName( )] ); // Switch to last playlist - } - else - { - currentPage_->selectPlaylist( autoPlaylist ); - if (currentPage_->getPlaylistName() != autoPlaylist ) - currentPage_->selectPlaylist( "all" ); - } - - if (rememberMenu && lastMenuPlaylists_.find( currentPage_->getCollectionName( ) ) != lastMenuPlaylists_.end( )) - { - currentPage_->setScrollOffsetIndex( lastMenuOffsets_[currentPage_->getCollectionName( )] ); - } - - state = RETROFE_COLLECTION_DOWN_MENU_ENTER; - currentPage_->onNewItemSelected( ); - - if ( attractMode_ ) // Check playlist change in attract mode - { - attractModePlaylistCollectionNumber_ += 1; - int attractModePlaylistCollectionNumber = 0; - config_.getProperty( "attractModePlaylistCollectionNumber", attractModePlaylistCollectionNumber ); - // Check if playlist should be changed - if ( attractModePlaylistCollectionNumber_ > 0 && attractModePlaylistCollectionNumber_ >= attractModePlaylistCollectionNumber ) - { - attractModePlaylistCollectionNumber_ = 0; - currentPage_->nextPlaylist( ); - std::string attractModeSkipPlaylist = ""; - config_.getProperty( "attractModeSkipPlaylist", attractModeSkipPlaylist ); - if (currentPage_->getPlaylistName( ) == attractModeSkipPlaylist) - currentPage_->nextPlaylist( ); - state = RETROFE_PLAYLIST_REQUEST; - } - } - - } - break; - - - // Start menu enter animation - case RETROFE_COLLECTION_DOWN_MENU_ENTER: - currentPage_->enterMenu( ); - state = RETROFE_COLLECTION_DOWN_ENTER; - break; - - - // Waiting for enter animation to stop - case RETROFE_COLLECTION_DOWN_ENTER: - if ( currentPage_->isIdle( ) ) - { - int attractModePlaylistCollectionNumber = 0; - config_.getProperty( "attractModePlaylistCollectionNumber", attractModePlaylistCollectionNumber ); - if (!( attractMode_ && attractModePlaylistCollectionNumber > 0 && attractModePlaylistCollectionNumber_ == 0 )) - { - currentPage_->setScrolling(Page::ScrollDirectionForward); - currentPage_->scroll(true); - currentPage_->updateScrollPeriod( ); - } - state = RETROFE_COLLECTION_DOWN_SCROLL; - } - break; - - // Waiting for scrolling animation to stop - case RETROFE_COLLECTION_DOWN_SCROLL: - if ( currentPage_->isMenuIdle( ) ) - { - std::string attractModeSkipCollection = ""; - config_.getProperty( "attractModeSkipCollection", attractModeSkipCollection ); - // Check if we need to skip this collection in attract mode or if we can select it - if ( attractMode_ && currentPage_->getSelectedItem( )->name == attractModeSkipCollection ) - { - currentPage_->setScrolling(Page::ScrollDirectionForward); - currentPage_->scroll(true); - currentPage_->updateScrollPeriod( ); - } - else - { - RETROFE_STATE state_tmp = processUserInput( currentPage_ ); - if ( state_tmp == RETROFE_COLLECTION_DOWN_REQUEST ) - { - state = RETROFE_COLLECTION_DOWN_REQUEST; - } - else if ( state_tmp == RETROFE_COLLECTION_UP_REQUEST ) - { - state = RETROFE_COLLECTION_UP_REQUEST; - } - else - { - currentPage_->setScrolling(Page::ScrollDirectionIdle); // Stop scrolling - nextPageItem_ = currentPage_->getSelectedItem( ); - bool enterOnCollection = true; - config_.getProperty( "enterOnCollection", enterOnCollection ); - if ( currentPage_->getSelectedItem( )->leaf || (!attractMode_ && !enterOnCollection) ) // Current selection is a game or enterOnCollection is not set - { - state = RETROFE_HIGHLIGHT_REQUEST; - } - else // Current selection is a menu - { - state = RETROFE_COLLECTION_HIGHLIGHT_EXIT; - } - } - } - } - break; - - - // Start onHighlightExit animation - case RETROFE_COLLECTION_HIGHLIGHT_REQUEST: - currentPage_->highlightExit( ); - state = RETROFE_COLLECTION_HIGHLIGHT_EXIT; - break; - - // Wait for onHighlightExit animation to finish; load art - case RETROFE_COLLECTION_HIGHLIGHT_EXIT: - if (currentPage_->isIdle( )) - { - currentPage_->highlightLoadArt( ); - state = RETROFE_COLLECTION_HIGHLIGHT_LOAD_ART; - } - break; - - // Start onHighlightEnter animation - case RETROFE_COLLECTION_HIGHLIGHT_LOAD_ART: - currentPage_->highlightEnter( ); - if ( currentPage_->getSelectedItem( ) ) - l.LEDBlinky( 9, currentPage_->getSelectedItem( )->collectionInfo->name, currentPage_->getSelectedItem( ) ); - state = RETROFE_COLLECTION_HIGHLIGHT_ENTER; - break; - - // Wait for onHighlightEnter animation to finish - case RETROFE_COLLECTION_HIGHLIGHT_ENTER: - if (currentPage_->isIdle( )) - { - RETROFE_STATE state_tmp = processUserInput( currentPage_ ); - if ( state_tmp == RETROFE_COLLECTION_DOWN_REQUEST ) - { - state = RETROFE_COLLECTION_DOWN_REQUEST; - } - else if ( state_tmp == RETROFE_COLLECTION_UP_REQUEST ) - { - state = RETROFE_COLLECTION_UP_REQUEST; - } - else - { - state = RETROFE_NEXT_PAGE_REQUEST; - } - } - break; - - // Start exit animation - case RETROFE_COLLECTION_UP_REQUEST: - if ( !pages_.empty( ) && currentPage_->getMenuDepth( ) == 1) // Inside a collection with a different layout - { - currentPage_->stop( ); - m.clearPage( ); - menuMode_ = false; - state = RETROFE_COLLECTION_UP_EXIT; - } - else if ( currentPage_->getMenuDepth( ) > 1 ) // Inside a collection with the same layout - { - currentPage_->exitMenu( ); - state = RETROFE_COLLECTION_UP_EXIT; - } - else // Not in a collection - { - state = RETROFE_COLLECTION_UP_ENTER; - } - break; - - // Wait for the menu exit animation to finish - case RETROFE_COLLECTION_UP_EXIT: - if ( currentPage_->isIdle( ) ) - { - lastMenuOffsets_[currentPage_->getCollectionName( )] = currentPage_->getScrollOffsetIndex( ); - lastMenuPlaylists_[currentPage_->getCollectionName( )] = currentPage_->getPlaylistName( ); - if (currentPage_->getMenuDepth( ) == 1) // Inside a collection with a different layout - { - currentPage_->deInitialize( ); - delete currentPage_; - currentPage_ = pages_.top( ); - pages_.pop( ); - currentPage_->allocateGraphicsMemory( ); - } - else // Inside a collection with the same layout - { - currentPage_->popCollection( ); - } - config_.setProperty( "currentCollection", currentPage_->getCollectionName( ) ); - - bool rememberMenu = false; - config_.getProperty( "rememberMenu", rememberMenu ); - - std::string autoPlaylist = "all"; - config_.getProperty( "autoPlaylist", autoPlaylist ); - - if (rememberMenu && lastMenuPlaylists_.find( currentPage_->getCollectionName( ) ) != lastMenuPlaylists_.end( )) - { - currentPage_->selectPlaylist( lastMenuPlaylists_[currentPage_->getCollectionName( )] ); // Switch to last playlist - } - else - { - currentPage_->selectPlaylist( autoPlaylist ); - if (currentPage_->getPlaylistName() != autoPlaylist ) - currentPage_->selectPlaylist( "all" ); - } - - if ( rememberMenu && lastMenuOffsets_.find( currentPage_->getCollectionName( ) ) != lastMenuOffsets_.end( ) ) - { - currentPage_->setScrollOffsetIndex( lastMenuOffsets_[currentPage_->getCollectionName( )] ); - } - - currentPage_->onNewItemSelected( ); - state = RETROFE_COLLECTION_UP_MENU_ENTER; - } - break; - - - // Start menu enter animation - case RETROFE_COLLECTION_UP_MENU_ENTER: - currentPage_->enterMenu( ); - state = RETROFE_COLLECTION_UP_ENTER; - break; - - - // Waiting for enter animation to stop - case RETROFE_COLLECTION_UP_ENTER: - if ( currentPage_->isIdle( ) ) - { - currentPage_->setScrolling(Page::ScrollDirectionBack); - currentPage_->scroll(false); - currentPage_->updateScrollPeriod( ); - state = RETROFE_COLLECTION_UP_SCROLL; - } - break; - - // Waiting for scrolling animation to stop - case RETROFE_COLLECTION_UP_SCROLL: - if ( currentPage_->isMenuIdle( ) ) - { - RETROFE_STATE state_tmp; - state_tmp = processUserInput( currentPage_ ); - if ( state_tmp == RETROFE_COLLECTION_DOWN_REQUEST ) - { - state = RETROFE_COLLECTION_DOWN_REQUEST; - } - else if ( state_tmp == RETROFE_COLLECTION_UP_REQUEST ) - { - state = RETROFE_COLLECTION_UP_REQUEST; - } - else - { - currentPage_->setScrolling(Page::ScrollDirectionIdle); // Stop scrolling - nextPageItem_ = currentPage_->getSelectedItem( ); - bool enterOnCollection = true; - config_.getProperty( "enterOnCollection", enterOnCollection ); - if ( currentPage_->getSelectedItem( )->leaf || !enterOnCollection ) // Current selection is a game or enterOnCollection is not set - { - state = RETROFE_HIGHLIGHT_REQUEST; - } - else // Current selection is a menu - { - state = RETROFE_COLLECTION_HIGHLIGHT_EXIT; - } - } - } - break; - - - // Launching a menu entry - case RETROFE_HANDLE_MENUENTRY: - - // Empty event queue - SDL_Event e; - while ( SDL_PollEvent( &e ) ) - input_.update(e); - input_.resetStates( ); - - // Handle menu entry - m.handleEntry( currentPage_->getSelectedItem( ) ); - - // Empty event queue - while ( SDL_PollEvent( &e ) ) - input_.update(e); - input_.resetStates( ); - - state = RETROFE_IDLE; - break; - - // Launching game; start onGameEnter animation - case RETROFE_LAUNCH_ENTER: - currentPage_->enterGame( ); // Start onGameEnter animation - currentPage_->playSelect( ); // Play launch sound - state = RETROFE_LAUNCH_REQUEST; - break; - - // Wait for onGameEnter animation to finish; launch game; start onGameExit animation - case RETROFE_LAUNCH_REQUEST: - if ( currentPage_->isIdle( ) && !currentPage_->isSelectPlaying( ) ) - { - nextPageItem_ = currentPage_->getSelectedItem( ); - launchEnter( ); - CollectionInfoBuilder cib(config_, *metadb_); - std::string attractModeSkipPlaylist = ""; - std::string lastPlayedSkipCollection = ""; - int size = 0; - config_.getProperty( "attractModeSkipPlaylist", attractModeSkipPlaylist ); - config_.getProperty( "lastPlayedSkipCollection", lastPlayedSkipCollection ); - config_.getProperty( "lastplayedSize", size ); - - if (currentPage_->getPlaylistName( ) != attractModeSkipPlaylist && - nextPageItem_->collectionInfo->name != lastPlayedSkipCollection) - cib.updateLastPlayedPlaylist( currentPage_->getCollection(), nextPageItem_, size ); // Update last played playlist if not currently in the skip playlist (e.g. settings) - - l.LEDBlinky( 3, nextPageItem_->collectionInfo->name, nextPageItem_ ); - if (l.run(nextPageItem_->collectionInfo->name, nextPageItem_)) // Run and check if we need to reboot - { - attract_.reset( ); - reboot_ = true; - state = RETROFE_QUIT_REQUEST; - } - else - { - launchExit( ); - l.LEDBlinky( 4 ); - currentPage_->exitGame( ); - state = RETROFE_LAUNCH_EXIT; - } - } - break; - - // Wait for onGameExit animation to finish - case RETROFE_LAUNCH_EXIT: - if ( currentPage_->isIdle( ) ) - { - state = RETROFE_IDLE; - } - break; - - // Go back a page; start onMenuExit animation - case RETROFE_BACK_REQUEST: - if (currentPage_->getMenuDepth( ) == 1) - { - currentPage_->stop( ); - m.clearPage( ); - menuMode_ = false; - } - else - { - currentPage_->exitMenu( ); - } - state = RETROFE_BACK_MENU_EXIT; - break; - - // Wait for onMenuExit animation to finish; load previous page; load art - case RETROFE_BACK_MENU_EXIT: - if ( currentPage_->isIdle( ) ) - { - lastMenuOffsets_[currentPage_->getCollectionName( )] = currentPage_->getScrollOffsetIndex( ); - lastMenuPlaylists_[currentPage_->getCollectionName( )] = currentPage_->getPlaylistName( ); - if (currentPage_->getMenuDepth( ) == 1) - { - currentPage_->deInitialize( ); - delete currentPage_; - currentPage_ = pages_.top( ); - pages_.pop( ); - currentPage_->allocateGraphicsMemory( ); - } - else - { - currentPage_->popCollection( ); - } - config_.setProperty( "currentCollection", currentPage_->getCollectionName( ) ); - - bool rememberMenu = false; - config_.getProperty( "rememberMenu", rememberMenu ); - - std::string autoPlaylist = "all"; - config_.getProperty( "autoPlaylist", autoPlaylist ); - - if (rememberMenu && lastMenuPlaylists_.find( currentPage_->getCollectionName( ) ) != lastMenuPlaylists_.end( )) - { - currentPage_->selectPlaylist( lastMenuPlaylists_[currentPage_->getCollectionName( )] ); // Switch to last playlist - } - else - { - currentPage_->selectPlaylist( autoPlaylist ); - if (currentPage_->getPlaylistName() != autoPlaylist ) - currentPage_->selectPlaylist( "all" ); - } - - if ( rememberMenu && lastMenuOffsets_.find( currentPage_->getCollectionName( ) ) != lastMenuOffsets_.end( ) ) - { - currentPage_->setScrollOffsetIndex( lastMenuOffsets_[currentPage_->getCollectionName( )] ); - } - - currentPage_->onNewItemSelected( ); - currentPage_->reallocateMenuSpritePoints( ); - state = RETROFE_BACK_MENU_LOAD_ART; - } - break; - - // Start onMenuEnter animation - case RETROFE_BACK_MENU_LOAD_ART: - currentPage_->enterMenu( ); - state = RETROFE_BACK_MENU_ENTER; - break; - - // Wait for onMenuEnter animation to finish - case RETROFE_BACK_MENU_ENTER: - if ( currentPage_->isIdle( ) ) - { - bool collectionInputClear = false; - config_.getProperty( "collectionInputClear", collectionInputClear ); - if ( collectionInputClear ) - { - // Empty event queue - SDL_Event e; - while ( SDL_PollEvent( &e ) ) - input_.update(e); - input_.resetStates( ); - } - state = RETROFE_IDLE; - } - break; - - // Start menu mode - case RETROFE_MENUMODE_START_REQUEST: - if ( currentPage_->isIdle( ) ) - { - lastMenuOffsets_[currentPage_->getCollectionName( )] = currentPage_->getScrollOffsetIndex( ); - lastMenuPlaylists_[currentPage_->getCollectionName( )] = currentPage_->getPlaylistName( ); - std::string layoutName; - config_.getProperty( "layout", layoutName ); - PageBuilder pb( layoutName, "layout", config_, &fontcache_, true ); - Page *page = pb.buildPage( ); - if ( page ) - { - currentPage_->freeGraphicsMemory( ); - pages_.push( currentPage_ ); - currentPage_ = page; - menuMode_ = true; - m.setPage( page ); - } - config_.setProperty( "currentCollection", "menu" ); - CollectionInfo *info = getMenuCollection( "menu" ); - currentPage_->pushCollection(info); - currentPage_->onNewItemSelected( ); - currentPage_->reallocateMenuSpritePoints( ); - state = RETROFE_MENUMODE_START_LOAD_ART; - } - break; - - case RETROFE_MENUMODE_START_LOAD_ART: - currentPage_->start(); - state = RETROFE_MENUMODE_START_ENTER; - break; - - case RETROFE_MENUMODE_START_ENTER: - if ( currentPage_->isIdle( ) ) - { - SDL_Event e; - while ( SDL_PollEvent( &e ) ) - input_.update(e); - input_.resetStates( ); - state = RETROFE_IDLE; - } - break; - - // Wait for splash mode animation to finish - case RETROFE_NEW: - if ( currentPage_->isIdle( ) ) - { - state = RETROFE_IDLE; - } - break; - - // Start the onExit animation - case RETROFE_QUIT_REQUEST: - currentPage_->stop( ); - state = RETROFE_QUIT; - break; - - // Wait for onExit animation to finish before quitting RetroFE - case RETROFE_QUIT: - if ( currentPage_->isGraphicsIdle( ) ) - { - l.LEDBlinky( 2 ); - running = false; - } - break; - } - - // Handle screen updates and attract mode - if ( running ) - { - lastTime = currentTime_; - currentTime_ = static_cast( SDL_GetTicks( ) ) / 1000; - - if ( currentTime_ < lastTime ) - { - currentTime_ = lastTime; - } - - deltaTime = currentTime_ - lastTime; - double sleepTime; - if (state == RETROFE_IDLE) - sleepTime = fpsIdleTime - deltaTime*1000; - else - sleepTime = fpsTime - deltaTime*1000; - if ( sleepTime > 0 && sleepTime < 1000 ) - { - SDL_Delay( static_cast( sleepTime ) ); - } - - if ( currentPage_ ) - { - if (!splashMode) - { - int attractReturn = attract_.update( deltaTime, *currentPage_ ); - if (attractReturn == 1) // Change playlist - { - attract_.reset( attract_.isSet( ) ); - - bool cyclePlaylist = false; - config_.getProperty( "attractModeCyclePlaylist", cyclePlaylist ); - - std::string cycleString; - config_.getProperty( "cyclePlaylist", cycleString ); - std::vector cycleVector; - Utils::listToVector( cycleString, cycleVector, ',' ); - - if ( cyclePlaylist ) - currentPage_->nextCyclePlaylist( cycleVector ); - else - currentPage_->nextPlaylist( ); - - std::string attractModeSkipPlaylist = ""; - config_.getProperty( "attractModeSkipPlaylist", attractModeSkipPlaylist ); - if (currentPage_->getPlaylistName( ) == attractModeSkipPlaylist) - { - if ( cyclePlaylist ) - currentPage_->nextCyclePlaylist( cycleVector ); - else - currentPage_->nextPlaylist( ); - } - state = RETROFE_PLAYLIST_REQUEST; - } - if (attractReturn == 2) // Change collection - { - attract_.reset( attract_.isSet( ) ); - state = RETROFE_COLLECTION_DOWN_REQUEST; - } - } - if ( menuMode_ ) - { - attract_.reset( ); - } - currentPage_->update( deltaTime ); - SDL_PumpEvents( ); - input_.updateKeystate( ); - if (!splashMode) - { - if ( currentPage_->isAttractIdle( ) ) - { - if ( !attractMode_ && attract_.isSet( ) ) - { - currentPage_->attractEnter( ); - l.LEDBlinky( 5 ); - } - else if ( attractMode_ && !attract_.isSet( ) ) - { - currentPage_->attractExit( ); - l.LEDBlinky( 6 ); - } - else if ( attract_.isSet( ) ) - { - currentPage_->attract( ); - } - attractMode_ = attract_.isSet( ); - } - } - } - - render( ); - } - } - return reboot_; -} - - -// Check if we can go back a page or quite RetroFE -bool RetroFE::back(bool &exit) -{ - bool canGoBack = false; - bool exitOnBack = false; - config_.getProperty( "exitOnFirstPageBack", exitOnBack ); - exit = false; - - if ( currentPage_->getMenuDepth( ) <= 1 && pages_.empty( ) ) - { - exit = exitOnBack; - } - else - { - canGoBack = true; - } - - return canGoBack; -} - - -// Process the user input -RetroFE::RETROFE_STATE RetroFE::processUserInput( Page *page ) -{ - bool exit = false; - RETROFE_STATE state = RETROFE_IDLE; - - // Poll all events until we find an active one - SDL_Event e; - while ( SDL_PollEvent( &e ) ) - { - input_.update(e); - if ( e.type == SDL_KEYDOWN && !e.key.repeat ) - { - break; - } - } - - // Handle next/previous game inputs - if ( page->isHorizontalScroll( ) ) - { - if (input_.keystate(UserInput::KeyCodeRight)) - { - attract_.reset( ); - page->setScrolling(Page::ScrollDirectionForward); - page->scroll(true); - page->updateScrollPeriod( ); - return state; - } - else if (input_.keystate(UserInput::KeyCodeLeft)) - { - attract_.reset( ); - page->setScrolling(Page::ScrollDirectionBack); - page->scroll(false); - page->updateScrollPeriod( ); - return state; - } - } - else - { - if (input_.keystate(UserInput::KeyCodeDown)) - { - attract_.reset( ); - page->setScrolling(Page::ScrollDirectionForward); - page->scroll(true); - page->updateScrollPeriod( ); - return state; - } - else if (input_.keystate(UserInput::KeyCodeUp)) - { - attract_.reset( ); - page->setScrolling(Page::ScrollDirectionBack); - page->scroll(false); - page->updateScrollPeriod( ); - return state; - } - } - - // Ignore other keys while the menu is scrolling - if ( page->isIdle( ) && currentTime_ - keyLastTime_ > keyDelayTime_ ) - { - if ( input_.keystate(UserInput::KeyCodeMenu) && !menuMode_) - { - state = RETROFE_MENUMODE_START_REQUEST; - } - - // Handle Collection Up/Down keys - else if ((input_.keystate(UserInput::KeyCodeCollectionUp) && ( page->isHorizontalScroll( ) || !input_.keystate(UserInput::KeyCodeUp))) || - (input_.keystate(UserInput::KeyCodeCollectionLeft) && (!page->isHorizontalScroll( ) || !input_.keystate(UserInput::KeyCodeLeft)))) - { - attract_.reset( ); - bool backOnCollection = false; - config_.getProperty( "backOnCollection", backOnCollection ); - if ( page->getMenuDepth( ) == 1 || !backOnCollection ) - state = RETROFE_COLLECTION_UP_REQUEST; - else - state = RETROFE_BACK_REQUEST; - } - - else if ((input_.keystate(UserInput::KeyCodeCollectionDown) && ( page->isHorizontalScroll( ) || !input_.keystate(UserInput::KeyCodeDown))) || - (input_.keystate(UserInput::KeyCodeCollectionRight) && (!page->isHorizontalScroll( ) || !input_.keystate(UserInput::KeyCodeRight)))) - { - attract_.reset( ); - bool backOnCollection = false; - config_.getProperty( "backOnCollection", backOnCollection ); - if ( page->getMenuDepth( ) == 1 || !backOnCollection ) - state = RETROFE_COLLECTION_DOWN_REQUEST; - else - state = RETROFE_BACK_REQUEST; - } - - else if (input_.keystate(UserInput::KeyCodePageUp)) - { - attract_.reset( ); - page->pageScroll(Page::ScrollDirectionBack); - state = RETROFE_MENUJUMP_REQUEST; - } - - else if (input_.keystate(UserInput::KeyCodePageDown)) - { - attract_.reset( ); - page->pageScroll(Page::ScrollDirectionForward); - state = RETROFE_MENUJUMP_REQUEST; - } - - else if (input_.keystate(UserInput::KeyCodeLetterUp)) - { - attract_.reset( ); - bool cfwLetterSub; - config_.getProperty( "cfwLetterSub", cfwLetterSub ); - if (cfwLetterSub && page->hasSubs()) - page->cfwLetterSubScroll(Page::ScrollDirectionBack); - else - page->letterScroll(Page::ScrollDirectionBack); - state = RETROFE_MENUJUMP_REQUEST; - } - - else if (input_.keystate(UserInput::KeyCodeLetterDown)) - { - attract_.reset( ); - bool cfwLetterSub; - config_.getProperty( "cfwLetterSub", cfwLetterSub ); - if (cfwLetterSub && page->hasSubs()) - page->cfwLetterSubScroll(Page::ScrollDirectionForward); - else - page->letterScroll(Page::ScrollDirectionForward); - state = RETROFE_MENUJUMP_REQUEST; - } - - else if ( input_.keystate(UserInput::KeyCodeFavPlaylist) ) - { - attract_.reset( ); - page->favPlaylist( ); - state = RETROFE_PLAYLIST_REQUEST; - } - - else if ( input_.keystate(UserInput::KeyCodeNextPlaylist) || - (input_.keystate(UserInput::KeyCodePlaylistDown) && page->isHorizontalScroll( )) || - (input_.keystate(UserInput::KeyCodePlaylistRight) && !page->isHorizontalScroll( ))) - { - attract_.reset( ); - page->nextPlaylist( ); - state = RETROFE_PLAYLIST_REQUEST; - } - - else if ( input_.keystate(UserInput::KeyCodePrevPlaylist) || - (input_.keystate(UserInput::KeyCodePlaylistUp) && page->isHorizontalScroll( )) || - (input_.keystate(UserInput::KeyCodePlaylistLeft) && !page->isHorizontalScroll( ))) - { - attract_.reset( ); - page->prevPlaylist( ); - state = RETROFE_PLAYLIST_REQUEST; - } - - else if ( input_.keystate(UserInput::KeyCodeCyclePlaylist) || - input_.keystate(UserInput::KeyCodeNextCyclePlaylist) ) - { - attract_.reset( ); - std::string cycleString; - config_.getProperty( "cyclePlaylist", cycleString ); - std::vector cycleVector; - Utils::listToVector( cycleString, cycleVector, ',' ); - page->nextCyclePlaylist( cycleVector ); - state = RETROFE_PLAYLIST_REQUEST; - } - - else if ( input_.keystate(UserInput::KeyCodePrevCyclePlaylist) ) - { - attract_.reset( ); - std::string cycleString; - config_.getProperty( "cyclePlaylist", cycleString ); - std::vector cycleVector; - Utils::listToVector( cycleString, cycleVector, ',' ); - page->prevCyclePlaylist( cycleVector ); - state = RETROFE_PLAYLIST_REQUEST; - } - - else if ( input_.keystate(UserInput::KeyCodeRemovePlaylist) ) - { - attract_.reset( ); - page->removePlaylist( ); - state = RETROFE_PLAYLIST_REQUEST; - } - - else if ( input_.keystate(UserInput::KeyCodeAddPlaylist) ) - { - attract_.reset( ); - page->addPlaylist( ); - state = RETROFE_PLAYLIST_REQUEST; - } - - else if ( input_.keystate(UserInput::KeyCodeTogglePlaylist) ) - { - attract_.reset( ); - page->togglePlaylist( ); - state = RETROFE_PLAYLIST_REQUEST; - } - - else if ( input_.keystate(UserInput::KeyCodeSkipForward) ) - { - attract_.reset( ); - page->skipForward( ); - page->jukeboxJump( ); - keyLastTime_ = currentTime_; - } - - else if ( input_.keystate(UserInput::KeyCodeSkipBackward) ) - { - attract_.reset( ); - page->skipBackward( ); - page->jukeboxJump( ); - keyLastTime_ = currentTime_; - } - - else if ( input_.keystate(UserInput::KeyCodeSkipForwardp) ) - { - attract_.reset( ); - page->skipForwardp( ); - page->jukeboxJump( ); - keyLastTime_ = currentTime_; - } - - else if ( input_.keystate(UserInput::KeyCodeSkipBackwardp) ) - { - attract_.reset( ); - page->skipBackwardp( ); - page->jukeboxJump( ); - keyLastTime_ = currentTime_; - } - - else if ( input_.keystate(UserInput::KeyCodePause) ) - { - attract_.reset( ); - page->pause( ); - page->jukeboxJump( ); - keyLastTime_ = currentTime_; - } - - else if ( input_.keystate(UserInput::KeyCodeRestart) ) - { - attract_.reset( ); - page->restart( ); - keyLastTime_ = currentTime_; - } - - else if ( input_.keystate(UserInput::KeyCodeRandom) ) - { - attract_.reset( ); - page->selectRandom( ); - state = RETROFE_MENUJUMP_REQUEST; - } - - else if (input_.keystate(UserInput::KeyCodeAdminMode)) - { - //todo: add admin mode support - } - - else if (input_.keystate(UserInput::KeyCodeSelect)) - { - attract_.reset( ); - nextPageItem_ = page->getSelectedItem( ); - - if ( nextPageItem_ ) - { - if ( nextPageItem_->leaf ) - { - if ( menuMode_ ) - { - state = RETROFE_HANDLE_MENUENTRY; - } - else - { - state = RETROFE_LAUNCH_ENTER; - } - } - else - { - CollectionInfoBuilder cib(config_, *metadb_); - std::string attractModeSkipPlaylist = ""; - std::string lastPlayedSkipCollection = ""; - int size = 0; - config_.getProperty( "attractModeSkipPlaylist", attractModeSkipPlaylist ); - config_.getProperty( "lastPlayedSkipCollection", lastPlayedSkipCollection ); - config_.getProperty("lastplayedCollectionSize", size); - - if (currentPage_->getPlaylistName( ) != attractModeSkipPlaylist && - nextPageItem_->collectionInfo->name != lastPlayedSkipCollection) - cib.updateLastPlayedPlaylist( currentPage_->getCollection(), nextPageItem_, size ); // Update last played playlist if not currently in the skip playlist (e.g. settings) - state = RETROFE_NEXT_PAGE_REQUEST; - } - } - } - - else if (input_.keystate(UserInput::KeyCodeBack)) - { - attract_.reset( ); - if ( back( exit ) || exit ) - { - state = (exit) ? RETROFE_QUIT_REQUEST : RETROFE_BACK_REQUEST; - } - } - - else if (input_.keystate(UserInput::KeyCodeQuit)) - { - attract_.reset( ); - state = RETROFE_QUIT_REQUEST; - } - - else if (input_.keystate(UserInput::KeyCodeReboot)) - { - attract_.reset( ); - reboot_ = true; - state = RETROFE_QUIT_REQUEST; - } - - else if (input_.keystate(UserInput::KeyCodeSaveFirstPlaylist)) - { - attract_.reset( ); - if ( page->getMenuDepth( ) == 1 ) - { - firstPlaylist_ = page->getPlaylistName( ); - saveRetroFEState( ); - } - } - } - - if ( state != RETROFE_IDLE ) - { - keyLastTime_ = currentTime_; - return state; - } - - // Check if we're done scrolling - if ( !input_.keystate(UserInput::KeyCodeUp) && - !input_.keystate(UserInput::KeyCodeLeft) && - !input_.keystate(UserInput::KeyCodeDown) && - !input_.keystate(UserInput::KeyCodeRight) && - !input_.keystate(UserInput::KeyCodePlaylistUp) && - !input_.keystate(UserInput::KeyCodePlaylistLeft) && - !input_.keystate(UserInput::KeyCodePlaylistDown) && - !input_.keystate(UserInput::KeyCodePlaylistRight) && - !input_.keystate(UserInput::KeyCodeCollectionUp) && - !input_.keystate(UserInput::KeyCodeCollectionLeft) && - !input_.keystate(UserInput::KeyCodeCollectionDown) && - !input_.keystate(UserInput::KeyCodeCollectionRight) && - !input_.keystate(UserInput::KeyCodePageUp) && - !input_.keystate(UserInput::KeyCodePageDown) && - !input_.keystate(UserInput::KeyCodeLetterUp) && - !input_.keystate(UserInput::KeyCodeLetterDown) && - !input_.keystate(UserInput::KeyCodeCollectionUp) && - !input_.keystate(UserInput::KeyCodeCollectionDown) && - !attract_.isActive( ) ) - { - page->resetScrollPeriod( ); - if (page->isMenuScrolling( )) - { - attract_.reset( attract_.isSet( ) ); - state = RETROFE_HIGHLIGHT_REQUEST; - } - } - - return state; -} - - -// Load a page -Page *RetroFE::loadPage( ) -{ - std::string layoutName; - - config_.getProperty( "layout", layoutName ); - - PageBuilder pb( layoutName, "layout", config_, &fontcache_ ); - Page *page = pb.buildPage( ); - - if ( !page ) - { - Logger::write( Logger::ZONE_ERROR, "RetroFE", "Could not create page" ); - } - - return page; -} - - -// Load the splash page -Page *RetroFE::loadSplashPage( ) -{ - std::string layoutName; - config_.getProperty( "layout", layoutName ); - - PageBuilder pb( layoutName, "splash", config_, &fontcache_ ); - Page * page = pb.buildPage( ); - page->start( ); - - return page; -} - - -// Load a collection -CollectionInfo *RetroFE::getCollection(std::string collectionName) -{ - - // Check if subcollections should be merged or split - bool subsSplit = false; - config_.getProperty( "subsSplit", subsSplit ); - - // Build the collection - CollectionInfoBuilder cib(config_, *metadb_); - CollectionInfo *collection = cib.buildCollection( collectionName ); - collection->subsSplit = subsSplit; - cib.injectMetadata( collection ); - - DIR *dp; - struct dirent *dirp; - - std::string path = Utils::combinePath( Configuration::absolutePath, "collections", collectionName ); - dp = opendir( path.c_str( ) ); - - // Loading sub collection files - while ( (dirp = readdir( dp )) != NULL ) - { - std::string file = dirp->d_name; - - size_t position = file.find_last_of( "." ); - std::string basename = (std::string::npos == position)? file : file.substr( 0, position ); - - std::string comparator = ".sub"; - int start = file.length( ) - comparator.length( ); - - if ( start >= 0 ) - { - if ( file.compare( start, comparator.length( ), comparator ) == 0 ) - { - Logger::write( Logger::ZONE_INFO, "RetroFE", "Loading subcollection into menu: " + basename ); - - CollectionInfo *subcollection = cib.buildCollection( basename, collectionName ); - collection->addSubcollection( subcollection ); - subcollection->subsSplit = subsSplit; - cib.injectMetadata( subcollection ); - collection->hasSubs = true; - } - } - } - if (dp) closedir( dp ); - - bool menuSort = true; - config_.getProperty( "collections." + collectionName + ".list.menuSort", menuSort ); - - if (menuSort) - collection->sortItems( ); - - MenuParser mp; - mp.buildMenuItems( collection, menuSort); - - cib.addPlaylists( collection ); - collection->sortPlaylists( ); - - // Add extra info, if available - for ( std::vector::iterator it = collection->items.begin( ); it != collection->items.end( ); it++ ) - { - std::string path = Utils::combinePath( Configuration::absolutePath, "collections", collectionName, "info", (*it)->name + ".conf" ); - (*it)->loadInfo( path ); - } - - // Remove parenthesis and brackets, if so configured - bool showParenthesis = true; - bool showSquareBrackets = true; - - (void)config_.getProperty( "showParenthesis", showParenthesis ); - (void)config_.getProperty( "showSquareBrackets", showSquareBrackets ); - - typedef std::map *> Playlists_T; - for ( Playlists_T::iterator itP = collection->playlists.begin( ); itP != collection->playlists.end( ); itP++ ) - { - for ( std::vector ::iterator itI = itP->second->begin( ); itI != itP->second->end( ); itI++ ) - { - if ( !showParenthesis ) - { - std::string::size_type firstPos = (*itI)->title.find_first_of( "(" ); - std::string::size_type secondPos = (*itI)->title.find_first_of( ")", firstPos ); - - while ( firstPos != std::string::npos && secondPos != std::string::npos ) - { - firstPos = (*itI)->title.find_first_of( "(" ); - secondPos = (*itI)->title.find_first_of( ")", firstPos ); - - if ( firstPos != std::string::npos ) - { - (*itI)->title.erase( firstPos, (secondPos - firstPos) + 1 ); - } - } - } - if ( !showSquareBrackets ) - { - std::string::size_type firstPos = (*itI)->title.find_first_of( "[" ); - std::string::size_type secondPos = (*itI)->title.find_first_of( "]", firstPos ); - - while ( firstPos != std::string::npos && secondPos != std::string::npos ) - { - firstPos = (*itI)->title.find_first_of( "[" ); - secondPos = (*itI)->title.find_first_of( "]", firstPos ); - - if ( firstPos != std::string::npos && secondPos != std::string::npos ) - { - (*itI)->title.erase( firstPos, (secondPos - firstPos) + 1 ); - } - } - } - } - } - - return collection; -} - - -// Load a menu -CollectionInfo *RetroFE::getMenuCollection( std::string collectionName ) -{ - std::string menuPath = Utils::combinePath( Configuration::absolutePath, "menu" ); - std::string menuFile = Utils::combinePath( menuPath, collectionName + ".txt" ); - std::vector menuVector; - CollectionInfoBuilder cib( config_, *metadb_ ); - CollectionInfo *collection = new CollectionInfo( collectionName, menuPath, "", "", "" ); - cib.ImportBasicList( collection, menuFile, menuVector ); - for ( std::vector::iterator it = menuVector.begin( ); it != menuVector.end( ); ++it) - { - (*it)->leaf = false; - size_t position = (*it)->name.find( "=" ); - if ( position != std::string::npos ) - { - (*it)->ctrlType = Utils::trimEnds( (*it)->name.substr( position+1, (*it)->name.size( )-1 ) ); - (*it)->name = Utils::trimEnds( (*it)->name.substr( 0, position ) ); - (*it)->title = (*it)->name; - (*it)->fullTitle = (*it)->name; - (*it)->leaf = true; - } - (*it)->collectionInfo = collection; - collection->items.push_back( *it ); - } - collection->playlists["all"] = &collection->items; - return collection; -} - - -void RetroFE::saveRetroFEState( ) -{ - std::string file = Utils::combinePath(Configuration::absolutePath, "settings_saved.conf"); - Logger::write(Logger::ZONE_INFO, "RetroFE", "Saving settings_saved.conf"); - std::ofstream filestream; - try - { - filestream.open(file.c_str()); - filestream << "firstPlaylist = " << firstPlaylist_ << std::endl; - filestream.close(); - } - catch(std::exception &) - { - Logger::write(Logger::ZONE_ERROR, "RetroFE", "Save failed: " + file); - } -} +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ + + +#include "RetroFE.h" +#include "Collection/CollectionInfoBuilder.h" +#include "Collection/CollectionInfo.h" +#include "Database/Configuration.h" +#include "Collection/Item.h" +#include "Execute/Launcher.h" +#include "Menu/Menu.h" +#include "Utility/Log.h" +#include "Utility/Utils.h" +#include "Collection/MenuParser.h" +#include "SDL.h" +#include "Control/UserInput.h" +#include "Graphics/PageBuilder.h" +#include "Graphics/Page.h" +#include "Graphics/Component/ScrollingList.h" +#include "Graphics/Component/Video.h" +#include +#include "Video/VideoFactory.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux) || defined(__APPLE__) +#include +#include +#include +#include +#endif + +#ifdef WIN32 +#include +#include +#include +#endif + + +RetroFE::RetroFE( Configuration &c ) + : initialized(false) + , initializeError(false) + , initializeThread(NULL) + , config_(c) + , db_(NULL) + , metadb_(NULL) + , input_(config_) + , currentPage_(NULL) + , keyInputDisable_(0) + , currentTime_(0) + , lastLaunchReturnTime_(0) + , keyLastTime_(0) + , keyDelayTime_(.3f) + , reboot_(false) +{ + menuMode_ = false; + attractMode_ = false; + attractModePlaylistCollectionNumber_ = 0; + firstPlaylist_ = "all"; +} + + +RetroFE::~RetroFE( ) +{ + deInitialize( ); +} + + +// Render the current page to the screen +void RetroFE::render( ) +{ + + SDL_LockMutex( SDL::getMutex( ) ); + for ( int i = 0; i < SDL::getNumDisplays( ); ++i ) + { + SDL_SetRenderDrawColor( SDL::getRenderer( i ), 0x0, 0x0, 0x00, 0xFF ); + SDL_RenderClear( SDL::getRenderer( i ) ); + } + + if ( currentPage_ ) + { + currentPage_->draw( ); + } + + for ( int i = 0; i < SDL::getNumDisplays( ); ++i ) + { + SDL_RenderPresent( SDL::getRenderer( i ) ); + } + SDL_UnlockMutex( SDL::getMutex( ) ); + +} + + +// Initialize the configuration and database +int RetroFE::initialize( void *context ) +{ + + RetroFE *instance = static_cast(context); + + Logger::write( Logger::ZONE_INFO, "RetroFE", "Initializing" ); + + if ( !instance->input_.initialize( ) ) + { + Logger::write( Logger::ZONE_ERROR, "RetroFE", "Could not initialize user controls" ); + instance->initializeError = true; + return -1; + } + + instance->db_ = new DB( Utils::combinePath( Configuration::absolutePath, "meta.db" ) ); + + if ( !instance->db_->initialize( ) ) + { + Logger::write( Logger::ZONE_ERROR, "RetroFE", "Could not initialize database" ); + instance->initializeError = true; + return -1; + } + + instance->metadb_ = new MetadataDatabase( *(instance->db_), instance->config_ ); + + if ( !instance->metadb_->initialize( ) ) + { + Logger::write( Logger::ZONE_ERROR, "RetroFE", "Could not initialize meta database" ); + instance->initializeError = true; + return -1; + } + + instance->initialized = true; + return 0; + +} + + +// Launch a game/program +void RetroFE::launchEnter( ) +{ + + // Disable window focus + SDL_SetWindowGrab(SDL::getWindow( 0 ), SDL_FALSE); + + // Free the textures, and optionally take down SDL + freeGraphicsMemory( ); + + bool hideMouse = false; + int mouseX = 5000; + int mouseY = 5000; + config_.getProperty( "hideMouse", hideMouse ); + config_.getProperty( "mouseX", mouseX ); + config_.getProperty( "mouseY", mouseY ); + if ( hideMouse ) + SDL_WarpMouseGlobal( mouseX, mouseY ); +} + + +// Return from the launch of a game/program +void RetroFE::launchExit( ) +{ + + // Optionally set up SDL, and load the textures + allocateGraphicsMemory( ); + + // Restore the SDL settings + SDL_RestoreWindow( SDL::getWindow( 0 ) ); + SDL_RaiseWindow( SDL::getWindow( 0 ) ); + SDL_SetWindowGrab( SDL::getWindow( 0 ), SDL_TRUE ); + + // Empty event queue, but handle joystick add/remove events + SDL_Event e; + while ( SDL_PollEvent( &e ) ) + { + if ( e.type == SDL_JOYDEVICEADDED || e.type == SDL_JOYDEVICEREMOVED ) + { + input_.update( e ); + } + } + input_.resetStates( ); + attract_.reset( ); + + // Restore time settings + currentTime_ = static_cast( SDL_GetTicks( ) ) / 1000; + keyLastTime_ = currentTime_; + lastLaunchReturnTime_ = currentTime_; + + bool hideMouse = false; + int mouseX = 5000; + int mouseY = 5000; + config_.getProperty( "hideMouse", hideMouse ); + config_.getProperty( "mouseX", mouseX ); + config_.getProperty( "mouseY", mouseY ); + if ( hideMouse ) + SDL_WarpMouseGlobal( mouseX, mouseY ); + +} + + +// Free the textures, and optionall take down SDL +void RetroFE::freeGraphicsMemory( ) +{ + + // Free textures + if ( currentPage_ ) + { + currentPage_->freeGraphicsMemory( ); + } + + // Close down SDL + bool unloadSDL = false; + config_.getProperty( "unloadSDL", unloadSDL ); + if ( unloadSDL ) + { + currentPage_->deInitializeFonts( ); + SDL::deInitialize( ); + input_.clearJoysticks( ); + } + +} + + +// Optionally set up SDL, and load the textures +void RetroFE::allocateGraphicsMemory( ) +{ + + // Reopen SDL + bool unloadSDL = false; + config_.getProperty( "unloadSDL", unloadSDL ); + if ( unloadSDL ) + { + SDL::initialize( config_ ); + currentPage_->initializeFonts( ); + } + + // Allocate textures + if ( currentPage_ ) + { + currentPage_->allocateGraphicsMemory( ); + } + +} + + +// Deinitialize RetroFE +bool RetroFE::deInitialize( ) +{ + + bool retVal = true; + + // Free textures + freeGraphicsMemory( ); + + // Delete page + if ( currentPage_ ) + { + currentPage_->deInitialize( ); + delete currentPage_; + currentPage_ = NULL; + } + + // Delete databases + if ( metadb_ ) + { + delete metadb_; + metadb_ = NULL; + } + + if ( db_ ) + { + delete db_; + db_ = NULL; + } + + initialized = false; + + if ( reboot_ ) + { + Logger::write( Logger::ZONE_INFO, "RetroFE", "Rebooting" ); + } + else + { + Logger::write( Logger::ZONE_INFO, "RetroFE", "Exiting" ); + SDL::deInitialize( ); + gst_deinit( ); + } + + return retVal; +} + + +// Run RetroFE +bool RetroFE::run( ) +{ + + // Initialize SDL + if(! SDL::initialize( config_ ) ) return false; + fontcache_.initialize( ); + + // Define control configuration + std::string controlsConfPath = Utils::combinePath( Configuration::absolutePath, "controls.conf" ); + if ( !config_.import( "controls", controlsConfPath ) ) + { + Logger::write( Logger::ZONE_ERROR, "RetroFE", "Could not import \"" + controlsConfPath + "\"" ); + return false; + } + + float preloadTime = 0; + + // Initialize video + bool videoEnable = true; + int videoLoop = 0; + config_.getProperty( "videoEnable", videoEnable ); + config_.getProperty( "videoLoop", videoLoop ); + VideoFactory::setEnabled( videoEnable ); + VideoFactory::setNumLoops( videoLoop ); + Video::setEnabled( videoEnable ); + + initializeThread = SDL_CreateThread( initialize, "RetroFEInit", (void *)this ); + + if ( !initializeThread ) + { + Logger::write( Logger::ZONE_INFO, "RetroFE", "Could not initialize RetroFE" ); + return false; + } + + int attractModeTime = 0; + int attractModeNextTime = 0; + int attractModePlaylistTime = 0; + int attractModeCollectionTime = 0; + int attractModeMinTime = 1000; + int attractModeMaxTime = 5000; + bool LEDBlinkyCloseOnExit = true; + std::string firstCollection = "Main"; + bool running = true; + RETROFE_STATE state = RETROFE_NEW; + + config_.getProperty( "attractModeTime", attractModeTime ); + config_.getProperty( "attractModeNextTime", attractModeNextTime ); + config_.getProperty( "attractModePlaylistTime", attractModePlaylistTime ); + config_.getProperty( "attractModeCollectionTime", attractModeCollectionTime ); + config_.getProperty( "attractModeMinTime", attractModeMinTime ); + config_.getProperty( "attractModeMaxTime", attractModeMaxTime ); + config_.getProperty( "firstCollection", firstCollection ); + config_.getProperty( "LEDBlinkyCloseOnExit", LEDBlinkyCloseOnExit); // added checks for LEDBLINKY closing or not closing boolean + + attract_.idleTime = static_cast(attractModeTime); + attract_.idleNextTime = static_cast(attractModeNextTime); + attract_.idlePlaylistTime = static_cast(attractModePlaylistTime); + attract_.idleCollectionTime = static_cast(attractModeCollectionTime); + attract_.minTime = attractModeMinTime; + attract_.maxTime = attractModeMaxTime; + + int fps = 60; + int fpsIdle = 60; + config_.getProperty( "fps", fps ); + config_.getProperty( "fpsIdle", fpsIdle ); + double fpsTime = 1000.0 / static_cast(fps); + double fpsIdleTime = 1000.0 / static_cast(fpsIdle); + + int initializeStatus = 0; + bool inputClear = false; + + // load the initial splash screen, unload it once it is complete + currentPage_ = loadSplashPage( ); + state = RETROFE_ENTER; + bool splashMode = true; + bool exitSplashMode = false; + + Launcher l( config_ ); + Menu m( config_, input_ ); + preloadTime = static_cast( SDL_GetTicks( ) ) / 1000; + l.LEDBlinky( 1 ); + + while ( running ) + { + + float lastTime = 0; + float deltaTime = 0; + + // Exit splash mode when an active key is pressed + SDL_Event e; + if ( splashMode && SDL_PollEvent( &e ) ) + { + if ( input_.update( e ) && input_.keystate(UserInput::KeyCodeSelect) ) + { + exitSplashMode = true; + while ( SDL_PollEvent( &e ) ) + { + if ( e.type == SDL_JOYDEVICEADDED || e.type == SDL_JOYDEVICEREMOVED ) + { + input_.update( e ); + } + } + input_.resetStates( ); + attract_.reset( ); + } + } + + if ( !currentPage_ ) + { + Logger::write( Logger::ZONE_WARNING, "RetroFE", "Could not load page" ); + running = false; + break; + } + + switch(state) + { + + // Idle state; waiting for input + case RETROFE_IDLE: + + currentPage_->cleanup( ); + + // Not in splash mode + if ( currentPage_ && !splashMode ) + { + // account for when returning from a menu and the previous key was still "stuck" + if ( lastLaunchReturnTime_ == 0 || (currentTime_ - lastLaunchReturnTime_ > .3) ) + { + if ( currentPage_->isIdle( ) ) + { + state = processUserInput( currentPage_ ); + } + lastLaunchReturnTime_ = 0; + } + } + + // Handle end of splash mode + if ( (initialized || initializeError) && splashMode && (exitSplashMode || (currentPage_->getMinShowTime( ) <= (currentTime_ - preloadTime) && !(currentPage_->isPlaying( )))) ) + { + SDL_WaitThread( initializeThread, &initializeStatus ); + + if ( initializeError ) + { + state = RETROFE_QUIT_REQUEST; + break; + } + + currentPage_->stop( ); + state = RETROFE_SPLASH_EXIT; + + } + + break; + + // Load art on entering RetroFE + case RETROFE_LOAD_ART: + currentPage_->start( ); + state = RETROFE_ENTER; + break; + + // Wait for onEnter animation to finish + case RETROFE_ENTER: + if ( currentPage_->isIdle( ) ) + { + bool startCollectionEnter = false; + config_.getProperty( "startCollectionEnter", startCollectionEnter ); + nextPageItem_ = currentPage_->getSelectedItem( ); + if ( !splashMode && startCollectionEnter && !nextPageItem_->leaf ) + { + state = RETROFE_NEXT_PAGE_REQUEST; + } + else + { + state = RETROFE_IDLE; + } + } + break; + + // Handle end of splash mode + case RETROFE_SPLASH_EXIT: + if ( currentPage_->isIdle( ) ) + { + // delete the splash screen and use the standard menu + currentPage_->deInitialize( ); + delete currentPage_; + + currentPage_ = loadPage( ); + splashMode = false; + if ( currentPage_ ) + { + std::string firstCollection = "Main"; + + config_.getProperty( "firstCollection", firstCollection ); + config_.setProperty( "currentCollection", firstCollection ); + CollectionInfo *info = getCollection(firstCollection); + + currentPage_->pushCollection(info); + + config_.getProperty( "firstPlaylist", firstPlaylist_ ); + currentPage_->selectPlaylist( firstPlaylist_ ); + if (currentPage_->getPlaylistName() != firstPlaylist_ ) + currentPage_->selectPlaylist( "all" ); + + currentPage_->onNewItemSelected( ); + currentPage_->reallocateMenuSpritePoints( ); + + state = RETROFE_LOAD_ART; + } + else + { + state = RETROFE_QUIT_REQUEST; + } + } + break; + + // Switch playlist; start onHighlightExit animation + case RETROFE_PLAYLIST_REQUEST: + inputClear = false; + config_.getProperty( "playlistInputClear", inputClear ); + if ( inputClear ) + { + // Empty event queue + SDL_Event e; + while ( SDL_PollEvent( &e ) ) + input_.update(e); + input_.resetStates( ); + } + currentPage_->playlistExit( ); + currentPage_->setScrolling(Page::ScrollDirectionIdle); + state = RETROFE_PLAYLIST_EXIT; + break; + + // Switch playlist; wait for onHighlightExit animation to finish; load art + case RETROFE_PLAYLIST_EXIT: + if (currentPage_->isIdle( )) + { + currentPage_->onNewItemSelected( ); + state = RETROFE_PLAYLIST_LOAD_ART; + } + break; + + // Switch playlist; start onHighlightEnter animation + case RETROFE_PLAYLIST_LOAD_ART: + if (currentPage_->isIdle( )) + { + currentPage_->reallocateMenuSpritePoints( ); + currentPage_->playlistEnter( ); + state = RETROFE_PLAYLIST_ENTER; + } + break; + + // Switch playlist; wait for onHighlightEnter animation to finish + case RETROFE_PLAYLIST_ENTER: + if (currentPage_->isIdle( )) + { + state = RETROFE_IDLE; + } + break; + + // Jump in menu; start onMenuJumpExit animation + case RETROFE_MENUJUMP_REQUEST: + inputClear = false; + config_.getProperty( "jumpInputClear", inputClear ); + if ( inputClear ) + { + // Empty event queue + SDL_Event e; + while ( SDL_PollEvent( &e ) ) + input_.update(e); + input_.resetStates( ); + } + currentPage_->menuJumpExit( ); + currentPage_->setScrolling(Page::ScrollDirectionIdle); + state = RETROFE_MENUJUMP_EXIT; + break; + + // Jump in menu; wait for onMenuJumpExit animation to finish; load art + case RETROFE_MENUJUMP_EXIT: + if (currentPage_->isIdle( )) + { + currentPage_->onNewItemSelected( ); + state = RETROFE_MENUJUMP_LOAD_ART; + } + break; + + // Jump in menu; start onMenuJumpEnter animation + case RETROFE_MENUJUMP_LOAD_ART: + if (currentPage_->isIdle( )) + { + currentPage_->reallocateMenuSpritePoints( ); + currentPage_->menuJumpEnter( ); + state = RETROFE_MENUJUMP_ENTER; + } + break; + + // Jump in menu; wait for onMenuJump animation to finish + case RETROFE_MENUJUMP_ENTER: + if (currentPage_->isIdle( )) + { + state = RETROFE_IDLE; + } + break; + + // Start onHighlightExit animation + case RETROFE_HIGHLIGHT_REQUEST: + currentPage_->setScrolling(Page::ScrollDirectionIdle); + currentPage_->highlightExit( ); + state = RETROFE_HIGHLIGHT_EXIT; + break; + + // Wait for onHighlightExit animation to finish; load art + case RETROFE_HIGHLIGHT_EXIT: + if (currentPage_->isIdle( )) + { + currentPage_->highlightLoadArt( ); + state = RETROFE_HIGHLIGHT_LOAD_ART; + } + break; + + // Start onHighlightEnter animation + case RETROFE_HIGHLIGHT_LOAD_ART: + currentPage_->highlightEnter( ); + if ( currentPage_->getSelectedItem( ) ) + l.LEDBlinky( 9, currentPage_->getSelectedItem( )->collectionInfo->name, currentPage_->getSelectedItem( ) ); + state = RETROFE_HIGHLIGHT_ENTER; + break; + + // Wait for onHighlightEnter animation to finish + case RETROFE_HIGHLIGHT_ENTER: + RETROFE_STATE state_tmp; + if (currentPage_->isMenuIdle( ) && + ((state_tmp = processUserInput( currentPage_ )) == RETROFE_HIGHLIGHT_REQUEST || + state_tmp == RETROFE_MENUJUMP_REQUEST || + state_tmp == RETROFE_PLAYLIST_REQUEST) ) + { + state = state_tmp; + } + else if (currentPage_->isIdle( )) + { + state = RETROFE_IDLE; + } + break; + + // Next page; start onMenuExit animation + case RETROFE_NEXT_PAGE_REQUEST: + currentPage_->exitMenu( ); + state = RETROFE_NEXT_PAGE_MENU_EXIT; + break; + + // Wait for onMenuExit animation to finish; load new page if applicable; load art + case RETROFE_NEXT_PAGE_MENU_EXIT: + if ( currentPage_->isIdle( ) ) + { + if ( currentPage_->getSelectedItem( ) ) + l.LEDBlinky( 8, currentPage_->getSelectedItem( )->name, currentPage_->getSelectedItem( ) ); + lastMenuOffsets_[currentPage_->getCollectionName( )] = currentPage_->getScrollOffsetIndex( ); + lastMenuPlaylists_[currentPage_->getCollectionName( )] = currentPage_->getPlaylistName( ); + std::string nextPageName = nextPageItem_->name; + if ( !menuMode_ ) + { + // Load new layout if available + std::string layoutName; + config_.getProperty( "layout", layoutName ); + PageBuilder pb( layoutName, "layout", config_, &fontcache_ ); + Page *page = pb.buildPage( nextPageItem_->name ); + if ( page ) + { + currentPage_->freeGraphicsMemory( ); + pages_.push( currentPage_ ); + currentPage_ = page; + } + } + + config_.setProperty( "currentCollection", nextPageName ); + + CollectionInfo *info; + if ( menuMode_ ) + info = getMenuCollection( nextPageName ); + else + info = getCollection( nextPageName ); + + currentPage_->pushCollection(info); + + bool rememberMenu = false; + config_.getProperty( "rememberMenu", rememberMenu ); + + std::string autoPlaylist = "all"; + config_.getProperty( "autoPlaylist", autoPlaylist ); + + if (rememberMenu && lastMenuPlaylists_.find( nextPageName ) != lastMenuPlaylists_.end( )) + { + currentPage_->selectPlaylist( lastMenuPlaylists_[nextPageName] ); // Switch to last playlist + } + else + { + currentPage_->selectPlaylist( autoPlaylist ); + if (currentPage_->getPlaylistName() != autoPlaylist ) + currentPage_->selectPlaylist( "all" ); + } + + if ( rememberMenu && lastMenuOffsets_.find( nextPageName ) != lastMenuOffsets_.end( ) ) + { + currentPage_->setScrollOffsetIndex( lastMenuOffsets_[nextPageName] ); + } + + currentPage_->onNewItemSelected( ); + currentPage_->reallocateMenuSpritePoints( ); + + state = RETROFE_NEXT_PAGE_MENU_LOAD_ART; + + // Check if we've entered an empty collection and need to go back automatically + if (currentPage_->getCollectionSize() == 0) + { + bool backOnEmpty = false; + config_.getProperty( "backOnEmpty", backOnEmpty ); + if (backOnEmpty) + state = RETROFE_BACK_MENU_EXIT; + } + + + } + break; + + // Start onMenuEnter animation + case RETROFE_NEXT_PAGE_MENU_LOAD_ART: + if (currentPage_->getMenuDepth( ) != 1 ) + { + currentPage_->enterMenu( ); + } + else + { + currentPage_->start( ); + } + if ( currentPage_->getSelectedItem( ) ) + l.LEDBlinky( 9, currentPage_->getSelectedItem( )->collectionInfo->name, currentPage_->getSelectedItem( ) ); + state = RETROFE_NEXT_PAGE_MENU_ENTER; + break; + + // Wait for onMenuEnter animation to finish + case RETROFE_NEXT_PAGE_MENU_ENTER: + if ( currentPage_->isIdle( ) ) + { + inputClear = false; + config_.getProperty( "collectionInputClear", inputClear ); + if ( inputClear ) + { + // Empty event queue + SDL_Event e; + while ( SDL_PollEvent( &e ) ) + input_.update(e); + input_.resetStates( ); + } + state = RETROFE_IDLE; + } + break; + + // Start exit animation + case RETROFE_COLLECTION_DOWN_REQUEST: + if ( !pages_.empty( ) && currentPage_->getMenuDepth( ) == 1) // Inside a collection with a different layout + { + currentPage_->stop( ); + m.clearPage( ); + menuMode_ = false; + state = RETROFE_COLLECTION_DOWN_EXIT; + } + else if ( currentPage_->getMenuDepth( ) > 1 ) // Inside a collection with the same layout + { + currentPage_->exitMenu( ); + state = RETROFE_COLLECTION_DOWN_EXIT; + } + else // Not in a collection + { + state = RETROFE_COLLECTION_DOWN_ENTER; + + if ( attractMode_ ) // Check playlist change in attract mode + { + attractModePlaylistCollectionNumber_ += 1; + int attractModePlaylistCollectionNumber = 0; + config_.getProperty( "attractModePlaylistCollectionNumber", attractModePlaylistCollectionNumber ); + // Check if playlist should be changed + if ( attractModePlaylistCollectionNumber_ > 0 && attractModePlaylistCollectionNumber_ >= attractModePlaylistCollectionNumber ) + { + attractModePlaylistCollectionNumber_ = 0; + currentPage_->nextPlaylist( ); + std::string attractModeSkipPlaylist = ""; + config_.getProperty( "attractModeSkipPlaylist", attractModeSkipPlaylist ); + if (currentPage_->getPlaylistName( ) == attractModeSkipPlaylist) + currentPage_->nextPlaylist( ); + state = RETROFE_PLAYLIST_REQUEST; + } + } + } + break; + + // Wait for the menu exit animation to finish + case RETROFE_COLLECTION_DOWN_EXIT: + if ( currentPage_->isIdle( ) ) + { + lastMenuOffsets_[currentPage_->getCollectionName( )] = currentPage_->getScrollOffsetIndex( ); + lastMenuPlaylists_[currentPage_->getCollectionName( )] = currentPage_->getPlaylistName( ); + if (currentPage_->getMenuDepth( ) == 1) // Inside a collection with a different layout + { + currentPage_->deInitialize( ); + delete currentPage_; + currentPage_ = pages_.top( ); + pages_.pop( ); + currentPage_->allocateGraphicsMemory( ); + } + else // Inside a collection with the same layout + { + currentPage_->popCollection( ); + } + config_.setProperty( "currentCollection", currentPage_->getCollectionName( ) ); + + bool rememberMenu = false; + config_.getProperty( "rememberMenu", rememberMenu ); + + std::string autoPlaylist = "all"; + config_.getProperty( "autoPlaylist", autoPlaylist ); + + if (rememberMenu && lastMenuPlaylists_.find( currentPage_->getCollectionName( ) ) != lastMenuPlaylists_.end( )) + { + currentPage_->selectPlaylist( lastMenuPlaylists_[currentPage_->getCollectionName( )] ); // Switch to last playlist + } + else + { + currentPage_->selectPlaylist( autoPlaylist ); + if (currentPage_->getPlaylistName() != autoPlaylist ) + currentPage_->selectPlaylist( "all" ); + } + + if (rememberMenu && lastMenuPlaylists_.find( currentPage_->getCollectionName( ) ) != lastMenuPlaylists_.end( )) + { + currentPage_->setScrollOffsetIndex( lastMenuOffsets_[currentPage_->getCollectionName( )] ); + } + + state = RETROFE_COLLECTION_DOWN_MENU_ENTER; + currentPage_->onNewItemSelected( ); + + if ( attractMode_ ) // Check playlist change in attract mode + { + attractModePlaylistCollectionNumber_ += 1; + int attractModePlaylistCollectionNumber = 0; + config_.getProperty( "attractModePlaylistCollectionNumber", attractModePlaylistCollectionNumber ); + // Check if playlist should be changed + if ( attractModePlaylistCollectionNumber_ > 0 && attractModePlaylistCollectionNumber_ >= attractModePlaylistCollectionNumber ) + { + attractModePlaylistCollectionNumber_ = 0; + currentPage_->nextPlaylist( ); + std::string attractModeSkipPlaylist = ""; + config_.getProperty( "attractModeSkipPlaylist", attractModeSkipPlaylist ); + if (currentPage_->getPlaylistName( ) == attractModeSkipPlaylist) + currentPage_->nextPlaylist( ); + state = RETROFE_PLAYLIST_REQUEST; + } + } + + } + break; + + + // Start menu enter animation + case RETROFE_COLLECTION_DOWN_MENU_ENTER: + currentPage_->enterMenu( ); + state = RETROFE_COLLECTION_DOWN_ENTER; + break; + + + // Waiting for enter animation to stop + case RETROFE_COLLECTION_DOWN_ENTER: + if ( currentPage_->isIdle( ) ) + { + int attractModePlaylistCollectionNumber = 0; + config_.getProperty( "attractModePlaylistCollectionNumber", attractModePlaylistCollectionNumber ); + if (!( attractMode_ && attractModePlaylistCollectionNumber > 0 && attractModePlaylistCollectionNumber_ == 0 )) + { + currentPage_->setScrolling(Page::ScrollDirectionForward); + currentPage_->scroll(true); + currentPage_->updateScrollPeriod( ); + } + state = RETROFE_COLLECTION_DOWN_SCROLL; + } + break; + + // Waiting for scrolling animation to stop + case RETROFE_COLLECTION_DOWN_SCROLL: + if ( currentPage_->isMenuIdle( ) ) + { + std::string attractModeSkipCollection = ""; + config_.getProperty( "attractModeSkipCollection", attractModeSkipCollection ); + // Check if we need to skip this collection in attract mode or if we can select it + if ( attractMode_ && currentPage_->getSelectedItem( )->name == attractModeSkipCollection ) + { + currentPage_->setScrolling(Page::ScrollDirectionForward); + currentPage_->scroll(true); + currentPage_->updateScrollPeriod( ); + } + else + { + RETROFE_STATE state_tmp = processUserInput( currentPage_ ); + if ( state_tmp == RETROFE_COLLECTION_DOWN_REQUEST ) + { + state = RETROFE_COLLECTION_DOWN_REQUEST; + } + else if ( state_tmp == RETROFE_COLLECTION_UP_REQUEST ) + { + state = RETROFE_COLLECTION_UP_REQUEST; + } + else + { + currentPage_->setScrolling(Page::ScrollDirectionIdle); // Stop scrolling + nextPageItem_ = currentPage_->getSelectedItem( ); + bool enterOnCollection = true; + config_.getProperty( "enterOnCollection", enterOnCollection ); + if ( currentPage_->getSelectedItem( )->leaf || (!attractMode_ && !enterOnCollection) ) // Current selection is a game or enterOnCollection is not set + { + state = RETROFE_HIGHLIGHT_REQUEST; + } + else // Current selection is a menu + { + state = RETROFE_COLLECTION_HIGHLIGHT_EXIT; + } + } + } + } + break; + + + // Start onHighlightExit animation + case RETROFE_COLLECTION_HIGHLIGHT_REQUEST: + currentPage_->highlightExit( ); + state = RETROFE_COLLECTION_HIGHLIGHT_EXIT; + break; + + // Wait for onHighlightExit animation to finish; load art + case RETROFE_COLLECTION_HIGHLIGHT_EXIT: + if (currentPage_->isIdle( )) + { + currentPage_->highlightLoadArt( ); + state = RETROFE_COLLECTION_HIGHLIGHT_LOAD_ART; + } + break; + + // Start onHighlightEnter animation + case RETROFE_COLLECTION_HIGHLIGHT_LOAD_ART: + currentPage_->highlightEnter( ); + if ( currentPage_->getSelectedItem( ) ) + l.LEDBlinky( 9, currentPage_->getSelectedItem( )->collectionInfo->name, currentPage_->getSelectedItem( ) ); + state = RETROFE_COLLECTION_HIGHLIGHT_ENTER; + break; + + // Wait for onHighlightEnter animation to finish + case RETROFE_COLLECTION_HIGHLIGHT_ENTER: + if (currentPage_->isIdle( )) + { + RETROFE_STATE state_tmp = processUserInput( currentPage_ ); + if ( state_tmp == RETROFE_COLLECTION_DOWN_REQUEST ) + { + state = RETROFE_COLLECTION_DOWN_REQUEST; + } + else if ( state_tmp == RETROFE_COLLECTION_UP_REQUEST ) + { + state = RETROFE_COLLECTION_UP_REQUEST; + } + else + { + state = RETROFE_NEXT_PAGE_REQUEST; + } + } + break; + + // Start exit animation + case RETROFE_COLLECTION_UP_REQUEST: + if ( !pages_.empty( ) && currentPage_->getMenuDepth( ) == 1) // Inside a collection with a different layout + { + currentPage_->stop( ); + m.clearPage( ); + menuMode_ = false; + state = RETROFE_COLLECTION_UP_EXIT; + } + else if ( currentPage_->getMenuDepth( ) > 1 ) // Inside a collection with the same layout + { + currentPage_->exitMenu( ); + state = RETROFE_COLLECTION_UP_EXIT; + } + else // Not in a collection + { + state = RETROFE_COLLECTION_UP_ENTER; + } + break; + + // Wait for the menu exit animation to finish + case RETROFE_COLLECTION_UP_EXIT: + if ( currentPage_->isIdle( ) ) + { + lastMenuOffsets_[currentPage_->getCollectionName( )] = currentPage_->getScrollOffsetIndex( ); + lastMenuPlaylists_[currentPage_->getCollectionName( )] = currentPage_->getPlaylistName( ); + if (currentPage_->getMenuDepth( ) == 1) // Inside a collection with a different layout + { + currentPage_->deInitialize( ); + delete currentPage_; + currentPage_ = pages_.top( ); + pages_.pop( ); + currentPage_->allocateGraphicsMemory( ); + } + else // Inside a collection with the same layout + { + currentPage_->popCollection( ); + } + config_.setProperty( "currentCollection", currentPage_->getCollectionName( ) ); + + bool rememberMenu = false; + config_.getProperty( "rememberMenu", rememberMenu ); + + std::string autoPlaylist = "all"; + config_.getProperty( "autoPlaylist", autoPlaylist ); + + if (rememberMenu && lastMenuPlaylists_.find( currentPage_->getCollectionName( ) ) != lastMenuPlaylists_.end( )) + { + currentPage_->selectPlaylist( lastMenuPlaylists_[currentPage_->getCollectionName( )] ); // Switch to last playlist + } + else + { + currentPage_->selectPlaylist( autoPlaylist ); + if (currentPage_->getPlaylistName() != autoPlaylist ) + currentPage_->selectPlaylist( "all" ); + } + + if ( rememberMenu && lastMenuOffsets_.find( currentPage_->getCollectionName( ) ) != lastMenuOffsets_.end( ) ) + { + currentPage_->setScrollOffsetIndex( lastMenuOffsets_[currentPage_->getCollectionName( )] ); + } + + currentPage_->onNewItemSelected( ); + state = RETROFE_COLLECTION_UP_MENU_ENTER; + } + break; + + + // Start menu enter animation + case RETROFE_COLLECTION_UP_MENU_ENTER: + currentPage_->enterMenu( ); + state = RETROFE_COLLECTION_UP_ENTER; + break; + + + // Waiting for enter animation to stop + case RETROFE_COLLECTION_UP_ENTER: + if ( currentPage_->isIdle( ) ) + { + currentPage_->setScrolling(Page::ScrollDirectionBack); + currentPage_->scroll(false); + currentPage_->updateScrollPeriod( ); + state = RETROFE_COLLECTION_UP_SCROLL; + } + break; + + // Waiting for scrolling animation to stop + case RETROFE_COLLECTION_UP_SCROLL: + if ( currentPage_->isMenuIdle( ) ) + { + RETROFE_STATE state_tmp; + state_tmp = processUserInput( currentPage_ ); + if ( state_tmp == RETROFE_COLLECTION_DOWN_REQUEST ) + { + state = RETROFE_COLLECTION_DOWN_REQUEST; + } + else if ( state_tmp == RETROFE_COLLECTION_UP_REQUEST ) + { + state = RETROFE_COLLECTION_UP_REQUEST; + } + else + { + currentPage_->setScrolling(Page::ScrollDirectionIdle); // Stop scrolling + nextPageItem_ = currentPage_->getSelectedItem( ); + bool enterOnCollection = true; + config_.getProperty( "enterOnCollection", enterOnCollection ); + if ( currentPage_->getSelectedItem( )->leaf || !enterOnCollection ) // Current selection is a game or enterOnCollection is not set + { + state = RETROFE_HIGHLIGHT_REQUEST; + } + else // Current selection is a menu + { + state = RETROFE_COLLECTION_HIGHLIGHT_EXIT; + } + } + } + break; + + + // Launching a menu entry + case RETROFE_HANDLE_MENUENTRY: + + // Empty event queue + SDL_Event e; + while ( SDL_PollEvent( &e ) ) + input_.update(e); + input_.resetStates( ); + + // Handle menu entry + m.handleEntry( currentPage_->getSelectedItem( ) ); + + // Empty event queue + while ( SDL_PollEvent( &e ) ) + input_.update(e); + input_.resetStates( ); + + state = RETROFE_IDLE; + break; + + // Launching game; start onGameEnter animation + case RETROFE_LAUNCH_ENTER: + currentPage_->enterGame( ); // Start onGameEnter animation + currentPage_->playSelect( ); // Play launch sound + state = RETROFE_LAUNCH_REQUEST; + break; + + // Wait for onGameEnter animation to finish; launch game; start onGameExit animation + case RETROFE_LAUNCH_REQUEST: + if ( currentPage_->isIdle( ) && !currentPage_->isSelectPlaying( ) ) + { + nextPageItem_ = currentPage_->getSelectedItem( ); + launchEnter( ); + CollectionInfoBuilder cib(config_, *metadb_); + std::string attractModeSkipPlaylist = ""; + std::string lastPlayedSkipCollection = ""; + int size = 0; + config_.getProperty( "attractModeSkipPlaylist", attractModeSkipPlaylist ); + config_.getProperty( "lastPlayedSkipCollection", lastPlayedSkipCollection ); + config_.getProperty( "lastplayedSize", size ); + + if (currentPage_->getPlaylistName( ) != attractModeSkipPlaylist && + nextPageItem_->collectionInfo->name != lastPlayedSkipCollection) + cib.updateLastPlayedPlaylist( currentPage_->getCollection(), nextPageItem_, size ); // Update last played playlist if not currently in the skip playlist (e.g. settings) + + l.LEDBlinky( 3, nextPageItem_->collectionInfo->name, nextPageItem_ ); + if (l.run(nextPageItem_->collectionInfo->name, nextPageItem_)) // Run and check if we need to reboot + { + attract_.reset( ); + reboot_ = true; + state = RETROFE_QUIT_REQUEST; + } + else + { + launchExit( ); + l.LEDBlinky( 4 ); + currentPage_->exitGame( ); + state = RETROFE_LAUNCH_EXIT; + } + } + break; + + // Wait for onGameExit animation to finish + case RETROFE_LAUNCH_EXIT: + if ( currentPage_->isIdle( ) ) + { + state = RETROFE_IDLE; + } + break; + + // Go back a page; start onMenuExit animation + case RETROFE_BACK_REQUEST: + if (currentPage_->getMenuDepth( ) == 1) + { + currentPage_->stop( ); + m.clearPage( ); + menuMode_ = false; + } + else + { + currentPage_->exitMenu( ); + } + state = RETROFE_BACK_MENU_EXIT; + break; + + // Wait for onMenuExit animation to finish; load previous page; load art + case RETROFE_BACK_MENU_EXIT: + if ( currentPage_->isIdle( ) ) + { + lastMenuOffsets_[currentPage_->getCollectionName( )] = currentPage_->getScrollOffsetIndex( ); + lastMenuPlaylists_[currentPage_->getCollectionName( )] = currentPage_->getPlaylistName( ); + if (currentPage_->getMenuDepth( ) == 1) + { + currentPage_->deInitialize( ); + delete currentPage_; + currentPage_ = pages_.top( ); + pages_.pop( ); + currentPage_->allocateGraphicsMemory( ); + } + else + { + currentPage_->popCollection( ); + } + config_.setProperty( "currentCollection", currentPage_->getCollectionName( ) ); + + bool rememberMenu = false; + config_.getProperty( "rememberMenu", rememberMenu ); + + std::string autoPlaylist = "all"; + config_.getProperty( "autoPlaylist", autoPlaylist ); + + if (rememberMenu && lastMenuPlaylists_.find( currentPage_->getCollectionName( ) ) != lastMenuPlaylists_.end( )) + { + currentPage_->selectPlaylist( lastMenuPlaylists_[currentPage_->getCollectionName( )] ); // Switch to last playlist + } + else + { + currentPage_->selectPlaylist( autoPlaylist ); + if (currentPage_->getPlaylistName() != autoPlaylist ) + currentPage_->selectPlaylist( "all" ); + } + + if ( rememberMenu && lastMenuOffsets_.find( currentPage_->getCollectionName( ) ) != lastMenuOffsets_.end( ) ) + { + currentPage_->setScrollOffsetIndex( lastMenuOffsets_[currentPage_->getCollectionName( )] ); + } + + currentPage_->onNewItemSelected( ); + currentPage_->reallocateMenuSpritePoints( ); + state = RETROFE_BACK_MENU_LOAD_ART; + } + break; + + // Start onMenuEnter animation + case RETROFE_BACK_MENU_LOAD_ART: + currentPage_->enterMenu( ); + state = RETROFE_BACK_MENU_ENTER; + break; + + // Wait for onMenuEnter animation to finish + case RETROFE_BACK_MENU_ENTER: + if ( currentPage_->isIdle( ) ) + { + bool collectionInputClear = false; + config_.getProperty( "collectionInputClear", collectionInputClear ); + if ( collectionInputClear ) + { + // Empty event queue + SDL_Event e; + while ( SDL_PollEvent( &e ) ) + input_.update(e); + input_.resetStates( ); + } + state = RETROFE_IDLE; + } + break; + + // Start menu mode + case RETROFE_MENUMODE_START_REQUEST: + if ( currentPage_->isIdle( ) ) + { + lastMenuOffsets_[currentPage_->getCollectionName( )] = currentPage_->getScrollOffsetIndex( ); + lastMenuPlaylists_[currentPage_->getCollectionName( )] = currentPage_->getPlaylistName( ); + std::string layoutName; + config_.getProperty( "layout", layoutName ); + PageBuilder pb( layoutName, "layout", config_, &fontcache_, true ); + Page *page = pb.buildPage( ); + if ( page ) + { + currentPage_->freeGraphicsMemory( ); + pages_.push( currentPage_ ); + currentPage_ = page; + menuMode_ = true; + m.setPage( page ); + } + config_.setProperty( "currentCollection", "menu" ); + CollectionInfo *info = getMenuCollection( "menu" ); + currentPage_->pushCollection(info); + currentPage_->onNewItemSelected( ); + currentPage_->reallocateMenuSpritePoints( ); + state = RETROFE_MENUMODE_START_LOAD_ART; + } + break; + + case RETROFE_MENUMODE_START_LOAD_ART: + currentPage_->start(); + state = RETROFE_MENUMODE_START_ENTER; + break; + + case RETROFE_MENUMODE_START_ENTER: + if ( currentPage_->isIdle( ) ) + { + SDL_Event e; + while ( SDL_PollEvent( &e ) ) + input_.update(e); + input_.resetStates( ); + state = RETROFE_IDLE; + } + break; + + // Wait for splash mode animation to finish + case RETROFE_NEW: + if ( currentPage_->isIdle( ) ) + { + state = RETROFE_IDLE; + } + break; + + // Start the onExit animation + case RETROFE_QUIT_REQUEST: + currentPage_->stop( ); + state = RETROFE_QUIT; + break; + + // Wait for onExit animation to finish before quitting RetroFE + case RETROFE_QUIT: + if ( currentPage_->isGraphicsIdle( ) ) + { + if (LEDBlinkyCloseOnExit = yes) { + l.LEDBlinky(2); + running = false; + } + else running = false; + } + break; + } + + // Handle screen updates and attract mode + if ( running ) + { + lastTime = currentTime_; + currentTime_ = static_cast( SDL_GetTicks( ) ) / 1000; + + if ( currentTime_ < lastTime ) + { + currentTime_ = lastTime; + } + + deltaTime = currentTime_ - lastTime; + double sleepTime; + if (state == RETROFE_IDLE) + sleepTime = fpsIdleTime - deltaTime*1000; + else + sleepTime = fpsTime - deltaTime*1000; + if ( sleepTime > 0 && sleepTime < 1000 ) + { + SDL_Delay( static_cast( sleepTime ) ); + } + + if ( currentPage_ ) + { + if (!splashMode) + { + int attractReturn = attract_.update( deltaTime, *currentPage_ ); + if (attractReturn == 1) // Change playlist + { + attract_.reset( attract_.isSet( ) ); + + bool cyclePlaylist = false; + config_.getProperty( "attractModeCyclePlaylist", cyclePlaylist ); + + std::string cycleString; + config_.getProperty( "cyclePlaylist", cycleString ); + std::vector cycleVector; + Utils::listToVector( cycleString, cycleVector, ',' ); + + if ( cyclePlaylist ) + currentPage_->nextCyclePlaylist( cycleVector ); + else + currentPage_->nextPlaylist( ); + + std::string attractModeSkipPlaylist = ""; + config_.getProperty( "attractModeSkipPlaylist", attractModeSkipPlaylist ); + if (currentPage_->getPlaylistName( ) == attractModeSkipPlaylist) + { + if ( cyclePlaylist ) + currentPage_->nextCyclePlaylist( cycleVector ); + else + currentPage_->nextPlaylist( ); + } + state = RETROFE_PLAYLIST_REQUEST; + } + if (attractReturn == 2) // Change collection + { + attract_.reset( attract_.isSet( ) ); + state = RETROFE_COLLECTION_DOWN_REQUEST; + } + } + if ( menuMode_ ) + { + attract_.reset( ); + } + currentPage_->update( deltaTime ); + SDL_PumpEvents( ); + input_.updateKeystate( ); + if (!splashMode) + { + if ( currentPage_->isAttractIdle( ) ) + { + if ( !attractMode_ && attract_.isSet( ) ) + { + currentPage_->attractEnter( ); + l.LEDBlinky( 5 ); + } + else if ( attractMode_ && !attract_.isSet( ) ) + { + currentPage_->attractExit( ); + l.LEDBlinky( 6 ); + } + else if ( attract_.isSet( ) ) + { + currentPage_->attract( ); + } + attractMode_ = attract_.isSet( ); + } + } + } + + render( ); + } + } + return reboot_; +} + + +// Check if we can go back a page or quite RetroFE +bool RetroFE::back(bool &exit) +{ + bool canGoBack = false; + bool exitOnBack = false; + config_.getProperty( "exitOnFirstPageBack", exitOnBack ); + exit = false; + + if ( currentPage_->getMenuDepth( ) <= 1 && pages_.empty( ) ) + { + exit = exitOnBack; + } + else + { + canGoBack = true; + } + + return canGoBack; +} + + +// Process the user input +RetroFE::RETROFE_STATE RetroFE::processUserInput( Page *page ) +{ + bool exit = false; + RETROFE_STATE state = RETROFE_IDLE; + + // Poll all events until we find an active one + SDL_Event e; + while ( SDL_PollEvent( &e ) ) + { + input_.update(e); + if ( e.type == SDL_KEYDOWN && !e.key.repeat ) + { + break; + } + } + + // Handle next/previous game inputs + if ( page->isHorizontalScroll( ) ) + { + if (input_.keystate(UserInput::KeyCodeRight)) + { + attract_.reset( ); + page->setScrolling(Page::ScrollDirectionForward); + page->scroll(true); + page->updateScrollPeriod( ); + return state; + } + else if (input_.keystate(UserInput::KeyCodeLeft)) + { + attract_.reset( ); + page->setScrolling(Page::ScrollDirectionBack); + page->scroll(false); + page->updateScrollPeriod( ); + return state; + } + } + else + { + if (input_.keystate(UserInput::KeyCodeDown)) + { + attract_.reset( ); + page->setScrolling(Page::ScrollDirectionForward); + page->scroll(true); + page->updateScrollPeriod( ); + return state; + } + else if (input_.keystate(UserInput::KeyCodeUp)) + { + attract_.reset( ); + page->setScrolling(Page::ScrollDirectionBack); + page->scroll(false); + page->updateScrollPeriod( ); + return state; + } + } + + // Ignore other keys while the menu is scrolling + if ( page->isIdle( ) && currentTime_ - keyLastTime_ > keyDelayTime_ ) + { + if ( input_.keystate(UserInput::KeyCodeMenu) && !menuMode_) + { + state = RETROFE_MENUMODE_START_REQUEST; + } + + // Handle Collection Up/Down keys + else if ((input_.keystate(UserInput::KeyCodeCollectionUp) && ( page->isHorizontalScroll( ) || !input_.keystate(UserInput::KeyCodeUp))) || + (input_.keystate(UserInput::KeyCodeCollectionLeft) && (!page->isHorizontalScroll( ) || !input_.keystate(UserInput::KeyCodeLeft)))) + { + attract_.reset( ); + bool backOnCollection = false; + config_.getProperty( "backOnCollection", backOnCollection ); + if ( page->getMenuDepth( ) == 1 || !backOnCollection ) + state = RETROFE_COLLECTION_UP_REQUEST; + else + state = RETROFE_BACK_REQUEST; + } + + else if ((input_.keystate(UserInput::KeyCodeCollectionDown) && ( page->isHorizontalScroll( ) || !input_.keystate(UserInput::KeyCodeDown))) || + (input_.keystate(UserInput::KeyCodeCollectionRight) && (!page->isHorizontalScroll( ) || !input_.keystate(UserInput::KeyCodeRight)))) + { + attract_.reset( ); + bool backOnCollection = false; + config_.getProperty( "backOnCollection", backOnCollection ); + if ( page->getMenuDepth( ) == 1 || !backOnCollection ) + state = RETROFE_COLLECTION_DOWN_REQUEST; + else + state = RETROFE_BACK_REQUEST; + } + + else if (input_.keystate(UserInput::KeyCodePageUp)) + { + attract_.reset( ); + page->pageScroll(Page::ScrollDirectionBack); + state = RETROFE_MENUJUMP_REQUEST; + } + + else if (input_.keystate(UserInput::KeyCodePageDown)) + { + attract_.reset( ); + page->pageScroll(Page::ScrollDirectionForward); + state = RETROFE_MENUJUMP_REQUEST; + } + + else if (input_.keystate(UserInput::KeyCodeLetterUp)) + { + attract_.reset( ); + bool cfwLetterSub; + config_.getProperty( "cfwLetterSub", cfwLetterSub ); + if (cfwLetterSub && page->hasSubs()) + page->cfwLetterSubScroll(Page::ScrollDirectionBack); + else + page->letterScroll(Page::ScrollDirectionBack); + state = RETROFE_MENUJUMP_REQUEST; + } + + else if (input_.keystate(UserInput::KeyCodeLetterDown)) + { + attract_.reset( ); + bool cfwLetterSub; + config_.getProperty( "cfwLetterSub", cfwLetterSub ); + if (cfwLetterSub && page->hasSubs()) + page->cfwLetterSubScroll(Page::ScrollDirectionForward); + else + page->letterScroll(Page::ScrollDirectionForward); + state = RETROFE_MENUJUMP_REQUEST; + } + + else if ( input_.keystate(UserInput::KeyCodeFavPlaylist) ) + { + attract_.reset( ); + page->favPlaylist( ); + state = RETROFE_PLAYLIST_REQUEST; + } + + else if ( input_.keystate(UserInput::KeyCodeNextPlaylist) || + (input_.keystate(UserInput::KeyCodePlaylistDown) && page->isHorizontalScroll( )) || + (input_.keystate(UserInput::KeyCodePlaylistRight) && !page->isHorizontalScroll( ))) + { + attract_.reset( ); + page->nextPlaylist( ); + state = RETROFE_PLAYLIST_REQUEST; + } + + else if ( input_.keystate(UserInput::KeyCodePrevPlaylist) || + (input_.keystate(UserInput::KeyCodePlaylistUp) && page->isHorizontalScroll( )) || + (input_.keystate(UserInput::KeyCodePlaylistLeft) && !page->isHorizontalScroll( ))) + { + attract_.reset( ); + page->prevPlaylist( ); + state = RETROFE_PLAYLIST_REQUEST; + } + + else if ( input_.keystate(UserInput::KeyCodeCyclePlaylist) || + input_.keystate(UserInput::KeyCodeNextCyclePlaylist) ) + { + attract_.reset( ); + std::string cycleString; + config_.getProperty( "cyclePlaylist", cycleString ); + std::vector cycleVector; + Utils::listToVector( cycleString, cycleVector, ',' ); + page->nextCyclePlaylist( cycleVector ); + state = RETROFE_PLAYLIST_REQUEST; + } + + else if ( input_.keystate(UserInput::KeyCodePrevCyclePlaylist) ) + { + attract_.reset( ); + std::string cycleString; + config_.getProperty( "cyclePlaylist", cycleString ); + std::vector cycleVector; + Utils::listToVector( cycleString, cycleVector, ',' ); + page->prevCyclePlaylist( cycleVector ); + state = RETROFE_PLAYLIST_REQUEST; + } + + else if ( input_.keystate(UserInput::KeyCodeRemovePlaylist) ) + { + attract_.reset( ); + page->removePlaylist( ); + state = RETROFE_PLAYLIST_REQUEST; + } + + else if ( input_.keystate(UserInput::KeyCodeAddPlaylist) ) + { + attract_.reset( ); + page->addPlaylist( ); + state = RETROFE_PLAYLIST_REQUEST; + } + + else if ( input_.keystate(UserInput::KeyCodeTogglePlaylist) ) + { + attract_.reset( ); + page->togglePlaylist( ); + state = RETROFE_PLAYLIST_REQUEST; + } + + else if ( input_.keystate(UserInput::KeyCodeSkipForward) ) + { + attract_.reset( ); + page->skipForward( ); + page->jukeboxJump( ); + keyLastTime_ = currentTime_; + } + + else if ( input_.keystate(UserInput::KeyCodeSkipBackward) ) + { + attract_.reset( ); + page->skipBackward( ); + page->jukeboxJump( ); + keyLastTime_ = currentTime_; + } + + else if ( input_.keystate(UserInput::KeyCodeSkipForwardp) ) + { + attract_.reset( ); + page->skipForwardp( ); + page->jukeboxJump( ); + keyLastTime_ = currentTime_; + } + + else if ( input_.keystate(UserInput::KeyCodeSkipBackwardp) ) + { + attract_.reset( ); + page->skipBackwardp( ); + page->jukeboxJump( ); + keyLastTime_ = currentTime_; + } + + else if ( input_.keystate(UserInput::KeyCodePause) ) + { + attract_.reset( ); + page->pause( ); + page->jukeboxJump( ); + keyLastTime_ = currentTime_; + } + + else if ( input_.keystate(UserInput::KeyCodeRestart) ) + { + attract_.reset( ); + page->restart( ); + keyLastTime_ = currentTime_; + } + + else if ( input_.keystate(UserInput::KeyCodeRandom) ) + { + attract_.reset( ); + page->selectRandom( ); + state = RETROFE_MENUJUMP_REQUEST; + } + + else if (input_.keystate(UserInput::KeyCodeAdminMode)) + { + //todo: add admin mode support + } + + else if (input_.keystate(UserInput::KeyCodeSelect)) + { + attract_.reset( ); + nextPageItem_ = page->getSelectedItem( ); + + if ( nextPageItem_ ) + { + if ( nextPageItem_->leaf ) + { + if ( menuMode_ ) + { + state = RETROFE_HANDLE_MENUENTRY; + } + else + { + state = RETROFE_LAUNCH_ENTER; + } + } + else + { + CollectionInfoBuilder cib(config_, *metadb_); + std::string attractModeSkipPlaylist = ""; + std::string lastPlayedSkipCollection = ""; + int size = 0; + config_.getProperty( "attractModeSkipPlaylist", attractModeSkipPlaylist ); + config_.getProperty( "lastPlayedSkipCollection", lastPlayedSkipCollection ); + config_.getProperty("lastplayedCollectionSize", size); + + if (currentPage_->getPlaylistName( ) != attractModeSkipPlaylist && + nextPageItem_->collectionInfo->name != lastPlayedSkipCollection) + cib.updateLastPlayedPlaylist( currentPage_->getCollection(), nextPageItem_, size ); // Update last played playlist if not currently in the skip playlist (e.g. settings) + state = RETROFE_NEXT_PAGE_REQUEST; + } + } + } + + else if (input_.keystate(UserInput::KeyCodeBack)) + { + attract_.reset( ); + if ( back( exit ) || exit ) + { + state = (exit) ? RETROFE_QUIT_REQUEST : RETROFE_BACK_REQUEST; + } + } + + else if (input_.keystate(UserInput::KeyCodeQuit)) + { + attract_.reset( ); + state = RETROFE_QUIT_REQUEST; + } + + else if (input_.keystate(UserInput::KeyCodeReboot)) + { + attract_.reset( ); + reboot_ = true; + state = RETROFE_QUIT_REQUEST; + } + + else if (input_.keystate(UserInput::KeyCodeSaveFirstPlaylist)) + { + attract_.reset( ); + if ( page->getMenuDepth( ) == 1 ) + { + firstPlaylist_ = page->getPlaylistName( ); + saveRetroFEState( ); + } + } + } + + if ( state != RETROFE_IDLE ) + { + keyLastTime_ = currentTime_; + return state; + } + + // Check if we're done scrolling + if ( !input_.keystate(UserInput::KeyCodeUp) && + !input_.keystate(UserInput::KeyCodeLeft) && + !input_.keystate(UserInput::KeyCodeDown) && + !input_.keystate(UserInput::KeyCodeRight) && + !input_.keystate(UserInput::KeyCodePlaylistUp) && + !input_.keystate(UserInput::KeyCodePlaylistLeft) && + !input_.keystate(UserInput::KeyCodePlaylistDown) && + !input_.keystate(UserInput::KeyCodePlaylistRight) && + !input_.keystate(UserInput::KeyCodeCollectionUp) && + !input_.keystate(UserInput::KeyCodeCollectionLeft) && + !input_.keystate(UserInput::KeyCodeCollectionDown) && + !input_.keystate(UserInput::KeyCodeCollectionRight) && + !input_.keystate(UserInput::KeyCodePageUp) && + !input_.keystate(UserInput::KeyCodePageDown) && + !input_.keystate(UserInput::KeyCodeLetterUp) && + !input_.keystate(UserInput::KeyCodeLetterDown) && + !input_.keystate(UserInput::KeyCodeCollectionUp) && + !input_.keystate(UserInput::KeyCodeCollectionDown) && + !attract_.isActive( ) ) + { + page->resetScrollPeriod( ); + if (page->isMenuScrolling( )) + { + attract_.reset( attract_.isSet( ) ); + state = RETROFE_HIGHLIGHT_REQUEST; + } + } + + return state; +} + + +// Load a page +Page *RetroFE::loadPage( ) +{ + std::string layoutName; + + config_.getProperty( "layout", layoutName ); + + PageBuilder pb( layoutName, "layout", config_, &fontcache_ ); + Page *page = pb.buildPage( ); + + if ( !page ) + { + Logger::write( Logger::ZONE_ERROR, "RetroFE", "Could not create page" ); + } + + return page; +} + + +// Load the splash page +Page *RetroFE::loadSplashPage( ) +{ + std::string layoutName; + config_.getProperty( "layout", layoutName ); + + PageBuilder pb( layoutName, "splash", config_, &fontcache_ ); + Page * page = pb.buildPage( ); + page->start( ); + + return page; +} + + +// Load a collection +CollectionInfo *RetroFE::getCollection(std::string collectionName) +{ + + // Check if subcollections should be merged or split + bool subsSplit = false; + config_.getProperty( "subsSplit", subsSplit ); + + // Build the collection + CollectionInfoBuilder cib(config_, *metadb_); + CollectionInfo *collection = cib.buildCollection( collectionName ); + collection->subsSplit = subsSplit; + cib.injectMetadata( collection ); + + DIR *dp; + struct dirent *dirp; + + std::string path = Utils::combinePath( Configuration::absolutePath, "collections", collectionName ); + dp = opendir( path.c_str( ) ); + + // Loading sub collection files + while ( (dirp = readdir( dp )) != NULL ) + { + std::string file = dirp->d_name; + + size_t position = file.find_last_of( "." ); + std::string basename = (std::string::npos == position)? file : file.substr( 0, position ); + + std::string comparator = ".sub"; + int start = file.length( ) - comparator.length( ); + + if ( start >= 0 ) + { + if ( file.compare( start, comparator.length( ), comparator ) == 0 ) + { + Logger::write( Logger::ZONE_INFO, "RetroFE", "Loading subcollection into menu: " + basename ); + + CollectionInfo *subcollection = cib.buildCollection( basename, collectionName ); + collection->addSubcollection( subcollection ); + subcollection->subsSplit = subsSplit; + cib.injectMetadata( subcollection ); + collection->hasSubs = true; + } + } + } + if (dp) closedir( dp ); + + bool menuSort = true; + config_.getProperty( "collections." + collectionName + ".list.menuSort", menuSort ); + + if (menuSort) + collection->sortItems( ); + + MenuParser mp; + mp.buildMenuItems( collection, menuSort); + + cib.addPlaylists( collection ); + collection->sortPlaylists( ); + + // Add extra info, if available + for ( std::vector::iterator it = collection->items.begin( ); it != collection->items.end( ); it++ ) + { + std::string path = Utils::combinePath( Configuration::absolutePath, "collections", collectionName, "info", (*it)->name + ".conf" ); + (*it)->loadInfo( path ); + } + + // Remove parenthesis and brackets, if so configured + bool showParenthesis = true; + bool showSquareBrackets = true; + + (void)config_.getProperty( "showParenthesis", showParenthesis ); + (void)config_.getProperty( "showSquareBrackets", showSquareBrackets ); + + typedef std::map *> Playlists_T; + for ( Playlists_T::iterator itP = collection->playlists.begin( ); itP != collection->playlists.end( ); itP++ ) + { + for ( std::vector ::iterator itI = itP->second->begin( ); itI != itP->second->end( ); itI++ ) + { + if ( !showParenthesis ) + { + std::string::size_type firstPos = (*itI)->title.find_first_of( "(" ); + std::string::size_type secondPos = (*itI)->title.find_first_of( ")", firstPos ); + + while ( firstPos != std::string::npos && secondPos != std::string::npos ) + { + firstPos = (*itI)->title.find_first_of( "(" ); + secondPos = (*itI)->title.find_first_of( ")", firstPos ); + + if ( firstPos != std::string::npos ) + { + (*itI)->title.erase( firstPos, (secondPos - firstPos) + 1 ); + } + } + } + if ( !showSquareBrackets ) + { + std::string::size_type firstPos = (*itI)->title.find_first_of( "[" ); + std::string::size_type secondPos = (*itI)->title.find_first_of( "]", firstPos ); + + while ( firstPos != std::string::npos && secondPos != std::string::npos ) + { + firstPos = (*itI)->title.find_first_of( "[" ); + secondPos = (*itI)->title.find_first_of( "]", firstPos ); + + if ( firstPos != std::string::npos && secondPos != std::string::npos ) + { + (*itI)->title.erase( firstPos, (secondPos - firstPos) + 1 ); + } + } + } + } + } + + return collection; +} + + +// Load a menu +CollectionInfo *RetroFE::getMenuCollection( std::string collectionName ) +{ + std::string menuPath = Utils::combinePath( Configuration::absolutePath, "menu" ); + std::string menuFile = Utils::combinePath( menuPath, collectionName + ".txt" ); + std::vector menuVector; + CollectionInfoBuilder cib( config_, *metadb_ ); + CollectionInfo *collection = new CollectionInfo( collectionName, menuPath, "", "", "" ); + cib.ImportBasicList( collection, menuFile, menuVector ); + for ( std::vector::iterator it = menuVector.begin( ); it != menuVector.end( ); ++it) + { + (*it)->leaf = false; + size_t position = (*it)->name.find( "=" ); + if ( position != std::string::npos ) + { + (*it)->ctrlType = Utils::trimEnds( (*it)->name.substr( position+1, (*it)->name.size( )-1 ) ); + (*it)->name = Utils::trimEnds( (*it)->name.substr( 0, position ) ); + (*it)->title = (*it)->name; + (*it)->fullTitle = (*it)->name; + (*it)->leaf = true; + } + (*it)->collectionInfo = collection; + collection->items.push_back( *it ); + } + collection->playlists["all"] = &collection->items; + return collection; +} + + +void RetroFE::saveRetroFEState( ) +{ + std::string file = Utils::combinePath(Configuration::absolutePath, "settings_saved.conf"); + Logger::write(Logger::ZONE_INFO, "RetroFE", "Saving settings_saved.conf"); + std::ofstream filestream; + try + { + filestream.open(file.c_str()); + filestream << "firstPlaylist = " << firstPlaylist_ << std::endl; + filestream.close(); + } + catch(std::exception &) + { + Logger::write(Logger::ZONE_ERROR, "RetroFE", "Save failed: " + file); + } +} diff --git a/RetroFE/Source/RetroFE.h b/RetroFE/Source/RetroFE.h index 06511d830..15788bc36 100644 --- a/RetroFE/Source/RetroFE.h +++ b/RetroFE/Source/RetroFE.h @@ -1,144 +1,144 @@ -/* This file is part of RetroFE. - * - * RetroFE 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. - * - * RetroFE 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 RetroFE. If not, see . - */ -#pragma once - - -#include "Collection/Item.h" -#include "Control/UserInput.h" -#include "Database/DB.h" -#include "Database/MetadataDatabase.h" -#include "Execute/AttractMode.h" -#include "Graphics/FontCache.h" -#include "Video/IVideo.h" -#include "Video/VideoFactory.h" -#include -#include -#include -#include -#include -#include - - -class CollectionInfo; -class Configuration; -class Page; - - -class RetroFE -{ - -public: - RetroFE( Configuration &c ); - virtual ~RetroFE( ); - bool deInitialize( ); - bool run( ); - void freeGraphicsMemory( ); - void allocateGraphicsMemory( ); - void launchEnter( ); - void launchExit( ); - -private: - volatile bool initialized; - volatile bool initializeError; - SDL_Thread *initializeThread; - static int initialize( void *context ); - - enum RETROFE_STATE - { - RETROFE_IDLE, - RETROFE_LOAD_ART, - RETROFE_ENTER, - RETROFE_SPLASH_EXIT, - RETROFE_PLAYLIST_REQUEST, - RETROFE_PLAYLIST_EXIT, - RETROFE_PLAYLIST_LOAD_ART, - RETROFE_PLAYLIST_ENTER, - RETROFE_MENUJUMP_REQUEST, - RETROFE_MENUJUMP_EXIT, - RETROFE_MENUJUMP_LOAD_ART, - RETROFE_MENUJUMP_ENTER, - RETROFE_HIGHLIGHT_REQUEST, - RETROFE_HIGHLIGHT_EXIT, - RETROFE_HIGHLIGHT_LOAD_ART, - RETROFE_HIGHLIGHT_ENTER, - RETROFE_NEXT_PAGE_REQUEST, - RETROFE_NEXT_PAGE_MENU_EXIT, - RETROFE_NEXT_PAGE_MENU_LOAD_ART, - RETROFE_NEXT_PAGE_MENU_ENTER, - RETROFE_COLLECTION_UP_REQUEST, - RETROFE_COLLECTION_UP_EXIT, - RETROFE_COLLECTION_UP_MENU_ENTER, - RETROFE_COLLECTION_UP_ENTER, - RETROFE_COLLECTION_UP_SCROLL, - RETROFE_COLLECTION_HIGHLIGHT_REQUEST, - RETROFE_COLLECTION_HIGHLIGHT_EXIT, - RETROFE_COLLECTION_HIGHLIGHT_LOAD_ART, - RETROFE_COLLECTION_HIGHLIGHT_ENTER, - RETROFE_COLLECTION_DOWN_REQUEST, - RETROFE_COLLECTION_DOWN_EXIT, - RETROFE_COLLECTION_DOWN_MENU_ENTER, - RETROFE_COLLECTION_DOWN_ENTER, - RETROFE_COLLECTION_DOWN_SCROLL, - RETROFE_HANDLE_MENUENTRY, - RETROFE_LAUNCH_ENTER, - RETROFE_LAUNCH_REQUEST, - RETROFE_LAUNCH_EXIT, - RETROFE_BACK_REQUEST, - RETROFE_BACK_MENU_EXIT, - RETROFE_BACK_MENU_LOAD_ART, - RETROFE_BACK_MENU_ENTER, - RETROFE_MENUMODE_START_REQUEST, - RETROFE_MENUMODE_START_LOAD_ART, - RETROFE_MENUMODE_START_ENTER, - RETROFE_NEW, - RETROFE_QUIT_REQUEST, - RETROFE_QUIT, - }; - - void render( ); - bool back( bool &exit ); - void quit( ); - Page *loadPage( ); - Page *loadSplashPage( ); - RETROFE_STATE processUserInput( Page *page ); - void update( float dt, bool scrollActive ); - CollectionInfo *getCollection( std::string collectionName ); - CollectionInfo *getMenuCollection( std::string collectionName ); - void saveRetroFEState( ); - - Configuration &config_; - DB *db_; - MetadataDatabase *metadb_; - UserInput input_; - Page *currentPage_; - std::stack pages_; - float keyInputDisable_; - float currentTime_; - float lastLaunchReturnTime_; - float keyLastTime_; - float keyDelayTime_; - Item *nextPageItem_; - FontCache fontcache_; - AttractMode attract_; - bool menuMode_; - bool attractMode_; - int attractModePlaylistCollectionNumber_; - bool reboot_; - std::string firstPlaylist_; - - std::map lastMenuOffsets_; - std::map lastMenuPlaylists_; -}; +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ +#pragma once + + +#include "Collection/Item.h" +#include "Control/UserInput.h" +#include "Database/DB.h" +#include "Database/MetadataDatabase.h" +#include "Execute/AttractMode.h" +#include "Graphics/FontCache.h" +#include "Video/IVideo.h" +#include "Video/VideoFactory.h" +#include +#include +#include +#include +#include +#include + + +class CollectionInfo; +class Configuration; +class Page; + + +class RetroFE +{ + +public: + RetroFE( Configuration &c ); + virtual ~RetroFE( ); + bool deInitialize( ); + bool run( ); + void freeGraphicsMemory( ); + void allocateGraphicsMemory( ); + void launchEnter( ); + void launchExit( ); + +private: + volatile bool initialized; + volatile bool initializeError; + SDL_Thread *initializeThread; + static int initialize( void *context ); + + enum RETROFE_STATE + { + RETROFE_IDLE, + RETROFE_LOAD_ART, + RETROFE_ENTER, + RETROFE_SPLASH_EXIT, + RETROFE_PLAYLIST_REQUEST, + RETROFE_PLAYLIST_EXIT, + RETROFE_PLAYLIST_LOAD_ART, + RETROFE_PLAYLIST_ENTER, + RETROFE_MENUJUMP_REQUEST, + RETROFE_MENUJUMP_EXIT, + RETROFE_MENUJUMP_LOAD_ART, + RETROFE_MENUJUMP_ENTER, + RETROFE_HIGHLIGHT_REQUEST, + RETROFE_HIGHLIGHT_EXIT, + RETROFE_HIGHLIGHT_LOAD_ART, + RETROFE_HIGHLIGHT_ENTER, + RETROFE_NEXT_PAGE_REQUEST, + RETROFE_NEXT_PAGE_MENU_EXIT, + RETROFE_NEXT_PAGE_MENU_LOAD_ART, + RETROFE_NEXT_PAGE_MENU_ENTER, + RETROFE_COLLECTION_UP_REQUEST, + RETROFE_COLLECTION_UP_EXIT, + RETROFE_COLLECTION_UP_MENU_ENTER, + RETROFE_COLLECTION_UP_ENTER, + RETROFE_COLLECTION_UP_SCROLL, + RETROFE_COLLECTION_HIGHLIGHT_REQUEST, + RETROFE_COLLECTION_HIGHLIGHT_EXIT, + RETROFE_COLLECTION_HIGHLIGHT_LOAD_ART, + RETROFE_COLLECTION_HIGHLIGHT_ENTER, + RETROFE_COLLECTION_DOWN_REQUEST, + RETROFE_COLLECTION_DOWN_EXIT, + RETROFE_COLLECTION_DOWN_MENU_ENTER, + RETROFE_COLLECTION_DOWN_ENTER, + RETROFE_COLLECTION_DOWN_SCROLL, + RETROFE_HANDLE_MENUENTRY, + RETROFE_LAUNCH_ENTER, + RETROFE_LAUNCH_REQUEST, + RETROFE_LAUNCH_EXIT, + RETROFE_BACK_REQUEST, + RETROFE_BACK_MENU_EXIT, + RETROFE_BACK_MENU_LOAD_ART, + RETROFE_BACK_MENU_ENTER, + RETROFE_MENUMODE_START_REQUEST, + RETROFE_MENUMODE_START_LOAD_ART, + RETROFE_MENUMODE_START_ENTER, + RETROFE_NEW, + RETROFE_QUIT_REQUEST, + RETROFE_QUIT, + }; + + void render( ); + bool back( bool &exit ); + void quit( ); + Page *loadPage( ); + Page *loadSplashPage( ); + RETROFE_STATE processUserInput( Page *page ); + void update( float dt, bool scrollActive ); + CollectionInfo *getCollection( std::string collectionName ); + CollectionInfo *getMenuCollection( std::string collectionName ); + void saveRetroFEState( ); + + Configuration &config_; + DB *db_; + MetadataDatabase *metadb_; + UserInput input_; + Page *currentPage_; + std::stack pages_; + float keyInputDisable_; + float currentTime_; + float lastLaunchReturnTime_; + float keyLastTime_; + float keyDelayTime_; + Item *nextPageItem_; + FontCache fontcache_; + AttractMode attract_; + bool menuMode_; + bool attractMode_; + int attractModePlaylistCollectionNumber_; + bool reboot_; + std::string firstPlaylist_; + + std::map lastMenuOffsets_; + std::map lastMenuPlaylists_; +}; From ceeff216623d8a4ece6059a689187c03b063a10b Mon Sep 17 00:00:00 2001 From: James VanDusen Date: Tue, 3 Jan 2023 10:52:49 -0500 Subject: [PATCH 2/4] Added LEDBlinky Checks Checking based on LEDBlinkyCloseOnExit to ignore closing LEDBLINKY or not --- RetroFE/Source/Execute/Launcher.cpp | 840 ++++++++++++++-------------- RetroFE/Source/Execute/Launcher.h | 104 ++-- 2 files changed, 476 insertions(+), 468 deletions(-) diff --git a/RetroFE/Source/Execute/Launcher.cpp b/RetroFE/Source/Execute/Launcher.cpp index 116fef79a..e5384726a 100644 --- a/RetroFE/Source/Execute/Launcher.cpp +++ b/RetroFE/Source/Execute/Launcher.cpp @@ -1,416 +1,424 @@ -/* This file is part of RetroFE. - * - * RetroFE 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. - * - * RetroFE 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 RetroFE. If not, see . - */ - -#include "Launcher.h" -#include "../Collection/Item.h" -#include "../Utility/Log.h" -#include "../Database/Configuration.h" -#include "../Utility/Utils.h" -#include "../RetroFE.h" -#include "../SDL.h" -#include -#include -#include -#include -#ifdef WIN32 -#include -#include -#endif - -Launcher::Launcher(Configuration &c) - : config_(c) -{ -} - -bool Launcher::run(std::string collection, Item *collectionItem) -{ - std::string launcherName = collectionItem->collectionInfo->launcher; - std::string executablePath; - std::string selectedItemsDirectory; - std::string selectedItemsPath; - std::string extensionstr; - std::string matchedExtension; - std::string args; - - std::string launcherFile = Utils::combinePath( Configuration::absolutePath, "collections", collectionItem->collectionInfo->name, "launchers", collectionItem->name + ".conf" ); - std::ifstream launcherStream( launcherFile.c_str( ) ); - if (launcherStream.good( )) // Launcher file found - { - std::string line; - if (std::getline( launcherStream, line)) // Launcher found - { - launcherName = line; - } - } - launcherName = Utils::toLower(launcherName); - - if(!launcherExecutable(executablePath, launcherName)) - { - Logger::write(Logger::ZONE_ERROR, "Launcher", "Failed to find launcher executable (launcher: " + launcherName + " executable: " + executablePath + ")"); - return false; - } - if(!extensions(extensionstr, collection)) - { - Logger::write(Logger::ZONE_ERROR, "Launcher", "No file extensions configured for collection \"" + collection + "\""); - return false; - } - if(!collectionDirectory(selectedItemsDirectory, collection)) - { - Logger::write(Logger::ZONE_ERROR, "Launcher", "Could not find files in directory \"" + selectedItemsDirectory + "\" for collection \"" + collection + "\""); - return false; - } - if(!launcherArgs(args, launcherName)) - { - Logger::write(Logger::ZONE_ERROR, "Launcher", "No launcher arguments specified for launcher " + launcherName); - return false; - } - - - // Overwrite selectedItemsDirectory if already set in the file - if (collectionItem->filepath != "") - { - selectedItemsDirectory = collectionItem->filepath; - } - - // It is ok to continue if the file could not be found. We could be launching a merged romset - if (collectionItem->file == "") - findFile(selectedItemsPath, matchedExtension, selectedItemsDirectory, collectionItem->name, extensionstr); - else - findFile(selectedItemsPath, matchedExtension, selectedItemsDirectory, collectionItem->file, extensionstr); - - args = replaceVariables(args, - selectedItemsPath, - collectionItem->name, - Utils::getFileName(selectedItemsPath), - selectedItemsDirectory, - collection); - - executablePath = replaceVariables(executablePath, - selectedItemsPath, - collectionItem->name, - Utils::getFileName(selectedItemsPath), - selectedItemsDirectory, - collection); - - std::string currentDirectoryKey = "launchers." + launcherName + ".currentDirectory"; - std::string currentDirectory = Utils::getDirectory(executablePath); - - config_.getProperty(currentDirectoryKey, currentDirectory); - - currentDirectory = replaceVariables(currentDirectory, - selectedItemsPath, - collectionItem->name, - Utils::getFileName(selectedItemsPath), - selectedItemsDirectory, - collection); - - if(!execute(executablePath, args, currentDirectory)) - { - Logger::write(Logger::ZONE_ERROR, "Launcher", "Failed to launch."); - return false; - } - - bool reboot = false; - config_.getProperty("launchers." + launcherName + ".reboot", reboot); - - return reboot; -} - - -void Launcher::LEDBlinky( int command, std::string collection, Item *collectionItem ) -{ - std::string LEDBlinkyDirectory = ""; - config_.getProperty( "LEDBlinkyDirectory", LEDBlinkyDirectory ); - std::string exe = LEDBlinkyDirectory + "\\LEDBlinky.exe"; - std::string args = std::to_string( command ); - bool wait = false; - if ( command == 2 ) - wait = true; - if ( command == 8 ) - { - std::string launcherName = collectionItem->collectionInfo->launcher; - std::string launcherFile = Utils::combinePath( Configuration::absolutePath, "collections", collectionItem->collectionInfo->name, "launchers", collectionItem->name + ".conf" ); - std::ifstream launcherStream( launcherFile.c_str( ) ); - if (launcherStream.good( )) // Launcher file found - { - std::string line; - if (std::getline( launcherStream, line)) // Launcher found - { - launcherName = line; - } - } - launcherName = Utils::toLower( launcherName ); - std::string emulator = collection; - config_.getProperty("launchers." + launcherName + ".LEDBlinkyEmulator", emulator ); - args = args + " \"" + emulator + "\""; - } - if ( command == 3 || command == 9 ) - { - std::string launcherName = collectionItem->collectionInfo->launcher; - std::string launcherFile = Utils::combinePath( Configuration::absolutePath, "collections", collectionItem->collectionInfo->name, "launchers", collectionItem->name + ".conf" ); - std::ifstream launcherStream( launcherFile.c_str( ) ); - if (launcherStream.good( )) // Launcher file found - { - std::string line; - if (std::getline( launcherStream, line)) // Launcher found - { - launcherName = line; - } - } - launcherName = Utils::toLower( launcherName ); - std::string emulator = launcherName; - config_.getProperty("launchers." + launcherName + ".LEDBlinkyEmulator", emulator ); - args = args + " \"" + collectionItem->name + "\" \"" + emulator + "\""; - if ( emulator == "" ) - return; - } - if ( LEDBlinkyDirectory != "" && !execute( exe, args, LEDBlinkyDirectory, wait ) ) - { - Logger::write( Logger::ZONE_ERROR, "LEDBlinky", "Failed to launch." ); - } - return; -} - - -std::string Launcher::replaceVariables(std::string str, - std::string itemFilePath, - std::string itemName, - std::string itemFilename, - std::string itemDirectory, - std::string itemCollectionName) -{ - str = Utils::replace(str, "%ITEM_FILEPATH%", itemFilePath); - str = Utils::replace(str, "%ITEM_NAME%", itemName); - str = Utils::replace(str, "%ITEM_FILENAME%", itemFilename); - str = Utils::replace(str, "%ITEM_DIRECTORY%", itemDirectory); - str = Utils::replace(str, "%ITEM_COLLECTION_NAME%", itemCollectionName); - str = Utils::replace(str, "%RETROFE_PATH%", Configuration::absolutePath); -#ifdef WIN32 - str = Utils::replace(str, "%RETROFE_EXEC_PATH%", Utils::combinePath(Configuration::absolutePath, "core", "RetroFE.exe")); -#else - str = Utils::replace(str, "%RETROFE_EXEC_PATH%", Utils::combinePath(Configuration::absolutePath, "RetroFE")); -#endif - - return str; -} - -bool Launcher::execute(std::string executable, std::string args, std::string currentDirectory, bool wait) -{ - bool retVal = false; - std::string executionString = "\"" + executable + "\" " + args; - - Logger::write(Logger::ZONE_INFO, "Launcher", "Attempting to launch: " + executionString); - Logger::write(Logger::ZONE_INFO, "Launcher", " from within folder: " + currentDirectory); - -#ifdef WIN32 - STARTUPINFO startupInfo; - PROCESS_INFORMATION processInfo; - char applicationName[2048]; - char currDir[2048]; - memset(&applicationName, 0, sizeof(applicationName)); - memset(&startupInfo, 0, sizeof(startupInfo)); - memset(&processInfo, 0, sizeof(processInfo)); - strncpy(applicationName, executionString.c_str(), sizeof(applicationName)); - strncpy(currDir, currentDirectory.c_str(), sizeof(currDir)); - startupInfo.dwFlags = STARTF_USESTDHANDLES; - startupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); - startupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); - startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - startupInfo.wShowWindow = SW_SHOWDEFAULT; - - if(!CreateProcess(NULL, applicationName, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, currDir, &startupInfo, &processInfo)) -#else - const std::size_t last_slash_idx = executable.rfind(Utils::pathSeparator); - if (last_slash_idx != std::string::npos) - { - std::string applicationName = executable.substr(last_slash_idx + 1); - executionString = "cd \"" + currentDirectory + "\" && exec \"./" + applicationName + "\" " + args; - } - if(system(executionString.c_str()) != 0) -#endif - { - Logger::write(Logger::ZONE_ERROR, "Launcher", "Failed to run: " + executable); - } - - else - { -#ifdef WIN32 - if ( wait ) - { - while(WAIT_OBJECT_0 != MsgWaitForMultipleObjects(1, &processInfo.hProcess, FALSE, INFINITE, QS_ALLINPUT)) - { - MSG msg; - while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - { - DispatchMessage(&msg); - } - } - } - - // result = GetExitCodeProcess(processInfo.hProcess, &exitCode); - CloseHandle(processInfo.hProcess); -#endif - retVal = true; - } - - Logger::write(Logger::ZONE_INFO, "Launcher", "Completed"); - - return retVal; -} - -bool Launcher::launcherName(std::string &launcherName, std::string collection) -{ - std::string launcherKey = "collections." + collection + ".launcher"; - - // find the launcher for the particular item - if(!config_.getProperty(launcherKey, launcherName)) - { - std::stringstream ss; - - ss << "Launch failed. Could not find a configured launcher for collection \"" - << collection - << "\" (could not find a property for \"" - << launcherKey - << "\")"; - - Logger::write(Logger::ZONE_ERROR, "Launcher", ss.str()); - - return false; - } - - std::stringstream ss; - ss << "collections." - << collection - << " is configured to use launchers." - << launcherName - << "\""; - - Logger::write(Logger::ZONE_DEBUG, "Launcher", ss.str()); - - return true; -} - - - -bool Launcher::launcherExecutable(std::string &executable, std::string launcherName) -{ - std::string executableKey = "launchers." + launcherName + ".executable"; - - if(!config_.getProperty(executableKey, executable)) - { - return false; - } - - return true; -} - -bool Launcher::launcherArgs(std::string &args, std::string launcherName) -{ - std::string argsKey = "launchers." + launcherName + ".arguments"; - - if(!config_.getProperty(argsKey, args)) - { - Logger::write(Logger::ZONE_ERROR, "Launcher", "No arguments specified for: " + argsKey); - - return false; - } - return true; -} - -bool Launcher::extensions(std::string &extensions, std::string collection) -{ - std::string extensionsKey = "collections." + collection + ".list.extensions"; - - if(!config_.getProperty(extensionsKey, extensions)) - { - Logger::write(Logger::ZONE_ERROR, "Launcher", "No extensions specified for: " + extensionsKey); - return false; - } - - extensions = Utils::replace(extensions, " ", ""); - extensions = Utils::replace(extensions, ".", ""); - - return true; -} - -bool Launcher::collectionDirectory(std::string &directory, std::string collection) -{ - std::string itemsPathValue; - std::string mergedCollectionName; - - // find the items path folder (i.e. ROM path) - config_.getCollectionAbsolutePath(collection, itemsPathValue); - directory += itemsPathValue + Utils::pathSeparator; - - return true; -} - -bool Launcher::findFile(std::string &foundFilePath, std::string &foundFilename, std::string directory, std::string filenameWithoutExtension, std::string extensions) -{ - std::string extension; - bool fileFound = false; - std::stringstream ss; - ss << extensions; - - while(!fileFound && std::getline(ss, extension, ',') ) - { - std::string selectedItemsPath = directory + filenameWithoutExtension + "." + extension; - std::ifstream f(selectedItemsPath.c_str()); - - if (f.good()) - { - std::stringstream ss; - - ss <<"Checking to see if \"" - << selectedItemsPath << "\" exists [Yes]"; - - fileFound = true; - - Logger::write(Logger::ZONE_INFO, "Launcher", ss.str()); - - foundFilePath = selectedItemsPath; - foundFilename = extension; - } - else - { - std::stringstream ss; - - ss << "Checking to see if \"" - << selectedItemsPath << "\" exists [No]"; - - Logger::write(Logger::ZONE_WARNING, "Launcher", ss.str()); - } - - f.close(); - } - - // get the launchers executable - - if(!fileFound) - { - std::stringstream ss; - ss <<"Could not find any files with the name \"" - << filenameWithoutExtension << "\" in folder \"" - << directory; - - Logger::write(Logger::ZONE_WARNING, "Launcher", ss.str()); - - } - - return fileFound; -} - - +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ + +#include "Launcher.h" +#include "../Collection/Item.h" +#include "../Utility/Log.h" +#include "../Database/Configuration.h" +#include "../Utility/Utils.h" +#include "../RetroFE.h" +#include "../SDL.h" +#include +#include +#include +#include +#ifdef WIN32 +#include +#include +#endif + +bool LEDBlinkyCloseOnExit = true; + +Launcher::Launcher(Configuration &c) + : config_(c) +{ +} + +bool Launcher::run(std::string collection, Item *collectionItem) +{ + std::string launcherName = collectionItem->collectionInfo->launcher; + std::string executablePath; + std::string selectedItemsDirectory; + std::string selectedItemsPath; + std::string extensionstr; + std::string matchedExtension; + std::string args; + + std::string launcherFile = Utils::combinePath( Configuration::absolutePath, "collections", collectionItem->collectionInfo->name, "launchers", collectionItem->name + ".conf" ); + std::ifstream launcherStream( launcherFile.c_str( ) ); + if (launcherStream.good( )) // Launcher file found + { + std::string line; + if (std::getline( launcherStream, line)) // Launcher found + { + launcherName = line; + } + } + launcherName = Utils::toLower(launcherName); + + if(!launcherExecutable(executablePath, launcherName)) + { + Logger::write(Logger::ZONE_ERROR, "Launcher", "Failed to find launcher executable (launcher: " + launcherName + " executable: " + executablePath + ")"); + return false; + } + if(!extensions(extensionstr, collection)) + { + Logger::write(Logger::ZONE_ERROR, "Launcher", "No file extensions configured for collection \"" + collection + "\""); + return false; + } + if(!collectionDirectory(selectedItemsDirectory, collection)) + { + Logger::write(Logger::ZONE_ERROR, "Launcher", "Could not find files in directory \"" + selectedItemsDirectory + "\" for collection \"" + collection + "\""); + return false; + } + if(!launcherArgs(args, launcherName)) + { + Logger::write(Logger::ZONE_ERROR, "Launcher", "No launcher arguments specified for launcher " + launcherName); + return false; + } + + + // Overwrite selectedItemsDirectory if already set in the file + if (collectionItem->filepath != "") + { + selectedItemsDirectory = collectionItem->filepath; + } + + // It is ok to continue if the file could not be found. We could be launching a merged romset + if (collectionItem->file == "") + findFile(selectedItemsPath, matchedExtension, selectedItemsDirectory, collectionItem->name, extensionstr); + else + findFile(selectedItemsPath, matchedExtension, selectedItemsDirectory, collectionItem->file, extensionstr); + + args = replaceVariables(args, + selectedItemsPath, + collectionItem->name, + Utils::getFileName(selectedItemsPath), + selectedItemsDirectory, + collection); + + executablePath = replaceVariables(executablePath, + selectedItemsPath, + collectionItem->name, + Utils::getFileName(selectedItemsPath), + selectedItemsDirectory, + collection); + + std::string currentDirectoryKey = "launchers." + launcherName + ".currentDirectory"; + std::string currentDirectory = Utils::getDirectory(executablePath); + + config_.getProperty(currentDirectoryKey, currentDirectory); + + currentDirectory = replaceVariables(currentDirectory, + selectedItemsPath, + collectionItem->name, + Utils::getFileName(selectedItemsPath), + selectedItemsDirectory, + collection); + + if(!execute(executablePath, args, currentDirectory)) + { + Logger::write(Logger::ZONE_ERROR, "Launcher", "Failed to launch."); + return false; + } + + bool reboot = false; + config_.getProperty("launchers." + launcherName + ".reboot", reboot); + + return reboot; +} + + +void Launcher::LEDBlinky( int command, std::string collection, Item *collectionItem ) +{ + // bool LEDBlinkyCloseOnExit = false; + // bool LEDBlinkyCloseOnExit = true; + config_.getProperty("LEDBlinkyCloseOnExit", LEDBlinkyCloseOnExit); + std::string LEDBlinkyDirectory = ""; + config_.getProperty( "LEDBlinkyDirectory", LEDBlinkyDirectory ); + std::string exe = LEDBlinkyDirectory + "\\LEDBlinky.exe"; + std::string args = std::to_string( command ); + bool wait = false; + if ( command == 2 ) + if (LEDBlinkyCloseOnExit = true) { + wait = true; + } + else wait = false; + if ( command == 8 ) + { + std::string launcherName = collectionItem->collectionInfo->launcher; + std::string launcherFile = Utils::combinePath( Configuration::absolutePath, "collections", collectionItem->collectionInfo->name, "launchers", collectionItem->name + ".conf" ); + std::ifstream launcherStream( launcherFile.c_str( ) ); + if (launcherStream.good( )) // Launcher file found + { + std::string line; + if (std::getline( launcherStream, line)) // Launcher found + { + launcherName = line; + } + } + launcherName = Utils::toLower( launcherName ); + std::string emulator = collection; + config_.getProperty("launchers." + launcherName + ".LEDBlinkyEmulator", emulator ); + args = args + " \"" + emulator + "\""; + } + if ( command == 3 || command == 9 ) + { + std::string launcherName = collectionItem->collectionInfo->launcher; + std::string launcherFile = Utils::combinePath( Configuration::absolutePath, "collections", collectionItem->collectionInfo->name, "launchers", collectionItem->name + ".conf" ); + std::ifstream launcherStream( launcherFile.c_str( ) ); + if (launcherStream.good( )) // Launcher file found + { + std::string line; + if (std::getline( launcherStream, line)) // Launcher found + { + launcherName = line; + } + } + launcherName = Utils::toLower( launcherName ); + std::string emulator = launcherName; + config_.getProperty("launchers." + launcherName + ".LEDBlinkyEmulator", emulator ); + args = args + " \"" + collectionItem->name + "\" \"" + emulator + "\""; + if ( emulator == "" ) + return; + } + if ( LEDBlinkyDirectory != "" && !execute( exe, args, LEDBlinkyDirectory, wait ) ) + { + Logger::write( Logger::ZONE_ERROR, "LEDBlinky", "Failed to launch." ); + } + return; +} + + +std::string Launcher::replaceVariables(std::string str, + std::string itemFilePath, + std::string itemName, + std::string itemFilename, + std::string itemDirectory, + std::string itemCollectionName) +{ + str = Utils::replace(str, "%ITEM_FILEPATH%", itemFilePath); + str = Utils::replace(str, "%ITEM_NAME%", itemName); + str = Utils::replace(str, "%ITEM_FILENAME%", itemFilename); + str = Utils::replace(str, "%ITEM_DIRECTORY%", itemDirectory); + str = Utils::replace(str, "%ITEM_COLLECTION_NAME%", itemCollectionName); + str = Utils::replace(str, "%RETROFE_PATH%", Configuration::absolutePath); +#ifdef WIN32 + str = Utils::replace(str, "%RETROFE_EXEC_PATH%", Utils::combinePath(Configuration::absolutePath, "core", "RetroFE.exe")); +#else + str = Utils::replace(str, "%RETROFE_EXEC_PATH%", Utils::combinePath(Configuration::absolutePath, "RetroFE")); +#endif + + return str; +} + +bool Launcher::execute(std::string executable, std::string args, std::string currentDirectory, bool wait) +{ + bool retVal = false; + std::string executionString = "\"" + executable + "\" " + args; + + Logger::write(Logger::ZONE_INFO, "Launcher", "Attempting to launch: " + executionString); + Logger::write(Logger::ZONE_INFO, "Launcher", " from within folder: " + currentDirectory); + +#ifdef WIN32 + STARTUPINFO startupInfo; + PROCESS_INFORMATION processInfo; + char applicationName[2048]; + char currDir[2048]; + memset(&applicationName, 0, sizeof(applicationName)); + memset(&startupInfo, 0, sizeof(startupInfo)); + memset(&processInfo, 0, sizeof(processInfo)); + strncpy(applicationName, executionString.c_str(), sizeof(applicationName)); + strncpy(currDir, currentDirectory.c_str(), sizeof(currDir)); + startupInfo.dwFlags = STARTF_USESTDHANDLES; + startupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); + startupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startupInfo.wShowWindow = SW_SHOWDEFAULT; + + if(!CreateProcess(NULL, applicationName, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, currDir, &startupInfo, &processInfo)) +#else + const std::size_t last_slash_idx = executable.rfind(Utils::pathSeparator); + if (last_slash_idx != std::string::npos) + { + std::string applicationName = executable.substr(last_slash_idx + 1); + executionString = "cd \"" + currentDirectory + "\" && exec \"./" + applicationName + "\" " + args; + } + if(system(executionString.c_str()) != 0) +#endif + { + Logger::write(Logger::ZONE_ERROR, "Launcher", "Failed to run: " + executable); + } + + else + { +#ifdef WIN32 + if ( wait ) + { + while(WAIT_OBJECT_0 != MsgWaitForMultipleObjects(1, &processInfo.hProcess, FALSE, INFINITE, QS_ALLINPUT)) + { + MSG msg; + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + DispatchMessage(&msg); + } + } + } + + // result = GetExitCodeProcess(processInfo.hProcess, &exitCode); + CloseHandle(processInfo.hProcess); +#endif + retVal = true; + } + + Logger::write(Logger::ZONE_INFO, "Launcher", "Completed"); + + return retVal; +} + +bool Launcher::launcherName(std::string &launcherName, std::string collection) +{ + std::string launcherKey = "collections." + collection + ".launcher"; + + // find the launcher for the particular item + if(!config_.getProperty(launcherKey, launcherName)) + { + std::stringstream ss; + + ss << "Launch failed. Could not find a configured launcher for collection \"" + << collection + << "\" (could not find a property for \"" + << launcherKey + << "\")"; + + Logger::write(Logger::ZONE_ERROR, "Launcher", ss.str()); + + return false; + } + + std::stringstream ss; + ss << "collections." + << collection + << " is configured to use launchers." + << launcherName + << "\""; + + Logger::write(Logger::ZONE_DEBUG, "Launcher", ss.str()); + + return true; +} + + + +bool Launcher::launcherExecutable(std::string &executable, std::string launcherName) +{ + std::string executableKey = "launchers." + launcherName + ".executable"; + + if(!config_.getProperty(executableKey, executable)) + { + return false; + } + + return true; +} + +bool Launcher::launcherArgs(std::string &args, std::string launcherName) +{ + std::string argsKey = "launchers." + launcherName + ".arguments"; + + if(!config_.getProperty(argsKey, args)) + { + Logger::write(Logger::ZONE_ERROR, "Launcher", "No arguments specified for: " + argsKey); + + return false; + } + return true; +} + +bool Launcher::extensions(std::string &extensions, std::string collection) +{ + std::string extensionsKey = "collections." + collection + ".list.extensions"; + + if(!config_.getProperty(extensionsKey, extensions)) + { + Logger::write(Logger::ZONE_ERROR, "Launcher", "No extensions specified for: " + extensionsKey); + return false; + } + + extensions = Utils::replace(extensions, " ", ""); + extensions = Utils::replace(extensions, ".", ""); + + return true; +} + +bool Launcher::collectionDirectory(std::string &directory, std::string collection) +{ + std::string itemsPathValue; + std::string mergedCollectionName; + + // find the items path folder (i.e. ROM path) + config_.getCollectionAbsolutePath(collection, itemsPathValue); + directory += itemsPathValue + Utils::pathSeparator; + + return true; +} + +bool Launcher::findFile(std::string &foundFilePath, std::string &foundFilename, std::string directory, std::string filenameWithoutExtension, std::string extensions) +{ + std::string extension; + bool fileFound = false; + std::stringstream ss; + ss << extensions; + + while(!fileFound && std::getline(ss, extension, ',') ) + { + std::string selectedItemsPath = directory + filenameWithoutExtension + "." + extension; + std::ifstream f(selectedItemsPath.c_str()); + + if (f.good()) + { + std::stringstream ss; + + ss <<"Checking to see if \"" + << selectedItemsPath << "\" exists [Yes]"; + + fileFound = true; + + Logger::write(Logger::ZONE_INFO, "Launcher", ss.str()); + + foundFilePath = selectedItemsPath; + foundFilename = extension; + } + else + { + std::stringstream ss; + + ss << "Checking to see if \"" + << selectedItemsPath << "\" exists [No]"; + + Logger::write(Logger::ZONE_WARNING, "Launcher", ss.str()); + } + + f.close(); + } + + // get the launchers executable + + if(!fileFound) + { + std::stringstream ss; + ss <<"Could not find any files with the name \"" + << filenameWithoutExtension << "\" in folder \"" + << directory; + + Logger::write(Logger::ZONE_WARNING, "Launcher", ss.str()); + + } + + return fileFound; +} + + diff --git a/RetroFE/Source/Execute/Launcher.h b/RetroFE/Source/Execute/Launcher.h index f5fcba8e1..a56633b3d 100644 --- a/RetroFE/Source/Execute/Launcher.h +++ b/RetroFE/Source/Execute/Launcher.h @@ -1,52 +1,52 @@ -/* This file is part of RetroFE. - * - * RetroFE 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. - * - * RetroFE 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 RetroFE. If not, see . - */ -#pragma once - -#include - -class Configuration; -class Item; -class RetroFE; - -class Launcher -{ -public: - Launcher(Configuration &c); - bool run(std::string collection, Item *collectionItem); - void LEDBlinky( int command, std::string collection = "", Item *collectionItem = NULL); - -private: - std::string replaceString( - std::string subject, - const std::string &search, - const std::string &replace); - - bool launcherName(std::string &launcherName, std::string collection); - bool launcherExecutable(std::string &executable, std::string launcherName); - bool launcherArgs(std::string &args, std::string launcherName); - bool extensions(std::string &extensions, std::string launcherName); - bool collectionDirectory(std::string &directory, std::string collection); - bool execute(std::string executable, std::string arguments, std::string currentDirectory, bool wait = true); - bool findFile(std::string &foundFilePath, std::string &foundFilename, std::string directory, std::string filenameWithoutExtension, std::string extensions); - std::string replaceVariables(std::string str, - std::string itemFilePath, - std::string itemName, - std::string itemFilename, - std::string itemDirectory, - std::string itemCollectionName); - - Configuration &config_; -}; +/* This file is part of RetroFE. + * + * RetroFE 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. + * + * RetroFE 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 RetroFE. If not, see . + */ +#pragma once + +#include + +class Configuration; +class Item; +class RetroFE; + +class Launcher +{ +public: + Launcher(Configuration &c); + bool run(std::string collection, Item *collectionItem); + void LEDBlinky( int command, std::string collection = "", Item *collectionItem = NULL); + +private: + std::string replaceString( + std::string subject, + const std::string &search, + const std::string &replace); + + bool launcherName(std::string &launcherName, std::string collection); + bool launcherExecutable(std::string &executable, std::string launcherName); + bool launcherArgs(std::string &args, std::string launcherName); + bool extensions(std::string &extensions, std::string launcherName); + bool collectionDirectory(std::string &directory, std::string collection); + bool execute(std::string executable, std::string arguments, std::string currentDirectory, bool wait = true); + bool findFile(std::string &foundFilePath, std::string &foundFilename, std::string directory, std::string filenameWithoutExtension, std::string extensions); + std::string replaceVariables(std::string str, + std::string itemFilePath, + std::string itemName, + std::string itemFilename, + std::string itemDirectory, + std::string itemCollectionName); + + Configuration &config_; +}; From 4b98547221c1b2c56d03a21de4b02068e152c171 Mon Sep 17 00:00:00 2001 From: James VanDusen Date: Tue, 3 Jan 2023 11:50:48 -0500 Subject: [PATCH 3/4] Boolean adjustments for ledblinky Adjusted the logic --- RetroFE/Source/RetroFE.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/RetroFE/Source/RetroFE.cpp b/RetroFE/Source/RetroFE.cpp index 6bef2ba1c..e3388a4b0 100644 --- a/RetroFE/Source/RetroFE.cpp +++ b/RetroFE/Source/RetroFE.cpp @@ -1310,9 +1310,11 @@ bool RetroFE::run( ) // Wait for onExit animation to finish before quitting RetroFE case RETROFE_QUIT: + bool LEDBlinkyCloseOnExit = true; + config_.getProperty("LEDBlinkyCloseOnExit", LEDBlinkyCloseOnExit); if ( currentPage_->isGraphicsIdle( ) ) { - if (LEDBlinkyCloseOnExit = yes) { + if (LEDBlinkyCloseOnExit == true) { l.LEDBlinky(2); running = false; } From f3322de861ca3f6e309bba9bd8af3a7aebe05c32 Mon Sep 17 00:00:00 2001 From: James VanDusen Date: Tue, 3 Jan 2023 11:51:32 -0500 Subject: [PATCH 4/4] boolean adjustments Setting the logic correctly to detect parameters --- RetroFE/Source/Execute/Launcher.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/RetroFE/Source/Execute/Launcher.cpp b/RetroFE/Source/Execute/Launcher.cpp index e5384726a..19fe7954b 100644 --- a/RetroFE/Source/Execute/Launcher.cpp +++ b/RetroFE/Source/Execute/Launcher.cpp @@ -30,7 +30,7 @@ #include #endif -bool LEDBlinkyCloseOnExit = true; +// bool LEDBlinkyCloseOnExit = true; Launcher::Launcher(Configuration &c) : config_(c) @@ -134,19 +134,20 @@ bool Launcher::run(std::string collection, Item *collectionItem) void Launcher::LEDBlinky( int command, std::string collection, Item *collectionItem ) { - // bool LEDBlinkyCloseOnExit = false; - // bool LEDBlinkyCloseOnExit = true; - config_.getProperty("LEDBlinkyCloseOnExit", LEDBlinkyCloseOnExit); std::string LEDBlinkyDirectory = ""; config_.getProperty( "LEDBlinkyDirectory", LEDBlinkyDirectory ); std::string exe = LEDBlinkyDirectory + "\\LEDBlinky.exe"; std::string args = std::to_string( command ); bool wait = false; - if ( command == 2 ) - if (LEDBlinkyCloseOnExit = true) { + if ( command == 2 ) + { + bool LEDBlinkyCloseOnExit; + config_.getProperty("LEDBlinkyCloseOnExit", LEDBlinkyCloseOnExit); + if (LEDBlinkyCloseOnExit == true) { wait = true; } else wait = false; + } if ( command == 8 ) { std::string launcherName = collectionItem->collectionInfo->launcher;