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