fixed linux-only crash when error message handler causes an error
[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 MemberCaller 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
66 #include "version.h"
67
68 #include "debugging/debugging.h"
69
70 #include "iundo.h"
71
72 #include <gtk/gtkmain.h>
73
74 #include "cmdlib.h"
75 #include "os/file.h"
76 #include "os/path.h"
77 #include "stream/stringstream.h"
78 #include "stream/textfilestream.h"
79
80 #include "gtkutil/messagebox.h"
81 #include "gtkutil/image.h"
82 #include "console.h"
83 #include "texwindow.h"
84 #include "map.h"
85 #include "mainframe.h"
86 #include "commands.h"
87 #include "preferences.h"
88 #include "environment.h"
89 #include "referencecache.h"
90 #include "stacktrace.h"
91
92 void show_splash();
93 void hide_splash();
94
95 void error_redirect (const gchar *domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data)
96 {
97   gboolean in_recursion;
98   gboolean is_fatal;
99   char buf[256];
100
101   in_recursion = (log_level & G_LOG_FLAG_RECURSION) != 0;
102   is_fatal = (log_level & G_LOG_FLAG_FATAL) != 0;
103   log_level = (GLogLevelFlags) (log_level & G_LOG_LEVEL_MASK);
104
105   if (!message)
106     message = "(0) message";
107
108   if (domain)
109     strcpy (buf, domain);
110   else
111     strcpy (buf, "**");
112   strcat (buf, "-");
113
114   switch (log_level)
115   {
116   case G_LOG_LEVEL_ERROR:
117     if (in_recursion)
118       strcat (buf, "ERROR (recursed) **: ");
119     else
120       strcat (buf, "ERROR **: ");
121     break;
122   case G_LOG_LEVEL_CRITICAL:
123     if (in_recursion)
124       strcat (buf, "CRITICAL (recursed) **: ");
125     else
126       strcat (buf, "CRITICAL **: ");
127     break;
128   case G_LOG_LEVEL_WARNING:
129     if (in_recursion)
130       strcat (buf, "WARNING (recursed) **: ");
131     else
132       strcat (buf, "WARNING **: ");
133     break;
134   case G_LOG_LEVEL_MESSAGE:
135     if (in_recursion)
136       strcat (buf, "Message (recursed): ");
137     else
138       strcat (buf, "Message: ");
139     break;
140   case G_LOG_LEVEL_INFO:
141     if (in_recursion)
142       strcat (buf, "INFO (recursed): ");
143     else
144       strcat (buf, "INFO: ");
145     break;
146   case G_LOG_LEVEL_DEBUG:
147     if (in_recursion)
148       strcat (buf, "DEBUG (recursed): ");
149     else
150       strcat (buf, "DEBUG: ");
151     break;
152   default:
153     /* we are used for a log level that is not defined by GLib itself,
154      * try to make the best out of it.
155      */
156     if (in_recursion)
157       strcat (buf, "LOG (recursed:");
158     else
159       strcat (buf, "LOG (");
160     if (log_level)
161     {
162       gchar string[] = "0x00): ";
163       gchar *p = string + 2;
164       guint i;
165
166       i = g_bit_nth_msf (log_level, -1);
167       *p = i >> 4;
168       p++;
169       *p = '0' + (i & 0xf);
170       if (*p > '9')
171         *p += 'A' - '9' - 1;
172
173       strcat (buf, string);
174     } else
175       strcat (buf, "): ");
176   }
177
178   strcat (buf, message);
179   if (is_fatal)
180     strcat (buf, "\naborting...\n");
181   else
182     strcat (buf, "\n");
183
184   printf ("%s\n", buf);
185
186   ERROR_MESSAGE("GTK+ error: " << buf);
187 }
188
189 #if defined (_DEBUG) && defined (WIN32) && defined (_MSC_VER)
190 #include "crtdbg.h"
191 #endif
192
193 void crt_init()
194 {
195 #if defined (_DEBUG) && defined (WIN32) && defined (_MSC_VER)
196   _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
197 #endif
198 }
199
200 class Lock
201 {
202   bool m_locked;
203 public:
204   Lock() : m_locked(false)
205   {
206   }
207   void lock()
208   {
209     m_locked = true;
210   }
211   void unlock()
212   {
213     m_locked = false;
214   }
215   bool locked() const
216   {
217     return m_locked;
218   }
219 };
220
221 class ScopedLock
222 {
223   Lock& m_lock;
224 public:
225   ScopedLock(Lock& lock) : m_lock(lock)
226   {
227     m_lock.lock();
228   }
229   ~ScopedLock()
230   {
231     m_lock.unlock();
232   }
233 };
234
235 class LineLimitedTextOutputStream : public TextOutputStream
236 {
237   TextOutputStream& outputStream;
238   std::size_t count;
239 public:
240   LineLimitedTextOutputStream(TextOutputStream& outputStream, std::size_t count)
241     : outputStream(outputStream), count(count)
242   {
243   }
244   std::size_t write(const char* buffer, std::size_t length)
245   {
246     if(count != 0)
247     {
248       const char* p = buffer;
249       const char* end = buffer+length;
250       for(;;)
251       {
252         p = std::find(p, end, '\n');
253         if(p == end)
254         {
255           break;
256         }
257         ++p;
258         if(--count == 0)
259         {
260           length = p - buffer;
261           break;
262         }
263       }
264       outputStream.write(buffer, length);
265     }
266     return length;
267   }
268 };
269
270 class PopupDebugMessageHandler : public DebugMessageHandler
271 {
272   StringOutputStream m_buffer;
273   Lock m_lock;
274 public:
275   TextOutputStream& getOutputStream()
276   {
277     if(!m_lock.locked())
278     {
279       return m_buffer;
280     }
281     return globalErrorStream();
282   }
283   bool handleMessage()
284   {
285     getOutputStream() << "----------------\n";
286     LineLimitedTextOutputStream outputStream(getOutputStream(), 24);
287     write_stack_trace(outputStream);
288     getOutputStream() << "----------------\n";
289     globalErrorStream() << m_buffer.c_str();
290     if(!m_lock.locked())
291     {
292       ScopedLock lock(m_lock);
293 #if defined _DEBUG
294       m_buffer << "Break into the debugger?\n";
295       bool handled = gtk_MessageBox(0, m_buffer.c_str(), "Radiant - Runtime Error", eMB_YESNO, eMB_ICONERROR) == eIDNO;
296       m_buffer.clear();
297       return handled;
298 #else
299       m_buffer << "Please report this error to the developers\n";
300       gtk_MessageBox(0, m_buffer.c_str(), "Radiant - Runtime Error", eMB_OK, eMB_ICONERROR);
301       m_buffer.clear();
302 #endif
303     }
304     return true;
305   }
306 };
307
308 typedef Static<PopupDebugMessageHandler> GlobalPopupDebugMessageHandler;
309
310 void streams_init()
311 {
312   GlobalErrorStream::instance().setOutputStream(getSysPrintErrorStream());
313   GlobalOutputStream::instance().setOutputStream(getSysPrintOutputStream());
314 }
315
316 void paths_init()
317 {
318   const char* home = environment_get_home_path();
319   Q_mkdir(home);
320
321   {
322     StringOutputStream path(256);
323     path << home << RADIANT_VERSION << '/';
324     g_strSettingsPath = path.c_str();
325   }
326
327   Q_mkdir(g_strSettingsPath.c_str());
328
329   g_strAppPath = environment_get_app_path();
330
331   // radiant is installed in the parent dir of "tools/"
332   // NOTE: this is not very easy for debugging
333   // maybe add options to lookup in several places?
334   // (for now I had to create symlinks)
335   {
336     StringOutputStream path(256);
337     path << g_strAppPath.c_str() << "bitmaps/";
338     BitmapsPath_set(path.c_str());
339   }
340
341   // we will set this right after the game selection is done
342   g_strGameToolsPath = g_strAppPath;
343 }
344
345 bool check_version_file(const char* filename, const char* version)
346 {
347   TextFileInputStream file(filename);
348   if(!file.failed())
349   {
350     char buf[10];
351     buf[file.read(buf, 9)] = '\0';
352
353     // chomp it (the hard way)
354     int chomp = 0;
355     while(buf[chomp] >= '0' && buf[chomp] <= '9')
356       chomp++;
357     buf[chomp] = '\0';
358
359     return string_equal(buf, version);
360   }
361   return false;
362 }
363
364 bool check_version()
365 {
366   // a safe check to avoid people running broken installations
367   // (otherwise, they run it, crash it, and blame us for not forcing them hard enough to pay attention while installing)
368   // make something idiot proof and someone will make better idiots, this may be overkill
369   // let's leave it disabled in debug mode in any case
370   // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=431
371 #ifndef _DEBUG
372 #define CHECK_VERSION
373 #endif
374 #ifdef CHECK_VERSION  
375   // locate and open RADIANT_MAJOR and RADIANT_MINOR
376   bool bVerIsGood = true;
377   {
378     StringOutputStream ver_file_name(256);
379     ver_file_name << AppPath_get() << "RADIANT_MAJOR";
380     bVerIsGood = check_version_file(ver_file_name.c_str(), RADIANT_MAJOR_VERSION);
381   }
382   {
383     StringOutputStream ver_file_name(256);
384     ver_file_name << AppPath_get() << "RADIANT_MINOR";
385     bVerIsGood = check_version_file(ver_file_name.c_str(), RADIANT_MINOR_VERSION);
386   }
387
388   if (!bVerIsGood)
389   {
390     StringOutputStream msg(256);
391     msg << "This editor binary (" RADIANT_VERSION ") doesn't match what the latest setup has configured in this directory\n"
392     "Make sure you run the right/latest editor binary you installed\n"
393     << AppPath_get();
394     gtk_MessageBox(0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONDEFAULT);
395   }
396   return bVerIsGood;
397 #else
398   return true;
399 #endif
400 }
401
402 void create_global_pid()
403 {
404   /*!
405   the global prefs loading / game selection dialog might fail for any reason we don't know about
406   we need to catch when it happens, to cleanup the stateful prefs which might be killing it
407   and to turn on console logging for lookup of the problem
408   this is the first part of the two step .pid system
409   http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
410   */
411   StringOutputStream g_pidFile(256); ///< the global .pid file (only for global part of the startup)
412
413   g_pidFile << SettingsPath_get() << "radiant.pid";
414
415   FILE *pid;
416   pid = fopen (g_pidFile.c_str(), "r");
417   if (pid != 0)
418   {
419     fclose (pid);
420
421     if (remove (g_pidFile.c_str()) == -1)
422     {
423       StringOutputStream msg(256);
424       msg << "WARNING: Could not delete " << g_pidFile.c_str();
425       gtk_MessageBox (0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONERROR );
426     }
427
428     // in debug, never prompt to clean registry, turn console logging auto after a failed start
429 #if !defined(_DEBUG)
430     StringOutputStream msg(256);
431     msg << "Radiant failed to start properly the last time it was run.\n"
432            "The failure may be related to current global preferences.\n"
433            "Do you want to reset global preferences to defaults?";
434
435     if (gtk_MessageBox (0, msg.c_str(), "Radiant - Startup Failure", eMB_YESNO, eMB_ICONQUESTION) == eIDYES)
436     {
437       g_GamesDialog.Reset();
438     }
439
440     msg.clear();
441     msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
442
443     gtk_MessageBox (0, msg.c_str(), "Radiant - Console Log", eMB_OK);
444 #endif
445
446     // set without saving, the class is not in a coherent state yet
447     // just do the value change and call to start logging, CGamesDialog will pickup when relevant
448     g_GamesDialog.m_bForceLogConsole = true;
449     Sys_LogFile(true);
450   }
451
452   // create a primary .pid for global init run
453   pid = fopen (g_pidFile.c_str(), "w");
454   if (pid)
455     fclose (pid);
456 }
457
458 void remove_global_pid()
459 {
460   StringOutputStream g_pidFile(256);
461   g_pidFile << SettingsPath_get() << "radiant.pid";
462
463   // close the primary
464   if (remove (g_pidFile.c_str()) == -1)
465   {
466     StringOutputStream msg(256);
467     msg << "WARNING: Could not delete " << g_pidFile.c_str();
468     gtk_MessageBox (0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONERROR );
469   }
470 }
471
472 /*!
473 now the secondary game dependant .pid file
474 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
475 */
476 void create_local_pid()
477 {
478   StringOutputStream g_pidGameFile(256); ///< the game-specific .pid file
479   g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
480
481   FILE *pid = fopen (g_pidGameFile.c_str(), "r");
482   if (pid != 0)
483   {
484     fclose (pid);
485     if (remove (g_pidGameFile.c_str()) == -1)
486     {
487       StringOutputStream msg;
488       msg << "WARNING: Could not delete " << g_pidGameFile.c_str();
489       gtk_MessageBox (0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONERROR );
490     }
491
492     // in debug, never prompt to clean registry, turn console logging auto after a failed start
493 #if !defined(_DEBUG)
494     StringOutputStream msg;
495     msg << "Radiant failed to start properly the last time it was run.\n"
496            "The failure may be caused by current preferences.\n"
497            "Do you want to reset all preferences to defaults?";
498
499     if (gtk_MessageBox (0, msg.c_str(), "Radiant - Startup Failure", eMB_YESNO, eMB_ICONQUESTION) == eIDYES)
500     {
501       Preferences_Reset();
502     }
503
504     msg.clear();
505     msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
506
507     gtk_MessageBox (0, msg.c_str(), "Radiant - Console Log", eMB_OK);
508 #endif
509
510     // force console logging on! (will go in prefs too)
511     g_GamesDialog.m_bForceLogConsole = true;
512     Sys_LogFile(true);
513   }
514   else
515   {
516     // create one, will remove right after entering message loop
517     pid = fopen (g_pidGameFile.c_str(), "w");
518     if (pid)
519       fclose (pid);
520   }
521 }
522
523
524 /*!
525 now the secondary game dependant .pid file
526 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
527 */
528 void remove_local_pid()
529 {
530   StringOutputStream g_pidGameFile(256);
531   g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
532   remove(g_pidGameFile.c_str());
533 }
534
535 void user_shortcuts_init()
536 {
537   StringOutputStream path(256);
538   path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
539   LoadCommandMap(path.c_str());
540   SaveCommandMap(path.c_str());
541 }
542
543 int main (int argc, char* argv[])
544 {
545   crt_init();
546
547   streams_init();
548
549   gtk_disable_setlocale();
550   gtk_init(&argc, &argv);
551
552   // redirect Gtk warnings to the console
553   g_log_set_handler ("Gdk", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
554                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, 0);
555   g_log_set_handler ("Gtk", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
556                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, 0);
557   g_log_set_handler ("GtkGLExt", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
558                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, 0);
559   g_log_set_handler ("GLib", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
560                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, 0);
561   g_log_set_handler (0, (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING|
562                                              G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, 0);
563
564   GlobalDebugMessageHandler::instance().setHandler(GlobalPopupDebugMessageHandler::instance());
565
566   environment_init(argc, argv);
567
568   paths_init();
569
570   if(!check_version())
571   {
572     return EXIT_FAILURE;
573   }
574
575   show_splash();
576
577   create_global_pid();
578
579   GlobalPreferences_Init();
580
581   g_GamesDialog.Init();
582
583   g_strGameToolsPath = g_pGameDescription->mGameToolsPath;
584   
585   remove_global_pid();
586
587   g_Preferences.Init(); // must occur before create_local_pid() to allow preferences to be reset
588
589   create_local_pid();
590
591   // in a very particular post-.pid startup
592   // we may have the console turned on and want to keep it that way
593   // so we use a latching system
594   if (g_GamesDialog.m_bForceLogConsole)
595   {
596     Sys_LogFile(true);
597     g_Console_enableLogging = true;
598     g_GamesDialog.m_bForceLogConsole = false;
599   }
600
601
602   Radiant_Initialise();
603
604   global_accel_init();
605
606   user_shortcuts_init();
607
608   g_pParentWnd = 0;
609   g_pParentWnd = new MainFrame();
610
611   hide_splash();
612
613   if (g_bLoadLastMap && !g_strLastMap.empty())
614   {
615     Map_LoadFile(g_strLastMap.c_str());
616   }
617   else
618   {
619     Map_New();
620   }
621
622   // load up shaders now that we have the map loaded
623   // eviltypeguy
624   TextureBrowser_ShowStartupShaders(GlobalTextureBrowser());
625
626
627   remove_local_pid();
628
629   gtk_main();
630
631   // avoid saving prefs when the app is minimized
632   if (g_pParentWnd->IsSleeping())
633   {
634     globalOutputStream() << "Shutdown while sleeping, not saving prefs\n";
635     g_preferences_globals.disable_ini = true;
636   }
637
638   Map_Free();
639
640   if (!Map_Unnamed(g_map))
641   {
642     g_strLastMap = Map_Name(g_map);
643   }
644
645   delete g_pParentWnd;
646
647   global_accel_destroy();
648
649   Radiant_Shutdown();
650
651   // close the log file if any
652   Sys_LogFile(false);
653
654   return EXIT_SUCCESS;
655 }
656