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