text: /**********************************************************************
  
    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 "Audacity.h" // This should always be included first; for USE_* macros and __UNIX__
  #include "AudacityApp.h"
  
  #include "Experimental.h"
  
  #if 0
  // This may be used to debug memory leaks.
  // See: Visual Leak Detector @ http://vld.codeplex.com/
  #include <vld.h>
  #endif
  
  #include <wx/setup.h> // for wxUSE_* macros
  #include <wx/wxcrtvararg.h>
  #include <wx/defs.h>
  #include <wx/app.h>
  #include <wx/bitmap.h>
  #include <wx/docview.h>
  #include <wx/event.h>
  #include <wx/ipc.h>
  #include <wx/window.h>
  #include <wx/intl.h>
  #include <wx/menu.h>
  #include <wx/snglinst.h>
  #include <wx/splash.h>
  #include <wx/stdpaths.h>
  #include <wx/sysopt.h>
  #include <wx/fontmap.h>
  
  #include <wx/fs_zip.h>
  #include <wx/image.h>
  
  #include <wx/dir.h>
  #include <wx/file.h>
  #include <wx/filename.h>
  
  #ifdef __WXGTK__
  #include <unistd.h>
  #ifdef HAVE_GTK
  #include <gtk/gtk.h>
  #endif
  #endif
  
  // chmod, lstat, geteuid
  #ifdef __UNIX__
  #include <sys/types.h>
  #include <sys/file.h>
  #include <sys/stat.h>
  #endif
  
  #if defined(__WXMSW__)
  #include <wx/msw/registry.h> // for wxRegKey
  #endif
  
  #include "AudacityLogger.h"
  #include "AboutDialog.h"
  #include "AColor.h"
  #include "AudioIO.h"
  #include "Benchmark.h"
  #include "Clipboard.h"
  #include "CrashReport.h"
  #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 "Track.h"
  #include "prefs/PrefsDialog.h"
  #include "Theme.h"
  #include "PlatformCompatibility.h"
  #include "FileNames.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/FileHistory.h"
  
  #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(EXPERIMENTAL_CRASH_REPORT)
  #include <wx/debugrpt.h>
  #include <wx/evtloop.h>
  #include <wx/textdlg.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
  
  
  ////////////////////////////////////////////////////////////
  /// 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()};
  
        wxFileConfig ini(wxEmptyString,
                         wxEmptyString,
                         fullPath,
                         wxEmptyString,
                         wxCONFIG_USE_LOCAL_FILE);
  
        wxString lang;
        if (ini.Read(wxT("/FromInno/Language"), &lang))
        {
           // 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!"));
        }
     }
  
     langCode = GUIPrefs::InitLang( 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 ((0<vMajor && vMajor < 2) ||
         (vMajor == 2 && vMinor < 2))
     {
        gPrefs->Write(wxT("/GUI/Shortcuts/FullDefaults"),1);
     }
  
     // Upgrading pre 2.4.0 configs, the selection toolbar is now split.
     if ((0<vMajor && vMajor < 2) ||
         (vMajor == 2 && vMinor < 4))
     {
        gPrefs->Write(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();
  }
  
  }
  
  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();
  
     //remove our logger
     std::unique_ptr<wxLog>{ 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 <dlfcn.h>
  /* 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 <glib-object.h>
  
  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<char**>(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(__WXMSW__) && !wxCHECK_VERSION(3, 1, 0)
  // Disable telling Windows that we support HiDPI displays.  It is forced on
  // in wxWidget versions between 3.0.0 and 3.1.0.
  IMPLEMENT_APP_NO_MAIN(AudacityApp)
  IMPLEMENT_WX_THEME_SUPPORT
  
  extern "C" int WINAPI WinMain(HINSTANCE hInstance,
                                HINSTANCE hPrevInstance,
                                wxCmdLineArgType WXUNUSED(lpCmdLine),
                                int nCmdShow)
  {
     wxDISABLE_DEBUG_SUPPORT();
  
     // Disable setting of HiDPI aware mode
     wxMSWDisableSettingHighDPIAware();
  
     /* NB: We pass NULL in place of lpCmdLine to behave the same as  */
     /*     Borland-specific wWinMain() above. If it becomes needed   */
     /*     to pass lpCmdLine to wxEntry() here, you'll have to fix   */
     /*     wWinMain() above too.                                     */
     return wxEntry(hInstance, hPrevInstance, NULL, nCmdShow);
  }
  
  #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;
  
           // DMM: If the project is dirty, that means it's been touched at
           // all, and it's not safe to open a NEW project directly in its
           // place.  Only if the project is brand-NEW clean and the user
           // hasn't done any action at all is it safe for Open to take place
           // inside the current project.
           //
           // If you try to Open a NEW project inside the current window when
           // there are no tracks, but there's an Undo history, etc, then
           // bad things can happen, including data files moving to the NEW
           // project directory, etc.
           if (proj && (
              ProjectHistory::Get( *proj ).GetDirty() ||
              !TrackList::Get( *proj ).empty()
           ) )
              proj = nullptr;
           // This project is clean; it's never been touched.  Therefore
           // all relevant member variables are in their initial state,
           // and it's okay to open a NEW project inside this window.
           ( void ) ProjectManager::OpenProject( proj, fullPathStr );
        }
        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)) {
                 wxFAIL_MSG(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(EXPERIMENTAL_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()
  {
  // Do not capture crashes in debug builds
  #if !defined(_DEBUG)
  #if defined(EXPERIMENTAL_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__
     /* 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. */
        FileNames::SetDefaultTempDir( wxString::Format(
           wxT("%s/audacity-%s"), envTempDir, wxGetUserId() ) );
     } else {
        /* On Unix systems, the default temp dir is in /var/tmp. */
        FileNames::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);
  
     FileNames::AddUniquePathToPathList(FileNames::DataDir(), audacityPathList);
  
  #ifdef AUDACITY_NAME
     FileNames::AddUniquePathToPathList(wxString::Format(wxT("%s/.%s-files"),
        home, wxT(AUDACITY_NAME)),
        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(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());
     FileNames::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.
     FileNames::SetDefaultTempDir( wxString::Format(
        wxT("%s/Library/Application Support/audacity/SessionData"), tmpDirLoc) );
  
     //FileNames::SetDefaultTempDir( wxString::Format(
     //   wxT("%s/audacity-%s"),
     //   tmpDirLoc,
     //   wxGetUserId() ) );
  #endif //__WXMAC__
  
     FileNames::SetAudacityPathList( std::move( audacityPathList ) );
  
     // Define languanges 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"));
     InitPreferences( configFileName );
     PopulatePreferences();
     // This test must follow PopulatePreferences, because if an error message
     // must be shown, we need internationalization to have been initialized
     // first, which was done in PopulatePreferences
     if ( !CheckWritablePreferences() ) {
        ::AudacityMessageBox(
           UnwritablePreferencesErrorMessage( configFileName ) );
        return false;
     }
  
  #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;
     }
  
     //<<<< 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 PluginManager
     PluginManager::Get().Initialize();
  
     // Initialize the ModuleManager, including loading found modules
     ModuleManager::Get().Initialize(*mCmdHandler);
  
     // 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<wxMenu>();
        auto urecentMenu = std::make_unique<wxMenu>();
        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<wxMenuBar>();
           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);
  
        SetExitOnFrameDelete(false);
  
  #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();
        wxWindow * pWnd = MakeHijackPanel();
        if (pWnd)
        {
           auto &window = GetProjectFrame( *project );
           window.Show(false);
           pWnd->SetParent( &window );
           SetTopWindow(pWnd);
           pWnd->Show(true);
        }
     }
  
     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);
     }
  
     #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;
        if (!ShowAutoRecoveryDialogIfNeeded(&project, &didRecoverAnything))
        {
           QuitAudacity(true);
        }
  
        //
        // Remainder of command line parsing, but only if we didn't recover
        //
        if (!didRecoverAnything)
        {
           if (parser->Found(wxT("t")))
           {
              RunBenchmark( nullptr, *project);
              QuitAudacity(true);
           }
  
           // As of wx3, there's no need to process the filename arguments as they
           // will be sent via the MacOpenFile() method.
  #if !defined(__WXMAC__)
           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));
           }
  #endif
        }
     } );
  
     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
  
     return TRUE;
  }
  
  void AudacityApp::InitCommandHandler()
  {
     mCmdHandler = std::make_unique<CommandHandler>();
     //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 = FileNames::TempDir();
     auto tempDefaultLoc = FileNames::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( FileNames::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( !FileNames::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), 0755);
     #endif
  
     FileNames::ResetTempDir();
     FileNames::UpdateDefaultPath(FileNames::Operation::Temp, temp);
  
     // Make sure the temp dir isn't locked by another process.
     if (!CreateSingleInstanceChecker(temp))
        return false;
  
     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<wxSingleInstanceChecker>();
  
     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<wxConnectionBase> 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<IPCServ>(IPC_APPL);
     mChecker = std::move(checker);
     return true;
  }
  #endif
  
  #if defined(__UNIX__)
  
  // Return true if there are no other instances of Audacity running,
  // false otherwise.
  //
  // Use "dir" for creating lockfiles (on OS X and Unix).
  
  bool AudacityApp::CreateSingleInstanceChecker(const wxString &dir)
  {
     wxUNIXaddress addr;
     addr.Filename(dir + wxT("/.audacity.sock"));
  
     mIPCServ.reset();
  
     // Try twice to either become the server or make a connection to one.  If
     // both attempts fail, then there's something wrong that we can't deal with,
     // like insufficient access to create the socket, no memory, etc.
     for (int attempts = 0; attempts < 2; ++attempts)
     {
        // Make sure only the current user has access to the socket after
        // it's created.
        wxUmaskChanger mask(077);
  
        // Create the socket and bind to it.
        //
        // Here is where the actual socket inode is created.  If it
        // already exists and is currently bound or abandoned, this will
        // fail. Otherwise, we have become the server.
        auto serv = std::make_unique<wxSocketServer>(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);
           return true;
        }
  
        // If we get here, then Audacity is currently active or it has
        // failed, leaving the socket inode defined in the filesystem.
        //
        // So, we try to connect to it and if that is successful, we
        // forward all filenames listed on the command line to the
        // active process.
        //
        // If we can't connect after several attempts, we assume we've
        // had a failure, cleanup the socket inode, and loop back up to
        // retry creating a server.
  
        // Setup the socket
        //
        // A wxSocketClient must not be deleted by us, but rather, let the
        // framework do appropriate delayed deletion after Destroy()
        Destroy_ptr<wxSocketClient> sock { safenew wxSocketClient() };
        sock->SetFlags(wxSOCKET_WAITALL);
  
        // We try up to 20 times since there's a small window
        // where the server may not have been fully initialized.
        for (int i = 0; i < 20; ++i)
        {
           // Attempt to connect to an active Audacity.
           sock->Connect(addr, true);
           if (sock->IsConnected())
           {
              // 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.
              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));
  
              return sock->Error();
           }
  
           wxMilliSleep(100);
  
           if (i == 0)
           {
              printf("Attempting to connect to Audacity failed...retrying\n");
           }
        }
  
        // At this point, we've exhausted our connections attempts. So, we assume
        // that we've had a failure and clean up any existing socket inodes.
        //
        // We could use POSIX shared memory to store the servers PID and check that
        // here to see if it's still active using kill(pid, 0), but I "think" it's
        // a pretty safe bet that it's not.
  
        // Clean up the socket it if still exists.
        wxFileName file(addr.Filename());
        if (file.Exists())
        {
           // Reset the sockets permissions in case they got munged somehow.
           file.SetPermissions( wxS_DEFAULT );
  
           // And remove it
           wxRemove(file.GetFullPath());
        }
     }
  
     // 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;
  }
  
  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<wxCmdLineParser> AudacityApp::ParseCommandLine()
  {
     auto parser = std::make_unique<wxCmdLineParser>(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, wxT("RecentFiles"));
  
     FinishPreferences();
  
  #ifdef USE_FFMPEG
     DropFFmpegLibs();
  #endif
  
     DeinitFFT();
  
     AudioIO::Deinit();
  
     MenuTable::DestroyRegistry();
  
     // Terminate the PluginManager (must be done before deleting the locale)
     PluginManager::Get().Terminate();
  
     if (mIPCServ)
     {
  #if defined(__UNIX__)
        wxUNIXaddress addr;
        if (mIPCServ->GetLocal(addr))
        {
           remove(OSFILENAME(addr.Filename()));
        }
  #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
  
  