2 * Copyright (C) 2019 Paul Davis <paul@linuxaudiosystems.com>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include <gtkmm/dialog.h>
22 #include <gtkmm/liststore.h>
23 #include <gtkmm/messagedialog.h>
24 #include <gtkmm/stock.h>
26 #include "pbd/basename.h"
27 #include "pbd/file_archive.h"
28 #include "pbd/file_utils.h"
31 #include "ardour/audioengine.h"
32 #include "ardour/filename_extensions.h"
33 #include "ardour/filesystem_paths.h"
34 #include "ardour/profile.h"
35 #include "ardour/recent_sessions.h"
36 #include "ardour/rc_configuration.h"
37 #include "ardour/search_paths.h"
38 #include "ardour/session.h"
39 #include "ardour/session_utils.h"
40 #include "ardour/template_utils.h"
42 #include "gtkmm2ext/application.h"
43 #include <gtkmm2ext/doi.h>
45 #include "ardour_ui.h"
47 #include "engine_dialog.h"
48 #include "new_user_wizard.h"
50 #include "plugin_scan_dialog.h"
51 #include "session_dialog.h"
53 #include "startup_fsm.h"
56 #include "gtk2ardour-version.h"
59 using namespace ARDOUR;
61 using namespace Gtkmm2ext;
67 StartupFSM::StartupFSM (EngineControl& amd)
68 : session_existing_sample_rate (0)
69 , session_is_new (false)
70 , new_user (NewUserWizard::required())
71 , new_session_required (ARDOUR_COMMAND_LINE::new_session || (!ARDOUR::Profile->get_mixbus() && new_user))
72 , _state (new_user ? WaitingForNewUser : WaitingForSessionPath)
73 , audiomidi_dialog (amd)
76 , pre_release_dialog (0)
77 , plugin_scan_dialog (0)
79 /* note that our initial state can be any of:
81 * WaitingForPreRelease: if this is a pre-release build of Ardour and the user has not testified to their fidelity to our creed
82 * WaitingForNewUser: if this is the first time any version appears to have been run on this machine by this user
83 * WaitingForSessionPath: if the previous two conditions are not true
86 if (string (VERSIONSTRING).find (".pre") != string::npos) {
87 string fn = Glib::build_filename (user_config_directory(), ".i_swear_that_i_will_heed_the_guidelines_stated_in_the_pre_release_dialog");
88 if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
89 set_state (WaitingForPreRelease);
93 Application* app = Application::instance ();
95 app->ShouldQuit.connect (sigc::mem_fun (*this, &StartupFSM::queue_finish));
96 app->ShouldLoad.connect (sigc::mem_fun (*this, &StartupFSM::load_from_application_api));
99 StartupFSM::~StartupFSM ()
101 delete session_dialog;
102 delete pre_release_dialog;
103 delete plugin_scan_dialog;
104 delete new_user_dialog;
108 StartupFSM::queue_finish ()
110 _signal_response (ExitProgram);
116 /* get the splash screen visible, if it isn't yet */
117 Splash::instance()->pop_front();
118 /* make it all happen on-screen */
119 ARDOUR_UI::instance()->flush_pending ();
121 DEBUG_TRACE (DEBUG::GuiStartup, string_compose (X_("State at startup: %1\n"), enum_2_string (_state)));
124 case WaitingForPreRelease:
125 show_pre_release_dialog ();
127 case WaitingForNewUser:
128 show_new_user_dialog ();
130 case WaitingForSessionPath:
131 if (ARDOUR_COMMAND_LINE::session_name.empty()) {
133 /* nothing given on the command line ... show new session dialog */
135 show_session_dialog (new_session_required);
139 if (get_session_parameters_from_command_line (new_session_required)) {
141 /* command line arguments all OK. Get engine parameters */
143 if (!new_session_required && session_existing_sample_rate > 0) {
144 audiomidi_dialog.set_desired_sample_rate (session_existing_sample_rate);
147 start_audio_midi_setup ();
151 /* command line arguments not good. Use
152 * dialog, but prime the dialog with
153 * the information we set up in
154 * get_session_parameters_from_command_line()
157 show_session_dialog (new_session_required);
162 fatal << string_compose (_("Programming error: %1"), string_compose (X_("impossible starting state in StartupFSM (%1)"), enum_2_string (_state))) << endmsg;
163 std::cerr << string_compose (_("Programming error: %1"), string_compose (X_("impossible starting state in StartupFSM (%1)"), enum_2_string (_state))) << std::endl;
168 DEBUG_TRACE (DEBUG::GuiStartup, string_compose (X_("State after startup: %1\n"), enum_2_string (_state)));
170 /* this may cause the delivery of ShouldLoad etc if we were invoked in
171 * particular ways. It will happen when the event loop runs again.
174 Application::instance()->ready ();
180 show_session_dialog (new_session_required);
184 StartupFSM::set_state (MainState ms)
186 DEBUG_TRACE (DEBUG::GuiStartup, string_compose (X_("new state: %1\n"), enum_2_string (ms)));
190 template<typename T> void
191 StartupFSM::end_dialog (T** d)
197 delete_when_idle (*d);
201 template<typename T> void
202 StartupFSM::end_dialog (T& d)
205 current_dialog_connection.disconnect ();
209 StartupFSM::dialog_response_handler (int response, StartupFSM::DialogID dialog_id)
211 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));
215 * 1) yes, a brand new user might have specified a command line
216 * argument naming a new session. We ignore it. You're new to Ardour?
217 * We want to guide you through the startup.
221 case WaitingForPreRelease:
223 case PreReleaseDialog:
225 /* any response value from the pre-release dialog means
228 end_dialog (&pre_release_dialog);
230 if (NewUserWizard::required()) {
231 show_new_user_dialog ();
233 show_session_dialog (new_session_required);
239 case WaitingForNewUser:
244 end_dialog (&new_user_dialog);
245 show_session_dialog (new_session_required);
248 _signal_response (ExitProgram);
256 case WaitingForSessionPath:
258 case NewSessionDialog:
261 case RESPONSE_ACCEPT:
262 switch (check_session_parameters (new_session_required)) {
264 /* Unrecoverable error */
265 _signal_response (ExitProgram);
268 end_dialog (&session_dialog);
269 start_audio_midi_setup ();
272 /* do nothing - keep dialog up for a
273 * retry. Errors were addressed by
274 * ::check_session_parameters()
281 _signal_response (ExitProgram);
292 case WaitingForEngineParams:
297 case RESPONSE_ACCEPT:
298 if (AudioEngine::instance()->running()) {
299 end_dialog (audiomidi_dialog);
302 /* just keep going */
306 _signal_response (ExitProgram);
315 case WaitingForPlugins:
318 end_dialog (&plugin_scan_dialog);
321 _signal_response (LoadSession);
324 _signal_response (ExitProgram);
335 StartupFSM::show_plugin_scan_dialog ()
337 set_state (WaitingForPlugins);
339 /* if the user does not ask to discover VSTs at startup, or if this is Mixbus, then the plugin scan
340 that we run here, during startup, should only use the existing plugin cache (if any).
343 const bool cache_only = (!Config->get_discover_vst_on_start() || Profile->get_mixbus());
344 const bool verbose = (new_user || Config->get_discover_vst_on_start());
346 plugin_scan_dialog = new PluginScanDialog (cache_only, verbose);
347 current_dialog_connection = plugin_scan_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), PluginDialog));
348 plugin_scan_dialog->set_position (WIN_POS_CENTER);
350 /* 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.
352 * See also comments in PluginScanDialog::start() to understand the absurd complexities behind this call.
355 DEBUG_TRACE (DEBUG::GuiStartup, string_compose ("starting plugin dialog, cache only ? %1\n", !Config->get_discover_vst_on_start()));
356 plugin_scan_dialog->start();
357 DEBUG_TRACE (DEBUG::GuiStartup, "plugin dialog done\n");
361 StartupFSM::show_new_user_dialog ()
363 set_state (WaitingForNewUser);
364 new_user_dialog = new NewUserWizard;
365 current_dialog_connection = new_user_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), NewUserDialog));
366 new_user_dialog->set_position (WIN_POS_CENTER);
367 new_user_dialog->present ();
371 StartupFSM::show_session_dialog (bool new_session_required)
373 set_state (WaitingForSessionPath);
374 session_dialog = new SessionDialog (new_session_required, session_name, session_path, session_template, false);
375 current_dialog_connection = session_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), NewSessionDialog));
376 session_dialog->set_position (WIN_POS_CENTER);
377 session_dialog->present ();
381 StartupFSM::show_audiomidi_dialog ()
383 set_state (WaitingForEngineParams);
384 current_dialog_connection = audiomidi_dialog.signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), AudioMIDISetup));
385 audiomidi_dialog.set_position (WIN_POS_CENTER);
386 audiomidi_dialog.present ();
390 StartupFSM::start_audio_midi_setup ()
392 bool setup_required = false;
394 if (AudioEngine::instance()->current_backend() == 0) {
395 /* backend is unknown ... */
396 setup_required = true;
398 } else if (session_is_new && AudioEngine::instance()->running() && AudioEngine::instance()->sample_rate () == session_existing_sample_rate) {
401 warning << "A running engine should not be possible at this point" << endmsg;
403 } else if (AudioEngine::instance()->setup_required()) {
404 /* backend is known, but setup is needed */
405 setup_required = true;
407 } else if (!AudioEngine::instance()->running()) {
408 /* should always be true during startup */
409 if (AudioEngine::instance()->start()) {
410 setup_required = true;
414 if (setup_required) {
415 if (!session_is_new && session_existing_sample_rate > 0) {
416 audiomidi_dialog.set_desired_sample_rate (session_existing_sample_rate);
418 show_audiomidi_dialog ();
419 DEBUG_TRACE (DEBUG::GuiStartup, "audiomidi shown and waiting\n");
422 DEBUG_TRACE (DEBUG::GuiStartup, "engine already running, audio/MIDI setup dialog not required\n");
428 StartupFSM::engine_running ()
430 DEBUG_TRACE (DEBUG::GuiStartup, "engine running, start plugin scan then attach UI to engine\n");
432 /* This may be very slow. See comments in PluginScanDialog::start() */
433 show_plugin_scan_dialog ();
435 DEBUG_TRACE (DEBUG::GuiStartup, "attach UI to engine\n");
437 /* This may be very slow: it will run the GUI's post-engine
438 initialization which is essentially unbounded in time/scope of what
442 ARDOUR_UI::instance()->attach_to_engine ();
444 /* now that we've done the plugin scan AND attached the UI to the engine, we can
445 proceed with the next (final) steps of startup. This uses the same response
446 signal mechanism we use for the other dialogs.
449 plugin_scan_dialog->response (RESPONSE_OK);
453 StartupFSM::get_session_parameters_from_command_line (bool new_session_required)
455 return get_session_parameters_from_path (ARDOUR_COMMAND_LINE::session_name, ARDOUR_COMMAND_LINE::load_template, new_session_required);
459 StartupFSM::get_session_parameters_from_path (string const & path, string const & template_name, bool new_session_required)
462 /* use GUI to ask the user */
466 if (Glib::file_test (path.c_str(), Glib::FILE_TEST_EXISTS)) {
468 session_is_new = false;
470 if (new_session_required) {
471 /* wait! it already exists */
473 if (!ask_about_loading_existing_session (path)) {
480 session_name = basename_nosuffix (path);
482 if (Glib::file_test (path.c_str(), Glib::FILE_TEST_IS_REGULAR)) {
483 /* session/snapshot file, change path to be dir */
484 session_path = Glib::path_get_dirname (path);
491 string program_version;
493 const string statefile_path = Glib::build_filename (session_path, session_name + ARDOUR::statefile_suffix);
494 if (Session::get_info_from_path (statefile_path, sr, fmt, program_version)) {
495 /* exists but we can't read it correctly */
496 error << string_compose (_("Cannot get existing session information from %1"), statefile_path) << endmsg;
500 session_existing_sample_rate = sr;
505 /* Everything after this involves a new session
507 * ... did the user give us a path or just a name?
510 if (path.find (G_DIR_SEPARATOR) == string::npos) {
511 /* user gave session name with no path info, use
512 default session folder.
514 session_name = ARDOUR_COMMAND_LINE::session_name;
515 session_path = Glib::build_filename (Config->get_default_session_parent_dir (), session_name);
517 session_name = basename_nosuffix (path);
522 if (!template_name.empty()) {
524 /* Allow the user to specify a template via path or name on the
528 bool have_resolved_template_name = false;
530 /* compare by name (path may or may not be UTF-8) */
532 vector<TemplateInfo> templates;
533 find_session_templates (templates, false);
534 for (vector<TemplateInfo>::iterator x = templates.begin(); x != templates.end(); ++x) {
535 if ((*x).name == template_name) {
536 session_template = (*x).path;
537 have_resolved_template_name = true;
542 /* look up script by name */
543 LuaScriptList scripts (LuaScripting::instance ().scripts (LuaScriptInfo::SessionInit));
544 LuaScriptList& as (LuaScripting::instance ().scripts (LuaScriptInfo::EditorAction));
545 for (LuaScriptList::const_iterator s = as.begin(); s != as.end(); ++s) {
546 if ((*s)->subtype & LuaScriptInfo::SessionSetup) {
547 scripts.push_back (*s);
550 std::sort (scripts.begin(), scripts.end(), LuaScripting::Sorter());
551 for (LuaScriptList::const_iterator s = scripts.begin(); s != scripts.end(); ++s) {
552 if ((*s)->name == template_name) {
553 session_template = "urn:ardour:" + (*s)->path;
554 have_resolved_template_name = true;
559 if (!have_resolved_template_name) {
560 /* this will produce a more or less meaninful error later:
561 * "ERROR: Could not open session template [abs-path to user-config dir]"
563 session_template = Glib::build_filename (ARDOUR::user_template_directory (), template_name);
567 /* We don't know what this is, because the session is new and the
568 * command line doesn't let us specify it. The user will get to decide
569 * in the audio/MIDI dialog.
572 session_existing_sample_rate = 0;
573 session_is_new = true;
575 /* this is an arbitrary default value but since the user insists on
576 * starting a new session from the command line, it will do as well as
577 * any other possible value. I mean, seriously, what else could it be
581 bus_profile.master_out_channels = 2;
588 * 1: failure but user can retry
589 * 0: success, seesion parameters ready for use
592 StartupFSM::check_session_parameters (bool must_be_new)
594 bool requested_new = false;
596 session_name = session_dialog->session_name (requested_new);
597 session_path = session_dialog->session_folder ();
600 assert (requested_new);
605 /* See if the specified session is a session archive */
607 int rv = ARDOUR::inflate_session (session_name, Config->get_default_session_parent_dir(), session_path, session_name);
609 MessageDialog msg (*session_dialog, string_compose (_("Extracting session-archive failed: %1"), inflate_error (rv)));
613 } else if (rv == 0) {
614 /* names are good (and session is unarchived/inflated */
619 /* check for ".ardour" in statefile name, because we don't want
622 * XXX Note this wierd conflation of a
623 * file-name-without-a-suffix and the session name. It's not
624 * really a session name at all, but rather the suffix-free
625 * name of a statefile (snapshot).
628 const string::size_type suffix_at = session_name.find (statefile_suffix);
630 if (suffix_at != string::npos) {
631 session_name = session_name.substr (0, suffix_at);
634 /* this shouldn't happen, but we catch it just in case it does */
636 if (session_name.empty()) {
637 return 1; /* keep running dialog */
640 if (session_dialog->use_session_template()) {
641 session_template = session_dialog->session_template_name();
644 if (session_name[0] == G_DIR_SEPARATOR ||
645 #ifdef PLATFORM_WINDOWS
646 // Windows file system .. detect absolute path
648 (session_name.length() > 3 && session_name[1] == ':' && session_name[2] == G_DIR_SEPARATOR)
650 // Sensible file systems
652 (session_name.length() > 2 && session_name[0] == '.' && session_name[1] == G_DIR_SEPARATOR) ||
653 (session_name.length() > 3 && session_name[0] == '.' && session_name[1] == '.' && session_name[2] == G_DIR_SEPARATOR)
658 /* user typed absolute path or cwd-relative path
659 specified into session name field. So ... infer
660 session path and name from what was given.
663 session_path = Glib::path_get_dirname (session_name);
664 session_name = Glib::path_get_basename (session_name);
668 /* session name is just a name */
671 /* check if name is legal */
673 const char illegal = Session::session_name_is_legal (session_name);
676 MessageDialog msg (*session_dialog,
677 string_compose (_("To ensure compatibility with various systems\n"
678 "session names may not contain a '%1' character"),
681 ARDOUR_COMMAND_LINE::session_name = ""; // cancel that
682 return 1; /* keep running dialog */
686 /* check if the currently-exists status matches whether or not
690 if (Glib::file_test (session_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
692 if (requested_new /*&& !nsm*/) {
694 std::string existing = Glib::build_filename (session_path, session_name);
696 if (!ask_about_loading_existing_session (existing)) {
697 session_dialog->clear_name ();
698 return 1; /* try again */
702 session_is_new = false;
706 /* does not exist at present */
708 if (!requested_new) {
709 ARDOUR_UI::pop_back_splash (*session_dialog);
710 MessageDialog msg (string_compose (_("There is no existing session at \"%1\""), session_path));
712 session_dialog->clear_name();
716 session_is_new = true;
721 string program_version;
722 const string statefile_path = Glib::build_filename (session_path, session_name + ARDOUR::statefile_suffix);
724 if (!session_is_new) {
726 if (Session::get_info_from_path (statefile_path, sr, fmt, program_version)) {
727 /* exists but we can't read it */
731 session_existing_sample_rate = sr;
735 bus_profile.master_out_channels = session_dialog->master_channel_count ();
742 StartupFSM::copy_demo_sessions ()
744 // TODO: maybe IFF brand_new_user
745 if (ARDOUR::Profile->get_mixbus () && Config->get_copy_demo_sessions ()) {
746 std::string dspd (Config->get_default_session_parent_dir());
747 Searchpath ds (ARDOUR::ardour_data_search_path());
748 ds.add_subdirectory_to_paths ("sessions");
749 vector<string> demos;
750 find_files_matching_pattern (demos, ds, ARDOUR::session_archive_suffix);
752 ARDOUR::RecentSessions rs;
753 ARDOUR::read_recent_sessions (rs);
755 for (vector<string>::iterator i = demos.begin(); i != demos.end (); ++i) {
756 /* "demo-session" must be inside "demo-session.<session_archive_suffix>" */
757 std::string name = basename_nosuffix (basename_nosuffix (*i));
758 std::string path = Glib::build_filename (dspd, name);
759 /* skip if session-dir already exists */
760 if (Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_DIR)) {
763 /* skip sessions that are already in 'recent'.
764 * eg. a new user changed <session-default-dir> shorly after installation
766 for (ARDOUR::RecentSessions::iterator r = rs.begin(); r != rs.end(); ++r) {
767 if ((*r).first == name) {
772 PBD::FileArchive ar (*i);
773 if (0 == ar.inflate (dspd)) {
774 store_recent_sessions (name, path);
775 info << string_compose (_("Copied Demo Session %1."), name) << endmsg;
785 StartupFSM::load_from_application_api (const std::string& path)
787 /* macOS El Capitan (and probably later) now somehow passes the command
788 line arguments to an app via the openFile delegate protocol. Ardour
789 already does its own command line processing, and having both
790 pathways active causes crashes. So, if the command line was already
791 set, do nothing here.
794 if (!ARDOUR_COMMAND_LINE::session_name.empty()) {
798 /* Cancel SessionDialog if it's visible to make macOS delegates work.
800 * there's a race condition here: we connect to app->ShouldLoad
801 * and then at some point (might) show a session dialog. The race is
802 * caused by the non-deterministic interaction between the macOS event
803 * loop(s) and the GDK one(s).
805 * - ShouldLoad does not arrive before we show the session dialog
806 * -> here we should hide the session dialog, then use the
807 * supplied path as if it was provided on the command line
808 * - ShouldLoad signal arrives before we show a session dialog
809 * -> don't bother showing the session dialog, just use the
810 * supplied path as if it was provided on the command line
814 if (session_dialog) {
815 session_dialog->hide ();
816 delete_when_idle (session_dialog);
820 /* no command line argument given ... must just be via
821 * desktop/finder/window manager API (e.g. double click on "foo.ardour"
825 if (get_session_parameters_from_path (path, string(), false)) {
826 _signal_response (LoadSession);
830 /* given parameters failed for some reason. This is probably true
831 * anyway, but force it to be true and then carry on with whatever the
832 * main event loop is doing.
835 set_state (WaitingForSessionPath);
839 StartupFSM::ask_about_loading_existing_session (const std::string& session_path)
841 std::string str = string_compose (_("This session\n%1\nalready exists. Do you want to open it?"), session_path);
843 MessageDialog msg (str,
845 Gtk::MESSAGE_WARNING,
850 msg.set_name (X_("OpenExistingDialog"));
851 msg.set_title (_("Open Existing Session"));
852 msg.set_wmclass (X_("existing_session"), PROGRAM_NAME);
853 msg.set_position (Gtk::WIN_POS_CENTER);
854 ARDOUR_UI::pop_back_splash (msg);
865 StartupFSM::show_pre_release_dialog ()
867 pre_release_dialog = new ArdourDialog (_("Pre-Release Warning"), true, false);
868 pre_release_dialog->add_button (Gtk::Stock::OK, Gtk::RESPONSE_OK);
870 Label* label = manage (new Label);
871 label->set_markup (string_compose (_("<span size=\"x-large\" weight=\"bold\">Welcome to this pre-release build of %1 %2</span>\n\n\
872 <span size=\"large\">There are still several issues and bugs to be worked on,\n\
873 as well as general workflow improvements, before this can be considered\n\
874 release software. So, a few guidelines:\n\
876 1) Please do <b>NOT</b> use this software with the expectation that it is stable or reliable\n\
877 though it may be so, depending on your workflow.\n\
878 2) Please wait for a helpful writeup of new features.\n\
879 3) <b>Please do NOT use the forums at ardour.org to report issues</b>.\n\
880 4) <b>Please do NOT file bugs for this alpha-development versions at this point in time</b>.\n\
881 There is no bug triaging before the initial development concludes and\n\
882 reporting issue for incomplete, ongoing work-in-progress is mostly useless.\n\
883 5) Please <b>DO</b> join us on IRC for real time discussions about %1 %2. You\n\
884 can get there directly from within the program via the Help->Chat menu option.\n\
885 6) Please <b>DO</b> submit patches for issues after discussing them on IRC.\n\
887 Full information on all the above can be found on the support page at\n\
889 http://ardour.org/support</span>\n\
890 "), PROGRAM_NAME, VERSIONSTRING));
893 current_dialog_connection = pre_release_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (this, &StartupFSM::dialog_response_handler), PreReleaseDialog));
895 pre_release_dialog->get_vbox()->set_border_width (12);
896 pre_release_dialog->get_vbox()->pack_start (*label, false, false, 12);
897 pre_release_dialog->get_vbox()->show_all ();
898 pre_release_dialog->set_position (WIN_POS_CENTER);
899 pre_release_dialog->present ();