]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/main.cpp
Merge branch 'radiant-cmdline-null-mapname' into 'master'
[xonotic/netradiant.git] / radiant / main.cpp
1 /*
2    Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3    For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5    This file is part of GtkRadiant.
6
7    GtkRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    GtkRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 /*! \mainpage GtkRadiant Documentation Index
23
24    \section intro_sec Introduction
25
26    This documentation is generated from comments in the source code.
27
28    \section links_sec Useful Links
29
30    \link include/itextstream.h include/itextstream.h \endlink - Global output and error message streams, similar to std::cout and std::cerr. \n
31
32    FileInputStream - similar to std::ifstream (binary mode) \n
33    FileOutputStream - similar to std::ofstream (binary mode) \n
34    TextFileInputStream - similar to std::ifstream (text mode) \n
35    TextFileOutputStream - similar to std::ofstream (text mode) \n
36    StringOutputStream - similar to std::stringstream \n
37
38    \link string/string.h string/string.h \endlink - C-style string comparison and memory management. \n
39    \link os/path.h os/path.h \endlink - Path manipulation for radiant's standard path format \n
40    \link os/file.h os/file.h \endlink - OS file-system access. \n
41
42    ::CopiedString - automatic string memory management \n
43    Array - automatic array memory management \n
44    HashTable - generic hashtable, similar to std::hash_map \n
45
46    \link math/vector.h math/vector.h \endlink - Vectors \n
47    \link math/matrix.h math/matrix.h \endlink - Matrices \n
48    \link math/quaternion.h math/quaternion.h \endlink - Quaternions \n
49    \link math/plane.h math/plane.h \endlink - Planes \n
50    \link math/aabb.h math/aabb.h \endlink - AABBs \n
51
52    Callback MemberCaller0 FunctionCaller - callbacks similar to using boost::function with boost::bind \n
53    SmartPointer SmartReference - smart-pointer and smart-reference similar to Loki's SmartPtr \n
54
55    \link generic/bitfield.h generic/bitfield.h \endlink - Type-safe bitfield \n
56    \link generic/enumeration.h generic/enumeration.h \endlink - Type-safe enumeration \n
57
58    DefaultAllocator - Memory allocation using new/delete, compliant with std::allocator interface \n
59
60    \link debugging/debugging.h debugging/debugging.h \endlink - Debugging macros \n
61
62  */
63
64 #include "main.h"
65 #include "globaldefs.h"
66
67 #include "version.h"
68
69 #include "debugging/debugging.h"
70
71 #include "iundo.h"
72
73 #include "uilib/uilib.h"
74
75 #include "cmdlib.h"
76 #include "os/file.h"
77 #include "os/path.h"
78 #include "stream/stringstream.h"
79 #include "stream/textfilestream.h"
80
81 #include "gtkutil/messagebox.h"
82 #include "gtkutil/image.h"
83 #include "console.h"
84 #include "texwindow.h"
85 #include "map.h"
86 #include "mainframe.h"
87 #include "commands.h"
88 #include "preferences.h"
89 #include "environment.h"
90 #include "referencecache.h"
91 #include "stacktrace.h"
92
93 #if GDEF_OS_WINDOWS
94 #include <windows.h>
95 #endif
96
97 void show_splash();
98 void hide_splash();
99
100 void error_redirect( const gchar *domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data ){
101         gboolean in_recursion;
102         gboolean is_fatal;
103         char buf[256];
104
105         in_recursion = ( log_level & G_LOG_FLAG_RECURSION ) != 0;
106         is_fatal = ( log_level & G_LOG_FLAG_FATAL ) != 0;
107         log_level = (GLogLevelFlags) ( log_level & G_LOG_LEVEL_MASK );
108
109         if ( !message ) {
110                 message = "(0) message";
111         }
112
113         if ( domain ) {
114                 strcpy( buf, domain );
115         }
116         else{
117                 strcpy( buf, "**" );
118         }
119         strcat( buf, "-" );
120
121         switch ( log_level )
122         {
123         case G_LOG_LEVEL_ERROR:
124                 if ( in_recursion ) {
125                         strcat( buf, "ERROR (recursed) **: " );
126                 }
127                 else{
128                         strcat( buf, "ERROR **: " );
129                 }
130                 break;
131         case G_LOG_LEVEL_CRITICAL:
132                 if ( in_recursion ) {
133                         strcat( buf, "CRITICAL (recursed) **: " );
134                 }
135                 else{
136                         strcat( buf, "CRITICAL **: " );
137                 }
138                 break;
139         case G_LOG_LEVEL_WARNING:
140                 if ( in_recursion ) {
141                         strcat( buf, "WARNING (recursed) **: " );
142                 }
143                 else{
144                         strcat( buf, "WARNING **: " );
145                 }
146                 break;
147         case G_LOG_LEVEL_MESSAGE:
148                 if ( in_recursion ) {
149                         strcat( buf, "Message (recursed): " );
150                 }
151                 else{
152                         strcat( buf, "Message: " );
153                 }
154                 break;
155         case G_LOG_LEVEL_INFO:
156                 if ( in_recursion ) {
157                         strcat( buf, "INFO (recursed): " );
158                 }
159                 else{
160                         strcat( buf, "INFO: " );
161                 }
162                 break;
163         case G_LOG_LEVEL_DEBUG:
164                 if ( in_recursion ) {
165                         strcat( buf, "DEBUG (recursed): " );
166                 }
167                 else{
168                         strcat( buf, "DEBUG: " );
169                 }
170                 break;
171         default:
172                 /* we are used for a log level that is not defined by GLib itself,
173                  * try to make the best out of it.
174                  */
175                 if ( in_recursion ) {
176                         strcat( buf, "LOG (recursed:" );
177                 }
178                 else{
179                         strcat( buf, "LOG (" );
180                 }
181                 if ( log_level ) {
182                         gchar string[] = "0x00): ";
183                         gchar *p = string + 2;
184                         guint i;
185
186                         i = g_bit_nth_msf( log_level, -1 );
187                         *p = i >> 4;
188                         p++;
189                         *p = '0' + ( i & 0xf );
190                         if ( *p > '9' ) {
191                                 *p += 'A' - '9' - 1;
192                         }
193
194                         strcat( buf, string );
195                 }
196                 else{
197                         strcat( buf, "): " );
198                 }
199         }
200
201         strcat( buf, message );
202         if ( is_fatal ) {
203                 strcat( buf, "\naborting...\n" );
204         }
205         else{
206                 strcat( buf, "\n" );
207         }
208
209         // spam it...
210         globalErrorStream() << buf << "\n";
211
212         if (is_fatal) {
213             ERROR_MESSAGE( "GTK+ error: " << buf );
214     }
215 }
216
217 #if GDEF_COMPILER_MSVC && GDEF_DEBUG
218 #include "crtdbg.h"
219 #endif
220
221 void crt_init(){
222 #if GDEF_COMPILER_MSVC && GDEF_DEBUG
223         _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
224 #endif
225 }
226
227 class Lock
228 {
229 bool m_locked;
230 public:
231 Lock() : m_locked( false ){
232 }
233 void lock(){
234         m_locked = true;
235 }
236 void unlock(){
237         m_locked = false;
238 }
239 bool locked() const {
240         return m_locked;
241 }
242 };
243
244 class ScopedLock
245 {
246 Lock& m_lock;
247 public:
248 ScopedLock( Lock& lock ) : m_lock( lock ){
249         m_lock.lock();
250 }
251 ~ScopedLock(){
252         m_lock.unlock();
253 }
254 };
255
256 class LineLimitedTextOutputStream : public TextOutputStream
257 {
258 TextOutputStream& outputStream;
259 std::size_t count;
260 public:
261 LineLimitedTextOutputStream( TextOutputStream& outputStream, std::size_t count )
262         : outputStream( outputStream ), count( count ){
263 }
264 std::size_t write( const char* buffer, std::size_t length ){
265         if ( count != 0 ) {
266                 const char* p = buffer;
267                 const char* end = buffer + length;
268                 for (;; )
269                 {
270                         p = std::find( p, end, '\n' );
271                         if ( p == end ) {
272                                 break;
273                         }
274                         ++p;
275                         if ( --count == 0 ) {
276                                 length = p - buffer;
277                                 break;
278                         }
279                 }
280                 outputStream.write( buffer, length );
281         }
282         return length;
283 }
284 };
285
286 class PopupDebugMessageHandler : public DebugMessageHandler
287 {
288 StringOutputStream m_buffer;
289 Lock m_lock;
290 public:
291 TextOutputStream& getOutputStream(){
292         if ( !m_lock.locked() ) {
293                 return m_buffer;
294         }
295         return globalErrorStream();
296 }
297 bool handleMessage(){
298         getOutputStream() << "----------------\n";
299         LineLimitedTextOutputStream outputStream( getOutputStream(), 24 );
300         write_stack_trace( outputStream );
301         getOutputStream() << "----------------\n";
302         globalErrorStream() << m_buffer.c_str();
303         if ( !m_lock.locked() ) {
304                 ScopedLock lock( m_lock );
305         if (GDEF_DEBUG) {
306             m_buffer << "Break into the debugger?\n";
307             bool handled = ui::alert(ui::root, m_buffer.c_str(), RADIANT_NAME " - Runtime Error", ui::alert_type::YESNO, ui::alert_icon::Error) == ui::alert_response::NO;
308             m_buffer.clear();
309             return handled;
310         } else {
311             m_buffer << "Please report this error to the developers\n";
312             ui::alert(ui::root, m_buffer.c_str(), RADIANT_NAME " - Runtime Error", ui::alert_type::OK, ui::alert_icon::Error);
313             m_buffer.clear();
314         }
315         }
316         return true;
317 }
318 };
319
320 typedef Static<PopupDebugMessageHandler> GlobalPopupDebugMessageHandler;
321
322 void streams_init(){
323         GlobalErrorStream::instance().setOutputStream( getSysPrintErrorStream() );
324         GlobalOutputStream::instance().setOutputStream( getSysPrintOutputStream() );
325 }
326
327 void paths_init(){
328         g_strSettingsPath = environment_get_home_path();
329
330         Q_mkdir( g_strSettingsPath.c_str() );
331
332         g_strAppFilePath = environment_get_app_filepath();
333         g_strAppPath = environment_get_app_path();
334         g_strLibPath = environment_get_lib_path();
335         g_strDataPath = environment_get_data_path();
336
337         // radiant is installed in the parent dir of "tools/"
338         // NOTE: this is not very easy for debugging
339         // maybe add options to lookup in several places?
340         // (for now I had to create symlinks)
341         {
342                 StringOutputStream path( 256 );
343                 path << g_strDataPath.c_str() << "bitmaps/";
344                 BitmapsPath_set( path.c_str() );
345         }
346
347         // we will set this right after the game selection is done
348         g_strGameToolsPath = g_strDataPath;
349 }
350
351 bool check_version_file( const char* filename, const char* version ){
352         TextFileInputStream file( filename );
353         if ( !file.failed() ) {
354                 char buf[10];
355                 buf[file.read( buf, 9 )] = '\0';
356
357                 // chomp it (the hard way)
358                 int chomp = 0;
359                 while ( buf[chomp] >= '0' && buf[chomp] <= '9' )
360                         chomp++;
361                 buf[chomp] = '\0';
362
363                 return string_equal( buf, version );
364         }
365         return false;
366 }
367
368 void create_global_pid(){
369         /*!
370            the global prefs loading / game selection dialog might fail for any reason we don't know about
371            we need to catch when it happens, to cleanup the stateful prefs which might be killing it
372            and to turn on console logging for lookup of the problem
373            this is the first part of the two step .pid system
374            http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
375          */
376         StringOutputStream g_pidFile( 256 ); ///< the global .pid file (only for global part of the startup)
377
378         g_pidFile << SettingsPath_get() << "radiant.pid";
379
380         FILE *pid;
381         pid = fopen( g_pidFile.c_str(), "r" );
382         if ( pid != 0 ) {
383                 fclose( pid );
384
385                 if ( remove( g_pidFile.c_str() ) == -1 ) {
386                         StringOutputStream msg( 256 );
387                         msg << "WARNING: Could not delete " << g_pidFile.c_str();
388                         ui::alert( ui::root, msg.c_str(), RADIANT_NAME, ui::alert_type::OK, ui::alert_icon::Error );
389                 }
390
391                 // in debug, never prompt to clean registry, turn console logging auto after a failed start
392                 if (!GDEF_DEBUG) {
393                         StringOutputStream msg(256);
394                         msg << RADIANT_NAME " failed to start properly the last time it was run.\n"
395                                         "The failure may be related to current global preferences.\n"
396                                         "Do you want to reset global preferences to defaults?";
397
398                         if (ui::alert(ui::root, msg.c_str(), RADIANT_NAME " - Startup Failure", ui::alert_type::YESNO, ui::alert_icon::Question) == ui::alert_response::YES) {
399                                 g_GamesDialog.Reset();
400                         }
401
402                         msg.clear();
403                         msg << "Logging console output to " << SettingsPath_get()
404                                 << "radiant.log\nRefer to the log if " RADIANT_NAME " fails to start again.";
405
406                         ui::alert(ui::root, msg.c_str(), RADIANT_NAME " - Console Log", ui::alert_type::OK);
407                 }
408
409                 // set without saving, the class is not in a coherent state yet
410                 // just do the value change and call to start logging, CGamesDialog will pickup when relevant
411                 g_GamesDialog.m_bForceLogConsole = true;
412                 Sys_LogFile( true );
413         }
414
415         // create a primary .pid for global init run
416         pid = fopen( g_pidFile.c_str(), "w" );
417         if ( pid ) {
418                 fclose( pid );
419         }
420 }
421
422 void remove_global_pid(){
423         StringOutputStream g_pidFile( 256 );
424         g_pidFile << SettingsPath_get() << "radiant.pid";
425
426         // close the primary
427         if ( remove( g_pidFile.c_str() ) == -1 ) {
428                 StringOutputStream msg( 256 );
429                 msg << "WARNING: Could not delete " << g_pidFile.c_str();
430                 ui::alert( ui::root, msg.c_str(), RADIANT_NAME, ui::alert_type::OK, ui::alert_icon::Error );
431         }
432 }
433
434 /*!
435    now the secondary game dependant .pid file
436    http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
437  */
438 void create_local_pid(){
439         StringOutputStream g_pidGameFile( 256 ); ///< the game-specific .pid file
440         g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
441
442         FILE *pid = fopen( g_pidGameFile.c_str(), "r" );
443         if ( pid != 0 ) {
444                 fclose( pid );
445                 if ( remove( g_pidGameFile.c_str() ) == -1 ) {
446                         StringOutputStream msg;
447                         msg << "WARNING: Could not delete " << g_pidGameFile.c_str();
448                         ui::alert( ui::root, msg.c_str(), RADIANT_NAME, ui::alert_type::OK, ui::alert_icon::Error );
449                 }
450
451                 // in debug, never prompt to clean registry, turn console logging auto after a failed start
452                 if (!GDEF_DEBUG) {
453                         StringOutputStream msg;
454                         msg << RADIANT_NAME " failed to start properly the last time it was run.\n"
455                                         "The failure may be caused by current preferences.\n"
456                                         "Do you want to reset all preferences to defaults?";
457
458                         if (ui::alert(ui::root, msg.c_str(), RADIANT_NAME " - Startup Failure", ui::alert_type::YESNO, ui::alert_icon::Question) == ui::alert_response::YES) {
459                                 Preferences_Reset();
460                         }
461
462                         msg.clear();
463                         msg << "Logging console output to " << SettingsPath_get()
464                                 << "radiant.log\nRefer to the log if " RADIANT_NAME " fails to start again.";
465
466                         ui::alert(ui::root, msg.c_str(), RADIANT_NAME " - Console Log", ui::alert_type::OK);
467                 }
468
469                 // force console logging on! (will go in prefs too)
470                 g_GamesDialog.m_bForceLogConsole = true;
471                 Sys_LogFile( true );
472         }
473         else
474         {
475                 // create one, will remove right after entering message loop
476                 pid = fopen( g_pidGameFile.c_str(), "w" );
477                 if ( pid ) {
478                         fclose( pid );
479                 }
480         }
481 }
482
483
484 /*!
485    now the secondary game dependant .pid file
486    http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
487  */
488 void remove_local_pid(){
489         StringOutputStream g_pidGameFile( 256 );
490         g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
491         remove( g_pidGameFile.c_str() );
492 }
493
494 void user_shortcuts_init(){
495         StringOutputStream path( 256 );
496         path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
497         LoadCommandMap( path.c_str() );
498         SaveCommandMap( path.c_str() );
499 }
500
501 void user_shortcuts_save(){
502         StringOutputStream path( 256 );
503         path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
504         SaveCommandMap( path.c_str() );
505 }
506
507 int main( int argc, char* argv[] ){
508         crt_init();
509
510         streams_init();
511
512 #if GDEF_OS_WINDOWS
513         HMODULE lib;
514         lib = LoadLibrary( "dwmapi.dll" );
515         if ( lib != 0 ) {
516                 void ( WINAPI *qDwmEnableComposition )( bool bEnable ) = ( void (WINAPI *) ( bool bEnable ) )GetProcAddress( lib, "DwmEnableComposition" );
517                 if ( qDwmEnableComposition ) {
518                         qDwmEnableComposition( FALSE );
519                 }
520                 FreeLibrary( lib );
521         }
522 #endif
523
524         const char* mapname = NULL;
525
526 #if GDEF_OS_WINDOWS
527         StringOutputStream mapname_buffer( 256 );
528 #endif
529
530     char const *error = NULL;
531
532         if ( !ui::init( &argc, &argv, "<filename.map>", &error) ) {
533                 g_print( "%s\n", error );
534                 return -1;
535         }
536
537         // Gtk already removed parsed `--options`
538         if ( argc == 2 ) {
539                 if ( strlen( argv[ 1 ] ) > 1 ) {
540                         mapname = argv[ 1 ];
541
542                         if ( g_str_has_suffix( mapname, ".map" ) ) {
543                                 if ( !g_path_is_absolute( mapname ) ) {
544                                         mapname = g_build_filename( g_get_current_dir(), mapname, NULL );
545                                 }
546
547 #if GDEF_OS_WINDOWS
548                                 mapname_buffer << PathCleaned( mapname );
549                                 mapname = mapname_buffer.c_str();
550 #endif
551                         }
552                         else {
553                                 g_print( "bad file name, will not load: %s\n", mapname );
554                                 mapname = NULL;
555                         }
556                 }
557         }
558         else if ( argc > 2 ) {
559                 g_print ( "%s\n", "too many arguments" );
560                 return -1;
561         }
562
563         // redirect Gtk warnings to the console
564         g_log_set_handler( "Gdk", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
565                                                                                                 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
566         g_log_set_handler( "Gtk", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
567                                                                                                 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
568         g_log_set_handler( "GtkGLExt", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
569                                                                                                          G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
570         g_log_set_handler( "GLib", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
571                                                                                                  G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
572         g_log_set_handler( 0, (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
573                                                                                         G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
574
575         GlobalDebugMessageHandler::instance().setHandler( GlobalPopupDebugMessageHandler::instance() );
576
577         environment_init(argc, (char const **) argv);
578
579         paths_init();
580
581         show_splash();
582
583         create_global_pid();
584
585         GlobalPreferences_Init();
586
587         g_GamesDialog.Init();
588
589         g_strGameToolsPath = g_pGameDescription->mGameToolsPath;
590
591         remove_global_pid();
592
593         g_Preferences.Init(); // must occur before create_local_pid() to allow preferences to be reset
594
595         create_local_pid();
596
597         // in a very particular post-.pid startup
598         // we may have the console turned on and want to keep it that way
599         // so we use a latching system
600         if ( g_GamesDialog.m_bForceLogConsole ) {
601                 Sys_LogFile( true );
602                 g_Console_enableLogging = true;
603                 g_GamesDialog.m_bForceLogConsole = false;
604         }
605
606
607         Radiant_Initialise();
608
609         user_shortcuts_init();
610
611         g_pParentWnd = 0;
612         g_pParentWnd = new MainFrame();
613
614         hide_splash();
615
616         if ( mapname != NULL ) {
617                 Map_LoadFile( mapname );
618         }
619         else if ( g_bLoadLastMap && !g_strLastMap.empty() ) {
620                 Map_LoadFile( g_strLastMap.c_str() );
621         }
622         else
623         {
624                 Map_New();
625         }
626
627         // load up shaders now that we have the map loaded
628         // eviltypeguy
629         TextureBrowser_ShowStartupShaders( GlobalTextureBrowser() );
630
631
632         remove_local_pid();
633
634         ui::main();
635
636         // avoid saving prefs when the app is minimized
637         if ( g_pParentWnd->IsSleeping() ) {
638                 globalOutputStream() << "Shutdown while sleeping, not saving prefs\n";
639                 g_preferences_globals.disable_ini = true;
640         }
641
642         Map_Free();
643
644         if ( !Map_Unnamed( g_map ) ) {
645                 g_strLastMap = Map_Name( g_map );
646         }
647
648         delete g_pParentWnd;
649
650         user_shortcuts_save();
651
652         Radiant_Shutdown();
653
654         // close the log file if any
655         Sys_LogFile( false );
656
657         return EXIT_SUCCESS;
658 }