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