diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index ee2e719c26..51747b3f13 100644 --- a/source/gui/scripting/ScriptFunctions.cpp +++ b/source/gui/scripting/ScriptFunctions.cpp @@ -356,6 +356,11 @@ int Crash(void* UNUSED(cbdata)) return *(int*)0; } +void DebugWarn(void* UNUSED(cbdata)) +{ + debug_warn(L"Warning at user's request."); +} + // Force a JS garbage collection cycle to take place immediately. // Writes an indication of how long this took to the console. void ForceGC(void* cbdata) @@ -416,5 +421,6 @@ void GuiScriptingInit(ScriptInterface& scriptInterface) scriptInterface.RegisterFunction("SetTurnLength"); scriptInterface.RegisterFunction("SetCameraTarget"); scriptInterface.RegisterFunction("Crash"); + scriptInterface.RegisterFunction("DebugWarn"); scriptInterface.RegisterFunction("ForceGC"); } diff --git a/source/lib/sysdep/os/unix/unix.cpp b/source/lib/sysdep/os/unix/unix.cpp index 91388f08d9..e3b9f03215 100644 --- a/source/lib/sysdep/os/unix/unix.cpp +++ b/source/lib/sysdep/os/unix/unix.cpp @@ -32,9 +32,13 @@ #include "lib/sysdep/cursor.h" #include "udbg.h" +#include + #define GNU_SOURCE #include +#include + #if OS_MACOSX #define URL_OPEN_COMMAND "open" #else @@ -49,6 +53,100 @@ void sys_display_msg(const wchar_t* caption, const wchar_t* msg) fprintf(stderr, "%ls: %ls\n", caption, msg); // must not use fwprintf, since stderr is byte-oriented } +static ErrorReaction try_gui_display_error(const wchar_t* text, bool manual_break, bool allow_suppress, bool no_continue) +{ + pid_t cpid = fork(); + if(cpid == -1) + return ER_NOT_IMPLEMENTED; + + if(cpid == 0) + { + // This is the child process + + // Set ASCII charset, to avoid font warnings from xmessage + setenv("LC_ALL", "C", 1); + + LibError err; // ignore UTF-8 errors + std::string message = utf8_from_wstring(text, &err); + + // Replace CRLF->LF + boost::algorithm::replace_all(message, "\r\n", "\n"); + + const char* cmd = "/usr/bin/xmessage"; + + char buttons[256] = ""; + const char* defaultButton = "Exit"; + + if(!no_continue) + { + strcat_s(buttons, sizeof(buttons), "Continue:100,"); + defaultButton = "Continue"; + } + + if(allow_suppress) + strcat_s(buttons, sizeof(buttons), "Suppress:101,"); + + strcat_s(buttons, sizeof(buttons), "Break:102,Debugger:103,Exit:104"); + + // Since execv wants non-const strings, we strdup them here + // and don't care about the memory leak + char* const argv[] = { + strdup(cmd), + strdup("-buttons"), buttons, + strdup("-default"), strdup(defaultButton), + strdup(message.c_str()), + NULL + }; + execv(cmd, argv); + + // If exec returns, it failed + //fprintf(stderr, "Error running %s: %d\n", cmd, errno); + exit(-1); + } + + // This is the parent process + + int status = 0; + waitpid(cpid, &status, 0); + + // If it didn't exist successfully, fall back to the non-GUI prompt + if(!WIFEXITED(status)) + return ER_NOT_IMPLEMENTED; + + switch(WEXITSTATUS(status)) + { + case 103: // Debugger + udbg_launch_debugger(); + //-fallthrough + + case 102: // Break + if(manual_break) + return ER_BREAK; + debug_break(); + return ER_CONTINUE; + + case 100: // Continue + if(!no_continue) + return ER_CONTINUE; + // continue isn't allowed, so this was invalid input. + return ER_NOT_IMPLEMENTED; + + case 101: // Suppress + if(allow_suppress) + return ER_SUPPRESS; + // suppress isn't allowed, so this was invalid input. + return ER_NOT_IMPLEMENTED; + + case 104: // Exit + abort(); + return ER_EXIT; // placebo; never reached + + } + + // Unexpected return value - fall back to the non-GUI prompt + return ER_NOT_IMPLEMENTED; +} + ErrorReaction sys_display_error(const wchar_t* text, size_t flags) { printf("%ls\n\n", text); @@ -57,7 +155,14 @@ ErrorReaction sys_display_error(const wchar_t* text, size_t flags) const bool allow_suppress = (flags & DE_ALLOW_SUPPRESS) != 0; const bool no_continue = (flags & DE_NO_CONTINUE ) != 0; - // until valid input given: + // Try the GUI prompt if possible + ErrorReaction ret = try_gui_display_error(text, manual_break, allow_suppress, no_continue); + if (ret != ER_NOT_IMPLEMENTED) + return ret; + + // Otherwise fall back to the terminal-based input + + // Loop until valid input given: for(;;) { if(!no_continue) @@ -85,7 +190,7 @@ ErrorReaction sys_display_error(const wchar_t* text, size_t flags) case 'c': case 'C': if(!no_continue) - return ER_CONTINUE; + return ER_CONTINUE; // continue isn't allowed, so this was invalid input. loop again. break; case 's': case 'S': diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index 695e2ff2d7..4497339e7a 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -403,6 +403,27 @@ static size_t ChooseCacheSize() } #endif +ErrorReaction psDisplayError(const wchar_t* UNUSED(text), size_t UNUSED(flags)) +{ + // If we're fullscreen, then sometimes (at least on some particular drivers on Linux) + // displaying the error dialog hangs the desktop since the dialog box is behind the + // fullscreen window. So we just force the game to windowed mode before displaying the dialog. + // (But only if we're in the main thread, and not if we're being reentrant.) + if (ThreadUtil::IsMainThread()) + { + static bool reentering = false; + if (!reentering) + { + reentering = true; + g_VideoMode.SetFullscreen(false); + reentering = false; + } + } + + // We don't actually implement the error display here, so return appropriately + return ER_NOT_IMPLEMENTED; +} + static void InitVfs(const CmdLineArgs& args) { TIMER(L"InitVfs"); @@ -418,6 +439,7 @@ static void InitVfs(const CmdLineArgs& args) AppHooks hooks = {0}; hooks.bundle_logs = psBundleLogs; hooks.get_log_dir = psLogDir; + hooks.display_error = psDisplayError; app_hooks_update(&hooks); const size_t cacheSize = ChooseCacheSize(); diff --git a/source/ps/VideoMode.cpp b/source/ps/VideoMode.cpp index 8d740be63e..ab457f6bde 100644 --- a/source/ps/VideoMode.cpp +++ b/source/ps/VideoMode.cpp @@ -254,7 +254,10 @@ bool CVideoMode::ResizeWindow(int w, int h) bool CVideoMode::SetFullscreen(bool fullscreen) { - debug_assert(m_IsInitialised); + // This might get called before initialisation by psDisplayError; + // if so then silently fail + if (!m_IsInitialised) + return false; // Check whether this is actually a change if (fullscreen == m_IsFullscreen) @@ -325,12 +328,17 @@ void CVideoMode::UpdateRenderer(int w, int h) SViewPort vp = { 0, 0, w, h }; - g_Renderer.SetViewport(vp); - g_Renderer.Resize(w, h); + if (CRenderer::IsInitialised()) + { + g_Renderer.SetViewport(vp); + g_Renderer.Resize(w, h); + } - g_GUI->UpdateResolution(); + if (g_GUI) + g_GUI->UpdateResolution(); - g_Console->UpdateScreenSize(w, h); + if (g_Console) + g_Console->UpdateScreenSize(w, h); if (g_Game) g_Game->GetView()->SetViewport(vp);