Use g_setenv and g_getenv for portability
[ardour.git] / gtk2_ardour / main.cc
1 /*
2     Copyright (C) 2001-2012 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <cstdlib>
21 #include <signal.h>
22 #include <cerrno>
23 #include <fstream>
24 #include <vector>
25
26 #include <sigc++/bind.h>
27 #include <gtkmm/settings.h>
28
29 #include "pbd/error.h"
30 #include "pbd/epa.h"
31 #include "pbd/file_utils.h"
32 #include "pbd/textreceiver.h"
33 #include "pbd/failed_constructor.h"
34 #include "pbd/pthread_utils.h"
35 #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
36 #include "pbd/boost_debug.h"
37 #endif
38
39 #include <jack/jack.h>
40
41 #include "ardour/revision.h"
42 #include "ardour/version.h"
43 #include "ardour/ardour.h"
44 #include "ardour/audioengine.h"
45 #include "ardour/session_utils.h"
46 #include "ardour/filesystem_paths.h"
47
48 #include <gtkmm/main.h>
49 #include <gtkmm2ext/application.h>
50 #include <gtkmm2ext/popup.h>
51 #include <gtkmm2ext/utils.h>
52
53 #include <fontconfig/fontconfig.h>
54
55 #include "version.h"
56 #include "utils.h"
57 #include "ardour_ui.h"
58 #include "opts.h"
59 #include "enums.h"
60
61 #include "i18n.h"
62
63 #ifdef __APPLE__
64 #include <Carbon/Carbon.h>
65 #endif
66
67 using namespace std;
68 using namespace Gtk;
69 using namespace ARDOUR_COMMAND_LINE;
70 using namespace ARDOUR;
71 using namespace PBD;
72
73 TextReceiver text_receiver ("ardour");
74
75 extern int curvetest (string);
76
77 static ARDOUR_UI  *ui = 0;
78 static const char* localedir = LOCALEDIR;
79
80 void
81 gui_jack_error ()
82 {
83         MessageDialog win (string_compose (_("%1 could not connect to JACK."), PROGRAM_NAME),
84                            false,
85                            Gtk::MESSAGE_INFO,
86                            Gtk::BUTTONS_NONE);
87         win.set_secondary_text(string_compose (_("There are several possible reasons:\n\
88 \n\
89 1) JACK is not running.\n\
90 2) JACK is running as another user, perhaps root.\n\
91 3) There is already another client called \"%1\".\n\
92 \n\
93 Please consider the possibilities, and perhaps (re)start JACK."), PROGRAM_NAME));
94
95         win.add_button (Stock::QUIT, RESPONSE_CLOSE);
96         win.set_default_response (RESPONSE_CLOSE);
97
98         win.show_all ();
99         win.set_position (Gtk::WIN_POS_CENTER);
100
101         if (!no_splash) {
102                 ui->hide_splash ();
103         }
104
105         /* we just don't care about the result, but we want to block */
106
107         win.run ();
108 }
109
110 static void export_search_path (const string& base_dir, const char* varname, const char* dir)
111 {
112         string path;
113         const char * cstr = g_getenv (varname);
114
115         if (cstr) {
116                 path = cstr;
117                 path += ':';
118         } else {
119                 path = "";
120         }
121         path += base_dir;
122         path += dir;
123
124         g_setenv (varname, path.c_str(), 1);
125 }
126
127 #ifdef __APPLE__
128
129 #include <mach-o/dyld.h>
130 #include <sys/param.h>
131
132 extern void set_language_preference (); // cocoacarbon.mm
133
134 void
135 fixup_bundle_environment (int, char* [])
136 {
137         if (!g_getenv ("ARDOUR_BUNDLED")) {
138                 return;
139         }
140
141         EnvironmentalProtectionAgency::set_global_epa (new EnvironmentalProtectionAgency (true, "PREBUNDLE_ENV"));
142
143         set_language_preference ();
144
145         char execpath[MAXPATHLEN+1];
146         uint32_t pathsz = sizeof (execpath);
147
148         _NSGetExecutablePath (execpath, &pathsz);
149
150         std::string path;
151         std::string exec_dir = Glib::path_get_dirname (execpath);
152         std::string bundle_dir;
153         std::string userconfigdir = user_config_directory();
154
155         bundle_dir = Glib::path_get_dirname (exec_dir);
156
157 #ifdef ENABLE_NLS
158         if (!ARDOUR::translations_are_enabled ()) {
159                 localedir = "/this/cannot/exist";
160         } else {
161                 /* force localedir into the bundle */
162                 
163                 vector<string> lpath;
164                 lpath.push_back (bundle_dir);
165                 lpath.push_back ("share");
166                 lpath.push_back ("locale");
167                 localedir = strdup (Glib::build_filename (lpath).c_str());
168         }
169 #endif
170                 
171         export_search_path (bundle_dir, "ARDOUR_DLL_PATH", "/lib");
172
173         /* inside an OS X .app bundle, there is no difference
174            between DATA and CONFIG locations, since OS X doesn't
175            attempt to do anything to expose the notion of
176            machine-independent shared data.
177         */
178
179         export_search_path (bundle_dir, "ARDOUR_DATA_PATH", "/Resources");
180         export_search_path (bundle_dir, "ARDOUR_CONFIG_PATH", "/Resources");
181         export_search_path (bundle_dir, "ARDOUR_INSTANT_XML_PATH", "/Resources");
182         export_search_path (bundle_dir, "LADSPA_PATH", "/Plugins");
183         export_search_path (bundle_dir, "VAMP_PATH", "/lib");
184         export_search_path (bundle_dir, "SUIL_MODULE_DIR", "/lib");
185         export_search_path (bundle_dir, "GTK_PATH", "/lib/gtkengines");
186
187         g_setenv ("PATH", (bundle_dir + "/MacOS:" + std::string(getenv ("PATH"))).c_str(), 1);
188
189         /* unset GTK_RC_FILES so that we only load the RC files that we define
190          */
191
192         g_unsetenv ("GTK_RC_FILES");
193
194         /* write a pango.rc file and tell pango to use it. we'd love
195            to put this into the PROGRAM_NAME.app bundle and leave it there,
196            but the user may not have write permission. so ...
197
198            we also have to make sure that the user ardour directory
199            actually exists ...
200         */
201
202         if (g_mkdir_with_parents (userconfigdir.c_str(), 0755) < 0) {
203                 error << string_compose (_("cannot create user %3 folder %1 (%2)"), userconfigdir, strerror (errno), PROGRAM_NAME)
204                       << endmsg;
205         } else {
206                 
207                 path = Glib::build_filename (userconfigdir, "pango.rc");
208                 std::ofstream pangorc (path.c_str());
209                 if (!pangorc) {
210                         error << string_compose (_("cannot open pango.rc file %1") , path) << endmsg;
211                 } else {
212                         pangorc << "[Pango]\nModuleFiles="
213                                 << Glib::build_filename (bundle_dir, "Resources/pango.modules") 
214                                 << endl;
215                         pangorc.close ();
216                         
217                         g_setenv ("PANGO_RC_FILE", path.c_str(), 1);
218                 }
219         }
220         
221         g_setenv ("CHARSETALIASDIR", bundle_dir.c_str(), 1);
222         g_setenv ("FONTCONFIG_FILE", Glib::build_filename (bundle_dir, "Resources/fonts.conf").c_str(), 1);
223         g_setenv ("GDK_PIXBUF_MODULE_FILE", Glib::build_filename (bundle_dir, "Resources/gdk-pixbuf.loaders").c_str(), 1);
224 }
225
226 static void load_custom_fonts() {
227 /* this code will only compile on OS X 10.6 and above, and we currently do not
228  * need it for earlier versions since we fall back on a non-monospace,
229  * non-custom font.
230  */
231 #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
232         std::string ardour_mono_file;
233
234         if (!find_file_in_search_path (ardour_data_search_path(), "ArdourMono.ttf", ardour_mono_file)) {
235                 cerr << _("Cannot find ArdourMono TrueType font") << endl;
236         }
237
238         CFStringRef ttf;
239         CFURLRef fontURL;
240         CFErrorRef error;
241         ttf = CFStringCreateWithBytes(
242                         kCFAllocatorDefault, (UInt8*) ardour_mono_file.c_str(),
243                         ardour_mono_file.length(),
244                         kCFStringEncodingUTF8, FALSE);
245         fontURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, ttf, kCFURLPOSIXPathStyle, TRUE);
246         if (CTFontManagerRegisterFontsForURL(fontURL, kCTFontManagerScopeProcess, &error) != true) {
247                 cerr << _("Cannot load ArdourMono TrueType font.") << endl;
248         }
249 #endif
250 }
251
252 #else
253
254 void
255 fixup_bundle_environment (int /*argc*/, char* argv[])
256 {
257         /* THIS IS FOR LINUX - its just about the only place where its
258          * acceptable to build paths directly using '/'.
259          */
260
261         if (!g_getenv ("ARDOUR_BUNDLED")) {
262                 return;
263         }
264
265         EnvironmentalProtectionAgency::set_global_epa (new EnvironmentalProtectionAgency (true, "PREBUNDLE_ENV"));
266
267         std::string path;
268         std::string dir_path = Glib::path_get_dirname (Glib::path_get_dirname (argv[0]));
269         std::string userconfigdir = user_config_directory();
270
271 #ifdef ENABLE_NLS
272         if (!ARDOUR::translations_are_enabled ()) {
273                 localedir = "/this/cannot/exist";
274         } else {
275                 /* force localedir into the bundle */
276                 vector<string> lpath;
277                 lpath.push_back (dir_path);
278                 lpath.push_back ("share");
279                 lpath.push_back ("locale");
280                 localedir = realpath (Glib::build_filename (lpath).c_str(), NULL);
281         }
282 #endif
283
284         /* note that this function is POSIX/Linux specific, so using / as
285            a dir separator in this context is just fine.
286         */
287
288         export_search_path (dir_path, "ARDOUR_DLL_PATH", "/lib");
289         export_search_path (dir_path, "ARDOUR_CONFIG_PATH", "/etc");
290         export_search_path (dir_path, "ARDOUR_INSTANT_XML_PATH", "/share");
291         export_search_path (dir_path, "ARDOUR_DATA_PATH", "/share");
292         export_search_path (dir_path, "LADSPA_PATH", "/plugins");
293         export_search_path (dir_path, "VAMP_PATH", "/lib");
294         export_search_path (dir_path, "SUIL_MODULE_DIR", "/lib");
295         export_search_path (dir_path, "GTK_PATH", "/lib/gtkengines");
296
297         g_setenv ("PATH", (dir_path + "/bin:" + std::string(getenv ("PATH"))).c_str(), 1);
298
299         /* unset GTK_RC_FILES so that we only load the RC files that we define
300          */
301
302         g_unsetenv ("GTK_RC_FILES");
303
304         /* Tell fontconfig where to find fonts.conf. Use the system version
305            if it exists, otherwise use the stuff we included in the bundle
306         */
307
308         if (Glib::file_test ("/etc/fonts/fonts.conf", Glib::FILE_TEST_EXISTS)) {
309                 g_setenv ("FONTCONFIG_FILE", "/etc/fonts/fonts.conf", 1);
310                 g_setenv ("FONTCONFIG_PATH", "/etc/fonts", 1);
311         } else {
312                 error << _("No fontconfig file found on your system. Things may looked very odd or ugly") << endmsg;
313         }
314
315         /* write a pango.rc file and tell pango to use it. we'd love
316            to put this into the Ardour.app bundle and leave it there,
317            but the user may not have write permission. so ...
318
319            we also have to make sure that the user ardour directory
320            actually exists ...
321         */
322
323         if (g_mkdir_with_parents (userconfigdir.c_str(), 0755) < 0) {
324                 error << string_compose (_("cannot create user %3 folder %1 (%2)"), userconfigdir, strerror (errno), PROGRAM_NAME)
325                       << endmsg;
326         } else {
327                 
328                 path = Glib::build_filename (userconfigdir, "pango.rc");
329                 std::ofstream pangorc (path.c_str());
330                 if (!pangorc) {
331                         error << string_compose (_("cannot open pango.rc file %1") , path) << endmsg;
332                 } else {
333                         pangorc << "[Pango]\nModuleFiles="
334                                 << Glib::build_filename (userconfigdir, "pango.modules")
335                                 << endl;
336                         pangorc.close ();
337                 }
338                 
339                 g_setenv ("PANGO_RC_FILE", path.c_str(), 1);
340                 
341                 /* similar for GDK pixbuf loaders, but there's no RC file required
342                    to specify where it lives.
343                 */
344                 
345                 g_setenv ("GDK_PIXBUF_MODULE_FILE", Glib::build_filename (userconfigdir, "gdk-pixbuf.loaders").c_str(), 1);
346         }
347
348         /* this doesn't do much but setting it should prevent various parts of the GTK/GNU stack
349            from looking outside the bundle to find the charset.alias file.
350         */
351         g_setenv ("CHARSETALIASDIR", dir_path.c_str(), 1);
352
353 }
354
355 static void load_custom_fonts() {
356         std::string ardour_mono_file;
357         if (!find_file_in_search_path (ardour_data_search_path(), "ArdourMono.ttf", ardour_mono_file)) {
358                 cerr << _("Cannot find ArdourMono TrueType font") << endl;
359         }
360
361         FcConfig *config = FcInitLoadConfigAndFonts();
362         FcBool ret = FcConfigAppFontAddFile(config, reinterpret_cast<const FcChar8*>(ardour_mono_file.c_str()));
363         if (ret == FcFalse) {
364                 cerr << _("Cannot load ArdourMono TrueType font.") << endl;
365         }
366         ret = FcConfigSetCurrent(config);
367         if (ret == FcFalse) {
368                 cerr << _("Failed to set fontconfig configuration.") << endl;
369         }
370 }
371
372 #endif
373
374 static gboolean
375 tell_about_jack_death (void* /* ignored */)
376 {
377         if (AudioEngine::instance()->processed_frames() == 0) {
378                 /* died during startup */
379                 MessageDialog msg (_("JACK exited"), false);
380                 msg.set_position (Gtk::WIN_POS_CENTER);
381                 msg.set_secondary_text (string_compose (_(
382 "JACK exited unexpectedly, and without notifying %1.\n\
383 \n\
384 This could be due to misconfiguration or to an error inside JACK.\n\
385 \n\
386 Click OK to exit %1."), PROGRAM_NAME));
387
388                 msg.run ();
389                 _exit (0);
390
391         } else {
392
393                 /* engine has already run, so this is a mid-session JACK death */
394
395                 MessageDialog* msg = manage (new MessageDialog (_("JACK exited"), false));
396                 msg->set_secondary_text (string_compose (_(
397 "JACK exited unexpectedly, and without notifying %1.\n\
398 \n\
399 This is probably due to an error inside JACK. You should restart JACK\n\
400 and reconnect %1 to it, or exit %1 now. You cannot save your\n\
401 session at this time, because we would lose your connection information.\n"), PROGRAM_NAME));
402                 msg->present ();
403         }
404         return false; /* do not call again */
405 }
406
407 static void
408 sigpipe_handler (int /*signal*/)
409 {
410         /* XXX fix this so that we do this again after a reconnect to JACK
411          */
412
413         static bool done_the_jack_thing = false;
414
415         if (!done_the_jack_thing) {
416                 AudioEngine::instance()->died ();
417                 g_idle_add (tell_about_jack_death, 0);
418                 done_the_jack_thing =  true;
419         }
420 }
421
422 #ifdef WINDOWS_VST_SUPPORT
423
424 extern int windows_vst_gui_init (int* argc, char** argv[]);
425
426 /* this is called from the entry point of a wine-compiled
427    executable that is linked against gtk2_ardour built
428    as a shared library.
429 */
430 extern "C" {
431 int ardour_main (int argc, char *argv[])
432 #else
433 int main (int argc, char *argv[])
434 #endif
435 {
436         fixup_bundle_environment (argc, argv);
437
438         load_custom_fonts(); /* needs to happend before any gtk and pango init calls */
439
440         if (!Glib::thread_supported()) {
441                 Glib::thread_init();
442         }
443
444 #ifdef ENABLE_NLS
445         gtk_set_locale ();
446 #endif
447
448 #ifdef WINDOWS_VST_SUPPORT
449         /* this does some magic that is needed to make GTK and Wine's own
450            X11 client interact properly.
451         */
452         windows_vst_gui_init (&argc, &argv);
453 #endif
454
455 #ifdef ENABLE_NLS
456         cerr << "bnd txt domain [" << PACKAGE << "] to " << localedir << endl;
457
458         (void) bindtextdomain (PACKAGE, localedir);
459         /* our i18n translations are all in UTF-8, so make sure
460            that even if the user locale doesn't specify UTF-8,
461            we use that when handling them.
462         */
463         (void) bind_textdomain_codeset (PACKAGE,"UTF-8");
464 #endif
465
466         pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0);
467
468         // catch error message system signals ();
469
470         text_receiver.listen_to (error);
471         text_receiver.listen_to (info);
472         text_receiver.listen_to (fatal);
473         text_receiver.listen_to (warning);
474
475 #ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
476         if (g_getenv ("BOOST_DEBUG")) {
477                 boost_debug_shared_ptr_show_live_debugging (true);
478         }
479 #endif
480
481         if (parse_opts (argc, argv)) {
482                 exit (1);
483         }
484
485         if (curvetest_file) {
486                 return curvetest (curvetest_file);
487         }
488
489         cout << PROGRAM_NAME
490              << VERSIONSTRING
491              << _(" (built using ")
492              << revision
493 #ifdef __GNUC__
494              << _(" and GCC version ") << __VERSION__
495 #endif
496              << ')'
497              << endl;
498
499         if (just_version) {
500                 exit (0);
501         }
502
503         if (no_splash) {
504                 cerr << _("Copyright (C) 1999-2012 Paul Davis") << endl
505                      << _("Some portions Copyright (C) Steve Harris, Ari Johnson, Brett Viren, Joel Baker, Robin Gareus") << endl
506                      << endl
507                      << string_compose (_("%1 comes with ABSOLUTELY NO WARRANTY"), PROGRAM_NAME) << endl
508                      << _("not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.") << endl
509                      << _("This is free software, and you are welcome to redistribute it ") << endl
510                      << _("under certain conditions; see the source for copying conditions.")
511                      << endl;
512         }
513
514         /* some GUI objects need this */
515
516         PBD::ID::init ();
517
518         if (::signal (SIGPIPE, sigpipe_handler)) {
519                 cerr << _("Cannot xinstall SIGPIPE error handler") << endl;
520         }
521
522         try {
523                 ui = new ARDOUR_UI (&argc, &argv, localedir);
524         } catch (failed_constructor& err) {
525                 error << string_compose (_("could not create %1 GUI"), PROGRAM_NAME) << endmsg;
526                 exit (1);
527         }
528
529         ui->run (text_receiver);
530         Gtkmm2ext::Application::instance()->cleanup();
531         ui = 0;
532
533         ARDOUR::cleanup ();
534         pthread_cancel_all ();
535
536         return 0;
537 }
538 #ifdef WINDOWS_VST_SUPPORT
539 } // end of extern C block
540 #endif
541