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 plugin_scan_dialog = new PluginScanDialog ((!Config->get_discover_vst_on_start() || Profile->get_mixbus()), new_user);
344 current_dialog_connection = plugin_scan_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), PluginDialog));
345 plugin_scan_dialog->set_position (WIN_POS_CENTER);
347 /* 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.
349 * See also comments in PluginScanDialog::start() to understand the absurd complexities behind this call.
352 DEBUG_TRACE (DEBUG::GuiStartup, string_compose ("starting plugin dialog, cache only ? %1\n", !Config->get_discover_vst_on_start()));
353 plugin_scan_dialog->start();
354 DEBUG_TRACE (DEBUG::GuiStartup, "plugin dialog done\n");
359 StartupFSM::show_new_user_dialog ()
361 set_state (WaitingForNewUser);
362 new_user_dialog = new NewUserWizard;
363 current_dialog_connection = new_user_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), NewUserDialog));
364 new_user_dialog->set_position (WIN_POS_CENTER);
365 new_user_dialog->present ();
369 StartupFSM::show_session_dialog (bool new_session_required)
371 set_state (WaitingForSessionPath);
372 session_dialog = new SessionDialog (new_session_required, session_name, session_path, session_template, false);
373 current_dialog_connection = session_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), NewSessionDialog));
374 session_dialog->set_position (WIN_POS_CENTER);
375 session_dialog->present ();
379 StartupFSM::show_audiomidi_dialog ()
381 set_state (WaitingForEngineParams);
382 current_dialog_connection = audiomidi_dialog.signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), AudioMIDISetup));
383 audiomidi_dialog.set_position (WIN_POS_CENTER);
384 audiomidi_dialog.present ();
388 StartupFSM::start_audio_midi_setup ()
390 bool setup_required = false;
392 if (AudioEngine::instance()->current_backend() == 0) {
393 /* backend is unknown ... */
394 setup_required = true;
396 } else if (session_is_new && AudioEngine::instance()->running() && AudioEngine::instance()->sample_rate () == session_existing_sample_rate) {
399 warning << "A running engine should not be possible at this point" << endmsg;
401 } else if (AudioEngine::instance()->setup_required()) {
402 /* backend is known, but setup is needed */
403 setup_required = true;
405 } else if (!AudioEngine::instance()->running()) {
406 /* should always be true during startup */
407 if (AudioEngine::instance()->start()) {
408 setup_required = true;
412 if (setup_required) {
413 if (!session_is_new && session_existing_sample_rate > 0) {
414 audiomidi_dialog.set_desired_sample_rate (session_existing_sample_rate);
416 show_audiomidi_dialog ();
417 DEBUG_TRACE (DEBUG::GuiStartup, "audiomidi shown and waiting\n");
420 DEBUG_TRACE (DEBUG::GuiStartup, "engine already running, audio/MIDI setup dialog not required\n");
426 StartupFSM::engine_running ()
428 DEBUG_TRACE (DEBUG::GuiStartup, "engine running, start plugin scan then attach UI to engine\n");
430 /* This may be very slow. See comments in PluginScanDialog::start() */
431 show_plugin_scan_dialog ();
433 DEBUG_TRACE (DEBUG::GuiStartup, "attach UI to engine\n");
435 /* This may be very slow: it will run the GUI's post-engine
436 initialization which is essentially unbounded in time/scope of what
440 ARDOUR_UI::instance()->attach_to_engine ();
442 /* now that we've done the plugin scan AND attached the UI to the engine, we can
443 proceed with the next (final) steps of startup. This uses the same response
444 signal mechanism we use for the other dialogs.
447 plugin_scan_dialog->response (RESPONSE_OK);
451 StartupFSM::get_session_parameters_from_command_line (bool new_session_required)
453 return get_session_parameters_from_path (ARDOUR_COMMAND_LINE::session_name, ARDOUR_COMMAND_LINE::load_template, new_session_required);
457 StartupFSM::get_session_parameters_from_path (string const & path, string const & template_name, bool new_session_required)
460 /* use GUI to ask the user */
464 if (Glib::file_test (path.c_str(), Glib::FILE_TEST_EXISTS)) {
466 session_is_new = false;
468 if (new_session_required) {
469 /* wait! it already exists */
471 if (!ask_about_loading_existing_session (path)) {
478 session_name = basename_nosuffix (path);
480 if (Glib::file_test (path.c_str(), Glib::FILE_TEST_IS_REGULAR)) {
481 /* session/snapshot file, change path to be dir */
482 session_path = Glib::path_get_dirname (path);
489 string program_version;
491 const string statefile_path = Glib::build_filename (session_path, session_name + ARDOUR::statefile_suffix);
492 if (Session::get_info_from_path (statefile_path, sr, fmt, program_version)) {
493 /* exists but we can't read it correctly */
494 error << string_compose (_("Cannot get existing session information from %1"), statefile_path) << endmsg;
498 session_existing_sample_rate = sr;
503 /* Everything after this involves a new session
505 * ... did the user give us a path or just a name?
508 if (path.find (G_DIR_SEPARATOR) == string::npos) {
509 /* user gave session name with no path info, use
510 default session folder.
512 session_name = ARDOUR_COMMAND_LINE::session_name;
513 session_path = Glib::build_filename (Config->get_default_session_parent_dir (), session_name);
515 session_name = basename_nosuffix (path);
520 if (!template_name.empty()) {
522 /* Allow the user to specify a template via path or name on the
526 bool have_resolved_template_name = false;
528 /* compare by name (path may or may not be UTF-8) */
530 vector<TemplateInfo> templates;
531 find_session_templates (templates, false);
532 for (vector<TemplateInfo>::iterator x = templates.begin(); x != templates.end(); ++x) {
533 if ((*x).name == template_name) {
534 session_template = (*x).path;
535 have_resolved_template_name = true;
540 /* look up script by name */
541 LuaScriptList scripts (LuaScripting::instance ().scripts (LuaScriptInfo::SessionInit));
542 LuaScriptList& as (LuaScripting::instance ().scripts (LuaScriptInfo::EditorAction));
543 for (LuaScriptList::const_iterator s = as.begin(); s != as.end(); ++s) {
544 if ((*s)->subtype & LuaScriptInfo::SessionSetup) {
545 scripts.push_back (*s);
548 std::sort (scripts.begin(), scripts.end(), LuaScripting::Sorter());
549 for (LuaScriptList::const_iterator s = scripts.begin(); s != scripts.end(); ++s) {
550 if ((*s)->name == template_name) {
551 session_template = "urn:ardour:" + (*s)->path;
552 have_resolved_template_name = true;
557 if (!have_resolved_template_name) {
558 /* this will produce a more or less meaninful error later:
559 * "ERROR: Could not open session template [abs-path to user-config dir]"
561 session_template = Glib::build_filename (ARDOUR::user_template_directory (), template_name);
565 /* We don't know what this is, because the session is new and the
566 * command line doesn't let us specify it. The user will get to decide
567 * in the audio/MIDI dialog.
570 session_existing_sample_rate = 0;
571 session_is_new = true;
573 /* this is an arbitrary default value but since the user insists on
574 * starting a new session from the command line, it will do as well as
575 * any other possible value. I mean, seriously, what else could it be
579 bus_profile.master_out_channels = 2;
586 * 1: failure but user can retry
587 * 0: success, seesion parameters ready for use
590 StartupFSM::check_session_parameters (bool must_be_new)
592 bool requested_new = false;
594 session_name = session_dialog->session_name (requested_new);
595 session_path = session_dialog->session_folder ();
598 assert (requested_new);
603 /* See if the specified session is a session archive */
605 int rv = ARDOUR::inflate_session (session_name, Config->get_default_session_parent_dir(), session_path, session_name);
607 MessageDialog msg (*session_dialog, string_compose (_("Extracting session-archive failed: %1"), inflate_error (rv)));
611 } else if (rv == 0) {
612 /* names are good (and session is unarchived/inflated */
617 /* check for ".ardour" in statefile name, because we don't want
620 * XXX Note this wierd conflation of a
621 * file-name-without-a-suffix and the session name. It's not
622 * really a session name at all, but rather the suffix-free
623 * name of a statefile (snapshot).
626 const string::size_type suffix_at = session_name.find (statefile_suffix);
628 if (suffix_at != string::npos) {
629 session_name = session_name.substr (0, suffix_at);
632 /* this shouldn't happen, but we catch it just in case it does */
634 if (session_name.empty()) {
635 return 1; /* keep running dialog */
638 if (session_dialog->use_session_template()) {
639 session_template = session_dialog->session_template_name();
642 if (session_name[0] == G_DIR_SEPARATOR ||
643 #ifdef PLATFORM_WINDOWS
644 // Windows file system .. detect absolute path
646 (session_name.length() > 3 && session_name[1] == ':' && session_name[2] == G_DIR_SEPARATOR)
648 // Sensible file systems
650 (session_name.length() > 2 && session_name[0] == '.' && session_name[1] == G_DIR_SEPARATOR) ||
651 (session_name.length() > 3 && session_name[0] == '.' && session_name[1] == '.' && session_name[2] == G_DIR_SEPARATOR)
656 /* user typed absolute path or cwd-relative path
657 specified into session name field. So ... infer
658 session path and name from what was given.
661 session_path = Glib::path_get_dirname (session_name);
662 session_name = Glib::path_get_basename (session_name);
666 /* session name is just a name */
669 /* check if name is legal */
671 const char illegal = Session::session_name_is_legal (session_name);
674 MessageDialog msg (*session_dialog,
675 string_compose (_("To ensure compatibility with various systems\n"
676 "session names may not contain a '%1' character"),
679 ARDOUR_COMMAND_LINE::session_name = ""; // cancel that
680 return 1; /* keep running dialog */
684 /* check if the currently-exists status matches whether or not
688 if (Glib::file_test (session_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
690 if (requested_new /*&& !nsm*/) {
692 std::string existing = Glib::build_filename (session_path, session_name);
694 if (!ask_about_loading_existing_session (existing)) {
695 session_dialog->clear_name ();
696 return 1; /* try again */
700 session_is_new = false;
704 /* does not exist at present */
706 if (!requested_new) {
707 ARDOUR_UI::pop_back_splash (*session_dialog);
708 MessageDialog msg (string_compose (_("There is no existing session at \"%1\""), session_path));
710 session_dialog->clear_name();
714 session_is_new = true;
719 string program_version;
720 const string statefile_path = Glib::build_filename (session_path, session_name + ARDOUR::statefile_suffix);
722 if (!session_is_new) {
724 if (Session::get_info_from_path (statefile_path, sr, fmt, program_version)) {
725 /* exists but we can't read it */
729 session_existing_sample_rate = sr;
733 bus_profile.master_out_channels = session_dialog->master_channel_count ();
740 StartupFSM::copy_demo_sessions ()
742 // TODO: maybe IFF brand_new_user
743 if (ARDOUR::Profile->get_mixbus () && Config->get_copy_demo_sessions ()) {
744 std::string dspd (Config->get_default_session_parent_dir());
745 Searchpath ds (ARDOUR::ardour_data_search_path());
746 ds.add_subdirectory_to_paths ("sessions");
747 vector<string> demos;
748 find_files_matching_pattern (demos, ds, ARDOUR::session_archive_suffix);
750 ARDOUR::RecentSessions rs;
751 ARDOUR::read_recent_sessions (rs);
753 for (vector<string>::iterator i = demos.begin(); i != demos.end (); ++i) {
754 /* "demo-session" must be inside "demo-session.<session_archive_suffix>" */
755 std::string name = basename_nosuffix (basename_nosuffix (*i));
756 std::string path = Glib::build_filename (dspd, name);
757 /* skip if session-dir already exists */
758 if (Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_DIR)) {
761 /* skip sessions that are already in 'recent'.
762 * eg. a new user changed <session-default-dir> shorly after installation
764 for (ARDOUR::RecentSessions::iterator r = rs.begin(); r != rs.end(); ++r) {
765 if ((*r).first == name) {
770 PBD::FileArchive ar (*i);
771 if (0 == ar.inflate (dspd)) {
772 store_recent_sessions (name, path);
773 info << string_compose (_("Copied Demo Session %1."), name) << endmsg;
783 StartupFSM::load_from_application_api (const std::string& path)
785 /* macOS El Capitan (and probably later) now somehow passes the command
786 line arguments to an app via the openFile delegate protocol. Ardour
787 already does its own command line processing, and having both
788 pathways active causes crashes. So, if the command line was already
789 set, do nothing here.
792 if (!ARDOUR_COMMAND_LINE::session_name.empty()) {
796 /* Cancel SessionDialog if it's visible to make macOS delegates work.
798 * there's a race condition here: we connect to app->ShouldLoad
799 * and then at some point (might) show a session dialog. The race is
800 * caused by the non-deterministic interaction between the macOS event
801 * loop(s) and the GDK one(s).
803 * - ShouldLoad does not arrive before we show the session dialog
804 * -> here we should hide the session dialog, then use the
805 * supplied path as if it was provided on the command line
806 * - ShouldLoad signal arrives before we show a session dialog
807 * -> don't bother showing the session dialog, just use the
808 * supplied path as if it was provided on the command line
812 if (session_dialog) {
813 session_dialog->hide ();
814 delete_when_idle (session_dialog);
818 /* no command line argument given ... must just be via
819 * desktop/finder/window manager API (e.g. double click on "foo.ardour"
823 if (get_session_parameters_from_path (path, string(), false)) {
824 _signal_response (LoadSession);
828 /* given parameters failed for some reason. This is probably true
829 * anyway, but force it to be true and then carry on with whatever the
830 * main event loop is doing.
833 set_state (WaitingForSessionPath);
837 StartupFSM::ask_about_loading_existing_session (const std::string& session_path)
839 std::string str = string_compose (_("This session\n%1\nalready exists. Do you want to open it?"), session_path);
841 MessageDialog msg (str,
843 Gtk::MESSAGE_WARNING,
848 msg.set_name (X_("OpenExistingDialog"));
849 msg.set_title (_("Open Existing Session"));
850 msg.set_wmclass (X_("existing_session"), PROGRAM_NAME);
851 msg.set_position (Gtk::WIN_POS_CENTER);
852 ARDOUR_UI::pop_back_splash (msg);
863 StartupFSM::show_pre_release_dialog ()
865 pre_release_dialog = new ArdourDialog (_("Pre-Release Warning"), true, false);
866 pre_release_dialog->add_button (Gtk::Stock::OK, Gtk::RESPONSE_OK);
868 Label* label = manage (new Label);
869 label->set_markup (string_compose (_("<span size=\"x-large\" weight=\"bold\">Welcome to this pre-release build of %1 %2</span>\n\n\
870 <span size=\"large\">There are still several issues and bugs to be worked on,\n\
871 as well as general workflow improvements, before this can be considered\n\
872 release software. So, a few guidelines:\n\
874 1) Please do <b>NOT</b> use this software with the expectation that it is stable or reliable\n\
875 though it may be so, depending on your workflow.\n\
876 2) Please wait for a helpful writeup of new features.\n\
877 3) <b>Please do NOT use the forums at ardour.org to report issues</b>.\n\
878 4) <b>Please do NOT file bugs for this alpha-development versions at this point in time</b>.\n\
879 There is no bug triaging before the initial development concludes and\n\
880 reporting issue for incomplete, ongoing work-in-progress is mostly useless.\n\
881 5) Please <b>DO</b> join us on IRC for real time discussions about %1 %2. You\n\
882 can get there directly from within the program via the Help->Chat menu option.\n\
883 6) Please <b>DO</b> submit patches for issues after discussing them on IRC.\n\
885 Full information on all the above can be found on the support page at\n\
887 http://ardour.org/support</span>\n\
888 "), PROGRAM_NAME, VERSIONSTRING));
891 current_dialog_connection = pre_release_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (this, &StartupFSM::dialog_response_handler), PreReleaseDialog));
893 pre_release_dialog->get_vbox()->set_border_width (12);
894 pre_release_dialog->get_vbox()->pack_start (*label, false, false, 12);
895 pre_release_dialog->get_vbox()->show_all ();
896 pre_release_dialog->set_position (WIN_POS_CENTER);
897 pre_release_dialog->present ();