// SPDX-License-Identifier: MIT // // EmulationStation Desktop Edition // SystemScreenSaver.cpp // // Screensaver, supporting the following modes: // Dim, black, slideshow, video. // #include "SystemScreenSaver.h" #if defined(_RPI_) #include "components/VideoPlayerComponent.h" #endif #include "components/VideoVlcComponent.h" #include "utils/FileSystemUtil.h" #include "views/gamelist/IGameListView.h" #include "views/ViewController.h" #include "FileData.h" #include "FileFilterIndex.h" #include "Log.h" #include "PowerSaver.h" #include "Sound.h" #include "SystemData.h" #include #include #if defined(_WIN64) #include #endif #define FADE_TIME 300 SystemScreenSaver::SystemScreenSaver( Window* window) : mVideoScreensaver(nullptr), mImageScreensaver(nullptr), mWindow(window), mVideosCounted(false), mVideoCount(0), mImagesCounted(false), mImageCount(0), mState(STATE_INACTIVE), mOpacity(0.0f), mTimer(0), mSystemName(""), mGameName(""), mCurrentGame(nullptr), mStopBackgroundAudio(true) { mWindow->setScreenSaver(this); std::string path = getTitleFolder(); if (!Utils::FileSystem::exists(path)) Utils::FileSystem::createDirectory(path); srand((unsigned int)time(nullptr)); mVideoChangeTime = 30000; } SystemScreenSaver::~SystemScreenSaver() { // Delete subtitle file, if it exists. remove(getTitlePath().c_str()); mCurrentGame = nullptr; delete mVideoScreensaver; delete mImageScreensaver; } bool SystemScreenSaver::allowSleep() { return ((mVideoScreensaver == nullptr) && (mImageScreensaver == nullptr)); } bool SystemScreenSaver::isScreenSaverActive() { return (mState != STATE_INACTIVE); } void SystemScreenSaver::startScreenSaver() { std::string screensaver_behavior = Settings::getInstance()->getString("ScreenSaverBehavior"); // Set mPreviousGame which will be used to avoid showing the same game again during // the random selection. if ((screensaver_behavior == "video" || screensaver_behavior == "slideshow") && mCurrentGame != nullptr) mPreviousGame = mCurrentGame; if (!mVideoScreensaver && (screensaver_behavior == "video")) { // Configure to fade out the windows, skip fading if mode is set to Instant. mState = PowerSaver::getMode() == PowerSaver::INSTANT ? STATE_SCREENSAVER_ACTIVE : STATE_FADE_OUT_WINDOW; mVideoChangeTime = Settings::getInstance()->getInt("ScreenSaverSwapVideoTimeout"); mOpacity = 0.0f; // Load a random video. std::string path = ""; pickRandomVideo(path); int retry = 200; while (retry > 0 && ((path.empty() || !Utils::FileSystem::exists(path)) || mCurrentGame == nullptr)) { retry--; pickRandomVideo(path); } if (!path.empty() && Utils::FileSystem::exists(path)) { #if defined(_RPI_) // Create the correct type of video component if (Settings::getInstance()->getBool("ScreenSaverOmxPlayer")) mVideoScreensaver = new VideoPlayerComponent(mWindow, getTitlePath()); else mVideoScreensaver = new VideoVlcComponent(mWindow, getTitlePath()); #else mVideoScreensaver = new VideoVlcComponent(mWindow, getTitlePath()); #endif mVideoScreensaver->topWindow(true); mVideoScreensaver->setOrigin(0.5f, 0.5f); mVideoScreensaver->setPosition(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f); if (Settings::getInstance()->getBool("ScreenSaverStretchVideos")) mVideoScreensaver->setResize(static_cast(Renderer::getScreenWidth()), static_cast(Renderer::getScreenHeight())); else mVideoScreensaver->setMaxSize(static_cast(Renderer::getScreenWidth()), static_cast(Renderer::getScreenHeight())); mVideoScreensaver->setVideo(path); mVideoScreensaver->setScreensaverMode(true); mVideoScreensaver->onShow(); PowerSaver::runningScreenSaver(true); mTimer = 0; return; } } else if (screensaver_behavior == "slideshow") { // Configure to fade out the windows, skip fading if mode is set to Instant. mState = PowerSaver::getMode() == PowerSaver::INSTANT ? STATE_SCREENSAVER_ACTIVE : STATE_FADE_OUT_WINDOW; mVideoChangeTime = Settings::getInstance()->getInt("ScreenSaverSwapImageTimeout"); mOpacity = 0.0f; // Load a random image. std::string path = ""; if (Settings::getInstance()->getBool("SlideshowScreenSaverCustomImageSource")) { pickRandomCustomImage(path); // Custom images are not tied to the game list. mCurrentGame = nullptr; } else { pickRandomGameListImage(path); } if (!mImageScreensaver) mImageScreensaver = new ImageComponent(mWindow, false, false); mTimer = 0; mImageScreensaver->setImage(path); mImageScreensaver->setOrigin(0.5f, 0.5f); mImageScreensaver->setPosition(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f); if (Settings::getInstance()->getBool("ScreenSaverStretchImages")) mImageScreensaver->setResize(static_cast(Renderer::getScreenWidth()), static_cast(Renderer::getScreenHeight())); else mImageScreensaver->setMaxSize(static_cast(Renderer::getScreenWidth()), static_cast(Renderer::getScreenHeight())); std::string bg_audio_file = Settings::getInstance()-> getString("SlideshowScreenSaverBackgroundAudioFile"); if ((!mBackgroundAudio) && (bg_audio_file != "")) { if (Utils::FileSystem::exists(bg_audio_file)) { // Pause PowerSaver so that the background audio keeps playing. PowerSaver::pause(); mBackgroundAudio = Sound::get(bg_audio_file); mBackgroundAudio->play(); } } PowerSaver::runningScreenSaver(true); mTimer = 0; return; } // No videos. Just use a standard screensaver. mState = STATE_SCREENSAVER_ACTIVE; mCurrentGame = nullptr; } void SystemScreenSaver::stopScreenSaver() { if ((mBackgroundAudio) && (mStopBackgroundAudio)) { mBackgroundAudio->stop(); mBackgroundAudio.reset(); // If we were playing audio, we paused PowerSaver. PowerSaver::resume(); } // So that we stop the background audio next time, unless we're restarting the screensaver. mStopBackgroundAudio = true; delete mVideoScreensaver; mVideoScreensaver = nullptr; delete mImageScreensaver; mImageScreensaver = nullptr; // We need this to loop through different videos. mState = STATE_INACTIVE; PowerSaver::runningScreenSaver(false); } void SystemScreenSaver::renderScreenSaver() { std::string screensaver_behavior = Settings::getInstance()->getString("ScreenSaverBehavior"); if (mVideoScreensaver && screensaver_behavior == "video") { // Render black background. Renderer::setMatrix(Transform4x4f::Identity()); Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0x000000FF, 0x000000FF); // Only render the video if the state requires it. if (static_cast(mState) >= STATE_FADE_IN_VIDEO) { Transform4x4f transform = Transform4x4f::Identity(); mVideoScreensaver->render(transform); } } else if (mImageScreensaver && screensaver_behavior == "slideshow") { // Render black background. Renderer::setMatrix(Transform4x4f::Identity()); Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0x000000FF, 0x000000FF); // Only render the video if the state requires it. if ((int)mState >= STATE_FADE_IN_VIDEO) { if (mImageScreensaver->hasImage()) { mImageScreensaver->setOpacity(255 - static_cast(mOpacity * 255)); Transform4x4f transform = Transform4x4f::Identity(); mImageScreensaver->render(transform); } } // Check if we need to restart the background audio. if ((mBackgroundAudio) && (Settings::getInstance()-> getString("SlideshowScreenSaverBackgroundAudioFile") != "")) { if (!mBackgroundAudio->isPlaying()) mBackgroundAudio->play(); } } else if (mState != STATE_INACTIVE) { #if !defined(USE_OPENGL_21) Renderer::setMatrix(Transform4x4f::Identity()); unsigned char color = screensaver_behavior == "dim" ? 0x000000A0 : 0x000000FF; Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), color, color); #endif } } unsigned long SystemScreenSaver::countGameListNodes(const char *nodeName) { unsigned long nodeCount = 0; std::vector::const_iterator it; for (it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend(); ++it) { // We only want nodes from game systems that are not collections. if (!(*it)->isGameSystem() || (*it)->isCollection()) continue; FileData* rootFileData = (*it)->getRootFolder(); FileType type = GAME; std::vector allFiles = rootFileData->getFilesRecursive(type, true); std::vector::const_iterator itf; // Declare an iterator to a vector of strings. for (itf=allFiles.cbegin() ; itf < allFiles.cend(); itf++) { if ((strcmp(nodeName, "video") == 0 && (*itf)->getVideoPath() != "") || (strcmp(nodeName, "image") == 0 && (*itf)->getImagePath() != "")) nodeCount++; } } return nodeCount; } void SystemScreenSaver::countVideos() { if (!mVideosCounted) { mVideoCount = countGameListNodes("video"); mVideosCounted = true; } } void SystemScreenSaver::countImages() { if (!mImagesCounted) { mImageCount = countGameListNodes("image"); mImagesCounted = true; } } void SystemScreenSaver::pickGameListNode(unsigned long index, const char *nodeName, std::string& path) { std::vector::const_iterator it; for (it = SystemData::sSystemVector.cbegin(); it != SystemData::sSystemVector.cend(); ++it) { // We only want nodes from game systems that are not collections. if (!(*it)->isGameSystem() || (*it)->isCollection()) continue; FileData* rootFileData = (*it)->getRootFolder(); FileType type = GAME; std::vector allFiles = rootFileData->getFilesRecursive(type, true); std::vector::const_iterator itf; // Declare an iterator to a vector of strings. for (itf=allFiles.cbegin() ; itf < allFiles.cend(); itf++) { if ((strcmp(nodeName, "video") == 0 && (*itf)->getVideoPath() != "") || (strcmp(nodeName, "image") == 0 && (*itf)->getImagePath() != "")) { if (index-- == 0) { // We have it. path = ""; if (strcmp(nodeName, "video") == 0) path = (*itf)->getVideoPath(); else if (strcmp(nodeName, "image") == 0) path = (*itf)->getImagePath(); mSystemName = (*it)->getFullName(); mGameName = (*itf)->getName(); mCurrentGame = (*itf); // End of getting FileData. #if defined(_RPI_) if (Settings::getInstance()->getString("ScreenSaverGameInfo") != "never") writeSubtitle(mGameName.c_str(), mSystemName.c_str(), (Settings::getInstance()->getString("ScreenSaverGameInfo") == "always")); #endif return; } } } } } void SystemScreenSaver::pickRandomVideo(std::string& path) { countVideos(); mCurrentGame = nullptr; if (mVideoCount < 2) mPreviousGame = nullptr; // If there are more than 1 videos available, keep trying until the same game is // not shown again. if (mVideoCount > 0) { do { int video = static_cast((static_cast(rand()) / static_cast(RAND_MAX)) * static_cast(mVideoCount)); pickGameListNode(video, "video", path); } while (mPreviousGame && mCurrentGame == mPreviousGame); } } void SystemScreenSaver::pickRandomGameListImage(std::string& path) { countImages(); mCurrentGame = nullptr; if (mImageCount < 2) mPreviousGame = nullptr; // If there are more than 1 images available, keep trying until the same game is // not shown again. if (mImageCount > 0) { do { int image = static_cast((static_cast(rand()) / static_cast(RAND_MAX)) * static_cast(mImageCount)); pickGameListNode(image, "image", path); } while (mPreviousGame && mCurrentGame == mPreviousGame); } } void SystemScreenSaver::pickRandomCustomImage(std::string& path) { std::string imageDir = Settings::getInstance()->getString("SlideshowScreenSaverImageDir"); if ((imageDir != "") && (Utils::FileSystem::exists(imageDir))) { std::string imageFilter = Settings::getInstance()-> getString("SlideshowScreenSaverImageFilter"); std::vector matchingFiles; Utils::FileSystem::stringList dirContent = Utils::FileSystem::getDirContent( imageDir, Settings::getInstance()->getBool("SlideshowScreenSaverRecurse")); for (Utils::FileSystem::stringList::const_iterator it = dirContent.cbegin(); it != dirContent.cend(); ++it) { if (Utils::FileSystem::isRegularFile(*it)) { // If the image filter is empty, or the file extension is in the filter // string, add it to the matching files list. if ((imageFilter.length() <= 0) || (imageFilter.find( Utils::FileSystem::getExtension(*it)) != std::string::npos)) matchingFiles.push_back(*it); } } int fileCount = static_cast(matchingFiles.size()); if (fileCount > 0) { // Get a random index in the range 0 to fileCount (exclusive). int randomIndex = rand() % fileCount; path = matchingFiles[randomIndex]; } else { LOG(LogError) << "Slideshow Screensaver - No image files found\n"; } } else { LOG(LogError) << "Slideshow Screensaver - Image directory does not exist: " << imageDir << "\n"; } } void SystemScreenSaver::update(int deltaTime) { // Use this to update the fade value for the current fade stage. if (mState == STATE_FADE_OUT_WINDOW) { mOpacity += static_cast(deltaTime) / FADE_TIME; if (mOpacity >= 1.0f) { mOpacity = 1.0f; // Update to the next state. mState = STATE_FADE_IN_VIDEO; } } else if (mState == STATE_FADE_IN_VIDEO) { mOpacity -= static_cast(deltaTime) / FADE_TIME; if (mOpacity <= 0.0f) { mOpacity = 0.0f; // Update to the next state. mState = STATE_SCREENSAVER_ACTIVE; } } else if (mState == STATE_SCREENSAVER_ACTIVE) { // Update the timer that swaps the videos. mTimer += deltaTime; if (mTimer > mVideoChangeTime) nextGame(); } // If we have a loaded video then update it. if (mVideoScreensaver) mVideoScreensaver->update(deltaTime); if (mImageScreensaver) mImageScreensaver->update(deltaTime); } void SystemScreenSaver::nextGame() { mStopBackgroundAudio = false; stopScreenSaver(); startScreenSaver(); mState = STATE_SCREENSAVER_ACTIVE; } FileData* SystemScreenSaver::getCurrentGame() { return mCurrentGame; } void SystemScreenSaver::launchGame() { if (mCurrentGame != nullptr) { // Launching game ViewController::get()->goToGameList(mCurrentGame->getSystem()); IGameListView* view = ViewController::get()-> getGameListView(mCurrentGame->getSystem()).get(); view->setCursor(mCurrentGame); ViewController::get()->resetMovingCamera(); ViewController::get()->launch(mCurrentGame); } }