File: RadiantApp.cpp

package info (click to toggle)
darkradiant 3.9.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 41,080 kB
  • sloc: cpp: 264,743; ansic: 10,659; python: 1,852; xml: 1,650; sh: 92; makefile: 21
file content (321 lines) | stat: -rw-r--r-- 9,932 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
#include "RadiantApp.h"

#include "i18n.h"
#include "iradiant.h"
#include "version.h"

#include "log/PIDFile.h"
#include "module/CoreModule.h"
#include "messages/GameConfigNeededMessage.h"
#include "ui/prefdialog/GameSetupDialog.h"
#include "module/StaticModule.h"
#include "settings/LocalisationProvider.h"
#include "log/PopupErrorHandler.h"

#include <wx/wxprec.h>
#include <wx/event.h>
#include <wx/cmdline.h>
#include <wx/xrc/xmlres.h>
#include <sigc++/functors/mem_fun.h>

#ifndef __linux__
#include "ui/splash/Splash.h"
#endif

#ifdef POSIX
#include <libintl.h>
#endif
#include <exception>
#include <iomanip>

#if defined (_DEBUG) && defined (WIN32) && defined (_MSC_VER)
#include "crtdbg.h"
#endif

#if defined(__linux__)
// Function to intercept unwanted Gtk log messages on Linux
#include <glib.h>
GLogWriterOutput
log_black_hole(GLogLevelFlags, const GLogField*, gsize, gpointer)
{
    return G_LOG_WRITER_HANDLED;
}
#endif

// The startup event which will be queued in App::OnInit()
wxDEFINE_EVENT(EV_RadiantStartup, wxCommandEvent);

/**
 * Implements wxWidget's ArtProvider interface to allow custom stock item IDs for
 * bitmaps used in toolbars and other controls. The schema for these custom ArtIDs
 * is "darkradiant:filename.png" where filename.png is a file in DR's bitmap folder.
 * This schema is also valid when specified in XRC files.
 */
class RadiantApp::ArtProvider final: public wxArtProvider
{
    std::string _searchPath;

public:
    // Use an absolute file path to the list of search paths this provider is covering
    ArtProvider(const std::string& searchPath) :
        _searchPath(searchPath)
    {
        wxArtProvider::Push(this);
    }

    wxBitmap CreateBitmap(const wxArtID& id, const wxArtClient& client, const wxSize& size) override
    {
        auto filename = id.ToStdString();
        const auto& prefix = ArtIdPrefix();

        // We listen only to "darkradiant" art IDs
        if (string::starts_with(filename, prefix))
        {
            auto filePath = _searchPath + filename.substr(prefix.length());

            if (os::fileOrDirExists(filePath)) {
                return wxBitmap(wxImage(filePath));
            }
        }

        return wxNullBitmap;
    }

    static const std::string& ArtIdPrefix()
    {
        static std::string _artIdPrefix = "darkradiant:";
        return _artIdPrefix;
    }
};

RadiantApp::RadiantApp()
{
#if defined(__linux__)
    // The native Wayland backend for GTK does not implement the mouse pointer
    // warping functions used in the FreezePointer class.  Forcing the backend
    // to X11 will let us run using XWayland which does provide emulation of
    // this functionality.
    setenv("GDK_BACKEND", "x11", 0);
#endif
}

RadiantApp::~RadiantApp()
{}

bool RadiantApp::OnInit()
{
	if (!wxApp::OnInit()) return false;

	// Initialise the debug flags
#if defined (_DEBUG) && defined (WIN32) && defined (_MSC_VER)
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

#if defined(__linux__)
    // Intercept and discard Gtk log messages emitted from wxGTK, which we
    // cannot control or fix, and which obliterate any attempt to use console
    // output for debugging (due to fresh Gtk-CRITICAL messages being emitted
    // several times per second)
    g_log_set_writer_func(log_black_hole, nullptr, nullptr);
#endif

	// Initialise the context (application path / settings path, is
	// OS-specific)
	_context.initialise(wxApp::argc, wxApp::argv);

	try
	{
		_coreModule.reset(new module::CoreModule(_context));

		auto* radiant = _coreModule->get();

		module::RegistryReference::Instance().setRegistry(radiant->getModuleRegistry());
		module::initialiseStreams(radiant->getLogWriter());
	}
	catch (module::CoreModule::FailureException& ex)
	{
		// Streams are not yet initialised, so log to std::err at this point
		std::cerr << ex.what() << std::endl;
		return false;
	}

	// Register the localisation helper before initialising the modules
	settings::LocalisationProvider::Initialise(_context);
	auto& languageManager = _coreModule->get()->getLanguageManager();
	languageManager.registerProvider(settings::LocalisationProvider::Instance());

#if defined(POSIX) && !defined(__APPLE__)
	// greebo: not sure if this is needed
	// Other POSIX gettext initialisation
	setlocale(LC_ALL, "");
	textdomain(GETTEXT_PACKAGE);
	bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
#endif

	// reset some locale settings back to standard c
	// this is e.g. needed for parsing float values from textfiles
	setlocale(LC_NUMERIC, "C");
	setlocale(LC_TIME, "C");

    // Set up art provider, logging, XRC file handlers
    initWxWidgets();

	// Register to the start up signal
	Bind(EV_RadiantStartup, &RadiantApp::onStartupEvent, this);

	AddPendingEvent(wxCommandEvent(EV_RadiantStartup));

	return true;
}

void RadiantApp::initWxWidgets()
{
    // Stop wx's unhelpful debug messages about missing keyboard accel
    // strings from cluttering up the console
    wxLog::SetLogLevel(wxLOG_Warning);

    // On Linux, display wxWidgets log messages on stderr rather than popping up largely
    // useless (modal and often repeated) dialog boxes. TODO: find a better solution for
    // Windows as well (probably redirecting them to the in-application Console would be
    // best since stderr is not easily accessible).
#if defined(__linux__)
    wxLog::SetActiveTarget(new wxLogStderr() /* lifetime managed by wxWidgets */);
#endif

    wxFileSystem::AddHandler(new wxLocalFSHandler);
    wxXmlResource::Get()->InitAllHandlers();

    // Our XRC resource files are stored in the ui/ folder.
    wxXmlResource::Get()->Load(_context.getRuntimeDataPath() + "ui/*.xrc");

    // We only need PNG and JPEG for our local images. BMP is enabled by default.
#if wxUSE_LIBPNG
    wxImage::AddHandler(new wxPNGHandler);
#endif
#if wxUSE_LIBJPEG
    wxImage::AddHandler(new wxJPEGHandler);
#endif

    // Register the local art provider
    _bitmapArtProvider = std::make_unique<ArtProvider>(_context.getBitmapsPath());
}

void RadiantApp::cleanupWxWidgets()
{
    _bitmapArtProvider.reset();
    wxImage::CleanUpHandlers();
    wxXmlResource::Get()->ClearHandlers();
    wxFileSystem::CleanUpHandlers();
}

int RadiantApp::OnExit()
{
	// Issue a shutdown() call to all the modules
	module::GlobalModuleRegistry().shutdownModules();

	auto& languageManager = _coreModule->get()->getLanguageManager();
	languageManager.clearProvider();

	// Clean up static resources
	settings::LocalisationProvider::Cleanup();

	_coreModule.reset();

    cleanupWxWidgets();

	return wxApp::OnExit();
}

void RadiantApp::OnInitCmdLine(wxCmdLineParser& parser)
{
	// Remove '/' as parameter starter to allow for "/path" style args
	parser.SetSwitchChars("-");

	parser.AddLongSwitch("disable-sound", _("Disable sound for this session."));
	parser.AddLongOption("verbose", _("Verbose logging."));

	parser.AddParam("Map file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
	parser.AddParam("fs_game=<game>", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
	parser.AddParam("fs_game_base=<gamebase>", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
}

bool RadiantApp::OnExceptionInMainLoop()
{
    // This method is called by the main loop controlling code, from within the catch(...)
    // block. Let's re-throw the current exception and catch it to print the error message
    // at the very least.
#if defined(__linux__)
    try {
        throw;
    }
    catch (const std::exception& e) {
        std::cerr << "Fatal exception in main loop:\n" << e.what() << std::endl;
    }

    abort();
#else
    try {
        throw;
    }
    catch (const std::exception& ex) {
        rError() << "Unhandled Exception: " << ex.what() << std::endl;
        radiant::PopupErrorHandler::HandleError(_("Real Hard DarkRadiant Failure"),
            std::string(ex.what()) + "\n\n" + _("Break into the debugger?"));
    }
    return wxApp::OnExceptionInMainLoop();
#endif
}

void RadiantApp::onStartupEvent(wxCommandEvent& ev)
{
	// Create the radiant.pid file in the settings folder
	// (emits a warning if the file already exists (due to a previous startup failure))
	applog::PIDFile pidFile(PID_FILENAME);

#ifndef __linux__
	// We skip the splash screen in Linux, but the other platforms will show a progress bar
	// Connect the progress callback to the Splash instance.
	ui::Splash::OnAppStartup();
#endif

	// In first-startup scenarios the game configuration is not present
	// in which case the GameManager will dispatch a message asking
	// for showing a dialog or similar. Connect the listener.
	_coreModule->get()->getMessageBus().addListener(radiant::IMessage::Type::GameConfigNeeded,
        radiant::TypeListener<game::ConfigurationNeeded>(ui::GameSetupDialog::HandleGameConfigMessage));

	// Pick up all the statically defined modules and register them
	module::internal::StaticModuleList::RegisterModules();

	// Register to the modules unloading event, we need to get notified
	// before the DLLs/SOs are relased to give wxWidgets a chance to clean up
	_modulesUnloadingHandler = _coreModule->get()->getModuleRegistry().signal_modulesUnloading()
		.connect(sigc::mem_fun(this, &RadiantApp::onModulesUnloading));

	try
	{
		// Startup the application
		_coreModule->get()->startup();
	}
	catch (const radiant::IRadiant::StartupFailure& ex)
	{
		// An unhandled exception during module initialisation => display a popup and exit
		rError() << "Unhandled Exception: " << ex.what() << std::endl;
		wxutil::Messagebox::ShowFatalError(ex.what(), nullptr);
	}

	// Scope ends here, PIDFile is deleted by its destructor
}

void RadiantApp::onModulesUnloading()
{
	// We need to delete all pending objects before unloading modules
	// wxWidgets needs a chance to delete them before memory access is denied
	if (wxTheApp != nullptr)
	{
		wxTheApp->ProcessIdle();
        wxTheApp->DeletePendingEvents();
	}

	_modulesUnloadingHandler.disconnect();
}