Files
fs2open.github.com/code/osapi/dialogs.cpp
T
Goober5000 1b8a79c64e two fixes for issue 6793
1. more robust handling of missing fireball graphics
2. fix handling of error dialogs on Linux so that the engine doesn't silently continue on an error!

Fixes #6793.
2026-05-15 22:09:29 -04:00

550 lines
13 KiB
C++

#include "osapi/dialogs.h"
#include "osapi/osapi.h"
#include "parse/parselo.h"
#include "cmdline/cmdline.h"
#include "graphics/2d.h"
#include "scripting/ade.h"
#include "utils/string_utils.h"
#include <SDL_messagebox.h>
#include <SDL_clipboard.h>
#include <string>
#include <algorithm>
extern "C" {
#include <lauxlib.h>
#include <lualib.h>
}
namespace
{
const char* Separator = "------------------------------------------------------------------\n";
const int Messagebox_lines = 30;
template<typename Stream>
void LuaDebugPrint(Stream& stream, lua_Debug &ar)
{
if (ar.name == nullptr)
{
// Invalid lua_Debug struct
return;
}
stream << "Name:\t\t" << ar.name << "\n";
stream << "Name of:\t" << ar.namewhat << "\n";
stream << "Function type:\t" << ar.what << "\n";
stream << "Defined on:\t" << ar.linedefined << "\n";
stream << "Upvalues:\t" << ar.nups << "\n";
stream << "\n";
stream << "Source:\t\t" << ar.source << "\n";
stream << "Short source:\t" << ar.short_src << "\n";
stream << "Current line:\t" << ar.currentline << "\n";
stream << "- Function line:\t" << (ar.linedefined ? (1 + ar.currentline - ar.linedefined) : 0) << "\n";
}
SCP_string truncateLines(SCP_stringstream& s, int maxLines)
{
SCP_stringstream outStream;
s.seekp(0, std::ios::beg);
for (SCP_string line; std::getline(s, line);)
{
outStream << line << "\n";
--maxLines;
if (maxLines <= 0)
{
outStream << "[...]";
break;
}
}
return outStream.str();
}
void set_clipboard_text(const char* text)
{
// Make sure video is enabled
if (!SDL_InitSubSystem(SDL_INIT_VIDEO))
{
SDL_SetClipboardText(text);
}
}
}
int Global_warning_count = 0;
int Global_error_count = 0;
namespace os
{
namespace dialogs
{
//This function is a helper function to determine if the dialogs should have a parent window
//Normally, this should always be FSO's window, but there is an issue with this in fullscreen
//where occasionally (when knossos is on the same screen as FSO in a multi-monitor setup on
//windows) this results in the dialog losing focus, unable to regain it, thus rendering the
//buttons not clickable. So as a hotfix, the dialog has no blocking parent in that case and
//is instead rendered as it's own window
SDL_Window* getDialogParent() {
return !(gr_is_viewport_window()) ? NULL : os::getSDLMainWindow();
}
void AssertMessage(const char * text, const char * filename, int linenum, const char * format, ...)
{
// We only want to display the file name
filename = util::get_file_part(filename);
SCP_stringstream msgStream;
msgStream << "Assert: \"" << text << "\"\n";
msgStream << "File: " << filename << "\n";
msgStream << "Line: " << linenum << "\n";
if (format != nullptr)
{
SCP_string buffer;
va_list args;
va_start(args, format);
vsprintf(buffer, format, args);
va_end(args);
msgStream << buffer << "\n";
mprintf(("ASSERTION: \"%s\" at %s:%d\n %s\n", text, filename, linenum, buffer.c_str()));
}
else
{
// No additional message
mprintf(("ASSERTION: \"%s\" at %s:%d\n", text, filename, linenum));
}
if (running_unittests) {
throw AssertException(msgStream.str());
}
msgStream << "\n";
msgStream << dump_stacktrace();
SCP_string messageText = msgStream.str();
set_clipboard_text(messageText.c_str());
messageText = truncateLines(msgStream, Messagebox_lines);
messageText += "\n[ This info is in the clipboard so you can paste it somewhere now ]\n";
messageText += "\n\nUse Debug to break into Debugger, Exit will close the application.\n";
Error(messageText.c_str());
}
void LuaError(lua_State * L, const char * format, ...)
{
SCP_stringstream msgStream;
//WMC - if format is set to NULL, assume this is acting as an
//error handler for Lua.
if (format == NULL)
{
msgStream << "LUA ERROR: " << lua_tostring_nullsafe(L, -1);
lua_pop(L, -1);
}
else
{
SCP_string formatText;
va_list args;
va_start(args, format);
vsprintf(formatText, format, args);
va_end(args);
msgStream << formatText;
}
msgStream << "\n";
msgStream << "\n";
msgStream << Separator;
// Get the stack via the debug.traceback() function
lua_getglobal(L, LUA_DBLIBNAME);
if (!lua_isnil(L, -1))
{
msgStream << "\n";
lua_getfield(L, -1, "traceback");
lua_remove(L, -2);
if (lua_pcall(L, 0, 1, 0) != 0)
msgStream << "Error while retrieving stack: " << lua_tostring_nullsafe(L, -1);
else
msgStream << lua_tostring_nullsafe(L, -1);
lua_pop(L, 1);
}
msgStream << "\n";
msgStream << Separator;
char stackText[1024];
stackText[0] = '\0';
scripting::ade_stackdump(L, stackText);
msgStream << stackText;
msgStream << "\n";
msgStream << Separator;
nprintf(("scripting","Lua Error: %s\n", msgStream.str().c_str()));
if (running_unittests) {
throw LuaErrorException(msgStream.str());
}
if (Cmdline_lua_devmode) {
return;
}
if (Cmdline_noninteractive) {
throw LuaErrorException(msgStream.str());
return;
}
set_clipboard_text(msgStream.str().c_str());
// truncate text
auto truncatedText = truncateLines(msgStream, Messagebox_lines);
SCP_stringstream boxTextStream;
boxTextStream << truncatedText << "\n";
boxTextStream << "\n[ This info is in the clipboard so you can paste it somewhere now ]\n";
auto boxText = boxTextStream.str();
const SDL_MessageBoxButtonData buttons[] = {
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 2, "Exit" },
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 1, "Continue" },
{ /* .flags, .buttonid, .text */ 0, 0, "Debug" },
};
SDL_MessageBoxData boxData;
memset(&boxData, 0, sizeof(boxData));
boxData.buttons = buttons;
boxData.numbuttons = 3;
boxData.colorScheme = nullptr;
boxData.flags = SDL_MESSAGEBOX_ERROR;
boxData.message = boxText.c_str();
boxData.title = "Error!";
boxData.window = getDialogParent();
gr_activate(0);
int buttonId = -1; // if dialog is silently suppressed
if (SDL_ShowMessageBox(&boxData, &buttonId) < 0)
{
// Call failed
buttonId = 1;
}
switch (buttonId)
{
case 2: // Exit
abort();
case 0: // Debug
Int3();
break;
case 1: // Continue
default:
break;
}
gr_activate(1);
}
void Error(const char * filename, int line, const char * format, ...)
{
SCP_string formatText;
filename = util::get_file_part(filename);
va_list args;
va_start(args, format);
vsprintf(formatText, format, args);
va_end(args);
SCP_stringstream messageStream;
messageStream << "Error: " << formatText << "\n";
messageStream << "File: " << filename << "\n";
messageStream << "Line: " << line << "\n";
Error(messageStream.str().c_str());
}
void Error(const char* text)
{
mprintf(("\n%s\n", text));
// also output to stderr so the message is visible if the dialog is suppressed
// (e.g., the SDL message box is dismissed silently by the window manager when the
// game is fullscreen on Linux)
fprintf(stderr, "\n%s\n", text);
fflush(stderr);
if (running_unittests) {
throw ErrorException(text);
}
if (Cmdline_noninteractive) {
abort();
return;
}
SCP_stringstream messageStream;
messageStream << text << "\n";
messageStream << dump_stacktrace();
SCP_string fullText = messageStream.str();
set_clipboard_text(fullText.c_str());
fullText = truncateLines(messageStream, Messagebox_lines);
fullText += "\n[ This info is in the clipboard so you can paste it somewhere now ]\n";
fullText += "\n\nUse Debug to break into Debugger, Exit will close the application.\n";
const SDL_MessageBoxButtonData buttons[] = {
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "Exit" },
{ /* .flags, .buttonid, .text */ 0, 0, "Debug" },
};
SDL_MessageBoxData boxData;
memset(&boxData, 0, sizeof(boxData));
boxData.buttons = buttons;
boxData.numbuttons = 2;
boxData.colorScheme = nullptr;
boxData.flags = SDL_MESSAGEBOX_ERROR;
boxData.message = text;
boxData.title = "Error!";
boxData.window = getDialogParent();
gr_activate(0);
int buttonId = -1; // if dialog is silently suppressed
if (SDL_ShowMessageBox(&boxData, &buttonId) < 0)
{
// Call failed
buttonId = 1;
}
switch (buttonId)
{
case 0: // Debug
Int3();
break;
case 1: // Exit
default:
abort();
}
gr_activate(1);
}
// Actual implementation of the warning function. Used by the various warning functions
void WarningImpl(const char* filename, int line, const SCP_string& text)
{
filename = util::get_file_part(filename);
// output to the debug log before anything else (so that we have a complete record)
mprintf(("WARNING: \"%s\" at %s:%d\n", text.c_str(), filename, line));
// also output to stderr so the message is visible if the dialog is suppressed
// (e.g., the SDL message box is dismissed silently by the window manager when the
// game is fullscreen on Linux)
fprintf(stderr, "WARNING: \"%s\" at %s:%d\n", text.c_str(), filename, line);
fflush(stderr);
if (running_unittests) {
throw WarningException(text);
}
// now go for the additional popup window, if we want it ...
if (Cmdline_noninteractive) {
return;
}
SCP_stringstream boxMsgStream;
boxMsgStream << "Warning: " << text << "\n";
boxMsgStream << "File: " << filename << "\n";
boxMsgStream << "Line: " << line << "\n";
set_clipboard_text(boxMsgStream.str().c_str());
boxMsgStream << "\n";
SCP_string boxMessage = truncateLines(boxMsgStream, Messagebox_lines);
boxMessage += "\n[ This info is in the clipboard so you can paste it somewhere now ]\n";
boxMessage += "\n\nUse Debug to break into Debugger\n";
const SDL_MessageBoxButtonData buttons[] = {
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 2, "Exit" },
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 1, "Continue" },
{ /* .flags, .buttonid, .text */ 0, 0, "Debug" },
};
SDL_MessageBoxData boxData;
memset(&boxData, 0, sizeof(boxData));
boxData.buttons = buttons;
boxData.numbuttons = 3;
boxData.colorScheme = nullptr;
boxData.flags = SDL_MESSAGEBOX_WARNING;
boxData.message = boxMessage.c_str();
boxData.title = "Warning!";
boxData.window = getDialogParent();
gr_activate(0);
int buttonId = -1; // if dialog is silently suppressed
if (SDL_ShowMessageBox(&boxData, &buttonId) < 0)
{
// Call failed
buttonId = 1;
}
switch (buttonId)
{
case 2: // Exit
abort();
case 0: // Debug
Int3();
break;
case 1: // Continue
default:
break;
}
gr_activate(1);
}
void ReleaseWarning(const char* filename, int line, const char* format, ...) {
Global_warning_count++;
SCP_string msg;
va_list args;
va_start(args, format);
vsprintf(msg, format, args);
va_end(args);
WarningImpl(filename, line, msg);
}
void Warning(const char* filename, int line, const char* format, ...)
{
Global_warning_count++;
#ifndef NDEBUG
SCP_string msg;
va_list args;
va_start(args, format);
vsprintf(msg, format, args);
va_end(args);
WarningImpl(filename, line, msg);
#endif
}
void WarningEx(const char* filename, int line, const char* format, ...)
{
#ifndef NDEBUG
if (Cmdline_extra_warn) {
SCP_string msg;
va_list args;
va_start(args, format);
vsprintf(msg, format, args);
va_end(args);
Warning(filename, line, "%s", msg.c_str());
}
#endif
}
void Information(const char* filename, int line, const char* format, ...) {
SCP_string msg;
va_list args;
va_start(args, format);
vsprintf(msg, format, args);
va_end(args);
// Below is essentially a stripped down copy pasta of WarningImpl
filename = util::get_file_part(filename);
// output to the debug log before anything else (so that we have a complete record)
mprintf(("INFO: \"%s\" at %s:%d\n", msg.c_str(), filename, line));
// now go for the additional popup window, if we want it ...
if (Cmdline_noninteractive || running_unittests) {
return;
}
SCP_stringstream boxMsgStream;
boxMsgStream << "Information: " << msg << "\n";
boxMsgStream << "File: " << filename << "\n";
boxMsgStream << "Line: " << line << "\n";
set_clipboard_text(boxMsgStream.str().c_str());
boxMsgStream << "\n";
SCP_string boxMessage = truncateLines(boxMsgStream, Messagebox_lines);
boxMessage += "\n[ This info is in the clipboard so you can paste it somewhere now ]\n";
gr_activate(0);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Information", boxMessage.c_str(), getDialogParent());
gr_activate(1);
}
void Message(MessageType type, const char* message, const char* title)
{
if (running_unittests) {
throw WarningException(message);
}
int flags = 1;
switch (type)
{
case MESSAGEBOX_ERROR:
flags = SDL_MESSAGEBOX_ERROR;
if (title == NULL)
title = "Error";
break;
case MESSAGEBOX_INFORMATION:
flags = SDL_MESSAGEBOX_INFORMATION;
if (title == NULL)
title = "Information";
break;
case MESSAGEBOX_WARNING:
flags = SDL_MESSAGEBOX_WARNING;
if (title == NULL)
title = "Warning";
break;
default:
Int3();
title = ""; // Remove warning about unitialized variable
break;
}
SDL_ShowSimpleMessageBox(flags, title, message, getDialogParent());
}
}
}