1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   AudacityApp.cpp
6 
7   Dominic Mazzoni
8 
9 ******************************************************************//**
10 
11 \class AudacityApp
12 \brief AudacityApp is the 'main' class for Audacity
13 
14 It handles initialization and termination by subclassing wxApp.
15 
16 *//*******************************************************************/
17 
18 
19 #include "AudacityApp.h"
20 
21 
22 
23 #if 0
24 // This may be used to debug memory leaks.
25 // See: Visual Leak Detector @ http://vld.codeplex.com/
26 #include <vld.h>
27 #endif
28 
29 #include <wx/setup.h> // for wxUSE_* macros
30 #include <wx/wxcrtvararg.h>
31 #include <wx/defs.h>
32 #include <wx/evtloop.h>
33 #include <wx/app.h>
34 #include <wx/bitmap.h>
35 #include <wx/docview.h>
36 #include <wx/event.h>
37 #include <wx/ipc.h>
38 #include <wx/window.h>
39 #include <wx/intl.h>
40 #include <wx/menu.h>
41 #include <wx/snglinst.h>
42 #include <wx/splash.h>
43 #include <wx/stdpaths.h>
44 #include <wx/sysopt.h>
45 #include <wx/fontmap.h>
46 
47 #include <wx/fs_zip.h>
48 #include <wx/image.h>
49 
50 #include <wx/dir.h>
51 #include <wx/file.h>
52 #include <wx/filename.h>
53 
54 #ifdef __WXGTK__
55 #include <unistd.h>
56 #ifdef HAVE_GTK
57 #include <gtk/gtk.h>
58 #endif
59 #endif
60 
61 // chmod, lstat, geteuid
62 #ifdef __UNIX__
63 #include <sys/types.h>
64 #include <sys/file.h>
65 #include <sys/stat.h>
66 #include <stdio.h>
67 #endif
68 
69 #if defined(__WXMSW__)
70 #include <wx/msw/registry.h> // for wxRegKey
71 #endif
72 
73 #include "AudacityLogger.h"
74 #include "AboutDialog.h"
75 #include "ActiveProject.h"
76 #include "AColor.h"
77 #include "AudacityFileConfig.h"
78 #include "AudioIO.h"
79 #include "Benchmark.h"
80 #include "Clipboard.h"
81 #include "CrashReport.h" // for HAS_CRASH_REPORT
82 #include "commands/CommandHandler.h"
83 #include "commands/AppCommandEvent.h"
84 #include "widgets/ASlider.h"
85 #include "FFmpeg.h"
86 #include "Journal.h"
87 //#include "LangChoice.h"
88 #include "Languages.h"
89 #include "Menus.h"
90 #include "PluginManager.h"
91 #include "Project.h"
92 #include "ProjectAudioIO.h"
93 #include "ProjectAudioManager.h"
94 #include "ProjectFileIO.h"
95 #include "ProjectFileManager.h"
96 #include "ProjectHistory.h"
97 #include "ProjectManager.h"
98 #include "ProjectSettings.h"
99 #include "ProjectWindow.h"
100 #include "ProjectWindows.h"
101 #include "Screenshot.h"
102 #include "Sequence.h"
103 #include "SelectFile.h"
104 #include "TempDirectory.h"
105 #include "Track.h"
106 #include "prefs/PrefsDialog.h"
107 #include "Theme.h"
108 #include "PlatformCompatibility.h"
109 #include "AutoRecoveryDialog.h"
110 #include "SplashDialog.h"
111 #include "FFT.h"
112 #include "widgets/AudacityMessageBox.h"
113 #include "prefs/DirectoriesPrefs.h"
114 #include "prefs/GUIPrefs.h"
115 #include "tracks/ui/Scrubbing.h"
116 #include "FileConfig.h"
117 #include "widgets/FileHistory.h"
118 #include "update/UpdateManager.h"
119 #include "widgets/wxWidgetsBasicUI.h"
120 #include "LogWindow.h"
121 
122 #ifdef HAS_NETWORKING
123 #include "NetworkManager.h"
124 #endif
125 
126 #ifdef EXPERIMENTAL_EASY_CHANGE_KEY_BINDINGS
127 #include "prefs/KeyConfigPrefs.h"
128 #endif
129 
130 //temporarily commented out till it is added to all projects
131 //#include "Profiler.h"
132 
133 #include "ModuleManager.h"
134 
135 #include "import/Import.h"
136 
137 #if defined(USE_BREAKPAD)
138 #include "BreakpadConfigurer.h"
139 #endif
140 
141 #ifdef EXPERIMENTAL_SCOREALIGN
142 #include "effects/ScoreAlignDialog.h"
143 #endif
144 
145 #if 0
146 #ifdef _DEBUG
147     #ifdef _MSC_VER
148         #undef THIS_FILE
149         static char*THIS_FILE= __FILE__;
150         #define new new(_NORMAL_BLOCK, THIS_FILE, __LINE__)
151     #endif
152 #endif
153 #endif
154 
155 // DA: Logo for Splash Screen
156 #ifdef EXPERIMENTAL_DA
157 #include "../images/DarkAudacityLogoWithName.xpm"
158 #else
159 #include "../images/AudacityLogoWithName.xpm"
160 #endif
161 
162 #include <thread>
163 
164 
165 ////////////////////////////////////////////////////////////
166 /// Custom events
167 ////////////////////////////////////////////////////////////
168 
169 #if 0
170 #ifdef __WXGTK__
171 static void wxOnAssert(const wxChar *fileName, int lineNumber, const wxChar *msg)
172 {
173    if (msg)
174       wxPrintf("ASSERTION FAILED: %s\n%s: %d\n", (const char *)wxString(msg).mb_str(), (const char *)wxString(fileName).mb_str(), lineNumber);
175    else
176       wxPrintf("ASSERTION FAILED!\n%s: %d\n", (const char *)wxString(fileName).mb_str(), lineNumber);
177 
178    // Force core dump
179    int *i = 0;
180    if (*i)
181       exit(1);
182 
183    exit(0);
184 }
185 #endif
186 #endif
187 
188 namespace {
189 
PopulatePreferences()190 void PopulatePreferences()
191 {
192    bool resetPrefs = false;
193    wxString langCode = gPrefs->Read(wxT("/Locale/Language"), wxEmptyString);
194    bool writeLang = false;
195 
196    const wxFileName fn(
197       FileNames::ResourcesDir(),
198       wxT("FirstTime.ini"));
199    if (fn.FileExists())   // it will exist if the (win) installer put it there
200    {
201       const wxString fullPath{fn.GetFullPath()};
202 
203       auto pIni =
204          AudacityFileConfig::Create({}, {}, fullPath, {},
205             wxCONFIG_USE_LOCAL_FILE);
206       auto &ini = *pIni;
207 
208       wxString lang;
209       if (ini.Read(wxT("/FromInno/Language"), &lang) && !lang.empty())
210       {
211          // Only change "langCode" if the language was actually specified in the ini file.
212          langCode = lang;
213          writeLang = true;
214 
215          // Inno Setup doesn't allow special characters in the Name values, so "0" is used
216          // to represent the "@" character.
217          langCode.Replace(wxT("0"), wxT("@"));
218       }
219 
220       ini.Read(wxT("/FromInno/ResetPrefs"), &resetPrefs, false);
221 
222       bool gone = wxRemoveFile(fullPath);  // remove FirstTime.ini
223       if (!gone)
224       {
225          AudacityMessageBox(
226             XO("Failed to remove %s").Format(fullPath),
227             XO("Failed!"));
228       }
229    }
230 
231    // Use the system default language if one wasn't specified or if the user selected System.
232    if (langCode.empty())
233       langCode =
234          Languages::GetSystemLanguageCode(FileNames::AudacityPathList());
235 
236    langCode = GUIPrefs::SetLang( langCode );
237 
238    // User requested that the preferences be completely reset
239    if (resetPrefs)
240    {
241       // pop up a dialogue
242       auto prompt = XO(
243 "Reset Preferences?\n\nThis is a one-time question, after an 'install' where you asked to have the Preferences reset.");
244       int action = AudacityMessageBox(
245          prompt,
246          XO("Reset Audacity Preferences"),
247          wxYES_NO, NULL);
248       if (action == wxYES)   // reset
249       {
250          ResetPreferences();
251          writeLang = true;
252       }
253    }
254 
255    // Save the specified language
256    if (writeLang)
257    {
258       gPrefs->Write(wxT("/Locale/Language"), langCode);
259    }
260 
261    // In AUdacity 2.1.0 support for the legacy 1.2.x preferences (depreciated since Audacity
262    // 1.3.1) is dropped. As a result we can drop the import flag
263    // first time this version of Audacity is run we try to migrate
264    // old preferences.
265    bool newPrefsInitialized = false;
266    gPrefs->Read(wxT("/NewPrefsInitialized"), &newPrefsInitialized, false);
267    if (newPrefsInitialized) {
268       gPrefs->DeleteEntry(wxT("/NewPrefsInitialized"), true);  // take group as well if empty
269    }
270 
271    // record the Prefs version for future checking (this has not been used for a very
272    // long time).
273    gPrefs->Write(wxT("/PrefsVersion"), wxString(wxT(AUDACITY_PREFS_VERSION_STRING)));
274 
275    // Check if some prefs updates need to happen based on audacity version.
276    // Unfortunately we can't use the PrefsVersion prefs key because that resets things.
277    // In the future we may want to integrate that better.
278    // these are done on a case-by-case basis for now so they must be backwards compatible
279    // (meaning the changes won't mess audacity up if the user goes back to an earlier version)
280    int vMajor = gPrefs->Read(wxT("/Version/Major"), (long) 0);
281    int vMinor = gPrefs->Read(wxT("/Version/Minor"), (long) 0);
282    int vMicro = gPrefs->Read(wxT("/Version/Micro"), (long) 0);
283 
284    gPrefs->SetVersionKeysInit(vMajor, vMinor, vMicro);   // make a note of these initial values
285                                                             // for use by ToolManager::ReadConfig()
286 
287    // These integer version keys were introduced april 4 2011 for 1.3.13
288    // The device toolbar needs to be enabled due to removal of source selection features in
289    // the mixer toolbar.
290    if ((vMajor < 1) ||
291        (vMajor == 1 && vMinor < 3) ||
292        (vMajor == 1 && vMinor == 3 && vMicro < 13)) {
293 
294 
295       // Do a full reset of the Device Toolbar to get it on the screen.
296       if (gPrefs->Exists(wxT("/GUI/ToolBars/Device")))
297          gPrefs->DeleteGroup(wxT("/GUI/ToolBars/Device"));
298 
299       // We keep the mixer toolbar prefs (shown/not shown)
300       // the width of the mixer toolbar may have shrunk, the prefs will keep the larger value
301       // if the user had a device that had more than one source.
302       if (gPrefs->Exists(wxT("/GUI/ToolBars/Mixer"))) {
303          // Use the default width
304          gPrefs->Write(wxT("/GUI/ToolBars/Mixer/W"), -1);
305       }
306    }
307 
308    // In 2.1.0, the Meter toolbar was split and lengthened, but strange arrangements happen
309    // if upgrading due to the extra length.  So, if a user is upgrading, use the pre-2.1.0
310    // lengths, but still use the NEW split versions.
311    if (gPrefs->Exists(wxT("/GUI/ToolBars/Meter")) &&
312       !gPrefs->Exists(wxT("/GUI/ToolBars/CombinedMeter"))) {
313 
314       // Read in all of the existing values
315       long dock, order, show, x, y, w, h;
316       gPrefs->Read(wxT("/GUI/ToolBars/Meter/Dock"), &dock, -1);
317       gPrefs->Read(wxT("/GUI/ToolBars/Meter/Order"), &order, -1);
318       gPrefs->Read(wxT("/GUI/ToolBars/Meter/Show"), &show, -1);
319       gPrefs->Read(wxT("/GUI/ToolBars/Meter/X"), &x, -1);
320       gPrefs->Read(wxT("/GUI/ToolBars/Meter/Y"), &y, -1);
321       gPrefs->Read(wxT("/GUI/ToolBars/Meter/W"), &w, -1);
322       gPrefs->Read(wxT("/GUI/ToolBars/Meter/H"), &h, -1);
323 
324       // "Order" must be adjusted since we're inserting two NEW toolbars
325       if (dock > 0) {
326          wxString oldPath = gPrefs->GetPath();
327          gPrefs->SetPath(wxT("/GUI/ToolBars"));
328 
329          wxString bar;
330          long ndx = 0;
331          bool cont = gPrefs->GetFirstGroup(bar, ndx);
332          while (cont) {
333             long o;
334             if (gPrefs->Read(bar + wxT("/Order"), &o) && o >= order) {
335                gPrefs->Write(bar + wxT("/Order"), o + 2);
336             }
337             cont = gPrefs->GetNextGroup(bar, ndx);
338          }
339          gPrefs->SetPath(oldPath);
340 
341          // And override the height
342          h = 27;
343       }
344 
345       // Write the split meter bar values
346       gPrefs->Write(wxT("/GUI/ToolBars/RecordMeter/Dock"), dock);
347       gPrefs->Write(wxT("/GUI/ToolBars/RecordMeter/Order"), order);
348       gPrefs->Write(wxT("/GUI/ToolBars/RecordMeter/Show"), show);
349       gPrefs->Write(wxT("/GUI/ToolBars/RecordMeter/X"), -1);
350       gPrefs->Write(wxT("/GUI/ToolBars/RecordMeter/Y"), -1);
351       gPrefs->Write(wxT("/GUI/ToolBars/RecordMeter/W"), w);
352       gPrefs->Write(wxT("/GUI/ToolBars/RecordMeter/H"), h);
353       gPrefs->Write(wxT("/GUI/ToolBars/PlayMeter/Dock"), dock);
354       gPrefs->Write(wxT("/GUI/ToolBars/PlayMeter/Order"), order + 1);
355       gPrefs->Write(wxT("/GUI/ToolBars/PlayMeter/Show"), show);
356       gPrefs->Write(wxT("/GUI/ToolBars/PlayMeter/X"), -1);
357       gPrefs->Write(wxT("/GUI/ToolBars/PlayMeter/Y"), -1);
358       gPrefs->Write(wxT("/GUI/ToolBars/PlayMeter/W"), w);
359       gPrefs->Write(wxT("/GUI/ToolBars/PlayMeter/H"), h);
360 
361       // And hide the old combined meter bar
362       gPrefs->Write(wxT("/GUI/ToolBars/Meter/Dock"), -1);
363    }
364 
365    // Upgrading pre 2.2.0 configs we assume extended set of defaults.
366    if ((0<vMajor && vMajor < 2) ||
367        (vMajor == 2 && vMinor < 2))
368    {
369       gPrefs->Write(wxT("/GUI/Shortcuts/FullDefaults"),1);
370    }
371 
372    // Upgrading pre 2.4.0 configs, the selection toolbar is now split.
373    if ((0<vMajor && vMajor < 2) ||
374        (vMajor == 2 && vMinor < 4))
375    {
376       gPrefs->Write(wxT("/GUI/Toolbars/Selection/W"),"");
377       gPrefs->Write(wxT("/GUI/Toolbars/SpectralSelection/W"),"");
378       gPrefs->Write(wxT("/GUI/Toolbars/Time/X"),-1);
379       gPrefs->Write(wxT("/GUI/Toolbars/Time/Y"),-1);
380       gPrefs->Write(wxT("/GUI/Toolbars/Time/H"),55);
381       gPrefs->Write(wxT("/GUI/Toolbars/Time/W"),251);
382       gPrefs->Write(wxT("/GUI/Toolbars/Time/DockV2"),2);
383       gPrefs->Write(wxT("/GUI/Toolbars/Time/Dock"),2);
384       gPrefs->Write(wxT("/GUI/Toolbars/Time/Path"),"0,1");
385       gPrefs->Write(wxT("/GUI/Toolbars/Time/Show"),1);
386    }
387 
388    if (std::pair{ vMajor, vMinor } < std::pair{ 3, 1 } ) {
389       // Reset the control toolbar
390       gPrefs->Write(wxT("/GUI/Toolbars/Control/W"), -1);
391    }
392 
393    // write out the version numbers to the prefs file for future checking
394    gPrefs->Write(wxT("/Version/Major"), AUDACITY_VERSION);
395    gPrefs->Write(wxT("/Version/Minor"), AUDACITY_RELEASE);
396    gPrefs->Write(wxT("/Version/Micro"), AUDACITY_REVISION);
397 
398    gPrefs->Flush();
399 }
400 
401 #if defined(USE_BREAKPAD)
InitBreakpad()402 void InitBreakpad()
403 {
404     wxFileName databasePath;
405     databasePath.SetPath(wxStandardPaths::Get().GetUserLocalDataDir());
406     databasePath.AppendDir("crashreports");
407     databasePath.Mkdir(wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL);
408 
409     if(databasePath.DirExists())
410     {
411         const auto sentryRelease = wxString::Format(
412            "audacity@%d.%d.%d", AUDACITY_VERSION, AUDACITY_RELEASE, AUDACITY_REVISION
413         );
414         BreakpadConfigurer configurer;
415         configurer.SetDatabasePathUTF8(databasePath.GetPath().ToUTF8().data())
416             .SetSenderPathUTF8(wxFileName(wxStandardPaths::Get().GetExecutablePath()).GetPath().ToUTF8().data())
417     #if defined(CRASH_REPORT_URL)
418             .SetReportURL(CRASH_REPORT_URL)
419     #endif
420             .SetParameters({
421                 { "version", wxString(AUDACITY_VERSION_STRING).ToUTF8().data() },
422                 { "sentry[release]",  sentryRelease.ToUTF8().data() }
423             })
424             .Start();
425     }
426 }
427 #endif
428 }
429 
430 static bool gInited = false;
431 static bool gIsQuitting = false;
432 
CloseAllProjects(bool force)433 static bool CloseAllProjects( bool force )
434 {
435    ProjectManager::SetClosingAll(true);
436    auto cleanup = finally([]{ ProjectManager::SetClosingAll(false); });
437    while (AllProjects{}.size())
438    {
439       // Closing the project has global side-effect
440       // of deletion from gAudacityProjects
441       if ( force )
442       {
443          GetProjectFrame( **AllProjects{}.begin() ).Close(true);
444       }
445       else
446       {
447          if (! GetProjectFrame( **AllProjects{}.begin() ).Close())
448             return false;
449       }
450    }
451    return true;
452 }
453 
QuitAudacity(bool bForce)454 static void QuitAudacity(bool bForce)
455 {
456    // guard against recursion
457    if (gIsQuitting)
458       return;
459 
460    gIsQuitting = true;
461 
462    wxTheApp->SetExitOnFrameDelete(true);
463 
464    // Try to close each open window.  If the user hits Cancel
465    // in a Save Changes dialog, don't continue.
466    // BG: unless force is true
467 
468    // BG: Are there any projects open?
469    //-   if (!AllProjects{}.empty())
470 /*start+*/
471    if (AllProjects{}.empty())
472    {
473 #ifdef __WXMAC__
474       Clipboard::Get().Clear();
475 #endif
476    }
477    else
478 /*end+*/
479    {
480       if (AllProjects{}.size())
481          // PRL:  Always did at least once before close might be vetoed
482          // though I don't know why that is important
483          ProjectManager::SaveWindowSize();
484       bool closedAll = CloseAllProjects( bForce );
485       if ( !closedAll )
486       {
487          gIsQuitting = false;
488          return;
489       }
490    }
491 
492    ModuleManager::Get().Dispatch(AppQuiting);
493 
494 #ifdef EXPERIMENTAL_SCOREALIGN
495    CloseScoreAlignDialog();
496 #endif
497    CloseScreenshotTools();
498 
499    // Logger window is always destroyed on macOS,
500    // on other platforms - it prevents the runloop
501    // termination when exiting is requested
502    #if !defined(__WXMAC__)
503    LogWindow::Destroy();
504    #endif
505 
506    //print out profile if we have one by deleting it
507    //temporarily commented out till it is added to all projects
508    //DELETE Profiler::Instance();
509 
510    // Save last log for diagnosis
511    auto logger = AudacityLogger::Get();
512    if (logger)
513    {
514       wxFileName logFile(FileNames::DataDir(), wxT("lastlog.txt"));
515       logger->SaveLog(logFile.GetFullPath());
516    }
517 
518    //remove our logger
519    std::unique_ptr<wxLog>{ wxLog::SetActiveTarget(NULL) }; // DELETE
520 
521    if (bForce)
522    {
523       wxExit();
524    }
525 }
526 
QuitAudacity()527 static void QuitAudacity()
528 {
529    QuitAudacity(false);
530 }
531 
532 #if defined(__WXGTK__) && defined(HAVE_GTK)
533 
534 ///////////////////////////////////////////////////////////////////////////////
535 // Provide the ability to receive notification from the session manager
536 // when the user is logging out or shutting down.
537 //
538 // Most of this was taken from nsNativeAppSupportUnix.cpp from Mozilla.
539 ///////////////////////////////////////////////////////////////////////////////
540 
541 // TODO: May need updating.  Is this code too obsolete (relying on Gnome2 so's) to be
542 // worth keeping anymore?
543 // CB suggests we use libSM directly ref:
544 // http://www.x.org/archive/X11R7.7/doc/libSM/SMlib.html#The_Save_Yourself_Callback
545 
546 #include <dlfcn.h>
547 /* There is a conflict between the type names used in Glib >= 2.21 and those in
548  * wxGTK (http://trac.wxwidgets.org/ticket/10883)
549  * Happily we can avoid the hack, as we only need some of the headers, not
550  * the full GTK headers
551  */
552 #include <glib-object.h>
553 
554 typedef struct _GnomeProgram GnomeProgram;
555 typedef struct _GnomeModuleInfo GnomeModuleInfo;
556 typedef struct _GnomeClient GnomeClient;
557 
558 typedef enum
559 {
560   GNOME_SAVE_GLOBAL,
561   GNOME_SAVE_LOCAL,
562   GNOME_SAVE_BOTH
563 } GnomeSaveStyle;
564 
565 typedef enum
566 {
567   GNOME_INTERACT_NONE,
568   GNOME_INTERACT_ERRORS,
569   GNOME_INTERACT_ANY
570 } GnomeInteractStyle;
571 
572 typedef enum
573 {
574   GNOME_DIALOG_ERROR,
575   GNOME_DIALOG_NORMAL
576 } GnomeDialogType;
577 
578 typedef GnomeProgram * (*_gnome_program_init_fn)(const char *,
579                                                  const char *,
580                                                  const GnomeModuleInfo *,
581                                                  int,
582                                                  char **,
583                                                  const char *,
584                                                  ...);
585 typedef const GnomeModuleInfo * (*_libgnomeui_module_info_get_fn)();
586 typedef GnomeClient * (*_gnome_master_client_fn)(void);
587 typedef void (*GnomeInteractFunction)(GnomeClient *,
588                                       gint,
589                                       GnomeDialogType,
590                                       gpointer);
591 typedef void (*_gnome_client_request_interaction_fn)(GnomeClient *,
592                                                      GnomeDialogType,
593                                                      GnomeInteractFunction,
594                                                      gpointer);
595 typedef void (*_gnome_interaction_key_return_fn)(gint, gboolean);
596 
597 static _gnome_client_request_interaction_fn gnome_client_request_interaction;
598 static _gnome_interaction_key_return_fn gnome_interaction_key_return;
599 
interact_cb(GnomeClient *,gint key,GnomeDialogType,gpointer)600 static void interact_cb(GnomeClient * /* client */,
601                         gint key,
602                         GnomeDialogType /* type */,
603                         gpointer /* data */)
604 {
605    wxCloseEvent e(wxEVT_QUERY_END_SESSION, wxID_ANY);
606    e.SetEventObject(&wxGetApp());
607    e.SetCanVeto(true);
608 
609    wxGetApp().ProcessEvent(e);
610 
611    gnome_interaction_key_return(key, e.GetVeto());
612 }
613 
save_yourself_cb(GnomeClient * client,gint,GnomeSaveStyle,gboolean shutdown,GnomeInteractStyle interact,gboolean,gpointer)614 static gboolean save_yourself_cb(GnomeClient *client,
615                                  gint /* phase */,
616                                  GnomeSaveStyle /* style */,
617                                  gboolean shutdown,
618                                  GnomeInteractStyle interact,
619                                  gboolean /* fast */,
620                                  gpointer /* user_data */)
621 {
622    if (!shutdown || interact != GNOME_INTERACT_ANY) {
623       return TRUE;
624    }
625 
626    if (AllProjects{}.empty()) {
627       return TRUE;
628    }
629 
630    gnome_client_request_interaction(client,
631                                     GNOME_DIALOG_NORMAL,
632                                     interact_cb,
633                                     NULL);
634 
635    return TRUE;
636 }
637 
638 class GnomeShutdown
639 {
640  public:
GnomeShutdown()641    GnomeShutdown()
642    {
643       mArgv[0].reset(strdup("Audacity"));
644 
645       mGnomeui = dlopen("libgnomeui-2.so.0", RTLD_NOW);
646       if (!mGnomeui) {
647          return;
648       }
649 
650       mGnome = dlopen("libgnome-2.so.0", RTLD_NOW);
651       if (!mGnome) {
652          return;
653       }
654 
655       _gnome_program_init_fn gnome_program_init = (_gnome_program_init_fn)
656          dlsym(mGnome, "gnome_program_init");
657       _libgnomeui_module_info_get_fn libgnomeui_module_info_get = (_libgnomeui_module_info_get_fn)
658          dlsym(mGnomeui, "libgnomeui_module_info_get");
659       _gnome_master_client_fn gnome_master_client = (_gnome_master_client_fn)
660          dlsym(mGnomeui, "gnome_master_client");
661 
662       gnome_client_request_interaction = (_gnome_client_request_interaction_fn)
663          dlsym(mGnomeui, "gnome_client_request_interaction");
664       gnome_interaction_key_return = (_gnome_interaction_key_return_fn)
665          dlsym(mGnomeui, "gnome_interaction_key_return");
666 
667 
668       if (!gnome_program_init || !libgnomeui_module_info_get) {
669          return;
670       }
671 
672       gnome_program_init(mArgv[0].get(),
673                          "1.0",
674                          libgnomeui_module_info_get(),
675                          1,
676                          reinterpret_cast<char**>(mArgv),
677                          NULL);
678 
679       mClient = gnome_master_client();
680       if (mClient == NULL) {
681          return;
682       }
683 
684       g_signal_connect(mClient, "save-yourself", G_CALLBACK(save_yourself_cb), NULL);
685    }
686 
~GnomeShutdown()687    virtual ~GnomeShutdown()
688    {
689       // Do not dlclose() the libraries here lest you want segfaults...
690    }
691 
692  private:
693 
694    MallocString<> mArgv[1];
695    void *mGnomeui;
696    void *mGnome;
697    GnomeClient *mClient;
698 };
699 
700 // This variable exists to call the constructor and
701 // connect a signal for the 'save-yourself' message.
702 GnomeShutdown GnomeShutdownInstance;
703 
704 #endif
705 
706 // Where drag/drop or "Open With" filenames get stored until
707 // the timer routine gets around to picking them up.
708 static wxArrayString ofqueue;
709 
710 //
711 // DDE support for opening multiple files with one instance
712 // of Audacity.
713 //
714 
715 #define IPC_APPL wxT("audacity")
716 #define IPC_TOPIC wxT("System")
717 
718 class IPCConn final : public wxConnection
719 {
720 public:
IPCConn()721    IPCConn()
722    : wxConnection()
723    {
724    };
725 
~IPCConn()726    ~IPCConn()
727    {
728    };
729 
OnExec(const wxString & WXUNUSED (topic),const wxString & data)730    bool OnExec(const wxString & WXUNUSED(topic),
731                const wxString & data)
732    {
733       // Add the filename to the queue.  It will be opened by
734       // the OnTimer() event when it is safe to do so.
735       ofqueue.push_back(data);
736 
737       return true;
738    }
739 };
740 
741 class IPCServ final : public wxServer
742 {
743 public:
IPCServ(const wxString & appl)744    IPCServ(const wxString & appl)
745    : wxServer()
746    {
747       Create(appl);
748    };
749 
~IPCServ()750    ~IPCServ()
751    {
752    };
753 
OnAcceptConnection(const wxString & topic)754    wxConnectionBase *OnAcceptConnection(const wxString & topic) override
755    {
756       if (topic != IPC_TOPIC) {
757          return NULL;
758       }
759 
760       // Trust wxWidgets framework to DELETE it
761       return safenew IPCConn();
762    };
763 };
764 
765 #if defined(__WXMAC__)
766 
IMPLEMENT_APP_NO_MAIN(AudacityApp)767 IMPLEMENT_APP_NO_MAIN(AudacityApp)
768 IMPLEMENT_WX_THEME_SUPPORT
769 
770 int main(int argc, char *argv[])
771 {
772    wxDISABLE_DEBUG_SUPPORT();
773 
774    return wxEntry(argc, argv);
775 }
776 
777 #elif defined(__WXGTK__) && defined(NDEBUG)
778 
IMPLEMENT_APP_NO_MAIN(AudacityApp)779 IMPLEMENT_APP_NO_MAIN(AudacityApp)
780 IMPLEMENT_WX_THEME_SUPPORT
781 
782 int main(int argc, char *argv[])
783 {
784    wxDISABLE_DEBUG_SUPPORT();
785 
786    // Bug #1986 workaround - This doesn't actually reduce the number of
787    // messages, it simply hides them in Release builds. We'll probably
788    // never be able to get rid of the messages entirely, but we should
789    // look into what's causing them, so allow them to show in Debug
790    // builds.
791    stdout = freopen("/dev/null", "w", stdout);
792    stderr = freopen("/dev/null", "w", stderr);
793 
794    return wxEntry(argc, argv);
795 }
796 
797 #else
IMPLEMENT_APP(AudacityApp)798 IMPLEMENT_APP(AudacityApp)
799 #endif
800 
801 #ifdef __WXMAC__
802 
803 // in response of an open-document apple event
804 void AudacityApp::MacOpenFile(const wxString &fileName)
805 {
806    ofqueue.push_back(fileName);
807 }
808 
809 // in response of a print-document apple event
MacPrintFile(const wxString & fileName)810 void AudacityApp::MacPrintFile(const wxString &fileName)
811 {
812    ofqueue.push_back(fileName);
813 }
814 
815 // in response of a open-application apple event
MacNewFile()816 void AudacityApp::MacNewFile()
817 {
818    if (!gInited)
819       return;
820 
821    // This method should only be used on the Mac platform
822    // when no project windows are open.
823 
824    if (AllProjects{}.empty())
825       (void) ProjectManager::New();
826 }
827 
828 #endif //__WXMAC__
829 
830 // IPC communication
831 #define ID_IPC_SERVER   6200
832 #define ID_IPC_SOCKET   6201
833 
834 // we don't really care about the timer id, but set this value just in case we do in the future
835 #define kAudacityAppTimerID 0
836 
BEGIN_EVENT_TABLE(AudacityApp,wxApp)837 BEGIN_EVENT_TABLE(AudacityApp, wxApp)
838    EVT_IDLE( AudacityApp::OnIdle )
839 
840    EVT_QUERY_END_SESSION(AudacityApp::OnQueryEndSession)
841    EVT_END_SESSION(AudacityApp::OnEndSession)
842 
843    EVT_TIMER(kAudacityAppTimerID, AudacityApp::OnTimer)
844 #ifdef __WXMAC__
845    EVT_MENU(wxID_NEW, AudacityApp::OnMenuNew)
846    EVT_MENU(wxID_OPEN, AudacityApp::OnMenuOpen)
847    EVT_MENU(wxID_ABOUT, AudacityApp::OnMenuAbout)
848    EVT_MENU(wxID_PREFERENCES, AudacityApp::OnMenuPreferences)
849 #endif
850 
851    // Associate the handler with the menu id on all operating systems, even
852    // if they don't have an application menu bar like in macOS, so that
853    // other parts of the program can send the application a shut-down
854    // event
855    EVT_MENU(wxID_EXIT, AudacityApp::OnMenuExit)
856 
857 #ifndef __WXMSW__
858    EVT_SOCKET(ID_IPC_SERVER, AudacityApp::OnServerEvent)
859    EVT_SOCKET(ID_IPC_SOCKET, AudacityApp::OnSocketEvent)
860 #endif
861 
862    // Recent file event handlers.
863    EVT_MENU(FileHistory::ID_RECENT_CLEAR, AudacityApp::OnMRUClear)
864    EVT_MENU_RANGE(FileHistory::ID_RECENT_FIRST, FileHistory::ID_RECENT_LAST,
865       AudacityApp::OnMRUFile)
866 
867    // Handle AppCommandEvents (usually from a script)
868    EVT_APP_COMMAND(wxID_ANY, AudacityApp::OnReceiveCommand)
869 
870    // Global ESC key handling
871    EVT_KEY_DOWN(AudacityApp::OnKeyDown)
872 END_EVENT_TABLE()
873 
874 // backend for OnMRUFile
875 // TODO: Would be nice to make this handle not opening a file with more panache.
876 //  - Inform the user if DefaultOpenPath not set.
877 //  - Switch focus to correct instance of project window, if already open.
878 bool AudacityApp::MRUOpen(const FilePath &fullPathStr) {
879    // Most of the checks below are copied from ProjectManager::OpenFiles.
880    // - some rationalisation might be possible.
881 
882    auto pProj = GetActiveProject().lock();
883    auto proj = pProj.get();
884 
885    if (!fullPathStr.empty())
886    {
887       // verify that the file exists
888       if (wxFile::Exists(fullPathStr))
889       {
890          FileNames::UpdateDefaultPath(FileNames::Operation::Open, ::wxPathOnly(fullPathStr));
891 
892          // Make sure it isn't already open.
893          // Test here even though AudacityProject::OpenFile() also now checks, because
894          // that method does not return the bad result.
895          // That itself may be a FIXME.
896          if (ProjectFileManager::IsAlreadyOpen(fullPathStr))
897             return false;
898 
899          //! proj may be null
900          ( void ) ProjectManager::OpenProject( proj, fullPathStr,
901                true /* addtohistory */, false /* reuseNonemptyProject */ );
902       }
903       else {
904          // File doesn't exist - remove file from history
905          AudacityMessageBox(
906             XO(
907 "%s could not be found.\n\nIt has been removed from the list of recent files.")
908                .Format(fullPathStr) );
909          return(false);
910       }
911    }
912    return(true);
913 }
914 
SafeMRUOpen(const wxString & fullPathStr)915 bool AudacityApp::SafeMRUOpen(const wxString &fullPathStr)
916 {
917    return GuardedCall< bool >( [&]{ return MRUOpen( fullPathStr ); } );
918 }
919 
OnMRUClear(wxCommandEvent & WXUNUSED (event))920 void AudacityApp::OnMRUClear(wxCommandEvent& WXUNUSED(event))
921 {
922    FileHistory::Global().Clear();
923 }
924 
925 //vvv Basically, anything from Recent Files is treated as a .aup3, until proven otherwise,
926 // then it tries to Import(). Very questionable handling, imo.
927 // Better, for example, to check the file type early on.
OnMRUFile(wxCommandEvent & event)928 void AudacityApp::OnMRUFile(wxCommandEvent& event) {
929    int n = event.GetId() - FileHistory::ID_RECENT_FIRST;
930    auto &history = FileHistory::Global();
931    const auto &fullPathStr = history[ n ];
932 
933    // Try to open only if not already open.
934    // Test IsAlreadyOpen() here even though AudacityProject::MRUOpen() also now checks,
935    // because we don't want to Remove() just because it already exists,
936    // and AudacityApp::OnMacOpenFile() calls MRUOpen() directly.
937    // that method does not return the bad result.
938    // PRL: Don't call SafeMRUOpen
939    // -- if open fails for some exceptional reason of resource exhaustion that
940    // the user can correct, leave the file in history.
941    if (!ProjectFileManager::IsAlreadyOpen(fullPathStr) && !MRUOpen(fullPathStr))
942       history.Remove(n);
943 }
944 
OnTimer(wxTimerEvent & WXUNUSED (event))945 void AudacityApp::OnTimer(wxTimerEvent& WXUNUSED(event))
946 {
947    // Filenames are queued when Audacity receives a few of the
948    // AppleEvent messages (via wxWidgets).  So, open any that are
949    // in the queue and clean the queue.
950    if (gInited) {
951       if (ofqueue.size()) {
952          // Load each file on the queue
953          while (ofqueue.size()) {
954             wxString name;
955             name.swap(ofqueue[0]);
956             ofqueue.erase( ofqueue.begin() );
957 
958             // Get the user's attention if no file name was specified
959             if (name.empty()) {
960                // Get the users attention
961                if (auto project = GetActiveProject().lock()) {
962                   auto &window = GetProjectFrame( *project );
963                   window.Maximize();
964                   window.Raise();
965                   window.RequestUserAttention();
966                }
967                continue;
968             }
969 
970             // TODO: Handle failures better.
971             // Some failures are OK, e.g. file not found, just would-be-nices to do better,
972             // so FAIL_MSG is more a case of an enhancement request than an actual  problem.
973             // LL:  In all but one case an appropriate message is already displayed.  The
974             //      instance that a message is NOT displayed is when a failure to write
975             //      to the config file has occurred.
976             // PRL: Catch any exceptions, don't try this file again, continue to
977             // other files.
978             if (!SafeMRUOpen(name)) {
979                // Just log it.  Assertion failure is not appropriate on valid
980                // defensive path against bad file data.
981                wxLogMessage(wxT("MRUOpen failed"));
982             }
983          }
984       }
985    }
986 }
987 
988 #if defined(__WXMSW__)
989 #define WL(lang, sublang) (lang), (sublang),
990 #else
991 #define WL(lang,sublang)
992 #endif
993 
994 #if wxCHECK_VERSION(3, 0, 1)
995 wxLanguageInfo userLangs[] =
996 {
997    // Bosnian is defined in wxWidgets already
998 //   { wxLANGUAGE_USER_DEFINED, wxT("bs"), WL(0, SUBLANG_DEFAULT) wxT("Bosnian"), wxLayout_LeftToRight },
999 
1000    { wxLANGUAGE_USER_DEFINED, wxT("eu"), WL(0, SUBLANG_DEFAULT) wxT("Basque"), wxLayout_LeftToRight },
1001 };
1002 #endif
1003 
OnFatalException()1004 void AudacityApp::OnFatalException()
1005 {
1006 #if defined(HAS_CRASH_REPORT)
1007    CrashReport::Generate(wxDebugReport::Context_Exception);
1008 #endif
1009 
1010    exit(-1);
1011 }
1012 
1013 
1014 #ifdef _MSC_VER
1015 // If this is compiled with MSVC (Visual Studio)
1016 #pragma warning( push )
1017 #pragma warning( disable : 4702) // unreachable code warning.
1018 #endif //_MSC_VER
1019 
OnExceptionInMainLoop()1020 bool AudacityApp::OnExceptionInMainLoop()
1021 {
1022    // This function is invoked from catch blocks in the wxWidgets framework,
1023    // and throw; without argument re-throws the exception being handled,
1024    // letting us dispatch according to its type.
1025 
1026    try { throw; }
1027    catch ( AudacityException &e ) {
1028       (void)e;// Compiler food
1029       // Here is the catch-all for our own exceptions
1030 
1031       // Use CallAfter to delay this to the next pass of the event loop,
1032       // rather than risk doing it inside stack unwinding.
1033       auto pProject = ::GetActiveProject().lock();
1034       auto pException = std::current_exception();
1035       CallAfter( [pException, pProject] {
1036 
1037          // Restore the state of the project to what it was before the
1038          // failed operation
1039          if (pProject) {
1040             ProjectHistory::Get( *pProject ).RollbackState();
1041 
1042             // Forget pending changes in the TrackList
1043             TrackList::Get( *pProject ).ClearPendingTracks();
1044 
1045             ProjectWindow::Get( *pProject ).RedrawProject();
1046          }
1047 
1048          // Give the user an alert
1049          try { std::rethrow_exception( pException ); }
1050          catch( AudacityException &e )
1051             { e.DelayedHandlerAction(); }
1052 
1053       } );
1054 
1055       // Don't quit the program
1056       return true;
1057    }
1058    catch ( ... ) {
1059       // There was some other type of exception we don't know.
1060       // Let the inherited function do throw; again and whatever else it does.
1061       return wxApp::OnExceptionInMainLoop();
1062    }
1063    // Shouldn't ever reach this line
1064    return false;
1065 }
1066 #ifdef _MSC_VER
1067 #pragma warning( pop )
1068 #endif //_MSC_VER
1069 
AudacityApp()1070 AudacityApp::AudacityApp()
1071 {
1072 #if defined(USE_BREAKPAD)
1073     InitBreakpad();
1074 // Do not capture crashes in debug builds
1075 #elif !defined(_DEBUG)
1076 #if defined(HAS_CRASH_REPORT)
1077 #if defined(wxUSE_ON_FATAL_EXCEPTION) && wxUSE_ON_FATAL_EXCEPTION
1078    wxHandleFatalExceptions();
1079 #endif
1080 #endif
1081 #endif
1082 }
1083 
~AudacityApp()1084 AudacityApp::~AudacityApp()
1085 {
1086 }
1087 
1088 // The `main program' equivalent, creating the windows and returning the
1089 // main frame
OnInit()1090 bool AudacityApp::OnInit()
1091 {
1092    // JKC: ANSWER-ME: Who actually added the event loop guarantor?
1093    // Although 'blame' says Leland, I think it came from a donated patch.
1094 
1095    // PRL:  It was added by LL at 54676a72285ba7ee3a69920e91fa390a71ef10c9 :
1096    // "   Ensure OnInit() has an event loop
1097    //     And allow events to flow so the splash window updates under GTK"
1098    // then mistakenly lost in the merge at
1099    // 37168ebbf67ae869ab71a3b5cbbf1d2a48e824aa
1100    // then restored at 7687972aa4b2199f0717165235f3ef68ade71e08
1101 
1102    // Ensure we have an event loop during initialization
1103    wxEventLoopGuarantor eventLoop;
1104 
1105    // Inject basic GUI services behind the facade
1106    {
1107       static wxWidgetsBasicUI uiServices;
1108       (void)BasicUI::Install(&uiServices);
1109    }
1110 
1111    // Fire up SQLite
1112    if ( !ProjectFileIO::InitializeSQL() )
1113       this->CallAfter([]{
1114          ::AudacityMessageBox(
1115             XO("SQLite library failed to initialize.  Audacity cannot continue.") );
1116          QuitAudacity( true );
1117       });
1118 
1119 
1120    // cause initialization of wxWidgets' global logger target
1121    (void) AudacityLogger::Get();
1122 
1123 #if defined(__WXMAC__)
1124    // Disable window animation
1125    wxSystemOptions::SetOption(wxMAC_WINDOW_PLAIN_TRANSITION, 1);
1126 #endif
1127 
1128    // Some GTK themes produce larger combo boxes that make them taller
1129    // than our single toolbar height restriction.  This will remove some
1130    // of the extra space themes add.
1131 #if defined(__WXGTK3__) && defined(HAVE_GTK)
1132    GtkWidget *combo = gtk_combo_box_new();
1133    GtkCssProvider *provider = gtk_css_provider_new();
1134    gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(provider),
1135                                    ".linked entry,\n"
1136                                    ".linked button,\n"
1137                                    ".linked combobox box.linked button,\n"
1138                                    ".horizontal.linked entry,\n"
1139                                    ".horizontal.linked button,\n"
1140                                    ".horizontal.linked combobox box.linked button,\n"
1141                                    "combobox {\n"
1142                                    "   padding-top: 0px;\n"
1143                                    "   padding-bottom: 0px;\n"
1144                                    "   padding-left: 4px;\n"
1145                                    "   padding-right: 4px;\n"
1146                                    "   margin: 0px;\n"
1147                                    "   font-size: 95%;\n"
1148                                    "}", -1, NULL);
1149    gtk_style_context_add_provider_for_screen(gtk_widget_get_screen(combo),
1150                                              GTK_STYLE_PROVIDER (provider),
1151                                              GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1152    g_object_unref(provider);
1153    g_object_unref(combo);
1154 #elif defined(__WXGTK__) && defined(HAVE_GTK)
1155    gtk_rc_parse_string("style \"audacity\" {\n"
1156                        " GtkButton::inner_border = { 0, 0, 0, 0 }\n"
1157                        " GtkEntry::inner_border = { 0, 0, 0, 0 }\n"
1158                        " xthickness = 4\n"
1159                        " ythickness = 0\n"
1160                        "}\n"
1161                        "widget_class \"*GtkCombo*\" style \"audacity\"");
1162 #endif
1163 
1164    wxTheApp->SetAppName(AppName);
1165    // Explicitly set since OSX will use it for the "Quit" menu item
1166    wxTheApp->SetAppDisplayName(AppName);
1167    wxTheApp->SetVendorName(AppName);
1168 
1169    ::wxInitAllImageHandlers();
1170 
1171    // AddHandler takes ownership
1172    wxFileSystem::AddHandler(safenew wxZipFSHandler);
1173 
1174    //
1175    // Paths: set search path and temp dir path
1176    //
1177    FilePaths audacityPathList;
1178 
1179 #ifdef __WXGTK__
1180    wxStandardPaths standardPaths = wxStandardPaths::Get();
1181    wxString portablePrefix = wxPathOnly(wxPathOnly(standardPaths.GetExecutablePath()));
1182 
1183    // Make sure install prefix is set so wxStandardPath resolves paths properly
1184    if (wxDirExists(portablePrefix + L"/share/audacity")) {
1185       // use prefix relative to executable location to make Audacity portable
1186       standardPaths.SetInstallPrefix(portablePrefix);
1187    } else {
1188       // fallback to hard-coded prefix set during configuration
1189       standardPaths.SetInstallPrefix(wxT(INSTALL_PREFIX));
1190    }
1191    wxString installPrefix = standardPaths.GetInstallPrefix();
1192 
1193    /* Search path (for plug-ins, translations etc) is (in this order):
1194       * The AUDACITY_PATH environment variable
1195       * The current directory
1196       * The user's "~/.audacity-data" or "Portable Settings" directory
1197       * The user's "~/.audacity-files" directory
1198       * The "share" and "share/doc" directories in their install path */
1199    wxString home = wxGetHomeDir();
1200 
1201    wxString envTempDir = wxGetenv(wxT("TMPDIR"));
1202    if (!envTempDir.empty()) {
1203       /* On Unix systems, the environment variable TMPDIR may point to
1204          an unusual path when /tmp and /var/tmp are not desirable. */
1205       TempDirectory::SetDefaultTempDir( wxString::Format(
1206          wxT("%s/audacity-%s"), envTempDir, wxGetUserId() ) );
1207    } else {
1208       /* On Unix systems, the default temp dir is in /var/tmp. */
1209       TempDirectory::SetDefaultTempDir( wxString::Format(
1210          wxT("/var/tmp/audacity-%s"), wxGetUserId() ) );
1211    }
1212 
1213 // DA: Path env variable.
1214 #ifndef EXPERIMENTAL_DA
1215    wxString pathVar = wxGetenv(wxT("AUDACITY_PATH"));
1216 #else
1217    wxString pathVar = wxGetenv(wxT("DARKAUDACITY_PATH"));
1218 #endif
1219    if (!pathVar.empty())
1220       FileNames::AddMultiPathsToPathList(pathVar, audacityPathList);
1221    FileNames::AddUniquePathToPathList(::wxGetCwd(), audacityPathList);
1222 
1223    wxString progPath = wxPathOnly(argv[0]);
1224    FileNames::AddUniquePathToPathList(progPath, audacityPathList);
1225    // Add the path to modules:
1226    FileNames::AddUniquePathToPathList(progPath + L"/lib/audacity", audacityPathList);
1227 
1228    FileNames::AddUniquePathToPathList(FileNames::DataDir(), audacityPathList);
1229 
1230 #ifdef AUDACITY_NAME
1231    FileNames::AddUniquePathToPathList(wxString::Format(wxT("%s/.%s-files"),
1232       home, wxT(AUDACITY_NAME)),
1233       audacityPathList);
1234    FileNames::AddUniquePathToPathList(FileNames::ModulesDir(),
1235       audacityPathList);
1236    FileNames::AddUniquePathToPathList(wxString::Format(installPrefix + L"/share/%s", wxT(AUDACITY_NAME)),
1237       audacityPathList);
1238    FileNames::AddUniquePathToPathList(wxString::Format(installPrefix + L"/share/doc/%s", wxT(AUDACITY_NAME)),
1239       audacityPathList);
1240 #else //AUDACITY_NAME
1241    FileNames::AddUniquePathToPathList(wxString::Format(wxT("%s/.audacity-files"),
1242       home),
1243       audacityPathList)
1244    FileNames::AddUniquePathToPathList(FileNames::ModulesDir(),
1245       audacityPathList);
1246    FileNames::AddUniquePathToPathList(installPrefix + L"/share/audacity"),
1247       audacityPathList);
1248    FileNames::AddUniquePathToPathList(installPrefix + L"/share/doc/audacity",
1249       audacityPathList);
1250 #endif //AUDACITY_NAME
1251 
1252    FileNames::AddUniquePathToPathList(installPrefix + L"/share/locale",
1253       audacityPathList);
1254 
1255    FileNames::AddUniquePathToPathList(wxString::Format(wxT("./locale")),
1256       audacityPathList);
1257 
1258 #endif //__WXGTK__
1259 
1260 // JKC Bug 1220: Use path based on home directory on WXMAC
1261 #ifdef __WXMAC__
1262    wxFileName tmpFile;
1263    tmpFile.AssignHomeDir();
1264    wxString tmpDirLoc = tmpFile.GetPath(wxPATH_GET_VOLUME);
1265 #else
1266    wxFileName tmpFile;
1267    tmpFile.AssignTempFileName(wxT("nn"));
1268    wxString tmpDirLoc = tmpFile.GetPath(wxPATH_GET_VOLUME);
1269    ::wxRemoveFile(tmpFile.GetFullPath());
1270 #endif
1271 
1272 
1273 
1274    // On Mac and Windows systems, use the directory which contains Audacity.
1275 #ifdef __WXMSW__
1276    // On Windows, the path to the Audacity program is in argv[0]
1277    wxString progPath = wxPathOnly(argv[0]);
1278    FileNames::AddUniquePathToPathList(progPath, audacityPathList);
1279    FileNames::AddUniquePathToPathList(progPath + wxT("\\Languages"), audacityPathList);
1280 
1281    // See bug #1271 for explanation of location
1282    tmpDirLoc = FileNames::MkDir(wxStandardPaths::Get().GetUserLocalDataDir());
1283    TempDirectory::SetDefaultTempDir( wxString::Format(
1284       wxT("%s\\SessionData"), tmpDirLoc ) );
1285 #endif //__WXWSW__
1286 
1287 #ifdef __WXMAC__
1288    // On Mac OS X, the path to the Audacity program is in argv[0]
1289    wxString progPath = wxPathOnly(argv[0]);
1290 
1291    FileNames::AddUniquePathToPathList(progPath, audacityPathList);
1292    // If Audacity is a "bundle" package, then the root directory is
1293    // the great-great-grandparent of the directory containing the executable.
1294    //FileNames::AddUniquePathToPathList(progPath + wxT("/../../../"), audacityPathList);
1295 
1296    // These allow for searching the "bundle"
1297    FileNames::AddUniquePathToPathList(
1298       progPath + wxT("/../"), audacityPathList);
1299    FileNames::AddUniquePathToPathList(
1300       progPath + wxT("/../Resources"), audacityPathList);
1301 
1302    // JKC Bug 1220: Using an actual temp directory for session data on Mac was
1303    // wrong because it would get cleared out on a reboot.
1304    TempDirectory::SetDefaultTempDir( wxString::Format(
1305       wxT("%s/Library/Application Support/audacity/SessionData"), tmpDirLoc) );
1306 
1307    //TempDirectory::SetDefaultTempDir( wxString::Format(
1308    //   wxT("%s/audacity-%s"),
1309    //   tmpDirLoc,
1310    //   wxGetUserId() ) );
1311 #endif //__WXMAC__
1312 
1313    FileNames::SetAudacityPathList( std::move( audacityPathList ) );
1314 
1315    // Define languages for which we have translations, but that are not yet
1316    // supported by wxWidgets.
1317    //
1318    // TODO:  The whole Language initialization really need to be reworked.
1319    //        It's all over the place.
1320 #if wxCHECK_VERSION(3, 0, 1)
1321    for (size_t i = 0, cnt = WXSIZEOF(userLangs); i < cnt; i++)
1322    {
1323       wxLocale::AddLanguage(userLangs[i]);
1324    }
1325 #endif
1326 
1327    // Initialize preferences and language
1328    {
1329       wxFileName configFileName(FileNames::DataDir(), wxT("audacity.cfg"));
1330       auto appName = wxTheApp->GetAppName();
1331       InitPreferences( AudacityFileConfig::Create(
1332          appName, wxEmptyString,
1333          configFileName.GetFullPath(),
1334          wxEmptyString, wxCONFIG_USE_LOCAL_FILE) );
1335       PopulatePreferences();
1336    }
1337 
1338 #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__) && !defined(__CYGWIN__)
1339    this->AssociateFileTypes();
1340 #endif
1341 
1342    theTheme.SetOnPreferredSystemAppearanceChanged([this](PreferredSystemAppearance appearance){
1343        SetPreferredSystemAppearance(appearance);
1344    });
1345 
1346    theTheme.LoadPreferredTheme();
1347 
1348    // AColor depends on theTheme.
1349    AColor::Init();
1350 
1351    // If this fails, we must exit the program.
1352    if (!InitTempDir()) {
1353       FinishPreferences();
1354       return false;
1355    }
1356 
1357 #ifdef __WXMAC__
1358    // Bug2437:  When files are opened from Finder and another instance of
1359    // Audacity is running, we must return from OnInit() to wxWidgets before
1360    // MacOpenFile is called, informing us of the paths that need to be
1361    // opened.  So use CallAfter() to delay the rest of initialization.
1362    // See CreateSingleInstanceChecker() where we send those paths over a
1363    // socket to the prior instance.
1364 
1365    // This call is what probably makes the sleep unnecessary:
1366    MacFinishLaunching();
1367 
1368    using namespace std::chrono;
1369    // This sleep may be unnecessary, but it is harmless.  It less NS framework
1370    // events arrive on another thread, but it might not always be long enough.
1371    std::this_thread::sleep_for(100ms);
1372    CallAfter([this]{
1373       if (!InitPart2())
1374          exit(-1);
1375    });
1376    return true;
1377 #else
1378    return InitPart2();
1379 #endif
1380 }
1381 
InitPart2()1382 bool AudacityApp::InitPart2()
1383 {
1384 #if defined(__WXMAC__)
1385    SetExitOnFrameDelete(false);
1386 #endif
1387 
1388    // Make sure the temp dir isn't locked by another process.
1389    {
1390       auto key =
1391          PreferenceKey(FileNames::Operation::Temp, FileNames::PathType::_None);
1392       auto temp = gPrefs->Read(key);
1393       if (temp.empty() || !CreateSingleInstanceChecker(temp)) {
1394          FinishPreferences();
1395          return false;
1396       }
1397    }
1398 
1399    //<<<< Try to avoid dialogs before this point.
1400    // The reason is that InitTempDir starts the single instance checker.
1401    // If we're waiitng in a dialog before then we can very easily
1402    // start multiple instances, defeating the single instance checker.
1403 
1404    // Initialize the CommandHandler
1405    InitCommandHandler();
1406 
1407    // Initialize the ModuleManager, including loading found modules
1408    ModuleManager::Get().Initialize();
1409 
1410    // Initialize the PluginManager
1411    PluginManager::Get().Initialize();
1412 
1413    // Parse command line and handle options that might require
1414    // immediate exit...no need to initialize all of the audio
1415    // stuff to display the version string.
1416    std::shared_ptr< wxCmdLineParser > parser{ ParseCommandLine() };
1417    if (!parser)
1418    {
1419       // Either user requested help or a parsing error occurred
1420       exit(1);
1421    }
1422 
1423    if (parser->Found(wxT("v")))
1424    {
1425       wxPrintf("Audacity v%s\n", AUDACITY_VERSION_STRING);
1426       exit(0);
1427    }
1428 
1429    long lval;
1430    if (parser->Found(wxT("b"), &lval))
1431    {
1432       if (lval < 256 || lval > 100000000)
1433       {
1434          wxPrintf(_("Block size must be within 256 to 100000000\n"));
1435          exit(1);
1436       }
1437 
1438       Sequence::SetMaxDiskBlockSize(lval);
1439    }
1440 
1441    wxString fileName;
1442    if (parser->Found(wxT("j"), &fileName))
1443       Journal::SetInputFileName( fileName );
1444 
1445    // BG: Create a temporary window to set as the top window
1446    wxImage logoimage((const char **)AudacityLogoWithName_xpm);
1447    logoimage.Rescale(logoimage.GetWidth() / 2, logoimage.GetHeight() / 2);
1448    if( GetLayoutDirection() == wxLayout_RightToLeft)
1449       logoimage = logoimage.Mirror();
1450    wxBitmap logo(logoimage);
1451 
1452    AudacityProject *project;
1453    {
1454       // Bug 718: Position splash screen on same screen
1455       // as where Audacity project will appear.
1456       wxRect wndRect;
1457       bool bMaximized = false;
1458       bool bIconized = false;
1459       GetNextWindowPlacement(&wndRect, &bMaximized, &bIconized);
1460 
1461       wxSplashScreen temporarywindow(
1462          logo,
1463          wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_NO_TIMEOUT,
1464          0,
1465          NULL,
1466          wxID_ANY,
1467          wndRect.GetTopLeft(),
1468          wxDefaultSize,
1469          wxSTAY_ON_TOP);
1470 
1471       // Unfortunately with the Windows 10 Creators update, the splash screen
1472       // now appears before setting its position.
1473       // On a dual monitor screen it will appear on one screen and then
1474       // possibly jump to the second.
1475       // We could fix this by writing our own splash screen and using Hide()
1476       // until the splash scren was correctly positioned, then Show()
1477 
1478       // Possibly move it on to the second screen...
1479       temporarywindow.SetPosition( wndRect.GetTopLeft() );
1480       // Centered on whichever screen it is on.
1481       temporarywindow.Center();
1482       temporarywindow.SetTitle(_("Audacity is starting up..."));
1483       SetTopWindow(&temporarywindow);
1484       temporarywindow.Raise();
1485 
1486       // ANSWER-ME: Why is YieldFor needed at all?
1487       //wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI|wxEVT_CATEGORY_USER_INPUT|wxEVT_CATEGORY_UNKNOWN);
1488       wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI);
1489 
1490       //JKC: Would like to put module loading here.
1491 
1492       // More initialization
1493 
1494       InitDitherers();
1495       AudioIO::Init();
1496 
1497 #ifdef __WXMAC__
1498 
1499       // On the Mac, users don't expect a program to quit when you close the last window.
1500       // Create a menubar that will show when all project windows are closed.
1501 
1502       auto fileMenu = std::make_unique<wxMenu>();
1503       auto urecentMenu = std::make_unique<wxMenu>();
1504       auto recentMenu = urecentMenu.get();
1505       fileMenu->Append(wxID_NEW, wxString(_("&New")) + wxT("\tCtrl+N"));
1506       fileMenu->Append(wxID_OPEN, wxString(_("&Open...")) + wxT("\tCtrl+O"));
1507       fileMenu->AppendSubMenu(urecentMenu.release(), _("Open &Recent..."));
1508       fileMenu->Append(wxID_ABOUT, _("&About Audacity..."));
1509       fileMenu->Append(wxID_PREFERENCES, wxString(_("&Preferences...")) + wxT("\tCtrl+,"));
1510 
1511       {
1512          auto menuBar = std::make_unique<wxMenuBar>();
1513          menuBar->Append(fileMenu.release(), _("&File"));
1514 
1515          // PRL:  Are we sure wxWindows will not leak this menuBar?
1516          // The online documentation is not explicit.
1517          wxMenuBar::MacSetCommonMenuBar(menuBar.release());
1518       }
1519 
1520       auto &recentFiles = FileHistory::Global();
1521       recentFiles.UseMenu(recentMenu);
1522 
1523 #endif //__WXMAC__
1524       temporarywindow.Show(false);
1525    }
1526 
1527    // Must do this before creating the first project, else the early exit path
1528    // may crash
1529    if ( !Journal::Begin( FileNames::DataDir() ) )
1530       return false;
1531 
1532    // Workaround Bug 1377 - Crash after Audacity starts and low disk space warning appears
1533    // The temporary splash window is closed AND cleaned up, before attempting to create
1534    // a project and possibly creating a modal warning dialog by doing so.
1535    // Also fixes problem of warning being obscured.
1536    // Downside is that we have no splash screen for the (brief) time that we spend
1537    // creating the project.
1538    // Root cause is problem with wxSplashScreen and other dialogs co-existing, that
1539    // seemed to arrive with wx3.
1540    {
1541       project = ProjectManager::New();
1542    }
1543 
1544    if( ProjectSettings::Get( *project ).GetShowSplashScreen() ){
1545       // This may do a check-for-updates at every start up.
1546       // Mainly this is to tell users of ALPHAS who don't know that they have an ALPHA.
1547       // Disabled for now, after discussion.
1548       // project->MayCheckForUpdates();
1549       SplashDialog::DoHelpWelcome(*project);
1550    }
1551 
1552 #if defined(HAVE_UPDATES_CHECK)
1553    UpdateManager::Start();
1554 #endif
1555 
1556    #ifdef USE_FFMPEG
1557    FFmpegStartup();
1558    #endif
1559 
1560    Importer::Get().Initialize();
1561 
1562    // Bug1561: delay the recovery dialog, to avoid crashes.
1563    CallAfter( [=] () mutable {
1564       // Remove duplicate shortcuts when there's a change of version
1565       int vMajorInit, vMinorInit, vMicroInit;
1566       gPrefs->GetVersionKeysInit(vMajorInit, vMinorInit, vMicroInit);
1567       if (vMajorInit != AUDACITY_VERSION || vMinorInit != AUDACITY_RELEASE
1568          || vMicroInit != AUDACITY_REVISION) {
1569          CommandManager::Get(*project).RemoveDuplicateShortcuts();
1570       }
1571       //
1572       // Auto-recovery
1573       //
1574       bool didRecoverAnything = false;
1575       // This call may reassign project (passed by reference)
1576       if (!ShowAutoRecoveryDialogIfNeeded(project, &didRecoverAnything))
1577       {
1578          QuitAudacity(true);
1579       }
1580 
1581       //
1582       // Remainder of command line parsing, but only if we didn't recover
1583       //
1584       if (project && !didRecoverAnything)
1585       {
1586          if (parser->Found(wxT("t")))
1587          {
1588             RunBenchmark( nullptr, *project);
1589             QuitAudacity(true);
1590          }
1591 
1592          for (size_t i = 0, cnt = parser->GetParamCount(); i < cnt; i++)
1593          {
1594             // PRL: Catch any exceptions, don't try this file again, continue to
1595             // other files.
1596             SafeMRUOpen(parser->GetParam(i));
1597          }
1598       }
1599    } );
1600 
1601    gInited = true;
1602 
1603    ModuleManager::Get().Dispatch(AppInitialized);
1604 
1605    mTimer.SetOwner(this, kAudacityAppTimerID);
1606    mTimer.Start(200);
1607 
1608 #ifdef EXPERIMENTAL_EASY_CHANGE_KEY_BINDINGS
1609    CommandManager::SetMenuHook( [](const CommandID &id){
1610       if (::wxGetMouseState().ShiftDown()) {
1611          // Only want one page of the preferences
1612          PrefsPanel::Factories factories;
1613          factories.push_back(KeyConfigPrefsFactory( id ));
1614          const auto pProject = GetActiveProject().lock();
1615          auto pWindow = FindProjectFrame( pProject.get() );
1616          // pProject may be null
1617          GlobalPrefsDialog dialog( pWindow, pProject.get(), factories );
1618          dialog.ShowModal();
1619          MenuCreator::RebuildAllMenuBars();
1620          return true;
1621       }
1622       else
1623          return false;
1624    } );
1625 #endif
1626 
1627 #if defined(__WXMAC__)
1628    // The first time this version of Audacity is run or when the preferences
1629    // are reset, execute the "tccutil" command to reset the microphone permissions
1630    // currently assigned to Audacity.  The end result is that the user will be
1631    // prompted to approve/deny Audacity access (again).
1632    //
1633    // This should resolve confusion of why Audacity appears to record, but only
1634    // gets silence due to Audacity being denied microphone access previously.
1635    bool permsReset = false;
1636    gPrefs->Read(wxT("/MicrophonePermissionsReset"), &permsReset, false);
1637    if (!permsReset) {
1638       system("tccutil reset Microphone org.audacityteam.audacity");
1639       gPrefs->Write(wxT("/MicrophonePermissionsReset"), true);
1640    }
1641 #endif
1642 
1643 #if defined(__WXMAC__)
1644    // Bug 2709: Workaround CoreSVG locale issue
1645    Bind(wxEVT_MENU_OPEN, [=](wxMenuEvent &event)
1646    {
1647       wxSetlocale(LC_NUMERIC, wxString(wxT("C")));
1648       event.Skip();
1649    });
1650 
1651    Bind(wxEVT_MENU_CLOSE, [=](wxMenuEvent &event)
1652    {
1653       wxSetlocale(LC_NUMERIC, Languages::GetLocaleName());
1654       event.Skip();
1655    });
1656 #endif
1657 
1658    return TRUE;
1659 }
1660 
OnRun()1661 int AudacityApp::OnRun()
1662 {
1663    // Returns 0 to the command line if the run completed normally
1664    auto result = wxApp::OnRun();
1665    if (result == 0)
1666       // If not otherwise abnormal, report any journal sync failure
1667       result = Journal::GetExitCode();
1668    return result;
1669 }
1670 
OnIdle(wxIdleEvent & evt)1671 void AudacityApp::OnIdle( wxIdleEvent &evt )
1672 {
1673    evt.Skip();
1674    try {
1675       if ( Journal::Dispatch() )
1676          evt.RequestMore();
1677    }
1678    catch( ... ) {
1679       // Hmm, wxWidgets doesn't guard calls to the idle handler as for other
1680       // events.  So replicate some of the try-catch logic here.
1681       OnExceptionInMainLoop();
1682       // Fall through and return, allowing delayed handler action of
1683       // AudacityException to clean up
1684    }
1685 }
1686 
InitCommandHandler()1687 void AudacityApp::InitCommandHandler()
1688 {
1689    mCmdHandler = std::make_unique<CommandHandler>();
1690    //SetNextHandler(mCmdHandler);
1691 }
1692 
1693 // AppCommandEvent callback - just pass the event on to the CommandHandler
OnReceiveCommand(AppCommandEvent & event)1694 void AudacityApp::OnReceiveCommand(AppCommandEvent &event)
1695 {
1696    wxASSERT(NULL != mCmdHandler);
1697    mCmdHandler->OnReceiveCommand(event);
1698 }
1699 
OnKeyDown(wxKeyEvent & event)1700 void AudacityApp::OnKeyDown(wxKeyEvent &event)
1701 {
1702    if(event.GetKeyCode() == WXK_ESCAPE) {
1703       // Stop play, including scrub, but not record
1704       if ( auto project = ::GetActiveProject().lock() ) {
1705          auto token = ProjectAudioIO::Get( *project ).GetAudioIOToken();
1706          auto &scrubber = Scrubber::Get( *project );
1707          auto scrubbing = scrubber.HasMark();
1708          if (scrubbing)
1709             scrubber.Cancel();
1710          auto gAudioIO = AudioIO::Get();
1711          if((token > 0 &&
1712                   gAudioIO->IsAudioTokenActive(token) &&
1713                   gAudioIO->GetNumCaptureChannels() == 0) ||
1714             scrubbing)
1715             // ESC out of other play (but not record)
1716             ProjectAudioManager::Get( *project ).Stop();
1717          else
1718             event.Skip();
1719       }
1720    }
1721 
1722    event.Skip();
1723 }
1724 
1725 // Ensures directory is created and puts the name into result.
1726 // result is unchanged if unsuccessful.
SetToExtantDirectory(wxString & result,const wxString & dir)1727 void SetToExtantDirectory( wxString & result, const wxString & dir ){
1728    // don't allow path of "".
1729    if( dir.empty() )
1730       return;
1731    if( wxDirExists( dir ) ){
1732       result = dir;
1733       return;
1734    }
1735    // Use '/' so that this works on Mac and Windows alike.
1736    wxFileName name( dir + "/junkname.cfg" );
1737    if( name.Mkdir( wxS_DIR_DEFAULT , wxPATH_MKDIR_FULL ) )
1738       result = dir;
1739 }
1740 
InitTempDir()1741 bool AudacityApp::InitTempDir()
1742 {
1743    // We need to find a temp directory location.
1744    auto tempFromPrefs = TempDirectory::TempDir();
1745    auto tempDefaultLoc = TempDirectory::DefaultTempDir();
1746 
1747    wxString temp;
1748 
1749    #ifdef __WXGTK__
1750    if (tempFromPrefs.length() > 0 && tempFromPrefs[0] != wxT('/'))
1751       tempFromPrefs = wxT("");
1752    #endif
1753 
1754    // Stop wxWidgets from printing its own error messages
1755 
1756    wxLogNull logNo;
1757 
1758    // Try temp dir that was stored in prefs first
1759    if( TempDirectory::IsTempDirectoryNameOK( tempFromPrefs ) )
1760       SetToExtantDirectory( temp, tempFromPrefs );
1761 
1762    // If that didn't work, try the default location
1763 
1764    if (temp.empty())
1765       SetToExtantDirectory( temp, tempDefaultLoc );
1766 
1767    // Check temp directory ownership on *nix systems only
1768    #ifdef __UNIX__
1769    struct stat tempStatBuf;
1770    if ( lstat(temp.mb_str(), &tempStatBuf) != 0 ) {
1771       temp.clear();
1772    }
1773    else {
1774       if ( geteuid() != tempStatBuf.st_uid ) {
1775          temp.clear();
1776       }
1777    }
1778    #endif
1779 
1780    if (temp.empty()) {
1781       // Failed
1782       if( !TempDirectory::IsTempDirectoryNameOK( tempFromPrefs ) ) {
1783          AudacityMessageBox(XO(
1784 "Audacity could not find a safe place to store temporary files.\nAudacity needs a place where automatic cleanup programs won't delete the temporary files.\nPlease enter an appropriate directory in the preferences dialog."));
1785       } else {
1786          AudacityMessageBox(XO(
1787 "Audacity could not find a place to store temporary files.\nPlease enter an appropriate directory in the preferences dialog."));
1788       }
1789 
1790       // Only want one page of the preferences
1791       PrefsPanel::Factories factories;
1792       factories.push_back(DirectoriesPrefsFactory());
1793       GlobalPrefsDialog dialog(nullptr, nullptr, factories);
1794       dialog.ShowModal();
1795 
1796       AudacityMessageBox(XO(
1797 "Audacity is now going to exit. Please launch Audacity again to use the new temporary directory."));
1798       return false;
1799    }
1800 
1801    // The permissions don't always seem to be set on
1802    // some platforms.  Hopefully this fixes it...
1803    #ifdef __UNIX__
1804    chmod(OSFILENAME(temp), 0700);
1805    #endif
1806 
1807    TempDirectory::ResetTempDir();
1808    FileNames::UpdateDefaultPath(FileNames::Operation::Temp, temp);
1809 
1810    return true;
1811 }
1812 
1813 #if defined(__WXMSW__)
1814 
1815 // Return true if there are no other instances of Audacity running,
1816 // false otherwise.
1817 
CreateSingleInstanceChecker(const wxString & dir)1818 bool AudacityApp::CreateSingleInstanceChecker(const wxString &dir)
1819 {
1820    wxString name = wxString::Format(wxT("audacity-lock-%s"), wxGetUserId());
1821    mChecker.reset();
1822    auto checker = std::make_unique<wxSingleInstanceChecker>();
1823 
1824    auto runningTwoCopiesStr = XO("Running two copies of Audacity simultaneously may cause\ndata loss or cause your system to crash.\n\n");
1825 
1826    if (!checker->Create(name, dir))
1827    {
1828       // Error initializing the wxSingleInstanceChecker.  We don't know
1829       // whether there is another instance running or not.
1830 
1831       auto prompt = XO(
1832 "Audacity was not able to lock the temporary files directory.\nThis folder may be in use by another copy of Audacity.\n")
1833          + runningTwoCopiesStr
1834          + XO("Do you still want to start Audacity?");
1835       int action = AudacityMessageBox(
1836          prompt,
1837          XO("Error Locking Temporary Folder"),
1838          wxYES_NO | wxICON_EXCLAMATION, NULL);
1839       if (action == wxNO)
1840          return false;
1841    }
1842    else if ( checker->IsAnotherRunning() ) {
1843       // Parse the command line to ensure correct syntax, but
1844       // ignore options other than -v, and only use the filenames, if any.
1845       auto parser = ParseCommandLine();
1846       if (!parser)
1847       {
1848          // Complaints have already been made
1849          return false;
1850       }
1851 
1852       if (parser->Found(wxT("v")))
1853       {
1854          wxPrintf("Audacity v%s\n", AUDACITY_VERSION_STRING);
1855          return false;
1856       }
1857 
1858       // Windows and Linux require absolute file names as command may
1859       // not come from current working directory.
1860       FilePaths filenames;
1861       for (size_t i = 0, cnt = parser->GetParamCount(); i < cnt; i++)
1862       {
1863          wxFileName filename(parser->GetParam(i));
1864          if (filename.MakeAbsolute())
1865          {
1866             filenames.push_back(filename.GetLongPath());
1867          }
1868       }
1869 
1870       // On Windows, we attempt to make a connection
1871       // to an already active Audacity.  If successful, we send
1872       // the first command line argument (the audio file name)
1873       // to that Audacity for processing.
1874       wxClient client;
1875 
1876       // We try up to 50 times since there's a small window
1877       // where the server may not have been fully initialized.
1878       for (int i = 0; i < 50; i++)
1879       {
1880          std::unique_ptr<wxConnectionBase> conn{ client.MakeConnection(wxEmptyString, IPC_APPL, IPC_TOPIC) };
1881          if (conn)
1882          {
1883             bool ok = false;
1884             if (filenames.size() > 0)
1885             {
1886                for (size_t i = 0, cnt = filenames.size(); i < cnt; i++)
1887                {
1888                   ok = conn->Execute(filenames[i]);
1889                }
1890             }
1891             else
1892             {
1893                // Send an empty string to force existing Audacity to front
1894                ok = conn->Execute(wxEmptyString);
1895             }
1896 
1897             if (ok)
1898                return false;
1899          }
1900 
1901          wxMilliSleep(10);
1902       }
1903       // There is another copy of Audacity running.  Force quit.
1904 
1905       auto prompt =  XO(
1906 "The system has detected that another copy of Audacity is running.\n")
1907          + runningTwoCopiesStr
1908          + XO(
1909 "Use the New or Open commands in the currently running Audacity\nprocess to open multiple projects simultaneously.\n");
1910       AudacityMessageBox(
1911          prompt, XO("Audacity is already running"),
1912          wxOK | wxICON_ERROR);
1913 
1914       return false;
1915    }
1916 
1917    // Create the DDE IPC server
1918    mIPCServ = std::make_unique<IPCServ>(IPC_APPL);
1919    mChecker = std::move(checker);
1920    return true;
1921 }
1922 #endif
1923 
1924 #if defined(__UNIX__)
1925 
1926 #include <sys/ipc.h>
1927 #include <sys/sem.h>
1928 #include <sys/shm.h>
1929 
1930 // Return true if there are no other instances of Audacity running,
1931 // false otherwise.
1932 
CreateSingleInstanceChecker(const wxString & dir)1933 bool AudacityApp::CreateSingleInstanceChecker(const wxString &dir)
1934 {
1935    mIPCServ.reset();
1936 
1937    bool isServer = false;
1938    wxIPV4address addr;
1939    addr.LocalHost();
1940 
1941    struct sembuf op = {};
1942 
1943    // Generate the IPC key we'll use for both shared memory and semaphores.
1944    wxString datadir = FileNames::DataDir();
1945    key_t memkey = ftok(datadir.c_str(), 0);
1946    key_t servkey = ftok(datadir.c_str(), 1);
1947    key_t lockkey = ftok(datadir.c_str(), 2);
1948 
1949    // Create and map the shared memory segment where the port number
1950    // will be stored.
1951    int memid = shmget(memkey, sizeof(int), IPC_CREAT | S_IRUSR | S_IWUSR);
1952    int *portnum = (int *) shmat(memid, nullptr, 0);
1953 
1954    // Create (or return) the SERVER semaphore ID
1955    int servid = semget(servkey, 1, IPC_CREAT | S_IRUSR | S_IWUSR);
1956 
1957    // Create the LOCK semaphore only if it doesn't already exist.
1958    int lockid = semget(lockkey, 1, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
1959 
1960    // If the LOCK semaphore was successfully created, then this is the first
1961    // time Audacity has been run during this boot of the system. In this
1962    // case we know we'll become the "server" application, so set up the
1963    // semaphores to prepare for it.
1964    if (lockid != -1)
1965    {
1966       // Initialize value of each semaphore, 1 indicates released and 0
1967       // indicates acquired.
1968       //
1969       // Note that this action is NOT recorded in the semaphore's
1970       // UNDO buffer.
1971       semctl(servid, 0, SETVAL, 1);
1972       semctl(lockid, 0, SETVAL, 1);
1973 
1974       // Now acquire them so the semaphores will be set to the
1975       // released state when the process terminates.
1976       op.sem_num = 0;
1977       op.sem_op = -1;
1978       op.sem_flg = SEM_UNDO;
1979       if (semop(lockid, &op, 1) == -1 || semop(servid, &op, 1) == -1)
1980       {
1981          AudacityMessageBox(
1982             XO("Unable to acquire semaphores.\n\n"
1983                "This is likely due to a resource shortage\n"
1984                "and a reboot may be required."),
1985             XO("Audacity Startup Failure"),
1986             wxOK | wxICON_ERROR);
1987 
1988          return false;
1989       }
1990 
1991       // We will be the server...
1992       isServer = true;
1993    }
1994    // Something catastrophic must have happened, so bail.
1995    else if (errno != EEXIST)
1996    {
1997       AudacityMessageBox(
1998          XO("Unable to create semaphores.\n\n"
1999             "This is likely due to a resource shortage\n"
2000             "and a reboot may be required."),
2001          XO("Audacity Startup Failure"),
2002          wxOK | wxICON_ERROR);
2003 
2004       return false;
2005    }
2006    // Otherwise it's a normal startup and we need to determine whether
2007    // we'll be the server or the client.
2008    else
2009    {
2010       // Retrieve the LOCK semaphore since we wouldn't have gotten it above.
2011       lockid = semget(lockkey, 1, 0);
2012 
2013       // Acquire the LOCK semaphore. We may block here if another
2014       // process is currently setting up the server.
2015       op.sem_num = 0;
2016       op.sem_op = -1;
2017       op.sem_flg = SEM_UNDO;
2018       if (semop(lockid, &op, 1) == -1)
2019       {
2020          AudacityMessageBox(
2021             XO("Unable to acquire lock semaphore.\n\n"
2022                "This is likely due to a resource shortage\n"
2023                "and a reboot may be required."),
2024             XO("Audacity Startup Failure"),
2025             wxOK | wxICON_ERROR);
2026 
2027          return false;
2028       }
2029 
2030       // Try to acquire the SERVER semaphore. If it's not currently active, then
2031       // we will become the server. Otherwise, this will fail and we'll know that
2032       // the server is already active and we will become the client.
2033       op.sem_num = 0;
2034       op.sem_op = -1;
2035       op.sem_flg = IPC_NOWAIT | SEM_UNDO;
2036       if (semop(servid, &op, 1) == 0)
2037       {
2038          isServer = true;
2039       }
2040       else if (errno != EAGAIN)
2041       {
2042          AudacityMessageBox(
2043             XO("Unable to acquire server semaphore.\n\n"
2044                "This is likely due to a resource shortage\n"
2045                "and a reboot may be required."),
2046             XO("Audacity Startup Failure"),
2047             wxOK | wxICON_ERROR);
2048 
2049          return false;
2050       }
2051    }
2052 
2053    // Initialize the socket server if we're to be the server.
2054    if (isServer)
2055    {
2056       // The system will randomly assign a port
2057       addr.Service(0);
2058 
2059       // Create the socket and bind to it.
2060       auto serv = std::make_unique<wxSocketServer>(addr, wxSOCKET_NOWAIT);
2061       if (serv && serv->IsOk())
2062       {
2063          serv->SetEventHandler(*this, ID_IPC_SERVER);
2064          serv->SetNotify(wxSOCKET_CONNECTION_FLAG);
2065          serv->Notify(true);
2066          mIPCServ = std::move(serv);
2067 
2068          // Save the port number in shared memory so that clients
2069          // know where to connect.
2070          mIPCServ->GetLocal(addr);
2071          *portnum = addr.Service();
2072       }
2073 
2074       // Now that the server is active, we release the LOCK semaphore
2075       // to allow any waiters to continue. The SERVER semaphore will
2076       // remain locked for the duration of this processes execution
2077       // and will be cleaned up by the system.
2078       op.sem_num = 0;
2079       op.sem_op = 1;
2080       semop(lockid, &op, 1);
2081 
2082       // Bail if the server creation failed.
2083       if (mIPCServ == nullptr)
2084       {
2085          AudacityMessageBox(
2086             XO("The Audacity IPC server failed to initialize.\n\n"
2087                "This is likely due to a resource shortage\n"
2088                "and a reboot may be required."),
2089             XO("Audacity Startup Failure"),
2090             wxOK | wxICON_ERROR);
2091 
2092          return false;
2093       }
2094 
2095       // We've successfully created the socket server and the app
2096       // should continue to initialize.
2097       return true;
2098    }
2099 
2100    // Retrieve the port number that the server is listening on.
2101    addr.Service(*portnum);
2102 
2103    // Now release the LOCK semaphore.
2104    op.sem_num = 0;
2105    op.sem_op = 1;
2106    semop(lockid, &op, 1);
2107 
2108    // If we get here, then Audacity is currently active. So, we connect
2109    // to it and we forward all filenames listed on the command line to
2110    // the active process.
2111 
2112    // Setup the socket
2113    //
2114    // A wxSocketClient must not be deleted by us, but rather, let the
2115    // framework do appropriate delayed deletion after Destroy()
2116    Destroy_ptr<wxSocketClient> sock { safenew wxSocketClient() };
2117    sock->SetFlags(wxSOCKET_WAITALL);
2118 
2119    // Attempt to connect to an active Audacity.
2120    sock->Connect(addr, true);
2121    if (!sock->IsConnected())
2122    {
2123       // All attempts to become the server or connect to one have failed.  Not
2124       // sure what we can say about the error, but it's probably not because
2125       // Audacity is already running.
2126       AudacityMessageBox(
2127          XO("An unrecoverable error has occurred during startup"),
2128          XO("Audacity Startup Failure"),
2129          wxOK | wxICON_ERROR);
2130 
2131       return false;
2132    }
2133 
2134    // Parse the command line to ensure correct syntax, but ignore
2135    // options other than -v, and only use the filenames, if any.
2136    auto parser = ParseCommandLine();
2137    if (!parser)
2138    {
2139       // Complaints have already been made
2140       return false;
2141    }
2142 
2143    // Display Audacity's version if requested
2144    if (parser->Found(wxT("v")))
2145    {
2146       wxPrintf("Audacity v%s\n", AUDACITY_VERSION_STRING);
2147 
2148       return false;
2149    }
2150 
2151 #if defined(__WXMAC__)
2152    // On macOS the client gets events from the wxWidgets framework that
2153    // go to AudacityApp::MacOpenFile. Forward the file names to the prior
2154    // instance via the socket.
2155    for (const auto &filename: ofqueue)
2156    {
2157       auto str = filename.c_str().AsWChar();
2158       sock->WriteMsg(str, (filename.length() + 1) * sizeof(*str));
2159    }
2160 #endif
2161 
2162    // On macOS and Linux, forward any file names found in the command
2163    // line arguments.
2164    for (size_t j = 0, cnt = parser->GetParamCount(); j < cnt; ++j)
2165    {
2166       wxFileName filename(parser->GetParam(j));
2167       if (filename.MakeAbsolute())
2168       {
2169          const wxString param = filename.GetLongPath();
2170          sock->WriteMsg((const wxChar *) param, (param.length() + 1) * sizeof(wxChar));
2171       }
2172    }
2173 
2174    // Send an empty string to force existing Audacity to front
2175    sock->WriteMsg(wxEmptyString, sizeof(wxChar));
2176 
2177    // We've forwarded all of the filenames, so let the caller know
2178    // to terminate.
2179    return false;
2180 }
2181 
OnServerEvent(wxSocketEvent &)2182 void AudacityApp::OnServerEvent(wxSocketEvent & /* evt */)
2183 {
2184    wxSocketBase *sock;
2185 
2186    // Accept all pending connection requests
2187    do
2188    {
2189       sock = mIPCServ->Accept(false);
2190       if (sock)
2191       {
2192          // Setup the socket
2193          sock->SetEventHandler(*this, ID_IPC_SOCKET);
2194          sock->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
2195          sock->Notify(true);
2196       }
2197    } while (sock);
2198 }
2199 
OnSocketEvent(wxSocketEvent & evt)2200 void AudacityApp::OnSocketEvent(wxSocketEvent & evt)
2201 {
2202    wxSocketBase *sock = evt.GetSocket();
2203 
2204    if (evt.GetSocketEvent() == wxSOCKET_LOST)
2205    {
2206       sock->Destroy();
2207       return;
2208    }
2209 
2210    // Read the length of the filename and bail if we have a short read
2211    wxChar name[PATH_MAX];
2212    sock->ReadMsg(&name, sizeof(name));
2213    if (!sock->Error())
2214    {
2215       // Add the filename to the queue.  It will be opened by
2216       // the OnTimer() event when it is safe to do so.
2217       ofqueue.push_back(name);
2218    }
2219 }
2220 
2221 #endif
2222 
ParseCommandLine()2223 std::unique_ptr<wxCmdLineParser> AudacityApp::ParseCommandLine()
2224 {
2225    auto parser = std::make_unique<wxCmdLineParser>(argc, argv);
2226    if (!parser)
2227    {
2228       return nullptr;
2229    }
2230 
2231    /*i18n-hint: This controls the number of bytes that Audacity will
2232     *           use when writing files to the disk */
2233    parser->AddOption(wxT("b"), wxT("blocksize"), _("set max disk block size in bytes"),
2234                      wxCMD_LINE_VAL_NUMBER);
2235 
2236    const auto journalOptionDescription =
2237    /*i18n-hint: brief help message for Audacity's command-line options
2238      A journal contains a sequence of user interface interactions to be repeated
2239      "log," "trail," "trace" have somewhat similar meanings */
2240       _("replay a journal file");
2241 
2242    parser->AddOption(wxT("j"), wxT("journal"), journalOptionDescription);
2243 
2244    /*i18n-hint: This displays a list of available options */
2245    parser->AddSwitch(wxT("h"), wxT("help"), _("this help message"),
2246                      wxCMD_LINE_OPTION_HELP);
2247 
2248    /*i18n-hint: This runs a set of automatic tests on Audacity itself */
2249    parser->AddSwitch(wxT("t"), wxT("test"), _("run self diagnostics"));
2250 
2251    /*i18n-hint: This displays the Audacity version */
2252    parser->AddSwitch(wxT("v"), wxT("version"), _("display Audacity version"));
2253 
2254    /*i18n-hint: This is a list of one or more files that Audacity
2255     *           should open upon startup */
2256    parser->AddParam(_("audio or project file name"),
2257                     wxCMD_LINE_VAL_STRING,
2258                     wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL);
2259 
2260    // Run the parser
2261    if (parser->Parse() == 0)
2262       return parser;
2263 
2264    return{};
2265 }
2266 
OnQueryEndSession(wxCloseEvent & event)2267 void AudacityApp::OnQueryEndSession(wxCloseEvent & event)
2268 {
2269    bool mustVeto = false;
2270 
2271 #ifdef __WXMAC__
2272    mustVeto = wxDialog::OSXHasModalDialogsOpen();
2273 #endif
2274 
2275    if ( mustVeto )
2276       event.Veto(true);
2277    else
2278       OnEndSession(event);
2279 }
2280 
OnEndSession(wxCloseEvent & event)2281 void AudacityApp::OnEndSession(wxCloseEvent & event)
2282 {
2283    bool force = !event.CanVeto();
2284 
2285    // Try to close each open window.  If the user hits Cancel
2286    // in a Save Changes dialog, don't continue.
2287    gIsQuitting = true;
2288    if (AllProjects{}.size())
2289       // PRL:  Always did at least once before close might be vetoed
2290       // though I don't know why that is important
2291       ProjectManager::SaveWindowSize();
2292    bool closedAll = CloseAllProjects( force );
2293    if ( !closedAll )
2294    {
2295       gIsQuitting = false;
2296       event.Veto();
2297    }
2298 }
2299 
OnExit()2300 int AudacityApp::OnExit()
2301 {
2302    gIsQuitting = true;
2303    while(Pending())
2304    {
2305       Dispatch();
2306    }
2307 
2308    Importer::Get().Terminate();
2309 
2310    if(gPrefs)
2311    {
2312       bool bFalse = false;
2313       //Should we change the commands.cfg location next startup?
2314       if(gPrefs->Read(wxT("/QDeleteCmdCfgLocation"), &bFalse))
2315       {
2316          gPrefs->DeleteEntry(wxT("/QDeleteCmdCfgLocation"));
2317          gPrefs->Write(wxT("/DeleteCmdCfgLocation"), true);
2318          gPrefs->Flush();
2319       }
2320    }
2321 
2322    FileHistory::Global().Save(*gPrefs);
2323 
2324    FinishPreferences();
2325 
2326    DeinitFFT();
2327 
2328 #ifdef HAS_NETWORKING
2329    audacity::network_manager::NetworkManager::GetInstance().Terminate();
2330 #endif
2331 
2332    AudioIO::Deinit();
2333 
2334    MenuTable::DestroyRegistry();
2335 
2336    // Terminate the PluginManager (must be done before deleting the locale)
2337    PluginManager::Get().Terminate();
2338 
2339    return 0;
2340 }
2341 
2342 // The following five methods are currently only used on Mac OS,
2343 // where it's possible to have a menu bar but no windows open.
2344 // It doesn't hurt any other platforms, though.
2345 
2346 // ...That is, as long as you check to see if no windows are open
2347 // before executing the stuff.
2348 // To fix this, check to see how many project windows are open,
2349 // and skip the event unless none are open (which should only happen
2350 // on the Mac, at least currently.)
2351 
OnMenuAbout(wxCommandEvent &)2352 void AudacityApp::OnMenuAbout(wxCommandEvent & /*event*/)
2353 {
2354    // This function shadows a similar function
2355    // in Menus.cpp, but should only be used on the Mac platform.
2356 #ifdef __WXMAC__
2357    // Modeless dialog, consistent with other Mac applications
2358    // Not more than one at once!
2359    const auto instance = AboutDialog::ActiveIntance();
2360    if (instance)
2361       instance->Raise();
2362    else
2363       // This dialog deletes itself when dismissed
2364       (safenew AboutDialog{ nullptr })->Show(true);
2365 #else
2366       wxASSERT(false);
2367 #endif
2368 }
2369 
OnMenuNew(wxCommandEvent & event)2370 void AudacityApp::OnMenuNew(wxCommandEvent & event)
2371 {
2372    // This function shadows a similar function
2373    // in Menus.cpp, but should only be used on the Mac platform
2374    // when no project windows are open. This check assures that
2375    // this happens, and enable the same code to be present on
2376    // all platforms.
2377 
2378    if(AllProjects{}.empty())
2379       (void) ProjectManager::New();
2380    else
2381       event.Skip();
2382 }
2383 
2384 
OnMenuOpen(wxCommandEvent & event)2385 void AudacityApp::OnMenuOpen(wxCommandEvent & event)
2386 {
2387    // This function shadows a similar function
2388    // in Menus.cpp, but should only be used on the Mac platform
2389    // when no project windows are open. This check assures that
2390    // this happens, and enable the same code to be present on
2391    // all platforms.
2392 
2393 
2394    if(AllProjects{}.empty())
2395       ProjectManager::OpenFiles(NULL);
2396    else
2397       event.Skip();
2398 
2399 
2400 }
2401 
OnMenuPreferences(wxCommandEvent & event)2402 void AudacityApp::OnMenuPreferences(wxCommandEvent & event)
2403 {
2404    // This function shadows a similar function
2405    // in Menus.cpp, but should only be used on the Mac platform
2406    // when no project windows are open. This check assures that
2407    // this happens, and enable the same code to be present on
2408    // all platforms.
2409 
2410    if(AllProjects{}.empty()) {
2411       GlobalPrefsDialog dialog(nullptr /* parent */, nullptr );
2412       dialog.ShowModal();
2413    }
2414    else
2415       event.Skip();
2416 
2417 }
2418 
OnMenuExit(wxCommandEvent & event)2419 void AudacityApp::OnMenuExit(wxCommandEvent & event)
2420 {
2421    // This function shadows a similar function
2422    // in Menus.cpp, but should only be used on the Mac platform
2423    // when no project windows are open. This check assures that
2424    // this happens, and enable the same code to be present on
2425    // all platforms.
2426 
2427    // LL:  Removed "if" to allow closing based on final project count.
2428    // if(AllProjects{}.empty())
2429       QuitAudacity();
2430 
2431    // LL:  Veto quit if projects are still open.  This can happen
2432    //      if the user selected Cancel in a Save dialog.
2433    event.Skip(AllProjects{}.empty());
2434 
2435 }
2436 
2437 #ifndef __WXMAC__
SetPreferredSystemAppearance(PreferredSystemAppearance)2438 void AudacityApp::SetPreferredSystemAppearance(PreferredSystemAppearance)
2439 {
2440    // Currently this is implemented only on macOS
2441 }
2442 #endif
2443 
2444 //BG: On Windows, associate the aup file type with Audacity
2445 /* We do this in the Windows installer now,
2446    to avoid issues where user doesn't have admin privileges, but
2447    in case that didn't work, allow the user to decide at startup.
2448 
2449    //v Should encapsulate this & allow access from Prefs, too,
2450    //      if people want to manually change associations.
2451 */
2452 #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__) && !defined(__CYGWIN__)
AssociateFileTypes()2453 void AudacityApp::AssociateFileTypes()
2454 {
2455    // Check pref in case user has already decided against it.
2456    bool bWantAssociateFiles = true;
2457    if (gPrefs->Read(wxT("/WantAssociateFiles"), &bWantAssociateFiles) &&
2458          !bWantAssociateFiles)
2459    {
2460       // User has already decided against it
2461       return;
2462    }
2463 
2464    wxRegKey associateFileTypes;
2465 
2466    auto IsDefined = [&](const wxString &type)
2467    {
2468       associateFileTypes.SetName(wxString::Format(wxT("HKCR\\%s"), type));
2469       bool bKeyExists = associateFileTypes.Exists();
2470       if (!bKeyExists)
2471       {
2472          // Not at HKEY_CLASSES_ROOT. Try HKEY_CURRENT_USER.
2473          associateFileTypes.SetName(wxString::Format(wxT("HKCU\\Software\\Classes\\%s"), type));
2474          bKeyExists = associateFileTypes.Exists();
2475       }
2476       return bKeyExists;
2477    };
2478 
2479    auto DefineType = [&](const wxString &type)
2480    {
2481       wxString root_key = wxT("HKCU\\Software\\Classes\\");
2482 
2483       // Start with HKEY_CLASSES_CURRENT_USER.
2484       associateFileTypes.SetName(wxString::Format(wxT("%s%s"), root_key, type));
2485       if (!associateFileTypes.Create(true))
2486       {
2487          // Not at HKEY_CLASSES_CURRENT_USER. Try HKEY_CURRENT_ROOT.
2488          root_key = wxT("HKCR\\");
2489          associateFileTypes.SetName(wxString::Format(wxT("%s%s"), root_key, type));
2490          if (!associateFileTypes.Create(true))
2491          {
2492             // Actually, can't create keys. Empty root_key to flag failure.
2493             root_key.empty();
2494          }
2495       }
2496 
2497       if (!root_key.empty())
2498       {
2499          associateFileTypes = wxT("Audacity.Project"); // Finally set value for the key
2500       }
2501 
2502       return root_key;
2503    };
2504 
2505    // Check for legacy and UP types
2506    if (IsDefined(wxT(".aup3")) && IsDefined(wxT(".aup")) && IsDefined(wxT("Audacity.Project")))
2507    {
2508       // Already defined, so bail
2509       return;
2510    }
2511 
2512    // File types are not currently associated.
2513    int wantAssoc =
2514       AudacityMessageBox(
2515          XO(
2516 "Audacity project (.aup3) files are not currently \nassociated with Audacity. \n\nAssociate them, so they open on double-click?"),
2517          XO("Audacity Project Files"),
2518          wxYES_NO | wxICON_QUESTION);
2519 
2520    if (wantAssoc == wxNO)
2521    {
2522       // User said no. Set a pref so we don't keep asking.
2523       gPrefs->Write(wxT("/WantAssociateFiles"), false);
2524       gPrefs->Flush();
2525       return;
2526    }
2527 
2528    // Show that user wants associations
2529    gPrefs->Write(wxT("/WantAssociateFiles"), true);
2530    gPrefs->Flush();
2531 
2532    wxString root_key;
2533 
2534    root_key = DefineType(wxT(".aup3"));
2535    if (root_key.empty())
2536    {
2537       //v Warn that we can't set keys. Ask whether to set pref for no retry?
2538    }
2539    else
2540    {
2541       DefineType(wxT(".aup"));
2542 
2543       associateFileTypes = wxT("Audacity.Project"); // Finally set value for .AUP key
2544       associateFileTypes.SetName(root_key + wxT("Audacity.Project"));
2545       if (!associateFileTypes.Exists())
2546       {
2547          associateFileTypes.Create(true);
2548          associateFileTypes = wxT("Audacity Project File");
2549       }
2550 
2551       associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell"));
2552       if (!associateFileTypes.Exists())
2553       {
2554          associateFileTypes.Create(true);
2555          associateFileTypes = wxT("");
2556       }
2557 
2558       associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open"));
2559       if (!associateFileTypes.Exists())
2560       {
2561          associateFileTypes.Create(true);
2562       }
2563 
2564       associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\command"));
2565       wxString tmpRegAudPath;
2566       if(associateFileTypes.Exists())
2567       {
2568          tmpRegAudPath = associateFileTypes.QueryDefaultValue().Lower();
2569       }
2570 
2571       if (!associateFileTypes.Exists() ||
2572             (tmpRegAudPath.Find(wxT("audacity.exe")) >= 0))
2573       {
2574          associateFileTypes.Create(true);
2575          associateFileTypes = (wxString)argv[0] + (wxString)wxT(" \"%1\"");
2576       }
2577 
2578 #if 0
2579       // These can be use later to support more startup messages
2580       // like maybe "Import into existing project" or some such.
2581       // Leaving here for an example...
2582       associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\ddeexec"));
2583       if (!associateFileTypes.Exists())
2584       {
2585          associateFileTypes.Create(true);
2586          associateFileTypes = wxT("%1");
2587       }
2588 
2589       associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\ddeexec\\Application"));
2590       if (!associateFileTypes.Exists())
2591       {
2592          associateFileTypes.Create(true);
2593          associateFileTypes = IPC_APPL;
2594       }
2595 
2596       associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\ddeexec\\Topic"));
2597       if (!associateFileTypes.Exists())
2598       {
2599          associateFileTypes.Create(true);
2600          associateFileTypes = IPC_TOPIC;
2601       }
2602 #endif
2603    }
2604 }
2605 #endif
2606 
2607