/********************************************************************** Audacity: A Digital Audio Editor AudacityApp.cpp Dominic Mazzoni ******************************************************************//** \class AudacityApp \brief AudacityApp is the 'main' class for Audacity It handles initialization and termination by subclassing wxApp. *//*******************************************************************/ #include "AudacityApp.h" #if 0 // This may be used to debug memory leaks. // See: Visual Leak Detector @ http://vld.codeplex.com/ #include #endif #include // for wxUSE_* macros #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __WXGTK__ #include #ifdef HAVE_GTK #include #endif #endif // chmod, lstat, geteuid #ifdef __UNIX__ #include #include #include #include #endif #if defined(__WXMSW__) #include // for wxRegKey #endif #include "AudacityLogger.h" #include "AboutDialog.h" #include "AColor.h" #include "AudacityFileConfig.h" #include "AudioIO.h" #include "Benchmark.h" #include "Clipboard.h" #include "CrashReport.h" // for HAS_CRASH_REPORT #include "commands/CommandHandler.h" #include "commands/AppCommandEvent.h" #include "widgets/ASlider.h" #include "FFmpeg.h" //#include "LangChoice.h" #include "Languages.h" #include "Menus.h" #include "PluginManager.h" #include "Project.h" #include "ProjectAudioIO.h" #include "ProjectAudioManager.h" #include "ProjectFileIO.h" #include "ProjectFileManager.h" #include "ProjectHistory.h" #include "ProjectManager.h" #include "ProjectSettings.h" #include "ProjectWindow.h" #include "Screenshot.h" #include "Sequence.h" #include "TempDirectory.h" #include "Track.h" #include "prefs/PrefsDialog.h" #include "Theme.h" #include "PlatformCompatibility.h" #include "AutoRecoveryDialog.h" #include "SplashDialog.h" #include "FFT.h" #include "widgets/AudacityMessageBox.h" #include "prefs/DirectoriesPrefs.h" #include "prefs/GUIPrefs.h" #include "tracks/ui/Scrubbing.h" #include "widgets/FileConfig.h" #include "widgets/FileHistory.h" #include "update/UpdateManager.h" #ifdef HAS_NETWORKING #include "NetworkManager.h" #endif #ifdef EXPERIMENTAL_EASY_CHANGE_KEY_BINDINGS #include "prefs/KeyConfigPrefs.h" #endif //temporarily commented out till it is added to all projects //#include "Profiler.h" #include "ModuleManager.h" #include "import/Import.h" #if defined(USE_BREAKPAD) #include "BreakpadConfigurer.h" #endif #ifdef EXPERIMENTAL_SCOREALIGN #include "effects/ScoreAlignDialog.h" #endif #if 0 #ifdef _DEBUG #ifdef _MSC_VER #undef THIS_FILE static char*THIS_FILE= __FILE__; #define new new(_NORMAL_BLOCK, THIS_FILE, __LINE__) #endif #endif #endif // DA: Logo for Splash Screen #ifdef EXPERIMENTAL_DA #include "../images/DarkAudacityLogoWithName.xpm" #else #include "../images/AudacityLogoWithName.xpm" #endif #include //////////////////////////////////////////////////////////// /// Custom events //////////////////////////////////////////////////////////// #if 0 #ifdef __WXGTK__ static void wxOnAssert(const wxChar *fileName, int lineNumber, const wxChar *msg) { if (msg) wxPrintf("ASSERTION FAILED: %s\n%s: %d\n", (const char *)wxString(msg).mb_str(), (const char *)wxString(fileName).mb_str(), lineNumber); else wxPrintf("ASSERTION FAILED!\n%s: %d\n", (const char *)wxString(fileName).mb_str(), lineNumber); // Force core dump int *i = 0; if (*i) exit(1); exit(0); } #endif #endif namespace { void PopulatePreferences() { bool resetPrefs = false; wxString langCode = gPrefs->Read(wxT("/Locale/Language"), wxEmptyString); bool writeLang = false; const wxFileName fn( FileNames::ResourcesDir(), wxT("FirstTime.ini")); if (fn.FileExists()) // it will exist if the (win) installer put it there { const wxString fullPath{fn.GetFullPath()}; auto pIni = AudacityFileConfig::Create({}, {}, fullPath, {}, wxCONFIG_USE_LOCAL_FILE); auto &ini = *pIni; wxString lang; if (ini.Read(wxT("/FromInno/Language"), &lang) && !lang.empty()) { // Only change "langCode" if the language was actually specified in the ini file. langCode = lang; writeLang = true; // Inno Setup doesn't allow special characters in the Name values, so "0" is used // to represent the "@" character. langCode.Replace(wxT("0"), wxT("@")); } ini.Read(wxT("/FromInno/ResetPrefs"), &resetPrefs, false); bool gone = wxRemoveFile(fullPath); // remove FirstTime.ini if (!gone) { AudacityMessageBox( XO("Failed to remove %s").Format(fullPath), XO("Failed!")); } } // Use the system default language if one wasn't specified or if the user selected System. if (langCode.empty()) langCode = Languages::GetSystemLanguageCode(FileNames::AudacityPathList()); langCode = GUIPrefs::SetLang( langCode ); // User requested that the preferences be completely reset if (resetPrefs) { // pop up a dialogue auto prompt = XO( "Reset Preferences?\n\nThis is a one-time question, after an 'install' where you asked to have the Preferences reset."); int action = AudacityMessageBox( prompt, XO("Reset Audacity Preferences"), wxYES_NO, NULL); if (action == wxYES) // reset { gPrefs->DeleteAll(); writeLang = true; } } // Save the specified language if (writeLang) { gPrefs->Write(wxT("/Locale/Language"), langCode); } // In AUdacity 2.1.0 support for the legacy 1.2.x preferences (depreciated since Audacity // 1.3.1) is dropped. As a result we can drop the import flag // first time this version of Audacity is run we try to migrate // old preferences. bool newPrefsInitialized = false; gPrefs->Read(wxT("/NewPrefsInitialized"), &newPrefsInitialized, false); if (newPrefsInitialized) { gPrefs->DeleteEntry(wxT("/NewPrefsInitialized"), true); // take group as well if empty } // record the Prefs version for future checking (this has not been used for a very // long time). gPrefs->Write(wxT("/PrefsVersion"), wxString(wxT(AUDACITY_PREFS_VERSION_STRING))); // Check if some prefs updates need to happen based on audacity version. // Unfortunately we can't use the PrefsVersion prefs key because that resets things. // In the future we may want to integrate that better. // these are done on a case-by-case basis for now so they must be backwards compatible // (meaning the changes won't mess audacity up if the user goes back to an earlier version) int vMajor = gPrefs->Read(wxT("/Version/Major"), (long) 0); int vMinor = gPrefs->Read(wxT("/Version/Minor"), (long) 0); int vMicro = gPrefs->Read(wxT("/Version/Micro"), (long) 0); gPrefs->SetVersionKeysInit(vMajor, vMinor, vMicro); // make a note of these initial values // for use by ToolManager::ReadConfig() // These integer version keys were introduced april 4 2011 for 1.3.13 // The device toolbar needs to be enabled due to removal of source selection features in // the mixer toolbar. if ((vMajor < 1) || (vMajor == 1 && vMinor < 3) || (vMajor == 1 && vMinor == 3 && vMicro < 13)) { // Do a full reset of the Device Toolbar to get it on the screen. if (gPrefs->Exists(wxT("/GUI/ToolBars/Device"))) gPrefs->DeleteGroup(wxT("/GUI/ToolBars/Device")); // We keep the mixer toolbar prefs (shown/not shown) // the width of the mixer toolbar may have shrunk, the prefs will keep the larger value // if the user had a device that had more than one source. if (gPrefs->Exists(wxT("/GUI/ToolBars/Mixer"))) { // Use the default width gPrefs->Write(wxT("/GUI/ToolBars/Mixer/W"), -1); } } // In 2.1.0, the Meter toolbar was split and lengthened, but strange arrangements happen // if upgrading due to the extra length. So, if a user is upgrading, use the pre-2.1.0 // lengths, but still use the NEW split versions. if (gPrefs->Exists(wxT("/GUI/ToolBars/Meter")) && !gPrefs->Exists(wxT("/GUI/ToolBars/CombinedMeter"))) { // Read in all of the existing values long dock, order, show, x, y, w, h; gPrefs->Read(wxT("/GUI/ToolBars/Meter/Dock"), &dock, -1); gPrefs->Read(wxT("/GUI/ToolBars/Meter/Order"), &order, -1); gPrefs->Read(wxT("/GUI/ToolBars/Meter/Show"), &show, -1); gPrefs->Read(wxT("/GUI/ToolBars/Meter/X"), &x, -1); gPrefs->Read(wxT("/GUI/ToolBars/Meter/Y"), &y, -1); gPrefs->Read(wxT("/GUI/ToolBars/Meter/W"), &w, -1); gPrefs->Read(wxT("/GUI/ToolBars/Meter/H"), &h, -1); // "Order" must be adjusted since we're inserting two NEW toolbars if (dock > 0) { wxString oldPath = gPrefs->GetPath(); gPrefs->SetPath(wxT("/GUI/ToolBars")); wxString bar; long ndx = 0; bool cont = gPrefs->GetFirstGroup(bar, ndx); while (cont) { long o; if (gPrefs->Read(bar + wxT("/Order"), &o) && o >= order) { gPrefs->Write(bar + wxT("/Order"), o + 2); } cont = gPrefs->GetNextGroup(bar, ndx); } gPrefs->SetPath(oldPath); // And override the height h = 27; } // Write the split meter bar values gPrefs->Write(wxT("/GUI/ToolBars/RecordMeter/Dock"), dock); gPrefs->Write(wxT("/GUI/ToolBars/RecordMeter/Order"), order); gPrefs->Write(wxT("/GUI/ToolBars/RecordMeter/Show"), show); gPrefs->Write(wxT("/GUI/ToolBars/RecordMeter/X"), -1); gPrefs->Write(wxT("/GUI/ToolBars/RecordMeter/Y"), -1); gPrefs->Write(wxT("/GUI/ToolBars/RecordMeter/W"), w); gPrefs->Write(wxT("/GUI/ToolBars/RecordMeter/H"), h); gPrefs->Write(wxT("/GUI/ToolBars/PlayMeter/Dock"), dock); gPrefs->Write(wxT("/GUI/ToolBars/PlayMeter/Order"), order + 1); gPrefs->Write(wxT("/GUI/ToolBars/PlayMeter/Show"), show); gPrefs->Write(wxT("/GUI/ToolBars/PlayMeter/X"), -1); gPrefs->Write(wxT("/GUI/ToolBars/PlayMeter/Y"), -1); gPrefs->Write(wxT("/GUI/ToolBars/PlayMeter/W"), w); gPrefs->Write(wxT("/GUI/ToolBars/PlayMeter/H"), h); // And hide the old combined meter bar gPrefs->Write(wxT("/GUI/ToolBars/Meter/Dock"), -1); } // Upgrading pre 2.2.0 configs we assume extended set of defaults. if ((0Write(wxT("/GUI/Shortcuts/FullDefaults"),1); } // Upgrading pre 2.4.0 configs, the selection toolbar is now split. if ((0Write(wxT("/GUI/Toolbars/Selection/W"),""); gPrefs->Write(wxT("/GUI/Toolbars/SpectralSelection/W"),""); gPrefs->Write(wxT("/GUI/Toolbars/Time/X"),-1); gPrefs->Write(wxT("/GUI/Toolbars/Time/Y"),-1); gPrefs->Write(wxT("/GUI/Toolbars/Time/H"),55); gPrefs->Write(wxT("/GUI/Toolbars/Time/W"),251); gPrefs->Write(wxT("/GUI/Toolbars/Time/DockV2"),2); gPrefs->Write(wxT("/GUI/Toolbars/Time/Dock"),2); gPrefs->Write(wxT("/GUI/Toolbars/Time/Path"),"0,1"); gPrefs->Write(wxT("/GUI/Toolbars/Time/Show"),1); } // write out the version numbers to the prefs file for future checking gPrefs->Write(wxT("/Version/Major"), AUDACITY_VERSION); gPrefs->Write(wxT("/Version/Minor"), AUDACITY_RELEASE); gPrefs->Write(wxT("/Version/Micro"), AUDACITY_REVISION); gPrefs->Flush(); } #if defined(USE_BREAKPAD) void InitBreakpad() { wxFileName databasePath; databasePath.SetPath(wxStandardPaths::Get().GetUserLocalDataDir()); databasePath.AppendDir("crashreports"); databasePath.Mkdir(wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL); if(databasePath.DirExists()) { BreakpadConfigurer configurer; configurer.SetDatabasePathUTF8(databasePath.GetPath().ToUTF8().data()) .SetSenderPathUTF8(wxFileName(wxStandardPaths::Get().GetExecutablePath()).GetPath().ToUTF8().data()) #if defined(CRASH_REPORT_URL) .SetReportURL(CRASH_REPORT_URL) #endif .SetParameters({ { "version", wxString(AUDACITY_VERSION_STRING).ToUTF8().data() } }) .Start(); } } #endif } static bool gInited = false; static bool gIsQuitting = false; static void QuitAudacity(bool bForce) { // guard against recursion if (gIsQuitting) return; gIsQuitting = true; wxTheApp->SetExitOnFrameDelete(true); // Try to close each open window. If the user hits Cancel // in a Save Changes dialog, don't continue. // BG: unless force is true // BG: Are there any projects open? //- if (!AllProjects{}.empty()) /*start+*/ if (AllProjects{}.empty()) { #ifdef __WXMAC__ Clipboard::Get().Clear(); #endif } else /*end+*/ { if (AllProjects{}.size()) // PRL: Always did at least once before close might be vetoed // though I don't know why that is important ProjectManager::SaveWindowSize(); bool closedAll = AllProjects::Close( bForce ); if ( !closedAll ) { gIsQuitting = false; return; } } ModuleManager::Get().Dispatch(AppQuiting); #ifdef EXPERIMENTAL_SCOREALIGN CloseScoreAlignDialog(); #endif CloseScreenshotTools(); //print out profile if we have one by deleting it //temporarily commented out till it is added to all projects //DELETE Profiler::Instance(); // Save last log for diagnosis auto logger = AudacityLogger::Get(); if (logger) { wxFileName logFile(FileNames::DataDir(), wxT("lastlog.txt")); logger->SaveLog(logFile.GetFullPath()); } //remove our logger std::unique_ptr{ wxLog::SetActiveTarget(NULL) }; // DELETE if (bForce) { wxExit(); } } static void QuitAudacity() { QuitAudacity(false); } #if defined(__WXGTK__) && defined(HAVE_GTK) /////////////////////////////////////////////////////////////////////////////// // Provide the ability to receive notification from the session manager // when the user is logging out or shutting down. // // Most of this was taken from nsNativeAppSupportUnix.cpp from Mozilla. /////////////////////////////////////////////////////////////////////////////// // TODO: May need updating. Is this code too obsolete (relying on Gnome2 so's) to be // worth keeping anymore? // CB suggests we use libSM directly ref: // http://www.x.org/archive/X11R7.7/doc/libSM/SMlib.html#The_Save_Yourself_Callback #include /* There is a conflict between the type names used in Glib >= 2.21 and those in * wxGTK (http://trac.wxwidgets.org/ticket/10883) * Happily we can avoid the hack, as we only need some of the headers, not * the full GTK headers */ #include typedef struct _GnomeProgram GnomeProgram; typedef struct _GnomeModuleInfo GnomeModuleInfo; typedef struct _GnomeClient GnomeClient; typedef enum { GNOME_SAVE_GLOBAL, GNOME_SAVE_LOCAL, GNOME_SAVE_BOTH } GnomeSaveStyle; typedef enum { GNOME_INTERACT_NONE, GNOME_INTERACT_ERRORS, GNOME_INTERACT_ANY } GnomeInteractStyle; typedef enum { GNOME_DIALOG_ERROR, GNOME_DIALOG_NORMAL } GnomeDialogType; typedef GnomeProgram * (*_gnome_program_init_fn)(const char *, const char *, const GnomeModuleInfo *, int, char **, const char *, ...); typedef const GnomeModuleInfo * (*_libgnomeui_module_info_get_fn)(); typedef GnomeClient * (*_gnome_master_client_fn)(void); typedef void (*GnomeInteractFunction)(GnomeClient *, gint, GnomeDialogType, gpointer); typedef void (*_gnome_client_request_interaction_fn)(GnomeClient *, GnomeDialogType, GnomeInteractFunction, gpointer); typedef void (*_gnome_interaction_key_return_fn)(gint, gboolean); static _gnome_client_request_interaction_fn gnome_client_request_interaction; static _gnome_interaction_key_return_fn gnome_interaction_key_return; static void interact_cb(GnomeClient * /* client */, gint key, GnomeDialogType /* type */, gpointer /* data */) { wxCloseEvent e(wxEVT_QUERY_END_SESSION, wxID_ANY); e.SetEventObject(&wxGetApp()); e.SetCanVeto(true); wxGetApp().ProcessEvent(e); gnome_interaction_key_return(key, e.GetVeto()); } static gboolean save_yourself_cb(GnomeClient *client, gint /* phase */, GnomeSaveStyle /* style */, gboolean shutdown, GnomeInteractStyle interact, gboolean /* fast */, gpointer /* user_data */) { if (!shutdown || interact != GNOME_INTERACT_ANY) { return TRUE; } if (AllProjects{}.empty()) { return TRUE; } gnome_client_request_interaction(client, GNOME_DIALOG_NORMAL, interact_cb, NULL); return TRUE; } class GnomeShutdown { public: GnomeShutdown() { mArgv[0].reset(strdup("Audacity")); mGnomeui = dlopen("libgnomeui-2.so.0", RTLD_NOW); if (!mGnomeui) { return; } mGnome = dlopen("libgnome-2.so.0", RTLD_NOW); if (!mGnome) { return; } _gnome_program_init_fn gnome_program_init = (_gnome_program_init_fn) dlsym(mGnome, "gnome_program_init"); _libgnomeui_module_info_get_fn libgnomeui_module_info_get = (_libgnomeui_module_info_get_fn) dlsym(mGnomeui, "libgnomeui_module_info_get"); _gnome_master_client_fn gnome_master_client = (_gnome_master_client_fn) dlsym(mGnomeui, "gnome_master_client"); gnome_client_request_interaction = (_gnome_client_request_interaction_fn) dlsym(mGnomeui, "gnome_client_request_interaction"); gnome_interaction_key_return = (_gnome_interaction_key_return_fn) dlsym(mGnomeui, "gnome_interaction_key_return"); if (!gnome_program_init || !libgnomeui_module_info_get) { return; } gnome_program_init(mArgv[0].get(), "1.0", libgnomeui_module_info_get(), 1, reinterpret_cast(mArgv), NULL); mClient = gnome_master_client(); if (mClient == NULL) { return; } g_signal_connect(mClient, "save-yourself", G_CALLBACK(save_yourself_cb), NULL); } virtual ~GnomeShutdown() { // Do not dlclose() the libraries here lest you want segfaults... } private: MallocString<> mArgv[1]; void *mGnomeui; void *mGnome; GnomeClient *mClient; }; // This variable exists to call the constructor and // connect a signal for the 'save-yourself' message. GnomeShutdown GnomeShutdownInstance; #endif // Where drag/drop or "Open With" filenames get stored until // the timer routine gets around to picking them up. static wxArrayString ofqueue; // // DDE support for opening multiple files with one instance // of Audacity. // #define IPC_APPL wxT("audacity") #define IPC_TOPIC wxT("System") class IPCConn final : public wxConnection { public: IPCConn() : wxConnection() { }; ~IPCConn() { }; bool OnExec(const wxString & WXUNUSED(topic), const wxString & data) { // Add the filename to the queue. It will be opened by // the OnTimer() event when it is safe to do so. ofqueue.push_back(data); return true; } }; class IPCServ final : public wxServer { public: IPCServ(const wxString & appl) : wxServer() { Create(appl); }; ~IPCServ() { }; wxConnectionBase *OnAcceptConnection(const wxString & topic) override { if (topic != IPC_TOPIC) { return NULL; } // Trust wxWidgets framework to DELETE it return safenew IPCConn(); }; }; #if defined(__WXMAC__) IMPLEMENT_APP_NO_MAIN(AudacityApp) IMPLEMENT_WX_THEME_SUPPORT int main(int argc, char *argv[]) { wxDISABLE_DEBUG_SUPPORT(); return wxEntry(argc, argv); } #elif defined(__WXGTK__) && defined(NDEBUG) IMPLEMENT_APP_NO_MAIN(AudacityApp) IMPLEMENT_WX_THEME_SUPPORT int main(int argc, char *argv[]) { wxDISABLE_DEBUG_SUPPORT(); // Bug #1986 workaround - This doesn't actually reduce the number of // messages, it simply hides them in Release builds. We'll probably // never be able to get rid of the messages entirely, but we should // look into what's causing them, so allow them to show in Debug // builds. stdout = freopen("/dev/null", "w", stdout); stderr = freopen("/dev/null", "w", stderr); return wxEntry(argc, argv); } #else IMPLEMENT_APP(AudacityApp) #endif #ifdef __WXMAC__ // in response of an open-document apple event void AudacityApp::MacOpenFile(const wxString &fileName) { ofqueue.push_back(fileName); } // in response of a print-document apple event void AudacityApp::MacPrintFile(const wxString &fileName) { ofqueue.push_back(fileName); } // in response of a open-application apple event void AudacityApp::MacNewFile() { if (!gInited) return; // This method should only be used on the Mac platform // when no project windows are open. if (AllProjects{}.empty()) (void) ProjectManager::New(); } #endif //__WXMAC__ // IPC communication #define ID_IPC_SERVER 6200 #define ID_IPC_SOCKET 6201 // we don't really care about the timer id, but set this value just in case we do in the future #define kAudacityAppTimerID 0 BEGIN_EVENT_TABLE(AudacityApp, wxApp) EVT_QUERY_END_SESSION(AudacityApp::OnQueryEndSession) EVT_END_SESSION(AudacityApp::OnEndSession) EVT_TIMER(kAudacityAppTimerID, AudacityApp::OnTimer) #ifdef __WXMAC__ EVT_MENU(wxID_NEW, AudacityApp::OnMenuNew) EVT_MENU(wxID_OPEN, AudacityApp::OnMenuOpen) EVT_MENU(wxID_ABOUT, AudacityApp::OnMenuAbout) EVT_MENU(wxID_PREFERENCES, AudacityApp::OnMenuPreferences) #endif // Associate the handler with the menu id on all operating systems, even // if they don't have an application menu bar like in macOS, so that // other parts of the program can send the application a shut-down // event EVT_MENU(wxID_EXIT, AudacityApp::OnMenuExit) #ifndef __WXMSW__ EVT_SOCKET(ID_IPC_SERVER, AudacityApp::OnServerEvent) EVT_SOCKET(ID_IPC_SOCKET, AudacityApp::OnSocketEvent) #endif // Recent file event handlers. EVT_MENU(FileHistory::ID_RECENT_CLEAR, AudacityApp::OnMRUClear) EVT_MENU_RANGE(FileHistory::ID_RECENT_FIRST, FileHistory::ID_RECENT_LAST, AudacityApp::OnMRUFile) // Handle AppCommandEvents (usually from a script) EVT_APP_COMMAND(wxID_ANY, AudacityApp::OnReceiveCommand) // Global ESC key handling EVT_KEY_DOWN(AudacityApp::OnKeyDown) END_EVENT_TABLE() // backend for OnMRUFile // TODO: Would be nice to make this handle not opening a file with more panache. // - Inform the user if DefaultOpenPath not set. // - Switch focus to correct instance of project window, if already open. bool AudacityApp::MRUOpen(const FilePath &fullPathStr) { // Most of the checks below are copied from ProjectManager::OpenFiles. // - some rationalisation might be possible. AudacityProject *proj = GetActiveProject(); if (!fullPathStr.empty()) { // verify that the file exists if (wxFile::Exists(fullPathStr)) { FileNames::UpdateDefaultPath(FileNames::Operation::Open, ::wxPathOnly(fullPathStr)); // Make sure it isn't already open. // Test here even though AudacityProject::OpenFile() also now checks, because // that method does not return the bad result. // That itself may be a FIXME. if (ProjectFileManager::IsAlreadyOpen(fullPathStr)) return false; ( void ) ProjectManager::OpenProject( proj, fullPathStr, true /* addtohistory */, false /* reuseNonemptyProject */ ); } else { // File doesn't exist - remove file from history AudacityMessageBox( XO( "%s could not be found.\n\nIt has been removed from the list of recent files.") .Format(fullPathStr) ); return(false); } } return(true); } bool AudacityApp::SafeMRUOpen(const wxString &fullPathStr) { return GuardedCall< bool >( [&]{ return MRUOpen( fullPathStr ); } ); } void AudacityApp::OnMRUClear(wxCommandEvent& WXUNUSED(event)) { FileHistory::Global().Clear(); } //vvv Basically, anything from Recent Files is treated as a .aup3, until proven otherwise, // then it tries to Import(). Very questionable handling, imo. // Better, for example, to check the file type early on. void AudacityApp::OnMRUFile(wxCommandEvent& event) { int n = event.GetId() - FileHistory::ID_RECENT_FIRST; auto &history = FileHistory::Global(); const auto &fullPathStr = history[ n ]; // Try to open only if not already open. // Test IsAlreadyOpen() here even though AudacityProject::MRUOpen() also now checks, // because we don't want to Remove() just because it already exists, // and AudacityApp::OnMacOpenFile() calls MRUOpen() directly. // that method does not return the bad result. // PRL: Don't call SafeMRUOpen // -- if open fails for some exceptional reason of resource exhaustion that // the user can correct, leave the file in history. if (!ProjectFileManager::IsAlreadyOpen(fullPathStr) && !MRUOpen(fullPathStr)) history.Remove(n); } void AudacityApp::OnTimer(wxTimerEvent& WXUNUSED(event)) { // Filenames are queued when Audacity receives a few of the // AppleEvent messages (via wxWidgets). So, open any that are // in the queue and clean the queue. if (gInited) { if (ofqueue.size()) { // Load each file on the queue while (ofqueue.size()) { wxString name; name.swap(ofqueue[0]); ofqueue.erase( ofqueue.begin() ); // Get the user's attention if no file name was specified if (name.empty()) { // Get the users attention AudacityProject *project = GetActiveProject(); if (project) { auto &window = GetProjectFrame( *project ); window.Maximize(); window.Raise(); window.RequestUserAttention(); } continue; } // TODO: Handle failures better. // Some failures are OK, e.g. file not found, just would-be-nices to do better, // so FAIL_MSG is more a case of an enhancement request than an actual problem. // LL: In all but one case an appropriate message is already displayed. The // instance that a message is NOT displayed is when a failure to write // to the config file has occurred. // PRL: Catch any exceptions, don't try this file again, continue to // other files. if (!SafeMRUOpen(name)) { // Just log it. Assertion failure is not appropriate on valid // defensive path against bad file data. wxLogMessage(wxT("MRUOpen failed")); } } } } } #if defined(__WXMSW__) #define WL(lang, sublang) (lang), (sublang), #else #define WL(lang,sublang) #endif #if wxCHECK_VERSION(3, 0, 1) wxLanguageInfo userLangs[] = { // Bosnian is defined in wxWidgets already // { wxLANGUAGE_USER_DEFINED, wxT("bs"), WL(0, SUBLANG_DEFAULT) wxT("Bosnian"), wxLayout_LeftToRight }, { wxLANGUAGE_USER_DEFINED, wxT("eu"), WL(0, SUBLANG_DEFAULT) wxT("Basque"), wxLayout_LeftToRight }, }; #endif void AudacityApp::OnFatalException() { #if defined(HAS_CRASH_REPORT) CrashReport::Generate(wxDebugReport::Context_Exception); #endif exit(-1); } #ifdef _MSC_VER // If this is compiled with MSVC (Visual Studio) #pragma warning( push ) #pragma warning( disable : 4702) // unreachable code warning. #endif //_MSC_VER bool AudacityApp::OnExceptionInMainLoop() { // This function is invoked from catch blocks in the wxWidgets framework, // and throw; without argument re-throws the exception being handled, // letting us dispatch according to its type. try { throw; } catch ( AudacityException &e ) { (void)e;// Compiler food // Here is the catch-all for our own exceptions // Use CallAfter to delay this to the next pass of the event loop, // rather than risk doing it inside stack unwinding. auto pProject = ::GetActiveProject(); auto pException = std::current_exception(); CallAfter( [=] // Capture pException by value! { // Restore the state of the project to what it was before the // failed operation if (pProject) { ProjectHistory::Get( *pProject ).RollbackState(); // Forget pending changes in the TrackList TrackList::Get( *pProject ).ClearPendingTracks(); ProjectWindow::Get( *pProject ).RedrawProject(); } // Give the user an alert try { std::rethrow_exception( pException ); } catch( AudacityException &e ) { e.DelayedHandlerAction(); } } ); // Don't quit the program return true; } catch ( ... ) { // There was some other type of exception we don't know. // Let the inherited function do throw; again and whatever else it does. return wxApp::OnExceptionInMainLoop(); } // Shouldn't ever reach this line return false; } #ifdef _MSC_VER #pragma warning( pop ) #endif //_MSC_VER AudacityApp::AudacityApp() { #if defined(USE_BREAKPAD) InitBreakpad(); // Do not capture crashes in debug builds #elif !defined(_DEBUG) #if defined(HAS_CRASH_REPORT) #if defined(wxUSE_ON_FATAL_EXCEPTION) && wxUSE_ON_FATAL_EXCEPTION wxHandleFatalExceptions(); #endif #endif #endif } AudacityApp::~AudacityApp() { } // The `main program' equivalent, creating the windows and returning the // main frame bool AudacityApp::OnInit() { // JKC: ANSWER-ME: Who actually added the event loop guarantor? // Although 'blame' says Leland, I think it came from a donated patch. // PRL: It was added by LL at 54676a72285ba7ee3a69920e91fa390a71ef10c9 : // " Ensure OnInit() has an event loop // And allow events to flow so the splash window updates under GTK" // then mistakenly lost in the merge at // 37168ebbf67ae869ab71a3b5cbbf1d2a48e824aa // then restored at 7687972aa4b2199f0717165235f3ef68ade71e08 // Ensure we have an event loop during initialization wxEventLoopGuarantor eventLoop; // Fire up SQLite if ( !ProjectFileIO::InitializeSQL() ) this->CallAfter([]{ ::AudacityMessageBox( XO("SQLite library failed to initialize. Audacity cannot continue.") ); QuitAudacity( true ); }); // cause initialization of wxWidgets' global logger target (void) AudacityLogger::Get(); #if defined(__WXMAC__) // Disable window animation wxSystemOptions::SetOption(wxMAC_WINDOW_PLAIN_TRANSITION, 1); #endif // Some GTK themes produce larger combo boxes that make them taller // than our single toolbar height restriction. This will remove some // of the extra space themes add. #if defined(__WXGTK3__) && defined(HAVE_GTK) GtkWidget *combo = gtk_combo_box_new(); GtkCssProvider *provider = gtk_css_provider_new(); gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(provider), ".linked entry,\n" ".linked button,\n" ".linked combobox box.linked button,\n" ".horizontal.linked entry,\n" ".horizontal.linked button,\n" ".horizontal.linked combobox box.linked button,\n" "combobox {\n" " padding-top: 0px;\n" " padding-bottom: 0px;\n" " padding-left: 4px;\n" " padding-right: 4px;\n" " margin: 0px;\n" " font-size: 95%;\n" "}", -1, NULL); gtk_style_context_add_provider_for_screen(gtk_widget_get_screen(combo), GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); g_object_unref(provider); g_object_unref(combo); #elif defined(__WXGTK__) && defined(HAVE_GTK) gtk_rc_parse_string("style \"audacity\" {\n" " GtkButton::inner_border = { 0, 0, 0, 0 }\n" " GtkEntry::inner_border = { 0, 0, 0, 0 }\n" " xthickness = 4\n" " ythickness = 0\n" "}\n" "widget_class \"*GtkCombo*\" style \"audacity\""); #endif // Don't use AUDACITY_NAME here. // We want Audacity with a capital 'A' // DA: App name #ifndef EXPERIMENTAL_DA wxString appName = wxT("Audacity"); #else wxString appName = wxT("DarkAudacity"); #endif wxTheApp->SetAppName(appName); // Explicitly set since OSX will use it for the "Quit" menu item wxTheApp->SetAppDisplayName(appName); wxTheApp->SetVendorName(appName); ::wxInitAllImageHandlers(); // AddHandler takes ownership wxFileSystem::AddHandler(safenew wxZipFSHandler); // // Paths: set search path and temp dir path // FilePaths audacityPathList; #ifdef __WXGTK__ // Make sure install prefix is set so wxStandardPath resolves paths properly wxStandardPaths::Get().SetInstallPrefix(wxT(INSTALL_PREFIX)); /* Search path (for plug-ins, translations etc) is (in this order): * The AUDACITY_PATH environment variable * The current directory * The user's "~/.audacity-data" or "Portable Settings" directory * The user's "~/.audacity-files" directory * The "share" and "share/doc" directories in their install path */ wxString home = wxGetHomeDir(); wxString envTempDir = wxGetenv(wxT("TMPDIR")); if (!envTempDir.empty()) { /* On Unix systems, the environment variable TMPDIR may point to an unusual path when /tmp and /var/tmp are not desirable. */ TempDirectory::SetDefaultTempDir( wxString::Format( wxT("%s/audacity-%s"), envTempDir, wxGetUserId() ) ); } else { /* On Unix systems, the default temp dir is in /var/tmp. */ TempDirectory::SetDefaultTempDir( wxString::Format( wxT("/var/tmp/audacity-%s"), wxGetUserId() ) ); } // DA: Path env variable. #ifndef EXPERIMENTAL_DA wxString pathVar = wxGetenv(wxT("AUDACITY_PATH")); #else wxString pathVar = wxGetenv(wxT("DARKAUDACITY_PATH")); #endif if (!pathVar.empty()) FileNames::AddMultiPathsToPathList(pathVar, audacityPathList); FileNames::AddUniquePathToPathList(::wxGetCwd(), audacityPathList); wxString progPath = wxPathOnly(argv[0]); FileNames::AddUniquePathToPathList(progPath, audacityPathList); // Add the path to modules: FileNames::AddUniquePathToPathList(progPath + L"/lib/audacity", audacityPathList); FileNames::AddUniquePathToPathList(FileNames::DataDir(), audacityPathList); #ifdef AUDACITY_NAME FileNames::AddUniquePathToPathList(wxString::Format(wxT("%s/.%s-files"), home, wxT(AUDACITY_NAME)), audacityPathList); FileNames::AddUniquePathToPathList(FileNames::ModulesDir(), audacityPathList); FileNames::AddUniquePathToPathList(wxString::Format(wxT("%s/share/%s"), wxT(INSTALL_PREFIX), wxT(AUDACITY_NAME)), audacityPathList); FileNames::AddUniquePathToPathList(wxString::Format(wxT("%s/share/doc/%s"), wxT(INSTALL_PREFIX), wxT(AUDACITY_NAME)), audacityPathList); #else //AUDACITY_NAME FileNames::AddUniquePathToPathList(wxString::Format(wxT("%s/.audacity-files"), home), audacityPathList) FileNames::AddUniquePathToPathList(FileNames::ModulesDir(), audacityPathList); FileNames::AddUniquePathToPathList(wxString::Format(wxT("%s/share/audacity"), wxT(INSTALL_PREFIX)), audacityPathList); FileNames::AddUniquePathToPathList(wxString::Format(wxT("%s/share/doc/audacity"), wxT(INSTALL_PREFIX)), audacityPathList); #endif //AUDACITY_NAME FileNames::AddUniquePathToPathList(wxString::Format(wxT("%s/share/locale"), wxT(INSTALL_PREFIX)), audacityPathList); FileNames::AddUniquePathToPathList(wxString::Format(wxT("./locale")), audacityPathList); #endif //__WXGTK__ // JKC Bug 1220: Use path based on home directory on WXMAC #ifdef __WXMAC__ wxFileName tmpFile; tmpFile.AssignHomeDir(); wxString tmpDirLoc = tmpFile.GetPath(wxPATH_GET_VOLUME); #else wxFileName tmpFile; tmpFile.AssignTempFileName(wxT("nn")); wxString tmpDirLoc = tmpFile.GetPath(wxPATH_GET_VOLUME); ::wxRemoveFile(tmpFile.GetFullPath()); #endif // On Mac and Windows systems, use the directory which contains Audacity. #ifdef __WXMSW__ // On Windows, the path to the Audacity program is in argv[0] wxString progPath = wxPathOnly(argv[0]); FileNames::AddUniquePathToPathList(progPath, audacityPathList); FileNames::AddUniquePathToPathList(progPath + wxT("\\Languages"), audacityPathList); // See bug #1271 for explanation of location tmpDirLoc = FileNames::MkDir(wxStandardPaths::Get().GetUserLocalDataDir()); TempDirectory::SetDefaultTempDir( wxString::Format( wxT("%s\\SessionData"), tmpDirLoc ) ); #endif //__WXWSW__ #ifdef __WXMAC__ // On Mac OS X, the path to the Audacity program is in argv[0] wxString progPath = wxPathOnly(argv[0]); FileNames::AddUniquePathToPathList(progPath, audacityPathList); // If Audacity is a "bundle" package, then the root directory is // the great-great-grandparent of the directory containing the executable. //FileNames::AddUniquePathToPathList(progPath + wxT("/../../../"), audacityPathList); // These allow for searching the "bundle" FileNames::AddUniquePathToPathList( progPath + wxT("/../"), audacityPathList); FileNames::AddUniquePathToPathList( progPath + wxT("/../Resources"), audacityPathList); // JKC Bug 1220: Using an actual temp directory for session data on Mac was // wrong because it would get cleared out on a reboot. TempDirectory::SetDefaultTempDir( wxString::Format( wxT("%s/Library/Application Support/audacity/SessionData"), tmpDirLoc) ); //TempDirectory::SetDefaultTempDir( wxString::Format( // wxT("%s/audacity-%s"), // tmpDirLoc, // wxGetUserId() ) ); #endif //__WXMAC__ FileNames::SetAudacityPathList( std::move( audacityPathList ) ); // Define languages for which we have translations, but that are not yet // supported by wxWidgets. // // TODO: The whole Language initialization really need to be reworked. // It's all over the place. #if wxCHECK_VERSION(3, 0, 1) for (size_t i = 0, cnt = WXSIZEOF(userLangs); i < cnt; i++) { wxLocale::AddLanguage(userLangs[i]); } #endif // Initialize preferences and language { wxFileName configFileName(FileNames::DataDir(), wxT("audacity.cfg")); auto appName = wxTheApp->GetAppName(); InitPreferences( AudacityFileConfig::Create( appName, wxEmptyString, configFileName.GetFullPath(), wxEmptyString, wxCONFIG_USE_LOCAL_FILE) ); PopulatePreferences(); } #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__) && !defined(__CYGWIN__) this->AssociateFileTypes(); #endif theTheme.EnsureInitialised(); // AColor depends on theTheme. AColor::Init(); // If this fails, we must exit the program. if (!InitTempDir()) { FinishPreferences(); return false; } #ifdef __WXMAC__ // Bug2437: When files are opened from Finder and another instance of // Audacity is running, we must return from OnInit() to wxWidgets before // MacOpenFile is called, informing us of the paths that need to be // opened. So use CallAfter() to delay the rest of initialization. // See CreateSingleInstanceChecker() where we send those paths over a // socket to the prior instance. // This call is what probably makes the sleep unnecessary: MacFinishLaunching(); using namespace std::chrono; // This sleep may be unnecessary, but it is harmless. It less NS framework // events arrive on another thread, but it might not always be long enough. std::this_thread::sleep_for(100ms); CallAfter([this]{ if (!InitPart2()) exit(-1); }); return true; #else return InitPart2(); #endif } bool AudacityApp::InitPart2() { #if defined(__WXMAC__) SetExitOnFrameDelete(false); #endif // Make sure the temp dir isn't locked by another process. { auto key = PreferenceKey(FileNames::Operation::Temp, FileNames::PathType::_None); auto temp = gPrefs->Read(key); if (temp.empty() || !CreateSingleInstanceChecker(temp)) { FinishPreferences(); return false; } } //<<<< Try to avoid dialogs before this point. // The reason is that InitTempDir starts the single instance checker. // If we're waiitng in a dialog before then we can very easily // start multiple instances, defeating the single instance checker. // Initialize the CommandHandler InitCommandHandler(); // Initialize the ModuleManager, including loading found modules ModuleManager::Get().Initialize(); // Initialize the PluginManager PluginManager::Get().Initialize(); // Parse command line and handle options that might require // immediate exit...no need to initialize all of the audio // stuff to display the version string. std::shared_ptr< wxCmdLineParser > parser{ ParseCommandLine().release() }; if (!parser) { // Either user requested help or a parsing error occurred exit(1); } if (parser->Found(wxT("v"))) { wxPrintf("Audacity v%s\n", AUDACITY_VERSION_STRING); exit(0); } long lval; if (parser->Found(wxT("b"), &lval)) { if (lval < 256 || lval > 100000000) { wxPrintf(_("Block size must be within 256 to 100000000\n")); exit(1); } Sequence::SetMaxDiskBlockSize(lval); } // BG: Create a temporary window to set as the top window wxImage logoimage((const char **)AudacityLogoWithName_xpm); logoimage.Rescale(logoimage.GetWidth() / 2, logoimage.GetHeight() / 2); if( GetLayoutDirection() == wxLayout_RightToLeft) logoimage = logoimage.Mirror(); wxBitmap logo(logoimage); AudacityProject *project; { // Bug 718: Position splash screen on same screen // as where Audacity project will appear. wxRect wndRect; bool bMaximized = false; bool bIconized = false; GetNextWindowPlacement(&wndRect, &bMaximized, &bIconized); wxSplashScreen temporarywindow( logo, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_NO_TIMEOUT, 0, NULL, wxID_ANY, wndRect.GetTopLeft(), wxDefaultSize, wxSTAY_ON_TOP); // Unfortunately with the Windows 10 Creators update, the splash screen // now appears before setting its position. // On a dual monitor screen it will appear on one screen and then // possibly jump to the second. // We could fix this by writing our own splash screen and using Hide() // until the splash scren was correctly positioned, then Show() // Possibly move it on to the second screen... temporarywindow.SetPosition( wndRect.GetTopLeft() ); // Centered on whichever screen it is on. temporarywindow.Center(); temporarywindow.SetTitle(_("Audacity is starting up...")); SetTopWindow(&temporarywindow); temporarywindow.Raise(); // ANSWER-ME: Why is YieldFor needed at all? //wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI|wxEVT_CATEGORY_USER_INPUT|wxEVT_CATEGORY_UNKNOWN); wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI); //JKC: Would like to put module loading here. // More initialization InitDitherers(); AudioIO::Init(); #ifdef __WXMAC__ // On the Mac, users don't expect a program to quit when you close the last window. // Create a menubar that will show when all project windows are closed. auto fileMenu = std::make_unique(); auto urecentMenu = std::make_unique(); auto recentMenu = urecentMenu.get(); fileMenu->Append(wxID_NEW, wxString(_("&New")) + wxT("\tCtrl+N")); fileMenu->Append(wxID_OPEN, wxString(_("&Open...")) + wxT("\tCtrl+O")); fileMenu->AppendSubMenu(urecentMenu.release(), _("Open &Recent...")); fileMenu->Append(wxID_ABOUT, _("&About Audacity...")); fileMenu->Append(wxID_PREFERENCES, wxString(_("&Preferences...")) + wxT("\tCtrl+,")); { auto menuBar = std::make_unique(); menuBar->Append(fileMenu.release(), _("&File")); // PRL: Are we sure wxWindows will not leak this menuBar? // The online documentation is not explicit. wxMenuBar::MacSetCommonMenuBar(menuBar.release()); } auto &recentFiles = FileHistory::Global(); recentFiles.UseMenu(recentMenu); #endif //__WXMAC__ temporarywindow.Show(false); } // Workaround Bug 1377 - Crash after Audacity starts and low disk space warning appears // The temporary splash window is closed AND cleaned up, before attempting to create // a project and possibly creating a modal warning dialog by doing so. // Also fixes problem of warning being obscured. // Downside is that we have no splash screen for the (brief) time that we spend // creating the project. // Root cause is problem with wxSplashScreen and other dialogs co-existing, that // seemed to arrive with wx3. { project = ProjectManager::New(); } if( ProjectSettings::Get( *project ).GetShowSplashScreen() ){ // This may do a check-for-updates at every start up. // Mainly this is to tell users of ALPHAS who don't know that they have an ALPHA. // Disabled for now, after discussion. // project->MayCheckForUpdates(); SplashDialog::DoHelpWelcome(*project); } #if defined(HAVE_UPDATES_CHECK) UpdateManager::Start(); #endif #ifdef USE_FFMPEG FFmpegStartup(); #endif Importer::Get().Initialize(); // Bug1561: delay the recovery dialog, to avoid crashes. CallAfter( [=] () mutable { // Remove duplicate shortcuts when there's a change of version int vMajorInit, vMinorInit, vMicroInit; gPrefs->GetVersionKeysInit(vMajorInit, vMinorInit, vMicroInit); if (vMajorInit != AUDACITY_VERSION || vMinorInit != AUDACITY_RELEASE || vMicroInit != AUDACITY_REVISION) { CommandManager::Get(*project).RemoveDuplicateShortcuts(); } // // Auto-recovery // bool didRecoverAnything = false; // This call may reassign project (passed by reference) if (!ShowAutoRecoveryDialogIfNeeded(project, &didRecoverAnything)) { QuitAudacity(true); } // // Remainder of command line parsing, but only if we didn't recover // if (project && !didRecoverAnything) { if (parser->Found(wxT("t"))) { RunBenchmark( nullptr, *project); QuitAudacity(true); } for (size_t i = 0, cnt = parser->GetParamCount(); i < cnt; i++) { // PRL: Catch any exceptions, don't try this file again, continue to // other files. SafeMRUOpen(parser->GetParam(i)); } } } ); gInited = true; ModuleManager::Get().Dispatch(AppInitialized); mTimer.SetOwner(this, kAudacityAppTimerID); mTimer.Start(200); #ifdef EXPERIMENTAL_EASY_CHANGE_KEY_BINDINGS CommandManager::SetMenuHook( [](const CommandID &id){ if (::wxGetMouseState().ShiftDown()) { // Only want one page of the preferences PrefsPanel::Factories factories; factories.push_back(KeyConfigPrefsFactory( id )); const auto pProject = GetActiveProject(); auto pWindow = FindProjectFrame( pProject ); GlobalPrefsDialog dialog( pWindow, pProject, factories ); dialog.ShowModal(); MenuCreator::RebuildAllMenuBars(); return true; } else return false; } ); #endif #if defined(__WXMAC__) // The first time this version of Audacity is run or when the preferences // are reset, execute the "tccutil" command to reset the microphone permissions // currently assigned to Audacity. The end result is that the user will be // prompted to approve/deny Audacity access (again). // // This should resolve confusion of why Audacity appears to record, but only // gets silence due to Audacity being denied microphone access previously. bool permsReset = false; gPrefs->Read(wxT("/MicrophonePermissionsReset"), &permsReset, false); if (!permsReset) { system("tccutil reset Microphone org.audacityteam.audacity"); gPrefs->Write(wxT("/MicrophonePermissionsReset"), true); } #endif #if defined(__WXMAC__) // Bug 2709: Workaround CoreSVG locale issue Bind(wxEVT_MENU_OPEN, [=](wxMenuEvent &event) { wxSetlocale(LC_NUMERIC, wxString(wxT("C"))); event.Skip(); }); Bind(wxEVT_MENU_CLOSE, [=](wxMenuEvent &event) { wxSetlocale(LC_NUMERIC, Languages::GetLocaleName()); event.Skip(); }); #endif return TRUE; } void AudacityApp::InitCommandHandler() { mCmdHandler = std::make_unique(); //SetNextHandler(mCmdHandler); } // AppCommandEvent callback - just pass the event on to the CommandHandler void AudacityApp::OnReceiveCommand(AppCommandEvent &event) { wxASSERT(NULL != mCmdHandler); mCmdHandler->OnReceiveCommand(event); } void AudacityApp::OnKeyDown(wxKeyEvent &event) { if(event.GetKeyCode() == WXK_ESCAPE) { // Stop play, including scrub, but not record auto project = ::GetActiveProject(); if ( project ) { auto token = ProjectAudioIO::Get( *project ).GetAudioIOToken(); auto &scrubber = Scrubber::Get( *project ); auto scrubbing = scrubber.HasMark(); if (scrubbing) scrubber.Cancel(); auto gAudioIO = AudioIO::Get(); if((token > 0 && gAudioIO->IsAudioTokenActive(token) && gAudioIO->GetNumCaptureChannels() == 0) || scrubbing) // ESC out of other play (but not record) ProjectAudioManager::Get( *project ).Stop(); else event.Skip(); } } event.Skip(); } // Ensures directory is created and puts the name into result. // result is unchanged if unsuccessful. void SetToExtantDirectory( wxString & result, const wxString & dir ){ // don't allow path of "". if( dir.empty() ) return; if( wxDirExists( dir ) ){ result = dir; return; } // Use '/' so that this works on Mac and Windows alike. wxFileName name( dir + "/junkname.cfg" ); if( name.Mkdir( wxS_DIR_DEFAULT , wxPATH_MKDIR_FULL ) ) result = dir; } bool AudacityApp::InitTempDir() { // We need to find a temp directory location. auto tempFromPrefs = TempDirectory::TempDir(); auto tempDefaultLoc = TempDirectory::DefaultTempDir(); wxString temp; #ifdef __WXGTK__ if (tempFromPrefs.length() > 0 && tempFromPrefs[0] != wxT('/')) tempFromPrefs = wxT(""); #endif // Stop wxWidgets from printing its own error messages wxLogNull logNo; // Try temp dir that was stored in prefs first if( TempDirectory::IsTempDirectoryNameOK( tempFromPrefs ) ) SetToExtantDirectory( temp, tempFromPrefs ); // If that didn't work, try the default location if (temp.empty()) SetToExtantDirectory( temp, tempDefaultLoc ); // Check temp directory ownership on *nix systems only #ifdef __UNIX__ struct stat tempStatBuf; if ( lstat(temp.mb_str(), &tempStatBuf) != 0 ) { temp.clear(); } else { if ( geteuid() != tempStatBuf.st_uid ) { temp.clear(); } } #endif if (temp.empty()) { // Failed if( !TempDirectory::IsTempDirectoryNameOK( tempFromPrefs ) ) { AudacityMessageBox(XO( "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.")); } else { AudacityMessageBox(XO( "Audacity could not find a place to store temporary files.\nPlease enter an appropriate directory in the preferences dialog.")); } // Only want one page of the preferences PrefsPanel::Factories factories; factories.push_back(DirectoriesPrefsFactory()); GlobalPrefsDialog dialog(nullptr, nullptr, factories); dialog.ShowModal(); AudacityMessageBox(XO( "Audacity is now going to exit. Please launch Audacity again to use the new temporary directory.")); return false; } // The permissions don't always seem to be set on // some platforms. Hopefully this fixes it... #ifdef __UNIX__ chmod(OSFILENAME(temp), 0700); #endif TempDirectory::ResetTempDir(); FileNames::UpdateDefaultPath(FileNames::Operation::Temp, temp); return true; } #if defined(__WXMSW__) // Return true if there are no other instances of Audacity running, // false otherwise. bool AudacityApp::CreateSingleInstanceChecker(const wxString &dir) { wxString name = wxString::Format(wxT("audacity-lock-%s"), wxGetUserId()); mChecker.reset(); auto checker = std::make_unique(); auto runningTwoCopiesStr = XO("Running two copies of Audacity simultaneously may cause\ndata loss or cause your system to crash.\n\n"); if (!checker->Create(name, dir)) { // Error initializing the wxSingleInstanceChecker. We don't know // whether there is another instance running or not. auto prompt = XO( "Audacity was not able to lock the temporary files directory.\nThis folder may be in use by another copy of Audacity.\n") + runningTwoCopiesStr + XO("Do you still want to start Audacity?"); int action = AudacityMessageBox( prompt, XO("Error Locking Temporary Folder"), wxYES_NO | wxICON_EXCLAMATION, NULL); if (action == wxNO) return false; } else if ( checker->IsAnotherRunning() ) { // Parse the command line to ensure correct syntax, but // ignore options other than -v, and only use the filenames, if any. auto parser = ParseCommandLine(); if (!parser) { // Complaints have already been made return false; } if (parser->Found(wxT("v"))) { wxPrintf("Audacity v%s\n", AUDACITY_VERSION_STRING); return false; } // Windows and Linux require absolute file names as command may // not come from current working directory. FilePaths filenames; for (size_t i = 0, cnt = parser->GetParamCount(); i < cnt; i++) { wxFileName filename(parser->GetParam(i)); if (filename.MakeAbsolute()) { filenames.push_back(filename.GetLongPath()); } } // On Windows, we attempt to make a connection // to an already active Audacity. If successful, we send // the first command line argument (the audio file name) // to that Audacity for processing. wxClient client; // We try up to 50 times since there's a small window // where the server may not have been fully initialized. for (int i = 0; i < 50; i++) { std::unique_ptr conn{ client.MakeConnection(wxEmptyString, IPC_APPL, IPC_TOPIC) }; if (conn) { bool ok = false; if (filenames.size() > 0) { for (size_t i = 0, cnt = filenames.size(); i < cnt; i++) { ok = conn->Execute(filenames[i]); } } else { // Send an empty string to force existing Audacity to front ok = conn->Execute(wxEmptyString); } if (ok) return false; } wxMilliSleep(10); } // There is another copy of Audacity running. Force quit. auto prompt = XO( "The system has detected that another copy of Audacity is running.\n") + runningTwoCopiesStr + XO( "Use the New or Open commands in the currently running Audacity\nprocess to open multiple projects simultaneously.\n"); AudacityMessageBox( prompt, XO("Audacity is already running"), wxOK | wxICON_ERROR); return false; } // Create the DDE IPC server mIPCServ = std::make_unique(IPC_APPL); mChecker = std::move(checker); return true; } #endif #if defined(__UNIX__) #include #include #include // Return true if there are no other instances of Audacity running, // false otherwise. bool AudacityApp::CreateSingleInstanceChecker(const wxString &dir) { mIPCServ.reset(); bool isServer = false; wxIPV4address addr; addr.LocalHost(); struct sembuf op = {}; // Generate the IPC key we'll use for both shared memory and semaphores. wxString datadir = FileNames::DataDir(); key_t memkey = ftok(datadir.c_str(), 0); key_t servkey = ftok(datadir.c_str(), 1); key_t lockkey = ftok(datadir.c_str(), 2); // Create and map the shared memory segment where the port number // will be stored. int memid = shmget(memkey, sizeof(int), IPC_CREAT | S_IRUSR | S_IWUSR); int *portnum = (int *) shmat(memid, nullptr, 0); // Create (or return) the SERVER semaphore ID int servid = semget(servkey, 1, IPC_CREAT | S_IRUSR | S_IWUSR); // Create the LOCK semaphore only if it doesn't already exist. int lockid = semget(lockkey, 1, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR); // If the LOCK semaphore was successfully created, then this is the first // time Audacity has been run during this boot of the system. In this // case we know we'll become the "server" application, so set up the // semaphores to prepare for it. if (lockid != -1) { // Initialize value of each semaphore, 1 indicates released and 0 // indicates acquired. // // Note that this action is NOT recorded in the semaphore's // UNDO buffer. semctl(servid, 0, SETVAL, 1); semctl(lockid, 0, SETVAL, 1); // Now acquire them so the semaphores will be set to the // released state when the process terminates. op.sem_num = 0; op.sem_op = -1; op.sem_flg = SEM_UNDO; if (semop(lockid, &op, 1) == -1 || semop(servid, &op, 1) == -1) { AudacityMessageBox( XO("Unable to acquire semaphores.\n\n" "This is likely due to a resource shortage\n" "and a reboot may be required."), XO("Audacity Startup Failure"), wxOK | wxICON_ERROR); return false; } // We will be the server... isServer = true; } // Something catastrophic must have happened, so bail. else if (errno != EEXIST) { AudacityMessageBox( XO("Unable to create semaphores.\n\n" "This is likely due to a resource shortage\n" "and a reboot may be required."), XO("Audacity Startup Failure"), wxOK | wxICON_ERROR); return false; } // Otherwise it's a normal startup and we need to determine whether // we'll be the server or the client. else { // Retrieve the LOCK semaphore since we wouldn't have gotten it above. lockid = semget(lockkey, 1, 0); // Acquire the LOCK semaphore. We may block here if another // process is currently setting up the server. op.sem_num = 0; op.sem_op = -1; op.sem_flg = SEM_UNDO; if (semop(lockid, &op, 1) == -1) { AudacityMessageBox( XO("Unable to acquire lock semaphore.\n\n" "This is likely due to a resource shortage\n" "and a reboot may be required."), XO("Audacity Startup Failure"), wxOK | wxICON_ERROR); return false; } // Try to acquire the SERVER semaphore. If it's not currently active, then // we will become the server. Otherwise, this will fail and we'll know that // the server is already active and we will become the client. op.sem_num = 0; op.sem_op = -1; op.sem_flg = IPC_NOWAIT | SEM_UNDO; if (semop(servid, &op, 1) == 0) { isServer = true; } else if (errno != EAGAIN) { AudacityMessageBox( XO("Unable to acquire server semaphore.\n\n" "This is likely due to a resource shortage\n" "and a reboot may be required."), XO("Audacity Startup Failure"), wxOK | wxICON_ERROR); return false; } } // Initialize the socket server if we're to be the server. if (isServer) { // The system will randomly assign a port addr.Service(0); // Create the socket and bind to it. auto serv = std::make_unique(addr, wxSOCKET_NOWAIT); if (serv && serv->IsOk()) { serv->SetEventHandler(*this, ID_IPC_SERVER); serv->SetNotify(wxSOCKET_CONNECTION_FLAG); serv->Notify(true); mIPCServ = std::move(serv); // Save the port number in shared memory so that clients // know where to connect. mIPCServ->GetLocal(addr); *portnum = addr.Service(); } // Now that the server is active, we release the LOCK semaphore // to allow any waiters to continue. The SERVER semaphore will // remain locked for the duration of this processes execution // and will be cleaned up by the system. op.sem_num = 0; op.sem_op = 1; semop(lockid, &op, 1); // Bail if the server creation failed. if (mIPCServ == nullptr) { AudacityMessageBox( XO("The Audacity IPC server failed to initialize.\n\n" "This is likely due to a resource shortage\n" "and a reboot may be required."), XO("Audacity Startup Failure"), wxOK | wxICON_ERROR); return false; } // We've successfully created the socket server and the app // should continue to initialize. return true; } // Retrieve the port number that the server is listening on. addr.Service(*portnum); // Now release the LOCK semaphore. op.sem_num = 0; op.sem_op = 1; semop(lockid, &op, 1); // If we get here, then Audacity is currently active. So, we connect // to it and we forward all filenames listed on the command line to // the active process. // Setup the socket // // A wxSocketClient must not be deleted by us, but rather, let the // framework do appropriate delayed deletion after Destroy() Destroy_ptr sock { safenew wxSocketClient() }; sock->SetFlags(wxSOCKET_WAITALL); // Attempt to connect to an active Audacity. sock->Connect(addr, true); if (!sock->IsConnected()) { // All attempts to become the server or connect to one have failed. Not // sure what we can say about the error, but it's probably not because // Audacity is already running. AudacityMessageBox( XO("An unrecoverable error has occurred during startup"), XO("Audacity Startup Failure"), wxOK | wxICON_ERROR); return false; } // Parse the command line to ensure correct syntax, but ignore // options other than -v, and only use the filenames, if any. auto parser = ParseCommandLine(); if (!parser) { // Complaints have already been made return false; } // Display Audacity's version if requested if (parser->Found(wxT("v"))) { wxPrintf("Audacity v%s\n", AUDACITY_VERSION_STRING); return false; } #if defined(__WXMAC__) // On macOS the client gets events from the wxWidgets framework that // go to AudacityApp::MacOpenFile. Forward the file names to the prior // instance via the socket. for (const auto &filename: ofqueue) { auto str = filename.c_str().AsWChar(); sock->WriteMsg(str, (filename.length() + 1) * sizeof(*str)); } #endif // On macOS and Linux, forward any file names found in the command // line arguments. for (size_t j = 0, cnt = parser->GetParamCount(); j < cnt; ++j) { wxFileName filename(parser->GetParam(j)); if (filename.MakeAbsolute()) { const wxString param = filename.GetLongPath(); sock->WriteMsg((const wxChar *) param, (param.length() + 1) * sizeof(wxChar)); } } // Send an empty string to force existing Audacity to front sock->WriteMsg(wxEmptyString, sizeof(wxChar)); // We've forwarded all of the filenames, so let the caller know // to terminate. return false; } void AudacityApp::OnServerEvent(wxSocketEvent & /* evt */) { wxSocketBase *sock; // Accept all pending connection requests do { sock = mIPCServ->Accept(false); if (sock) { // Setup the socket sock->SetEventHandler(*this, ID_IPC_SOCKET); sock->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG); sock->Notify(true); } } while (sock); } void AudacityApp::OnSocketEvent(wxSocketEvent & evt) { wxSocketBase *sock = evt.GetSocket(); if (evt.GetSocketEvent() == wxSOCKET_LOST) { sock->Destroy(); return; } // Read the length of the filename and bail if we have a short read wxChar name[PATH_MAX]; sock->ReadMsg(&name, sizeof(name)); if (!sock->Error()) { // Add the filename to the queue. It will be opened by // the OnTimer() event when it is safe to do so. ofqueue.push_back(name); } } #endif std::unique_ptr AudacityApp::ParseCommandLine() { auto parser = std::make_unique(argc, argv); if (!parser) { return nullptr; } /*i18n-hint: This controls the number of bytes that Audacity will * use when writing files to the disk */ parser->AddOption(wxT("b"), wxT("blocksize"), _("set max disk block size in bytes"), wxCMD_LINE_VAL_NUMBER); /*i18n-hint: This displays a list of available options */ parser->AddSwitch(wxT("h"), wxT("help"), _("this help message"), wxCMD_LINE_OPTION_HELP); /*i18n-hint: This runs a set of automatic tests on Audacity itself */ parser->AddSwitch(wxT("t"), wxT("test"), _("run self diagnostics")); /*i18n-hint: This displays the Audacity version */ parser->AddSwitch(wxT("v"), wxT("version"), _("display Audacity version")); /*i18n-hint: This is a list of one or more files that Audacity * should open upon startup */ parser->AddParam(_("audio or project file name"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL); // Run the parser if (parser->Parse() == 0) return parser; return{}; } void AudacityApp::OnQueryEndSession(wxCloseEvent & event) { bool mustVeto = false; #ifdef __WXMAC__ mustVeto = wxDialog::OSXHasModalDialogsOpen(); #endif if ( mustVeto ) event.Veto(true); else OnEndSession(event); } void AudacityApp::OnEndSession(wxCloseEvent & event) { bool force = !event.CanVeto(); // Try to close each open window. If the user hits Cancel // in a Save Changes dialog, don't continue. gIsQuitting = true; if (AllProjects{}.size()) // PRL: Always did at least once before close might be vetoed // though I don't know why that is important ProjectManager::SaveWindowSize(); bool closedAll = AllProjects::Close( force ); if ( !closedAll ) { gIsQuitting = false; event.Veto(); } } int AudacityApp::OnExit() { gIsQuitting = true; while(Pending()) { Dispatch(); } Importer::Get().Terminate(); if(gPrefs) { bool bFalse = false; //Should we change the commands.cfg location next startup? if(gPrefs->Read(wxT("/QDeleteCmdCfgLocation"), &bFalse)) { gPrefs->DeleteEntry(wxT("/QDeleteCmdCfgLocation")); gPrefs->Write(wxT("/DeleteCmdCfgLocation"), true); gPrefs->Flush(); } } FileHistory::Global().Save(*gPrefs); FinishPreferences(); #ifdef USE_FFMPEG DropFFmpegLibs(); #endif DeinitFFT(); AudioIO::Deinit(); MenuTable::DestroyRegistry(); // Terminate the PluginManager (must be done before deleting the locale) PluginManager::Get().Terminate(); #ifdef HAS_NETWORKING audacity::network_manager::NetworkManager::GetInstance().Terminate(); #endif return 0; } // The following five methods are currently only used on Mac OS, // where it's possible to have a menu bar but no windows open. // It doesn't hurt any other platforms, though. // ...That is, as long as you check to see if no windows are open // before executing the stuff. // To fix this, check to see how many project windows are open, // and skip the event unless none are open (which should only happen // on the Mac, at least currently.) void AudacityApp::OnMenuAbout(wxCommandEvent & /*event*/) { // This function shadows a similar function // in Menus.cpp, but should only be used on the Mac platform. #ifdef __WXMAC__ // Modeless dialog, consistent with other Mac applications // Not more than one at once! const auto instance = AboutDialog::ActiveIntance(); if (instance) instance->Raise(); else // This dialog deletes itself when dismissed (safenew AboutDialog{ nullptr })->Show(true); #else wxASSERT(false); #endif } void AudacityApp::OnMenuNew(wxCommandEvent & event) { // This function shadows a similar function // in Menus.cpp, but should only be used on the Mac platform // when no project windows are open. This check assures that // this happens, and enable the same code to be present on // all platforms. if(AllProjects{}.empty()) (void) ProjectManager::New(); else event.Skip(); } void AudacityApp::OnMenuOpen(wxCommandEvent & event) { // This function shadows a similar function // in Menus.cpp, but should only be used on the Mac platform // when no project windows are open. This check assures that // this happens, and enable the same code to be present on // all platforms. if(AllProjects{}.empty()) ProjectManager::OpenFiles(NULL); else event.Skip(); } void AudacityApp::OnMenuPreferences(wxCommandEvent & event) { // This function shadows a similar function // in Menus.cpp, but should only be used on the Mac platform // when no project windows are open. This check assures that // this happens, and enable the same code to be present on // all platforms. if(AllProjects{}.empty()) { GlobalPrefsDialog dialog(nullptr /* parent */, nullptr ); dialog.ShowModal(); } else event.Skip(); } void AudacityApp::OnMenuExit(wxCommandEvent & event) { // This function shadows a similar function // in Menus.cpp, but should only be used on the Mac platform // when no project windows are open. This check assures that // this happens, and enable the same code to be present on // all platforms. // LL: Removed "if" to allow closing based on final project count. // if(AllProjects{}.empty()) QuitAudacity(); // LL: Veto quit if projects are still open. This can happen // if the user selected Cancel in a Save dialog. event.Skip(AllProjects{}.empty()); } //BG: On Windows, associate the aup file type with Audacity /* We do this in the Windows installer now, to avoid issues where user doesn't have admin privileges, but in case that didn't work, allow the user to decide at startup. //v Should encapsulate this & allow access from Prefs, too, // if people want to manually change associations. */ #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__) && !defined(__CYGWIN__) void AudacityApp::AssociateFileTypes() { // Check pref in case user has already decided against it. bool bWantAssociateFiles = true; if (gPrefs->Read(wxT("/WantAssociateFiles"), &bWantAssociateFiles) && !bWantAssociateFiles) { // User has already decided against it return; } wxRegKey associateFileTypes; auto IsDefined = [&](const wxString &type) { associateFileTypes.SetName(wxString::Format(wxT("HKCR\\%s"), type)); bool bKeyExists = associateFileTypes.Exists(); if (!bKeyExists) { // Not at HKEY_CLASSES_ROOT. Try HKEY_CURRENT_USER. associateFileTypes.SetName(wxString::Format(wxT("HKCU\\Software\\Classes\\%s"), type)); bKeyExists = associateFileTypes.Exists(); } return bKeyExists; }; auto DefineType = [&](const wxString &type) { wxString root_key = wxT("HKCU\\Software\\Classes\\"); // Start with HKEY_CLASSES_CURRENT_USER. associateFileTypes.SetName(wxString::Format(wxT("%s%s"), root_key, type)); if (!associateFileTypes.Create(true)) { // Not at HKEY_CLASSES_CURRENT_USER. Try HKEY_CURRENT_ROOT. root_key = wxT("HKCR\\"); associateFileTypes.SetName(wxString::Format(wxT("%s%s"), root_key, type)); if (!associateFileTypes.Create(true)) { // Actually, can't create keys. Empty root_key to flag failure. root_key.empty(); } } if (!root_key.empty()) { associateFileTypes = wxT("Audacity.Project"); // Finally set value for the key } return root_key; }; // Check for legacy and UP types if (IsDefined(wxT(".aup3")) && IsDefined(wxT(".aup")) && IsDefined(wxT("Audacity.Project"))) { // Already defined, so bail return; } // File types are not currently associated. int wantAssoc = AudacityMessageBox( XO( "Audacity project (.aup3) files are not currently \nassociated with Audacity. \n\nAssociate them, so they open on double-click?"), XO("Audacity Project Files"), wxYES_NO | wxICON_QUESTION); if (wantAssoc == wxNO) { // User said no. Set a pref so we don't keep asking. gPrefs->Write(wxT("/WantAssociateFiles"), false); gPrefs->Flush(); return; } // Show that user wants associations gPrefs->Write(wxT("/WantAssociateFiles"), true); gPrefs->Flush(); wxString root_key; root_key = DefineType(wxT(".aup3")); if (root_key.empty()) { //v Warn that we can't set keys. Ask whether to set pref for no retry? } else { DefineType(wxT(".aup")); associateFileTypes = wxT("Audacity.Project"); // Finally set value for .AUP key associateFileTypes.SetName(root_key + wxT("Audacity.Project")); if (!associateFileTypes.Exists()) { associateFileTypes.Create(true); associateFileTypes = wxT("Audacity Project File"); } associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell")); if (!associateFileTypes.Exists()) { associateFileTypes.Create(true); associateFileTypes = wxT(""); } associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open")); if (!associateFileTypes.Exists()) { associateFileTypes.Create(true); } associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\command")); wxString tmpRegAudPath; if(associateFileTypes.Exists()) { tmpRegAudPath = associateFileTypes.QueryDefaultValue().Lower(); } if (!associateFileTypes.Exists() || (tmpRegAudPath.Find(wxT("audacity.exe")) >= 0)) { associateFileTypes.Create(true); associateFileTypes = (wxString)argv[0] + (wxString)wxT(" \"%1\""); } #if 0 // These can be use later to support more startup messages // like maybe "Import into existing project" or some such. // Leaving here for an example... associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\ddeexec")); if (!associateFileTypes.Exists()) { associateFileTypes.Create(true); associateFileTypes = wxT("%1"); } associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\ddeexec\\Application")); if (!associateFileTypes.Exists()) { associateFileTypes.Create(true); associateFileTypes = IPC_APPL; } associateFileTypes.SetName(root_key + wxT("Audacity.Project\\shell\\open\\ddeexec\\Topic")); if (!associateFileTypes.Exists()) { associateFileTypes.Create(true); associateFileTypes = IPC_TOPIC; } #endif } } #endif