Paul Davis
5122036054
In some startup flows, the entire loading process happens inside StartupFSM::start(). In others, that call gets things moving but we return from it and loading is not complete until later. Deleting the StartupFSM while still inside the ::start() call led to a use-after-free error. This new code will leak the StartupFSM in some startup flows.
1012 lines
30 KiB
C++
1012 lines
30 KiB
C++
/*
|
|
* Copyright (C) 2019 Paul Davis <paul@linuxaudiosystems.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include <vector>
|
|
|
|
#include <gtkmm/dialog.h>
|
|
#include <gtkmm/liststore.h>
|
|
#include <gtkmm/messagedialog.h>
|
|
#include <gtkmm/stock.h>
|
|
|
|
#include "pbd/basename.h"
|
|
#include "pbd/file_archive.h"
|
|
#include "pbd/file_utils.h"
|
|
|
|
#include "ardour/audioengine.h"
|
|
#include "ardour/audio_backend.h"
|
|
#include "ardour/filename_extensions.h"
|
|
#include "ardour/filesystem_paths.h"
|
|
#include "ardour/profile.h"
|
|
#include "ardour/recent_sessions.h"
|
|
#include "ardour/rc_configuration.h"
|
|
#include "ardour/search_paths.h"
|
|
#include "ardour/session.h"
|
|
#include "ardour/session_utils.h"
|
|
#include "ardour/template_utils.h"
|
|
|
|
#include "gtkmm2ext/application.h"
|
|
#include <gtkmm2ext/doi.h>
|
|
#include <gtkmm2ext/keyboard.h>
|
|
|
|
#include "ardour_message.h"
|
|
#include "ardour_ui.h"
|
|
#include "debug.h"
|
|
#include "engine_dialog.h"
|
|
#include "new_user_wizard.h"
|
|
#include "opts.h"
|
|
#include "plugin_scan_dialog.h"
|
|
#include "session_dialog.h"
|
|
#include "splash.h"
|
|
#include "startup_fsm.h"
|
|
|
|
#ifdef WAF_BUILD
|
|
#include "gtk2ardour-version.h"
|
|
#endif
|
|
|
|
#include "pbd/i18n.h"
|
|
|
|
using namespace ARDOUR;
|
|
using namespace Gtk;
|
|
using namespace Gtkmm2ext;
|
|
using namespace PBD;
|
|
|
|
using std::string;
|
|
using std::vector;
|
|
|
|
StartupFSM::StartupFSM (EngineControl& amd)
|
|
: session_existing_sample_rate (0)
|
|
, session_engine_hints ("EngineHints")
|
|
, session_is_new (false)
|
|
, session_name_edited (false)
|
|
, new_user (NewUserWizard::required())
|
|
, new_session_required (ARDOUR_COMMAND_LINE::new_session || (!ARDOUR::Profile->get_mixbus() && new_user))
|
|
, _state (new_user ? WaitingForNewUser : WaitingForSessionPath)
|
|
, audiomidi_dialog (amd)
|
|
, new_user_dialog (0)
|
|
, session_dialog (0)
|
|
, pre_release_dialog (0)
|
|
, plugin_scan_dialog (0)
|
|
{
|
|
/* note that our initial state can be any of:
|
|
*
|
|
* WaitingForPreRelease: if this is a pre-release build of Ardour and the user has not testified to their fidelity to our creed
|
|
* WaitingForNewUser: if this is the first time any version appears to have been run on this machine by this user
|
|
* WaitingForSessionPath: if the previous two conditions are not true
|
|
*/
|
|
|
|
if (string (VERSIONSTRING).find (".pre0") != string::npos) {
|
|
string fn = Glib::build_filename (user_config_directory(), ".i_swear_that_i_will_heed_the_guidelines_stated_in_the_pre_release_dialog");
|
|
if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
|
|
set_state (WaitingForPreRelease);
|
|
}
|
|
}
|
|
|
|
Application* app = Application::instance ();
|
|
|
|
app->ShouldQuit.connect (sigc::mem_fun (*this, &StartupFSM::queue_finish));
|
|
|
|
Gtkmm2ext::Keyboard::HideMightMeanQuit.connect (sigc::mem_fun (*this, &StartupFSM::dialog_hidden));
|
|
}
|
|
|
|
StartupFSM::~StartupFSM ()
|
|
{
|
|
delete session_dialog;
|
|
delete pre_release_dialog;
|
|
delete plugin_scan_dialog;
|
|
delete new_user_dialog;
|
|
}
|
|
|
|
void
|
|
StartupFSM::dialog_hidden (Gtk::Window* /* ignored */)
|
|
{
|
|
/* since this object only exists during startup, any attempt to close
|
|
* any dialog that we manage with Ctrl/Cmd-w is assumed to indicate a
|
|
* desire to quit on the part of the user.
|
|
*/
|
|
queue_finish ();
|
|
}
|
|
|
|
void
|
|
StartupFSM::queue_finish ()
|
|
{
|
|
_signal_response (QuitProgram);
|
|
}
|
|
|
|
void
|
|
StartupFSM::start ()
|
|
{
|
|
DEBUG_TRACE (DEBUG::GuiStartup, string_compose (X_("State at startup: %1\n"), enum_2_string (_state)));
|
|
|
|
switch (_state) {
|
|
case NotWaiting:
|
|
abort();
|
|
break;
|
|
|
|
case WaitingForPreRelease:
|
|
show_pre_release_dialog ();
|
|
break;
|
|
case WaitingForNewUser:
|
|
show_new_user_dialog ();
|
|
break;
|
|
case WaitingForSessionPath:
|
|
handle_waiting_for_session_path ();
|
|
break;
|
|
case WaitingForEngineParams:
|
|
start_audio_midi_setup ();
|
|
break;
|
|
default:
|
|
fatal << string_compose (_("Programming error: %1"), string_compose (X_("impossible starting state in StartupFSM (%1)"), enum_2_string (_state))) << endmsg;
|
|
std::cerr << string_compose (_("Programming error: %1"), string_compose (X_("impossible starting state in StartupFSM (%1)"), enum_2_string (_state))) << std::endl;
|
|
/* NOTREACHED */
|
|
abort ();
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::GuiStartup, string_compose (X_("State after startup: %1\n"), enum_2_string (_state)));
|
|
}
|
|
|
|
void
|
|
StartupFSM::reset()
|
|
{
|
|
show_session_dialog (new_session_required);
|
|
}
|
|
|
|
void
|
|
StartupFSM::set_state (MainState ms)
|
|
{
|
|
DEBUG_TRACE (DEBUG::GuiStartup, string_compose (X_("new state: %1\n"), enum_2_string (ms)));
|
|
_state = ms;
|
|
}
|
|
|
|
template<typename T> void
|
|
StartupFSM::end_dialog (T** d)
|
|
{
|
|
assert (d);
|
|
assert (*d);
|
|
|
|
end_dialog (**d);
|
|
delete_when_idle (*d);
|
|
*d = 0;
|
|
}
|
|
|
|
template<typename T> void
|
|
StartupFSM::end_dialog (T& d)
|
|
{
|
|
d.hide ();
|
|
current_dialog_connection.disconnect ();
|
|
}
|
|
|
|
void
|
|
StartupFSM::dialog_response_handler (int response, StartupFSM::DialogID dialog_id)
|
|
{
|
|
DEBUG_TRACE (DEBUG::GuiStartup, string_compose ("Response %1 from %2 (nsr: %3 / nu: %4)\n", enum_2_string (Gtk::ResponseType (response)), enum_2_string (dialog_id), new_session_required, new_user));
|
|
|
|
/* Notes:
|
|
*
|
|
* 1) yes, a brand new user might have specified a command line
|
|
* argument naming a new session. We ignore it. You're new to Ardour?
|
|
* We want to guide you through the startup.
|
|
*/
|
|
|
|
switch (_state) {
|
|
case NotWaiting:
|
|
abort();
|
|
break;
|
|
|
|
case WaitingForPreRelease:
|
|
switch (dialog_id) {
|
|
case ApplicationPseudoDialog:
|
|
/* this shouldn't happen; ignore it */
|
|
break;
|
|
case PreReleaseDialog:
|
|
default:
|
|
/* any response value from the pre-release dialog means
|
|
"move along now"
|
|
*/
|
|
end_dialog (&pre_release_dialog);
|
|
|
|
if (NewUserWizard::required()) {
|
|
show_new_user_dialog ();
|
|
} else {
|
|
handle_waiting_for_session_path ();
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case WaitingForNewUser:
|
|
switch (dialog_id) {
|
|
case ApplicationPseudoDialog:
|
|
/* this shouldn't happen; ignore it */
|
|
break;
|
|
case NewUserDialog:
|
|
switch (response) {
|
|
case RESPONSE_OK:
|
|
end_dialog (&new_user_dialog);
|
|
show_session_dialog (new_session_required);
|
|
break;
|
|
default:
|
|
_signal_response (QuitProgram);
|
|
}
|
|
default:
|
|
/* ERROR */
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case WaitingForSessionPath:
|
|
switch (dialog_id) {
|
|
case NewSessionDialog:
|
|
switch (response) {
|
|
case RESPONSE_OK:
|
|
case RESPONSE_ACCEPT:
|
|
switch (check_session_parameters (new_session_required)) {
|
|
case -1:
|
|
/* Unrecoverable error */
|
|
_signal_response (ExitProgram);
|
|
break;
|
|
case 0:
|
|
end_dialog (&session_dialog);
|
|
start_audio_midi_setup ();
|
|
break;
|
|
case 1:
|
|
/* do nothing - keep dialog up for a
|
|
* retry. Errors were addressed by
|
|
* ::check_session_parameters()
|
|
*/
|
|
session_dialog->present ();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
_signal_response (QuitProgram);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ApplicationPseudoDialog:
|
|
/* macOS, NSM etc. ... existence was already checked */
|
|
if (get_session_parameters_from_path (ARDOUR_COMMAND_LINE::session_name, string(), false)) {
|
|
start_audio_midi_setup ();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* ERROR */
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case WaitingForEngineParams:
|
|
switch (dialog_id) {
|
|
case AudioMIDISetup:
|
|
switch (response) {
|
|
case RESPONSE_OK:
|
|
case RESPONSE_ACCEPT:
|
|
if (AudioEngine::instance()->running()) {
|
|
/* prevent double clicks from changing engine state */
|
|
audiomidi_dialog.set_ui_sensitive (false);
|
|
end_dialog (audiomidi_dialog);
|
|
engine_running ();
|
|
} else {
|
|
/* just keep going */
|
|
}
|
|
break;
|
|
default:
|
|
_signal_response (QuitProgram);
|
|
}
|
|
break;
|
|
default:
|
|
/* ERROR */
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case WaitingForPlugins:
|
|
switch (dialog_id) {
|
|
case PluginDialog:
|
|
end_dialog (&plugin_scan_dialog);
|
|
switch (response) {
|
|
case RESPONSE_OK:
|
|
if (AudioEngine::instance()->running()) {
|
|
_signal_response (LoadSession);
|
|
} else {
|
|
/* Engine died unexpectedly (it was
|
|
* running after
|
|
* WaitingForEngineParams). Nothing to
|
|
* do but go back to the audio/MIDI
|
|
* setup. It would be nice, perhaps, to
|
|
* show an extra message indicating
|
|
* that something is not right.
|
|
*/
|
|
ArdourMessageDialog msg (_("The audio/MIDI engine has stopped running unexpectedly.\nSomething is probably wrong with your audio/MIDI device settings."));
|
|
msg.set_position (WIN_POS_CENTER);
|
|
msg.run();
|
|
/* This has been shown before, so we do
|
|
* not need start_audio_midi_setup ();
|
|
*/
|
|
show_audiomidi_dialog ();
|
|
}
|
|
break;
|
|
default:
|
|
_signal_response (QuitProgram);
|
|
break;
|
|
}
|
|
default:
|
|
/* ERROR */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
StartupFSM::handle_waiting_for_session_path ()
|
|
{
|
|
if (ARDOUR_COMMAND_LINE::session_name.empty()) {
|
|
|
|
/* nothing given on the command line ... show new session dialog */
|
|
|
|
show_session_dialog (new_session_required);
|
|
|
|
} else {
|
|
|
|
if (get_session_parameters_from_command_line (new_session_required)) {
|
|
|
|
/* command line arguments all OK. Get engine parameters */
|
|
|
|
if (!new_session_required && session_existing_sample_rate > 0) {
|
|
audiomidi_dialog.set_desired_sample_rate (session_existing_sample_rate);
|
|
}
|
|
|
|
start_audio_midi_setup ();
|
|
|
|
} else {
|
|
|
|
/* command line arguments not good. Use
|
|
* dialog, but prime the dialog with
|
|
* the information we set up in
|
|
* get_session_parameters_from_command_line()
|
|
*/
|
|
|
|
show_session_dialog (new_session_required);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
StartupFSM::show_plugin_scan_dialog ()
|
|
{
|
|
set_state (WaitingForPlugins);
|
|
|
|
/* if the user does not ask to discover AU/VSTs at startup, or if this is Mixbus, then the plugin scan
|
|
* that we run here, during startup, should only use the existing plugin cache (if any).
|
|
*/
|
|
const bool cache_only = (!Config->get_discover_plugins_on_start () || Profile->get_mixbus ());
|
|
const bool verbose = new_user;
|
|
|
|
plugin_scan_dialog = new PluginScanDialog (cache_only, verbose);
|
|
current_dialog_connection = plugin_scan_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), PluginDialog));
|
|
plugin_scan_dialog->set_position (WIN_POS_CENTER);
|
|
|
|
/* We don't show the plugin scan dialog by default. It will appear using it's own code if/when plugins are discovered, if required.
|
|
*
|
|
* See also comments in PluginScanDialog::start() to understand the absurd complexities behind this call.
|
|
*/
|
|
|
|
DEBUG_TRACE (DEBUG::GuiStartup, string_compose ("starting plugin dialog, cache only ? %1\n", cache_only));
|
|
plugin_scan_dialog->start();
|
|
DEBUG_TRACE (DEBUG::GuiStartup, "plugin dialog done\n");
|
|
}
|
|
|
|
void
|
|
StartupFSM::show_new_user_dialog ()
|
|
{
|
|
set_state (WaitingForNewUser);
|
|
new_user_dialog = new NewUserWizard;
|
|
current_dialog_connection = new_user_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), NewUserDialog));
|
|
new_user_dialog->set_position (WIN_POS_CENTER);
|
|
new_user_dialog->present ();
|
|
}
|
|
|
|
void
|
|
StartupFSM::show_session_dialog (bool new_session_required)
|
|
{
|
|
set_state (WaitingForSessionPath);
|
|
session_dialog = new SessionDialog (new_session_required, session_name, session_path, session_template, false);
|
|
current_dialog_connection = session_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), NewSessionDialog));
|
|
session_dialog->set_position (WIN_POS_CENTER);
|
|
session_dialog->present ();
|
|
}
|
|
|
|
void
|
|
StartupFSM::show_audiomidi_dialog ()
|
|
{
|
|
set_state (WaitingForEngineParams);
|
|
current_dialog_connection = audiomidi_dialog.signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), AudioMIDISetup));
|
|
audiomidi_dialog.set_position (WIN_POS_CENTER);
|
|
audiomidi_dialog.present ();
|
|
}
|
|
|
|
void
|
|
StartupFSM::start_audio_midi_setup ()
|
|
{
|
|
BootMessage (_("Starting Audio/MIDI Engine"));
|
|
bool setup_required = false;
|
|
|
|
std::shared_ptr<AudioBackend> backend = AudioEngine::instance()->current_backend();
|
|
if (!backend) {
|
|
/* backend is unknown ... */
|
|
setup_required = true;
|
|
|
|
} else if (session_is_new && AudioEngine::instance()->running() && AudioEngine::instance()->sample_rate () == session_existing_sample_rate) {
|
|
/* keep engine */
|
|
|
|
warning << "A running engine should not be possible at this point" << endmsg;
|
|
|
|
} else if (AudioEngine::instance()->setup_required()) {
|
|
/* backend is known, but setup is needed */
|
|
setup_required = true;
|
|
|
|
} else if (!AudioEngine::instance()->running()) {
|
|
/* should always be true during startup */
|
|
if (AudioEngine::instance()->start()) {
|
|
setup_required = true;
|
|
}
|
|
}
|
|
|
|
bool try_autostart = !new_user && (Config->get_try_autostart_engine () || g_getenv ("ARDOUR_TRY_AUTOSTART_ENGINE"));
|
|
if (session_is_new) {
|
|
try_autostart = false;
|
|
} else if (!backend) {
|
|
try_autostart = false;
|
|
} else if (try_autostart) {
|
|
/* if user has selected auto-start, check if autostart is possible */
|
|
bool ok = true;
|
|
std::string backend_name;
|
|
/* Allow auto-start if there is no backend information for the
|
|
* given session. This can happen when loading an old (v6) session,
|
|
* of if the user has been using externally started JACK.
|
|
*/
|
|
if (session_engine_hints.get_property ("backend", backend_name)) {
|
|
std::string input_device;
|
|
std::string output_device;
|
|
ok &= session_engine_hints.get_property ("input-device", input_device);
|
|
ok &= session_engine_hints.get_property ("output-device", output_device);
|
|
ok &= backend->name () == backend_name;
|
|
if (backend->use_separate_input_and_output_devices()) {
|
|
ok &= input_device == backend->input_device_name ();
|
|
ok &= output_device == backend->output_device_name ();
|
|
} else {
|
|
ok &= input_device == backend->device_name ();
|
|
ok &= output_device == backend->device_name ();
|
|
}
|
|
}
|
|
if (!ok) {
|
|
try_autostart = false;
|
|
ArdourMessageDialog msg (
|
|
_("Engine I/O device has changed since you last opened this session.\n"
|
|
"Please verify that the new device has enough ports, or you may lose some i/o connections."),
|
|
false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK, true);
|
|
msg.run ();
|
|
}
|
|
}
|
|
|
|
if (setup_required) {
|
|
if (try_autostart) {
|
|
AudioEngine::instance()->set_sample_rate(session_existing_sample_rate);
|
|
if (!AudioEngine::instance()->start ()) {
|
|
if (ARDOUR::AudioEngine::instance()->running()) {
|
|
DEBUG_TRACE (DEBUG::GuiStartup, "autostart successful, audio/MIDI setup dialog not required\n");
|
|
engine_running ();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!session_is_new && session_existing_sample_rate > 0) {
|
|
audiomidi_dialog.set_desired_sample_rate (session_existing_sample_rate);
|
|
}
|
|
|
|
show_audiomidi_dialog ();
|
|
DEBUG_TRACE (DEBUG::GuiStartup, "audiomidi shown and waiting\n");
|
|
|
|
} else {
|
|
|
|
DEBUG_TRACE (DEBUG::GuiStartup, "engine already running, audio/MIDI setup dialog not required\n");
|
|
engine_running ();
|
|
}
|
|
}
|
|
|
|
void
|
|
StartupFSM::engine_running ()
|
|
{
|
|
DEBUG_TRACE (DEBUG::GuiStartup, "engine running, start plugin scan then attach UI to engine\n");
|
|
|
|
/* This may be very slow. See comments in PluginScanDialog::start() */
|
|
show_plugin_scan_dialog ();
|
|
|
|
DEBUG_TRACE (DEBUG::GuiStartup, "attach UI to engine\n");
|
|
|
|
/* This may be very slow: it will run the GUI's post-engine
|
|
initialization which is essentially unbounded in time/scope of what
|
|
it can do.
|
|
*/
|
|
|
|
ARDOUR_UI::instance()->attach_to_engine ();
|
|
|
|
/* now that we've done the plugin scan AND attached the UI to the engine, we can
|
|
proceed with the next (final) steps of startup. This uses the same response
|
|
signal mechanism we use for the other dialogs.
|
|
*/
|
|
|
|
plugin_scan_dialog->response (RESPONSE_OK);
|
|
}
|
|
|
|
bool
|
|
StartupFSM::get_session_parameters_from_command_line (bool new_session_required)
|
|
{
|
|
return get_session_parameters_from_path (ARDOUR_COMMAND_LINE::session_name, ARDOUR_COMMAND_LINE::load_template, new_session_required);
|
|
}
|
|
|
|
bool
|
|
StartupFSM::get_session_parameters_from_path (string const & path_, string const & template_name, bool new_session_required)
|
|
{
|
|
if (path_.empty()) {
|
|
/* use GUI to ask the user */
|
|
return false;
|
|
}
|
|
|
|
string path (path_);
|
|
|
|
/* ... did the user give us a path or just a name? */
|
|
|
|
if (path.find (G_DIR_SEPARATOR) == string::npos) {
|
|
/* user gave session name with no path info, use
|
|
default session folder.
|
|
*/
|
|
path = Glib::build_filename (Config->get_default_session_parent_dir (), path);
|
|
}
|
|
|
|
if (Glib::file_test (path.c_str(), Glib::FILE_TEST_EXISTS)) {
|
|
|
|
session_is_new = false;
|
|
|
|
if (new_session_required) {
|
|
/* wait! it already exists */
|
|
|
|
if (!ask_about_loading_existing_session (path)) {
|
|
return false;
|
|
} else {
|
|
/* load it anyway */
|
|
}
|
|
}
|
|
|
|
session_name = basename_nosuffix (path);
|
|
|
|
if (Glib::file_test (path.c_str(), Glib::FILE_TEST_IS_REGULAR)) {
|
|
/* session/snapshot file, change path to be dir */
|
|
session_path = Glib::path_get_dirname (path);
|
|
} else {
|
|
session_path = path;
|
|
}
|
|
|
|
float sr;
|
|
SampleFormat fmt;
|
|
string program_version;
|
|
|
|
const string statefile_path = Glib::build_filename (session_path, session_name + ARDOUR::statefile_suffix);
|
|
switch (Session::get_info_from_path (statefile_path, sr, fmt, program_version, &session_engine_hints)) {
|
|
case 0:
|
|
/* OK */
|
|
break;
|
|
case -1:
|
|
error << string_compose (_("Session file %1 does not exist"), statefile_path) << endmsg;
|
|
return false;
|
|
break;
|
|
case -3:
|
|
error << string_compose (_("Session %1 is from a newer version of %2"), statefile_path, PROGRAM_NAME) << endmsg;
|
|
return false;
|
|
break;
|
|
default:
|
|
error << string_compose (_("Cannot get existing session information from %1"), statefile_path) << endmsg;
|
|
return false;
|
|
}
|
|
|
|
session_existing_sample_rate = sr;
|
|
return true;
|
|
|
|
}
|
|
|
|
/* Everything after this involves a new session */
|
|
|
|
session_name = basename_nosuffix (path);
|
|
session_path = path;
|
|
|
|
session_template = string ();
|
|
|
|
if (!template_name.empty()) {
|
|
|
|
/* Allow the user to specify a template via path or name on the
|
|
* command line
|
|
*/
|
|
|
|
bool have_resolved_template_name = false;
|
|
|
|
/* compare by name (path may or may not be UTF-8) */
|
|
|
|
vector<TemplateInfo> templates;
|
|
find_session_templates (templates, false);
|
|
for (vector<TemplateInfo>::iterator x = templates.begin(); x != templates.end(); ++x) {
|
|
if ((*x).name == template_name) {
|
|
session_template = (*x).path;
|
|
have_resolved_template_name = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* look up script by name */
|
|
LuaScriptList scripts (LuaScripting::instance ().scripts (LuaScriptInfo::SessionInit));
|
|
LuaScriptList& as (LuaScripting::instance ().scripts (LuaScriptInfo::EditorAction));
|
|
for (LuaScriptList::const_iterator s = as.begin(); s != as.end(); ++s) {
|
|
if ((*s)->subtype & LuaScriptInfo::SessionSetup) {
|
|
scripts.push_back (*s);
|
|
}
|
|
}
|
|
std::sort (scripts.begin(), scripts.end(), LuaScripting::Sorter());
|
|
for (LuaScriptList::const_iterator s = scripts.begin(); s != scripts.end(); ++s) {
|
|
if ((*s)->name == template_name) {
|
|
session_template = "urn:ardour:" + (*s)->path;
|
|
have_resolved_template_name = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!have_resolved_template_name) {
|
|
/* this will produce a more or less meaningful error later:
|
|
* "ERROR: Could not open session template [abs-path to user-config dir]"
|
|
*/
|
|
session_template = Glib::build_filename (ARDOUR::user_template_directory (), template_name);
|
|
}
|
|
}
|
|
|
|
/* We don't know what this is, because the session is new and the
|
|
* command line doesn't let us specify it. The user will get to decide
|
|
* in the audio/MIDI dialog.
|
|
*/
|
|
|
|
session_existing_sample_rate = 0;
|
|
session_is_new = true;
|
|
|
|
/* this is an arbitrary default value but since the user insists on
|
|
* starting a new session from the command line, it will do as well as
|
|
* any other possible value. I mean, seriously, what else could it be
|
|
* by default?
|
|
*/
|
|
|
|
bus_profile.master_out_channels = 2;
|
|
|
|
return true;
|
|
}
|
|
|
|
/** return values:
|
|
* -1: failure
|
|
* 1: failure but user can retry
|
|
* 0: success, session parameters ready for use
|
|
*/
|
|
int
|
|
StartupFSM::check_session_parameters (bool must_be_new)
|
|
{
|
|
bool requested_new = false;
|
|
|
|
session_name = session_dialog->session_name (requested_new);
|
|
session_path = session_dialog->session_folder ();
|
|
session_domain = session_dialog->session_domain ();
|
|
session_name_edited = session_dialog->was_new_name_edited ();
|
|
|
|
if (must_be_new) {
|
|
assert (requested_new);
|
|
}
|
|
|
|
if (!must_be_new) {
|
|
|
|
/* See if the specified session is a session archive */
|
|
|
|
int rv = ARDOUR::inflate_session (session_name, Config->get_default_session_parent_dir(), session_path, session_name);
|
|
if (rv < 0) {
|
|
ArdourMessageDialog msg (*session_dialog, string_compose (_("Extracting session-archive failed: %1"), inflate_error (rv)));
|
|
msg.run ();
|
|
return 1;
|
|
} else if (rv == 0) {
|
|
/* names are good (and session is unarchived/inflated */
|
|
float sr;
|
|
SampleFormat fmt;
|
|
string program_version;
|
|
const string statefile_path = Glib::build_filename (session_path, session_name + ARDOUR::statefile_suffix);
|
|
switch (Session::get_info_from_path (statefile_path, sr, fmt, program_version, &session_engine_hints)) {
|
|
case 0:
|
|
/* OK */
|
|
break;
|
|
case -1:
|
|
error << string_compose (_("Session file %1 does not exist"), statefile_path) << endmsg;
|
|
return -1;
|
|
break;
|
|
case -3:
|
|
error << string_compose (_("Session %1 is from a newer version of %2"), statefile_path, PROGRAM_NAME) << endmsg;
|
|
return -1;
|
|
break;
|
|
default:
|
|
error << string_compose (_("Cannot get existing session information from %1"), statefile_path) << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
session_existing_sample_rate = sr;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* check for ".ardour" in statefile name, because we don't want
|
|
* it
|
|
*
|
|
* XXX Note this weird conflation of a
|
|
* file-name-without-a-suffix and the session name. It's not
|
|
* really a session name at all, but rather the suffix-free
|
|
* name of a statefile (snapshot).
|
|
*/
|
|
|
|
const string::size_type suffix_at = session_name.find (statefile_suffix);
|
|
|
|
if (suffix_at != string::npos) {
|
|
session_name = session_name.substr (0, suffix_at);
|
|
}
|
|
|
|
/* this shouldn't happen, but we catch it just in case it does */
|
|
|
|
if (session_name.empty()) {
|
|
return 1; /* keep running dialog */
|
|
}
|
|
|
|
if (session_dialog->use_session_template()) {
|
|
session_template = session_dialog->session_template_name();
|
|
}
|
|
|
|
if (session_name[0] == G_DIR_SEPARATOR ||
|
|
#ifdef PLATFORM_WINDOWS
|
|
// Windows file system .. detect absolute path
|
|
// C:/*
|
|
(session_name.length() > 3 && session_name[1] == ':' && session_name[2] == G_DIR_SEPARATOR)
|
|
#else
|
|
// Sensible file systems
|
|
// /* or ./* or ../*
|
|
(session_name.length() > 2 && session_name[0] == '.' && session_name[1] == G_DIR_SEPARATOR) ||
|
|
(session_name.length() > 3 && session_name[0] == '.' && session_name[1] == '.' && session_name[2] == G_DIR_SEPARATOR)
|
|
#endif
|
|
)
|
|
{
|
|
|
|
/* user typed absolute path or cwd-relative path
|
|
specified into session name field. So ... infer
|
|
session path and name from what was given.
|
|
*/
|
|
|
|
session_path = Glib::path_get_dirname (session_name);
|
|
session_name = Glib::path_get_basename (session_name);
|
|
|
|
} else {
|
|
|
|
/* session name is just a name */
|
|
}
|
|
|
|
/* check if the currently-exists status matches whether or not
|
|
* it should be new
|
|
*/
|
|
|
|
if (Glib::file_test (session_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
|
|
|
|
if (requested_new /*&& !nsm*/) {
|
|
|
|
std::string existing = Glib::build_filename (session_path, session_name);
|
|
|
|
if (!ask_about_loading_existing_session (existing)) {
|
|
session_dialog->show ();
|
|
session_dialog->clear_name ();
|
|
return 1; /* try again */
|
|
}
|
|
}
|
|
|
|
session_is_new = false;
|
|
|
|
} else {
|
|
|
|
/* does not exist at present */
|
|
|
|
if (!requested_new) {
|
|
ArdourMessageDialog msg (string_compose (_("There is no existing session at \"%1\""), session_path));
|
|
msg.run ();
|
|
session_dialog->clear_name();
|
|
return 1;
|
|
}
|
|
|
|
session_is_new = true;
|
|
}
|
|
|
|
|
|
/* check if name is legal (error for new sessions only) */
|
|
std::string const& illegal = Session::session_name_is_legal (session_name);
|
|
|
|
if (!illegal.empty() && session_is_new) {
|
|
ArdourMessageDialog msg (*session_dialog,
|
|
string_compose (_("To ensure compatibility with various systems\n"
|
|
"session names may not contain a '%1' character"),
|
|
illegal));
|
|
msg.run ();
|
|
ARDOUR_COMMAND_LINE::session_name = ""; // cancel that
|
|
return 1; /* keep running dialog */
|
|
}
|
|
|
|
|
|
float sr;
|
|
SampleFormat fmt;
|
|
string program_version;
|
|
const string statefile_path = Glib::build_filename (session_path, session_name + ARDOUR::statefile_suffix);
|
|
|
|
if (!session_is_new) {
|
|
switch (Session::get_info_from_path (statefile_path, sr, fmt, program_version, &session_engine_hints)) {
|
|
case 0:
|
|
/* OK */
|
|
break;
|
|
case -1:
|
|
error << string_compose (_("Session file %1 does not exist"), statefile_path) << endmsg;
|
|
return -1;
|
|
break;
|
|
case -3:
|
|
error << string_compose (_("Session %1 is from a newer version of %2"), statefile_path, PROGRAM_NAME) << endmsg;
|
|
return -1;
|
|
break;
|
|
default:
|
|
error << string_compose (_("Cannot get existing session information from %1"), statefile_path) << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
session_existing_sample_rate = sr;
|
|
|
|
} else {
|
|
|
|
bus_profile.master_out_channels = session_dialog->master_channel_count ();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
StartupFSM::copy_demo_sessions ()
|
|
{
|
|
// TODO: maybe IFF brand_new_user
|
|
if (ARDOUR::Profile->get_mixbus () && Config->get_copy_demo_sessions ()) {
|
|
std::string dspd (Config->get_default_session_parent_dir());
|
|
Searchpath ds (ARDOUR::ardour_data_search_path());
|
|
ds.add_subdirectory_to_paths ("sessions");
|
|
vector<string> demos;
|
|
find_files_matching_pattern (demos, ds, ARDOUR::session_archive_suffix);
|
|
|
|
ARDOUR::RecentSessions rs;
|
|
ARDOUR::read_recent_sessions (rs);
|
|
|
|
for (vector<string>::iterator i = demos.begin(); i != demos.end (); ++i) {
|
|
/* "demo-session" must be inside "demo-session.<session_archive_suffix>" */
|
|
std::string name = basename_nosuffix (basename_nosuffix (*i));
|
|
std::string path = Glib::build_filename (dspd, name);
|
|
/* skip if session-dir already exists */
|
|
if (Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_DIR)) {
|
|
continue;
|
|
}
|
|
/* skip sessions that are already in 'recent'.
|
|
* eg. a new user changed <session-default-dir> shortly after installation
|
|
*/
|
|
for (ARDOUR::RecentSessions::iterator r = rs.begin(); r != rs.end(); ++r) {
|
|
if ((*r).first == name) {
|
|
continue;
|
|
}
|
|
}
|
|
try {
|
|
PBD::FileArchive ar (*i);
|
|
if (0 == ar.inflate (dspd)) {
|
|
store_recent_sessions (name, path);
|
|
info << string_compose (_("Copied Demo Session %1."), name) << endmsg;
|
|
}
|
|
} catch (...) {
|
|
/* relax ? */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
StartupFSM::ask_about_loading_existing_session (const std::string& session_path)
|
|
{
|
|
std::string str = string_compose (_("This session\n%1\nalready exists. Do you want to open it?"), session_path);
|
|
|
|
ArdourMessageDialog msg (str,
|
|
false,
|
|
Gtk::MESSAGE_WARNING,
|
|
Gtk::BUTTONS_YES_NO,
|
|
true);
|
|
|
|
|
|
msg.set_name (X_("OpenExistingDialog"));
|
|
msg.set_title (_("Open Existing Session"));
|
|
msg.set_wmclass (X_("existing_session"), PROGRAM_NAME);
|
|
msg.set_position (Gtk::WIN_POS_CENTER);
|
|
|
|
switch (msg.run()) {
|
|
case RESPONSE_YES:
|
|
return true;
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
StartupFSM::show_pre_release_dialog ()
|
|
{
|
|
pre_release_dialog = new ArdourDialog (_("Pre-Release Warning"), true, false);
|
|
pre_release_dialog->add_button (Gtk::Stock::OK, Gtk::RESPONSE_OK);
|
|
|
|
Label* label = manage (new Label);
|
|
label->set_markup (string_compose (_("<span size=\"x-large\" weight=\"bold\">Welcome to this pre-release build of %1 %2</span>\n\n\
|
|
<span size=\"large\">There are still several issues and bugs to be worked on,\n\
|
|
as well as general workflow improvements, before this can be considered\n\
|
|
release software. So, a few guidelines:\n\
|
|
\n\
|
|
1) Please do <b>NOT</b> use this software with the expectation that it is stable or reliable\n\
|
|
though it may be so, depending on your workflow.\n\
|
|
2) Please wait for a helpful writeup of new features.\n\
|
|
3) <b>Please do NOT use the forums at ardour.org to report issues</b>.\n\
|
|
4) <b>Please do NOT file bugs for this alpha-development versions at this point in time</b>.\n\
|
|
There is no bug triaging before the initial development concludes and\n\
|
|
reporting issue for incomplete, ongoing work-in-progress is mostly useless.\n\
|
|
5) Please <b>DO</b> join us on IRC for real time discussions about %1 %2. You\n\
|
|
can get there directly from within the program via the Help->Chat menu option.\n\
|
|
6) Please <b>DO</b> submit patches for issues after discussing them on IRC.\n\
|
|
\n\
|
|
Full information on all the above can be found on the support page at\n\
|
|
\n\
|
|
http://ardour.org/support</span>\n\
|
|
"), PROGRAM_NAME, VERSIONSTRING));
|
|
|
|
|
|
current_dialog_connection = pre_release_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (this, &StartupFSM::dialog_response_handler), PreReleaseDialog));
|
|
|
|
pre_release_dialog->get_vbox()->set_border_width (12);
|
|
pre_release_dialog->get_vbox()->pack_start (*label, false, false, 12);
|
|
pre_release_dialog->get_vbox()->show_all ();
|
|
pre_release_dialog->set_position (WIN_POS_CENTER);
|
|
pre_release_dialog->present ();
|
|
}
|
|
|
|
void
|
|
StartupFSM::handle_path (string const & path)
|
|
{
|
|
if (!ARDOUR_COMMAND_LINE::session_name.empty()) {
|
|
return;
|
|
}
|
|
|
|
ARDOUR_COMMAND_LINE::session_name = path;
|
|
|
|
dialog_response_handler (RESPONSE_OK, ApplicationPseudoDialog);
|
|
}
|