Made game launching on Windows much more seamless.

Also added an option to hide the taskbar.
This commit is contained in:
Leon Styhre 2020-07-18 13:21:44 +02:00
parent 6a0682b833
commit 8fcb56cfb3
19 changed files with 246 additions and 46 deletions

View file

@ -357,6 +357,7 @@ audio_output\libwaveout_plugin.dll
codec\libavcodec_plugin.dll
codec\libx264_plugin.dll
codec\libx265_plugin.dll
logger\libconsole_logger_plugin.dll
text_renderer\libfreetype_plugin.dll
video_chroma\libswscale_plugin.dll
video_output\libvmem_plugin.dll
@ -550,7 +551,9 @@ The new configuration will be added to the `~/.emulationstation/es_input.cfg` fi
Command line arguments
======================
You can use `--help` or `-h` to view a list of command line options. Briefly outlined here:
You can use `--help` or `-h` to view a list of command line options, as shown here.
### Unix:
```
--resolution [width] [height] Try to force a particular resolution
@ -574,6 +577,27 @@ You can use `--help` or `-h` to view a list of command line options. Briefly out
--help, -h Summon a sentient, angry tuba
```
### Windows:
```
--resolution [width] [height] Try to force a particular resolution
--gamelist-only Skip automatic game ROM search, only read from gamelist.xml
--ignore-gamelist Ignore the gamelist files (useful for troubleshooting)
--draw-framerate Display the framerate
--no-exit Don't show the exit option in the menu
--no-splash Don't show the splash screen
--debug Print debug information
--vsync [1/on or 0/off] Turn vsync on or off (default is on)
--max-vram [size] Max VRAM to use in Mb before swapping
Set to at least 20 to avoid unpredictable behavior
--force-kid Force the UI mode to Kid
--force-kiosk Force the UI mode to Kiosk
--force-disable-filters Force the UI to ignore applied filters in gamelist
--home [path] Directory to use as home path
--version, -v Displays version information
--help, -h Summon a sentient, angry tuba
```
As long as ES hasn't frozen, you can always press F4 to close the application.
As you can see above, you can override the home directory path using the `--home` flag. So by running for instance the command `emulationstation --home ~/games/emulation`, ES will use `~/games/emulation/.emulationstation` as its base directory.

View file

@ -20,7 +20,7 @@ BEGIN
BLOCK "040904E4"
BEGIN
VALUE "Comments", "\0"
VALUE "FileDescription", "EmulationStation - emulator frontend\0"
VALUE "FileDescription", "EmulationStation - Emulator Front-end\0"
VALUE "FileVersion", RESOURCE_VERSION_STRING
VALUE "InternalName", "emulationstation.exe\0"
VALUE "LegalCopyright", "\0"

View file

@ -8,6 +8,7 @@
#include "FileData.h"
#include "guis/GuiInfoPopup.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include "utils/TimeUtil.h"
@ -435,15 +436,6 @@ void FileData::launchGame(Window* window)
{
LOG(LogInfo) << "Attempting to launch game...";
AudioManager::getInstance()->deinit();
VolumeControl::getInstance()->deinit();
// TEMPORARY - Windows does not like it at all if you launch a game without
// first doing a deinit(). Need to fix this properly at a later date.
#ifdef _WIN64
window->deinit();
#endif
std::string command = "";
// Check if there is a launch command override for the game
@ -544,21 +536,21 @@ void FileData::launchGame(Window* window)
exitCode = launchEmulatorUnix(command);
#endif
// Notify the user in case of a failed game launch using a popup window.
if (exitCode != 0) {
LOG(LogWarning) << "...launch terminated with nonzero exit code " << exitCode << "!";
GuiInfoPopup* s = new GuiInfoPopup(window, "ERROR LAUNCHING GAME '" +
Utils::String::toUpper(metadata.get("name")) + "' (EXIT CODE " +
Utils::String::toUpper(std::to_string(exitCode) + ")"), 4000);
window->setInfoPopup(s);
}
else {
window->setLaunchedGame();
}
Scripting::fireEvent("game-end");
// TEMPORARY - Windows does not like it at all if you launch a game without
// first doing a deinit(). Need to fix this properly at a later date.
#ifdef _WIN64
window->init();
#endif
VolumeControl::getInstance()->init();
window->normalizeNextUpdate();
// Update number of times the game has been launched.
FileData* gameToUpdate = getSourceFileData();

View file

@ -485,6 +485,7 @@ void GuiMenu::openOtherSettings()
s->addSaveFunc([max_vram] { Settings::getInstance()->setInt("MaxVRAM",
(int)Math::round(max_vram->getValue())); });
#ifdef __unix__
// Fullscreen mode.
auto fullscreen_mode = std::make_shared<OptionListComponent<std::string>>
(mWindow, getHelpStyle(), "FULLSCREEN MODE", false);
@ -502,6 +503,7 @@ void GuiMenu::openOtherSettings()
}
Settings::getInstance()->setString("FullscreenMode", fullscreen_mode->getSelected());
});
#endif
// Power saver.
auto power_saver = std::make_shared<OptionListComponent<std::string>>
@ -541,7 +543,6 @@ void GuiMenu::openOtherSettings()
if (needReload)
ViewController::get()->reloadAll();
});
#endif
// When to save game metadata.
@ -593,6 +594,15 @@ void GuiMenu::openOtherSettings()
});
s->addRow(row);
#ifdef _WIN64
// Hide taskbar during ES program session.
auto hide_taskbar = std::make_shared<SwitchComponent>(mWindow);
hide_taskbar->setState(Settings::getInstance()->getBool("HideTaskbar"));
s->addWithLabel("HIDE TASKBAR (REQUIRES RESTART)", hide_taskbar);
s->addSaveFunc([hide_taskbar] { Settings::getInstance()->
setBool("HideTaskbar", hide_taskbar->getState()); });
#endif
// Allow overriding of the launch command per game (the option to disable this is
// intended primarily for testing purposes).
auto launchcommand_override = std::make_shared<SwitchComponent>(mWindow);

View file

@ -245,6 +245,8 @@ bool parseArgs(int argc, char* argv[])
Settings::getInstance()->setBool("Debug", true);
Log::setReportingLevel(LogDebug);
}
// Windowed mode is always selected on Windows.
#ifndef _WIN64
else if (strcmp(argv[i], "--fullscreen-normal") == 0) {
Settings::getInstance()->setString("FullscreenMode", "normal");
}
@ -254,6 +256,7 @@ bool parseArgs(int argc, char* argv[])
else if (strcmp(argv[i], "--windowed") == 0) {
Settings::getInstance()->setBool("Windowed", true);
}
#endif
else if (strcmp(argv[i], "--vsync") == 0) {
bool vsync = (strcmp(argv[i + 1], "on") == 0 ||
strcmp(argv[i + 1], "1") == 0) ? true : false;
@ -286,9 +289,11 @@ bool parseArgs(int argc, char* argv[])
" --no-exit Don't show the exit option in the menu\n"
" --no-splash Don't show the splash screen\n"
" --debug Print debug information\n"
#ifndef _WIN64
" --windowed Windowed mode, should be combined with --resolution\n"
" --fullscreen-normal Normal fullscreen mode\n"
" --fullscreen-borderless Borderless fullscreen mode (always on top)\n"
#endif
" --vsync [1/on or 0/off] Turn vsync on or off (default is on)\n"
" --max-vram [size] Max VRAM to use in Mb before swapping\n"
" Set to at least 20 to avoid unpredictable behavior\n"
@ -348,8 +353,8 @@ bool loadSystemConfigFile(std::string& errorMsg)
"(Check that the file extensions are supported.)";
errorMsg = "THE SYSTEMS CONFIGURATION FILE EXISTS, BUT NO\n"
"GAME FILES WERE FOUND. PLEASE MAKE SURE THAT\n"
"THE 'ROMDIRECTORY' SETTING IN ES_SETTINGS.CFG IS\n"
"POINTING TO YOUR ROM DIRECTORY AND THAT YOUR\n"
"THE \"ROMDIRECTORY\" SETTING IN ES_SETTINGS.CFG\n"
"IS POINTING TO YOUR ROM DIRECTORY AND THAT YOUR\n"
"GAME FILES ARE USING SUPPORTED FILE EXTENSIONS.\n"
"THE GAME SYSTEMS SUBDIRECTORIES ALSO NEED TO\n"
"MATCH THE PLATFORM TAGS IN ES_SYSTEMS.CFG.\n"
@ -391,6 +396,18 @@ int main(int argc, char* argv[])
outputToConsole(true);
#endif
#ifdef _WIN64
// Hide taskbar if the setting for this is enabled.
bool taskbarStateChanged = false;
unsigned int taskbarState;
if (Settings::getInstance()->getBool("HideTaskbar")) {
taskbarStateChanged = true;
taskbarState = getTaskbarState();
hideTaskbar();
}
#endif
// Call this ONLY when linking with FreeImage as a static library.
#ifdef FREEIMAGE_LIB
FreeImage_Initialise();
@ -586,6 +603,12 @@ int main(int argc, char* argv[])
FreeImage_DeInitialise();
#endif
#ifdef _WIN64
// If the taskbar state was changed (taskbar was hidden), then revert it.
if (taskbarStateChanged)
revertTaskbarState(taskbarState);
#endif
processQuitMode();
LOG(LogInfo) << "EmulationStation cleanly shutting down.";

View file

@ -408,6 +408,13 @@ bool ViewController::input(InputConfig* config, Input input)
if (mLockInput)
return true;
// If we have previously launched a game and there is now input registered, it means
// the user is back in ES, so unset the flag to indicate that a game has been launched
// and update all the GUI components to reflect this.
if (mWindow->getGameLaunchedState()) {
mWindow->unsetLaunchedGame();
}
// Open menu.
if (!(UIModeController::getInstance()->isUIModeKid() &&
!Settings::getInstance()->getBool("ShowKidStartMenu")) &&
@ -531,6 +538,11 @@ void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
break;
}
}
// If a game has been launched, then update all the GUI components to reflect this.
if (mWindow->getGameLaunchedState())
mWindow->setLaunchedGame();
// Redisplay the current view.
if (mCurrentView)
mCurrentView->onShow();

View file

@ -68,7 +68,6 @@ private:
TextComponent mDescription;
bool mVideoPlaying;
};
#endif // ES_APP_VIEWS_GAME_LIST_VIDEO_GAME_LIST_VIEW_H

View file

@ -511,6 +511,18 @@ void GuiComponent::onScreenSaverDeactivate()
getChild(i)->onScreenSaverDeactivate();
}
void GuiComponent::onGameLaunchedActivate()
{
for (unsigned int i = 0; i < getChildCount(); i++)
getChild(i)->onGameLaunchedActivate();
}
void GuiComponent::onGameLaunchedDeactivate()
{
for (unsigned int i = 0; i < getChildCount(); i++)
getChild(i)->onGameLaunchedDeactivate();
}
void GuiComponent::topWindow(bool isTop)
{
for (unsigned int i = 0; i < getChildCount(); i++)

View file

@ -157,6 +157,8 @@ public:
virtual void onScreenSaverActivate();
virtual void onScreenSaverDeactivate();
virtual void onGameLaunchedActivate();
virtual void onGameLaunchedDeactivate();
virtual void topWindow(bool isTop);
// Default implementation just handles <pos> and <size> tags as normalized float pairs.

View file

@ -68,6 +68,7 @@ int runSystemCommand(const std::wstring& cmd_utf16)
int launchEmulatorUnix(const std::string& cmd_utf8)
{
// TODO, replace with proper child process execution.
#ifdef __unix__
return system(cmd_utf8.c_str());
#else
@ -97,10 +98,6 @@ int launchEmulatorWindows(const std::wstring& cmd_utf16)
&si, // Pointer to the STARTUPINFOW structure.
&pi); // Pointer to the PROCESS_INFORMATION structure.
// Wait for the child process to exit.
WaitForSingleObject(pi.hThread, INFINITE);
WaitForSingleObject(pi.hProcess, INFINITE);
// If the return value is false, then something failed.
if (!processReturnValue) {
LPWSTR pBuffer = nullptr;
@ -132,6 +129,37 @@ int launchEmulatorWindows(const std::wstring& cmd_utf16)
#endif
}
unsigned int getTaskbarState()
{
#ifdef _WIN64
APPBARDATA barData;
barData.cbSize = sizeof(APPBARDATA);
return (UINT) SHAppBarMessage(ABM_GETSTATE, &barData);
#else
return 0;
#endif
}
void hideTaskbar()
{
#ifdef _WIN64
APPBARDATA barData;
barData.cbSize = sizeof(APPBARDATA);
barData.lParam = ABS_AUTOHIDE;
SHAppBarMessage(ABM_SETSTATE, &barData);
#endif
}
void revertTaskbarState(unsigned int& state)
{
#ifdef _WIN64
APPBARDATA barData;
barData.cbSize = sizeof(APPBARDATA);
barData.lParam = state;
SHAppBarMessage(ABM_SETSTATE, &barData);
#endif
}
QuitMode quitMode = QuitMode::QUIT;
int quitES(QuitMode mode)

View file

@ -29,6 +29,10 @@ int runSystemCommand(const std::wstring& cmd_utf16);
int launchEmulatorUnix(const std::string& cmd_utf8);
int launchEmulatorWindows(const std::wstring& cmd_utf16);
unsigned int getTaskbarState();
void hideTaskbar();
void revertTaskbarState(unsigned int& state);
// Clean, normal shutdown.
int quitES(QuitMode mode = QuitMode::QUIT);
// Immediately shut down the application as cleanly as possible.

View file

@ -32,7 +32,9 @@ std::vector<const char*> settings_dont_save {
"ShowExit", // --no-exit
"SplashScreen", // --no-splash
"VSync", // --vsync [1/on or 0/off]
#ifndef _WIN64
"Windowed", // --windowed
#endif
"WindowWidth", // Set via --resolution [width] [height]
"WindowHeight", // set via --resolution [width] [height]
@ -164,7 +166,9 @@ void Settings::setDefaults()
#else
mIntMap["MaxVRAM"] = 100;
#endif
#ifdef __unix__
mStringMap["FullscreenMode"] = "normal";
#endif
mStringMap["PowerSaverMode"] = "disabled";
// This setting only applies to raspberry pi but set it for all platforms so
// we don't get a warning if we encounter it on a different platform.
@ -181,6 +185,9 @@ void Settings::setDefaults()
mBoolMap["ScreenSaverOmxPlayer"] = false;
#endif
mStringMap["SaveGamelistsMode"] = "always";
#ifdef _WIN64
mBoolMap["HideTaskbar"] = false;
#endif
mStringMap["MediaDirectory"] = "";
mBoolMap["LaunchCommandOverride"] = true;
mBoolMap["CustomEventScripts"] = false;
@ -203,7 +210,9 @@ void Settings::setDefaults()
mBoolMap["ShowExit"] = true;
mBoolMap["SplashScreen"] = true;
mBoolMap["VSync"] = true;
#ifndef _WIN64
mBoolMap["Windowed"] = false;
#endif
mIntMap["WindowWidth"] = 0;
mIntMap["WindowHeight"] = 0;
mIntMap["ScreenWidth"] = 0;

View file

@ -56,12 +56,12 @@ void Window::pushGui(GuiComponent* gui)
void Window::removeGui(GuiComponent* gui)
{
for (auto i = mGuiStack.cbegin(); i != mGuiStack.cend(); i++) {
if (*i == gui) {
i = mGuiStack.erase(i);
for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); it++) {
if (*it == gui) {
it = mGuiStack.erase(it);
// We just popped the stack and the stack is not empty.
if (i == mGuiStack.cend() && mGuiStack.size()) {
if (it == mGuiStack.cend() && mGuiStack.size()) {
mGuiStack.back()->updateHelpPrompts();
mGuiStack.back()->topWindow(true);
}
@ -111,8 +111,8 @@ bool Window::init()
void Window::deinit()
{
// Hide all GUI elements on uninitialisation - this disable.
for (auto i = mGuiStack.cbegin(); i != mGuiStack.cend(); i++)
(*i)->onHide();
for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); it++)
(*it)->onHide();
InputManager::getInstance()->deinit();
ResourceManager::getInstance()->unloadAll();
@ -266,8 +266,14 @@ void Window::render()
}
unsigned int screensaverTime = (unsigned int)Settings::getInstance()->getInt("ScreenSaverTime");
if (mTimeSinceLastInput >= screensaverTime && screensaverTime != 0)
// If a game has been launched, reset the screensaver timer when it's been reached as we
// don't want to start the screensaver in the background when running a game.
if (mTimeSinceLastInput >= screensaverTime && screensaverTime != 0) {
if (mGameLaunchedState)
mTimeSinceLastInput = 0;
else
startScreenSaver();
}
// Always call the screensaver render function regardless of whether the screensaver is active
// or not because it may perform a fade on transition.
@ -420,12 +426,30 @@ bool Window::isProcessing()
[](GuiComponent* c) { return c->isProcessing(); }) > 0;
}
void Window::setLaunchedGame()
{
// Tell the GUI components that a game has been launched.
for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); it++)
(*it)->onGameLaunchedActivate();
mGameLaunchedState = true;
}
void Window::unsetLaunchedGame()
{
// Tell the GUI components that the user is back in ES again.
for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); it++)
(*it)->onGameLaunchedDeactivate();
mGameLaunchedState = false;
}
void Window::startScreenSaver()
{
if (mScreenSaver && !mRenderScreenSaver) {
// Tell the GUI components the screensaver is starting.
for (auto i = mGuiStack.cbegin(); i != mGuiStack.cend(); i++)
(*i)->onScreenSaverActivate();
for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); it++)
(*it)->onScreenSaverActivate();
mScreenSaver->startScreenSaver();
mRenderScreenSaver = true;
@ -440,8 +464,8 @@ bool Window::cancelScreenSaver()
mScreenSaver->resetCounts();
// Tell the GUI components the screensaver has stopped.
for (auto i = mGuiStack.cbegin(); i != mGuiStack.cend(); i++)
(*i)->onScreenSaverDeactivate();
for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); it++)
(*it)->onScreenSaverDeactivate();
return true;
}

View file

@ -86,6 +86,10 @@ public:
void renderScreenSaver();
bool isScreenSaverActive() { return mRenderScreenSaver; };
void setLaunchedGame();
void unsetLaunchedGame();
bool getGameLaunchedState() { return mGameLaunchedState; };
private:
void onSleep();
void onWake();
@ -98,6 +102,7 @@ private:
ScreenSaver* mScreenSaver;
InfoPopup* mInfoPopup;
bool mRenderScreenSaver;
bool mGameLaunchedState;
std::vector<GuiComponent*> mGuiStack;

View file

@ -67,8 +67,10 @@ VideoComponent::VideoComponent(
mVideoWidth(0),
mStartDelayed(false),
mIsPlaying(false),
mPause(false),
mShowing(false),
mScreensaverActive(false),
mGameLaunched(false),
mDisable(false),
mScreensaverMode(false),
mTargetIsMax(false),
@ -174,6 +176,9 @@ void VideoComponent::render(const Transform4x4f& parentTrans)
// Handle looping of the video.
handleLooping();
// Pause video in case a game has been launched.
pauseVideo();
}
void VideoComponent::renderSnapshot(const Transform4x4f& parentTrans)
@ -253,6 +258,8 @@ void VideoComponent::handleLooping()
void VideoComponent::startVideoWithDelay()
{
mPause = false;
// If not playing then either start the video or initiate the delay.
if (!mIsPlaying) {
// Set the video that we are going to be playing so we don't attempt to restart it.
@ -300,7 +307,7 @@ void VideoComponent::update(int deltaTime)
void VideoComponent::manageState()
{
// We will only show if the component is on display and the screensaver
// We will only show the video if the component is on display and the screensaver
// is not active.
bool show = mShowing && !mScreensaverActive && !mDisable;
@ -324,6 +331,12 @@ void VideoComponent::manageState()
if (show && !mVideoPath.empty())
startVideoWithDelay();
}
// If a game has just been launched and a video is actually shown, then request a
// pause of the video so it doesn't continue to play in the background while the
// game is running.
if (mGameLaunched && show && !mPause)
mPause = true;
}
void VideoComponent::onShow()
@ -350,6 +363,19 @@ void VideoComponent::onScreenSaverDeactivate()
manageState();
}
void VideoComponent::onGameLaunchedActivate()
{
mGameLaunched = true;
manageState();
}
void VideoComponent::onGameLaunchedDeactivate()
{
mGameLaunched = false;
stopVideo();
manageState();
}
void VideoComponent::topWindow(bool isTop)
{
mDisable = !isTop;

View file

@ -48,6 +48,8 @@ public:
virtual void onHide() override;
virtual void onScreenSaverActivate() override;
virtual void onScreenSaverDeactivate() override;
virtual void onGameLaunchedActivate() override;
virtual void onGameLaunchedDeactivate() override;
virtual void topWindow(bool isTop) override;
void onOriginChanged() override;
@ -83,6 +85,8 @@ private:
virtual void startVideo() = 0;
// Stop the video.
virtual void stopVideo() {};
// Pause the video when a game has been launched.
virtual void pauseVideo() {};
// Handle looping the video. Must be called periodically.
virtual void handleLooping();
@ -109,10 +113,12 @@ protected:
bool mStartDelayed;
unsigned mStartTime;
bool mIsPlaying;
bool mPause;
bool mShowing;
bool mDisable;
bool mScreensaverActive;
bool mScreensaverMode;
bool mGameLaunched;
bool mTargetIsMax;
Configuration mConfig;

View file

@ -231,13 +231,25 @@ void VideoVlcComponent::handleLooping()
(Settings::getInstance()->getBool("ScreenSaverVideoMute") && mScreensaverMode))
libvlc_audio_set_mute(mMediaPlayer, 1);
//libvlc_media_player_set_position(mMediaPlayer, 0.0f);
libvlc_media_player_set_media(mMediaPlayer, mMedia);
libvlc_media_player_play(mMediaPlayer);
}
}
}
void VideoVlcComponent::pauseVideo()
{
// If a game has been launched and the flag to pause the video has been
// set, then rewind and pause.
if (!mPause || !mMediaPlayer)
return;
if (libvlc_media_player_get_state(mMediaPlayer) == libvlc_Playing) {
libvlc_media_player_set_position(mMediaPlayer, 0.0f);
libvlc_media_player_pause(mMediaPlayer);
}
}
void VideoVlcComponent::startVideo()
{
if (!mIsPlaying) {
@ -336,6 +348,7 @@ void VideoVlcComponent::stopVideo()
{
mIsPlaying = false;
mStartDelayed = false;
mPause = false;
// Release the media player so it stops calling back to us.
if (mMediaPlayer) {
libvlc_media_player_stop(mMediaPlayer);

View file

@ -57,10 +57,13 @@ private:
// Calculates the correct mSize from our resizing information (set by setResize/setMaxSize).
// Used internally whenever the resizing parameters or texture change.
void resize();
// Start the video immediately.
virtual void startVideo() override;
// Stop the video.
virtual void stopVideo() override;
// Pause the video when a game has been launched.
virtual void pauseVideo() override;
// Handle looping the video. Must be called periodically.
virtual void handleLooping() override;

View file

@ -104,12 +104,20 @@ namespace Renderer
unsigned int windowFlags;
#ifdef _WIN64
// For Windows, always set the mode to windowed, as full screen mode seems to
// behave quite erratic. There may be a proper fix for this, but for now windowed
// mode seems to behave well and it's almost completely seamless, especially with
// a hidden taskbar.
windowFlags = getWindowFlags();
#else
if (Settings::getInstance()->getBool("Windowed"))
windowFlags = getWindowFlags();
else if (Settings::getInstance()->getString("FullscreenMode") == "borderless")
windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALWAYS_ON_TOP | getWindowFlags();
else
windowFlags = SDL_WINDOW_FULLSCREEN | getWindowFlags();
#endif
if ((sdlWindow = SDL_CreateWindow("EmulationStation", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, windowWidth, windowHeight, windowFlags)) == nullptr) {