audacia/src/AudacityApp.cpp
Leland Lucius cbf1bb558e AUP3: Removes OD code related to project file handling
This removes all of the OnDemand code embedded throughout
    the main codebase. Individual files related specifically
    to OD have been left in place, but removed from the build.
2020-07-01 01:14:05 -05:00

2325 lines
74 KiB
C++

/**********************************************************************
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 "DirManager.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 "MissingAliasFileDialog.h"
#include "PluginManager.h"
#include "Project.h"
#include "ProjectAudioIO.h"
#include "ProjectAudioManager.h"
#include "ProjectFileManager.h"
#include "ProjectHistory.h"
#include "ProjectManager.h"
#include "ProjectSettings.h"
#include "ProjectWindow.h"
#include "Screenshot.h"
#include "Sequence.h"
#include "WaveTrack.h"
#include "prefs/PrefsDialog.h"
#include "Theme.h"
#include "PlatformCompatibility.h"
#include "FileNames.h"
#include "AutoRecovery.h"
#include "AutoRecoveryDialog.h"
#include "SplashDialog.h"
#include "FFT.h"
#include "BlockFile.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, 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 .aup, 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"));
}
}
}
}
// Check if a warning for missing aliased files should be displayed
if (MissingAliasFilesDialog::ShouldShow()) {
// find which project owns the blockfile
// note: there may be more than 1, but just go with the first one.
//size_t numProjects = AllProjects{}.size();
auto marked = MissingAliasFilesDialog::Marked();
auto offendingProject = marked.second;
wxString missingFileName = marked.first;
// if there are no projects open, don't show the warning (user has closed it)
if (offendingProject) {
auto &window = GetProjectFrame( *offendingProject );
window.Iconize(false);
window.Raise();
auto errorMessage = XO(
"One or more external audio files could not be found.\n\
It is possible they were moved, deleted, or the drive they \
were on was unmounted.\n\
Silence is being substituted for the affected audio.\n\
The first detected missing file is:\n\
%s\n\
There may be additional missing files.\n\
Choose Help > Diagnostics > Check Dependencies to view a list of \
locations of the missing files.").Format( missingFileName ) ;
// if an old dialog exists, raise it if it is
if ( auto dialog = MissingAliasFilesDialog::Find( *offendingProject ) )
dialog->Raise();
else {
MissingAliasFilesDialog::Show(offendingProject.get(), XO("Files Missing"),
errorMessage, wxT(""), true);
}
}
// Only show this warning once per event (playback/menu item/etc).
MissingAliasFilesDialog::SetShouldShow(false);
}
}
#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;
// 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();
// Init DirManager, which initializes the temp directory
// 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);
}
wxString fileName;
if (parser->Found(wxT("d"), &fileName))
{
AutoSaveFile asf;
if (asf.Decode(fileName))
{
wxPrintf(_("File decoded successfully\n"));
}
else
{
wxPrintf( AutoSaveFile::FailureMessage( fileName ) .Translation() ); //Debug()?
}
exit(1);
}
// 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))
{
// Important: Prevent deleting any temporary files!
DirManager::SetDontDeleteTempFiles();
QuitAudacity(true);
}
//
// Remainder of command line parsing, but only if we didn't recover
//
if (!didRecoverAnything)
{
if (parser->Found(wxT("t")))
{
RunBenchmark( nullptr, ProjectSettings::Get( *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.
wxString tempFromPrefs = gPrefs->Read(wxT("/Directories/TempDir"), wxT(""));
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
bool bSuccess = gPrefs->Write(wxT("/Directories/TempDir"), temp) && gPrefs->Flush();
DirManager::SetTempDir(temp);
// Make sure the temp dir isn't locked by another process.
if (!CreateSingleInstanceChecker(temp))
return false;
return bSuccess;
}
#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 decodes an autosave file */
parser->AddOption(wxT("d"), wxT("decode"), _("decode an autosave file"),
wxCMD_LINE_VAL_STRING);
/*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()
{
wxRegKey associateFileTypes;
associateFileTypes.SetName(wxT("HKCR\\.AUP"));
bool bKeyExists = associateFileTypes.Exists();
if (!bKeyExists) {
// Not at HKEY_CLASSES_ROOT. Try HKEY_CURRENT_USER.
associateFileTypes.SetName(wxT("HKCU\\Software\\Classes\\.AUP"));
bKeyExists = associateFileTypes.Exists();
}
if (!bKeyExists) {
// File types are not currently associated.
// Check pref in case user has already decided against it.
bool bWantAssociateFiles = true;
if (!gPrefs->Read(wxT("/WantAssociateFiles"), &bWantAssociateFiles) ||
bWantAssociateFiles) {
// Either there's no pref or user does want associations
// and they got stepped on, so ask.
int wantAssoc =
AudacityMessageBox(
XO(
"Audacity project (.AUP) 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 == wxYES) {
gPrefs->Write(wxT("/WantAssociateFiles"), true);
gPrefs->Flush();
wxString root_key;
root_key = wxT("HKCU\\Software\\Classes\\");
associateFileTypes.SetName(root_key + wxT(".AUP")); // Start again with HKEY_CLASSES_ROOT.
if (!associateFileTypes.Create(true)) {
// Not at HKEY_CLASSES_USER. Try HKEY_CURRENT_ROOT.
root_key = wxT("HKCR\\");
associateFileTypes.SetName(root_key + wxT(".AUP"));
if (!associateFileTypes.Create(true)) {
// Actually, can't create keys. Empty root_key to flag failure.
root_key.Empty();
}
}
if (root_key.empty()) {
//v Warn that we can't set keys. Ask whether to set pref for no retry?
} else {
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
}
} else {
// User said no. Set a pref so we don't keep asking.
gPrefs->Write(wxT("/WantAssociateFiles"), false);
gPrefs->Flush();
}
}
}
}
#endif