2356 lines
73 KiB
C++
2356 lines
73 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 Dectector @ 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>
|
|
#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 "ondemand/ODManager.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
|
|
|
|
// Windows specific linker control...only needed once so
|
|
// this is a good place (unless we want to add another file).
|
|
#if defined(__WXMSW__)
|
|
//#if wxCHECK_VERSION(3, 0, 2) && !wxCHECK_VERSION(3, 1, 0)
|
|
#include <wx/init.h>
|
|
//#endif
|
|
// These lines ensure that Audacity gets WindowsXP themes.
|
|
// Without them we get the old-style Windows98/2000 look under XP.
|
|
# if !defined(__WXWINCE__)
|
|
# pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
|
|
# endif
|
|
|
|
// These lines allows conditional inclusion of the various libraries
|
|
// that Audacity can use.
|
|
|
|
# if defined(USE_LIBFLAC)
|
|
# pragma comment(lib, "libflac++")
|
|
# pragma comment(lib, "libflac")
|
|
# endif
|
|
|
|
# if defined(USE_LIBID3TAG)
|
|
# pragma comment(lib, "libid3tag")
|
|
# endif
|
|
|
|
# if defined(USE_LIBMAD)
|
|
# pragma comment(lib, "libmad")
|
|
# endif
|
|
|
|
# if defined(USE_LIBTWOLAME)
|
|
# pragma comment(lib, "twolame")
|
|
# endif
|
|
|
|
# if defined(USE_LIBVORBIS)
|
|
# pragma comment(lib, "libogg")
|
|
# pragma comment(lib, "libvorbis")
|
|
# endif
|
|
|
|
# if defined(USE_LV2)
|
|
# pragma comment(lib, "lv2")
|
|
# endif
|
|
|
|
# if defined(USE_MIDI)
|
|
# pragma comment(lib, "portsmf")
|
|
# endif
|
|
|
|
# if defined(EXPERIMENTAL_MIDI_OUT)
|
|
# pragma comment(lib, "portmidi")
|
|
# endif
|
|
|
|
# if defined(EXPERIMENTAL_SCOREALIGN)
|
|
# pragma comment(lib, "libscorealign")
|
|
# endif
|
|
|
|
# if defined(USE_NYQUIST)
|
|
# pragma comment(lib, "libnyquist")
|
|
# endif
|
|
|
|
# if defined(USE_PORTMIXER)
|
|
# pragma comment(lib, "portmixer")
|
|
# endif
|
|
|
|
# if defined(USE_SBSMS)
|
|
# pragma comment(lib, "sbsms")
|
|
# endif
|
|
|
|
# if defined(USE_SOUNDTOUCH)
|
|
# pragma comment(lib, "soundtouch")
|
|
# endif
|
|
|
|
# if defined(USE_VAMP)
|
|
# pragma comment(lib, "libvamp")
|
|
# endif
|
|
|
|
# if defined(_DEBUG)
|
|
# define D "d"
|
|
# else
|
|
# define D ""
|
|
# endif
|
|
# if wxCHECK_VERSION(3, 1, 0)
|
|
# define V "31"
|
|
# elif wxCHECK_VERSION(3, 0, 0)
|
|
# define V "30"
|
|
# else
|
|
# define V "28"
|
|
# endif
|
|
|
|
# if defined(EXPERIMENTAL_CRASH_REPORT)
|
|
# pragma comment(lib, "wxmsw" V "u" D "_qa")
|
|
# endif
|
|
# pragma comment(lib, "wxbase" V "u" D)
|
|
# pragma comment(lib, "wxbase" V "u" D "_net")
|
|
# pragma comment(lib, "wxmsw" V "u" D "_adv")
|
|
# pragma comment(lib, "wxmsw" V "u" D "_core")
|
|
# pragma comment(lib, "wxmsw" V "u" D "_html")
|
|
# pragma comment(lib, "wxpng" D)
|
|
# pragma comment(lib, "wxzlib" D)
|
|
# pragma comment(lib, "wxjpeg" D)
|
|
# pragma comment(lib, "wxtiff" D)
|
|
|
|
# undef V
|
|
# undef D
|
|
|
|
#endif //(__WXMSW__)
|
|
|
|
// 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);
|
|
}
|
|
|
|
// 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();
|
|
|
|
//release ODManager Threads
|
|
ODManager::Quit();
|
|
|
|
//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.GetHistoryFile(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 RemoveFileFromHistory() 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.RemoveFileFromHistory(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
|
|
|
|
int AudacityApp::FilterEvent(wxEvent & event)
|
|
{
|
|
(void)event;// compiler food (stops unused parameter warning)
|
|
#if !wxCHECK_VERSION(3, 0, 0) && defined(__WXGTK__)
|
|
// On wxGTK, there's a focus issue where dialogs do not automatically pass focus
|
|
// to the first child. This means that you can use the keyboard to navigate within
|
|
// the dialog. Watching for the ACTIVATE event allows us to set the focus ourselves
|
|
// when each dialog opens.
|
|
//
|
|
// See bug #57
|
|
//
|
|
if (event.GetEventType() == wxEVT_ACTIVATE)
|
|
{
|
|
wxActivateEvent & e = (wxActivateEvent &) event;
|
|
|
|
if (e.GetEventObject() && e.GetActive() && e.GetEventObject()->IsKindOf(CLASSINFO(wxDialog)))
|
|
{
|
|
((wxWindow *)e.GetEventObject())->SetFocus();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
#if defined(__WXMAC__) || defined(__WXGTK__)
|
|
if (event.GetEventType() == wxEVT_ACTIVATE)
|
|
{
|
|
wxActivateEvent & e = static_cast<wxActivateEvent &>(event);
|
|
|
|
const auto object = e.GetEventObject();
|
|
if (object && e.GetActive() &&
|
|
object->IsKindOf(CLASSINFO(wxWindow)))
|
|
{
|
|
const auto window = ((wxWindow *)e.GetEventObject());
|
|
window->SetFocus();
|
|
#if defined(__WXMAC__)
|
|
window->NavigateIn();
|
|
#endif
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return Event_Skip;
|
|
}
|
|
|
|
AudacityApp::AudacityApp()
|
|
{
|
|
// Do not capture crashes in debug builds
|
|
#if !defined(__WXDEBUG__)
|
|
#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
|
|
|
|
// 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-files directory in their home 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);
|
|
|
|
#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 occured
|
|
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);
|
|
recentFiles.AddFilesToMenu(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 {
|
|
//
|
|
// 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
|
|
PrefsDialog::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
|
|
PrefsDialog::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;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
wxString name = wxString::Format(wxT("audacity-lock-%s"), wxGetUserId());
|
|
mChecker.reset();
|
|
auto checker = std::make_unique<wxSingleInstanceChecker>();
|
|
|
|
#if defined(__UNIX__)
|
|
wxString sockFile(dir + wxT("/.audacity.sock"));
|
|
#endif
|
|
|
|
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());
|
|
}
|
|
|
|
#if defined(__WXMSW__)
|
|
// 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);
|
|
}
|
|
#else
|
|
// On Unix-like machines, we use a local (file based) socket to
|
|
// send the first command line argument to an already running
|
|
// Audacity.
|
|
wxUNIXaddress addr;
|
|
addr.Filename(sockFile);
|
|
|
|
{
|
|
// 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 50 times since there's a small window
|
|
// where the server may not have been fully initialized.
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
// Connect to the existing Audacity
|
|
sock->Connect(addr, true);
|
|
if (sock->IsConnected())
|
|
{
|
|
if (filenames.size() > 0)
|
|
{
|
|
for (size_t i = 0, cnt = filenames.size(); i < cnt; i++)
|
|
{
|
|
const wxString param = filenames[i];
|
|
sock->WriteMsg((const wxChar *) param, (param.length() + 1) * sizeof(wxChar));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Send an empty string to force existing Audacity to front
|
|
sock->WriteMsg(wxEmptyString, sizeof(wxChar));
|
|
}
|
|
|
|
return sock->Error();
|
|
}
|
|
|
|
wxMilliSleep(100);
|
|
}
|
|
}
|
|
#endif
|
|
// 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);
|
|
|
|
#ifdef __WXMAC__
|
|
// Bug 2052
|
|
// On mac, the lock file may persist and stop Audacity starting properly.
|
|
auto lockFileName = wxFileName(dir,name);
|
|
bool bIsLocked = lockFileName.IsOk() && lockFileName.FileExists();
|
|
if( bIsLocked ){
|
|
int action = AudacityMessageBox(
|
|
XO("If you're sure another copy of Audacity isn't\nrunning, Audacity can skip the test for\n'Audacity already running' next time\nby removing the lock file:\n\n%s\n\nDo you want to do that?")
|
|
.Format(lockFileName.GetFullName()),
|
|
XO("Possible Lock File Problem"),
|
|
wxYES_NO | wxICON_EXCLAMATION,
|
|
NULL);
|
|
if (action == wxYES){
|
|
// If locked, unlock.
|
|
lockFileName.SetPermissions( wxS_DEFAULT );
|
|
::wxRemoveFile( lockFileName.GetFullName() );
|
|
}
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
#if defined(__WXMSW__)
|
|
// Create the DDE IPC server
|
|
mIPCServ = std::make_unique<IPCServ>(IPC_APPL);
|
|
#else
|
|
int mask = umask(077);
|
|
remove(OSFILENAME(sockFile));
|
|
wxUNIXaddress addr;
|
|
addr.Filename(sockFile);
|
|
mIPCServ = std::make_unique<wxSocketServer>(addr, wxSOCKET_NOWAIT);
|
|
umask(mask);
|
|
|
|
if (!mIPCServ || !mIPCServ->IsOk())
|
|
{
|
|
// TODO: Complain here
|
|
return false;
|
|
}
|
|
|
|
mIPCServ->SetEventHandler(*this, ID_IPC_SERVER);
|
|
mIPCServ->SetNotify(wxSOCKET_CONNECTION_FLAG);
|
|
mIPCServ->Notify(true);
|
|
#endif
|
|
mChecker = std::move(checker);
|
|
return true;
|
|
}
|
|
|
|
#if defined(__UNIX__)
|
|
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();
|
|
|
|
// 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 = wxString(associateFileTypes).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
|
|
|