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>
44 #include <gtkmm2ext/keyboard.h>
46 #include "ardour_message.h"
47 #include "ardour_ui.h"
49 #include "engine_dialog.h"
50 #include "new_user_wizard.h"
52 #include "plugin_scan_dialog.h"
53 #include "session_dialog.h"
55 #include "startup_fsm.h"
58 #include "gtk2ardour-version.h"
61 using namespace ARDOUR;
63 using namespace Gtkmm2ext;
69 StartupFSM::StartupFSM (EngineControl& amd)
70 : session_existing_sample_rate (0)
71 , session_is_new (false)
72 , new_user (NewUserWizard::required())
73 , new_session_required (ARDOUR_COMMAND_LINE::new_session || (!ARDOUR::Profile->get_mixbus() && new_user))
74 , _state (new_user ? WaitingForNewUser : WaitingForSessionPath)
75 , audiomidi_dialog (amd)
78 , pre_release_dialog (0)
79 , plugin_scan_dialog (0)
81 /* note that our initial state can be any of:
83 * WaitingForPreRelease: if this is a pre-release build of Ardour and the user has not testified to their fidelity to our creed
84 * WaitingForNewUser: if this is the first time any version appears to have been run on this machine by this user
85 * WaitingForSessionPath: if the previous two conditions are not true
88 if (string (VERSIONSTRING).find (".pre") != string::npos) {
89 string fn = Glib::build_filename (user_config_directory(), ".i_swear_that_i_will_heed_the_guidelines_stated_in_the_pre_release_dialog");
90 if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
91 set_state (WaitingForPreRelease);
95 Application* app = Application::instance ();
97 app->ShouldQuit.connect (sigc::mem_fun (*this, &StartupFSM::queue_finish));
98 app->ShouldLoad.connect (sigc::mem_fun (*this, &StartupFSM::load_from_application_api));
100 Gtkmm2ext::Keyboard::HideMightMeanQuit.connect (sigc::mem_fun (*this, &StartupFSM::dialog_hidden));
103 StartupFSM::~StartupFSM ()
105 delete session_dialog;
106 delete pre_release_dialog;
107 delete plugin_scan_dialog;
108 delete new_user_dialog;
112 StartupFSM::dialog_hidden (Gtk::Window* /* ignored */)
114 /* since this object only exists during startup, any attempt to close
115 * any dialog that we manage with Ctrl/Cmd-w is assumed to indicate a
116 * desire to quit on the part of the user.
122 StartupFSM::queue_finish ()
124 _signal_response (ExitProgram);
130 /* get the splash screen visible, if it isn't yet */
131 Splash::instance()->pop_front();
132 /* make it all happen on-screen */
133 ARDOUR_UI::instance()->flush_pending ();
135 DEBUG_TRACE (DEBUG::GuiStartup, string_compose (X_("State at startup: %1\n"), enum_2_string (_state)));
138 case WaitingForPreRelease:
139 show_pre_release_dialog ();
141 case WaitingForNewUser:
142 show_new_user_dialog ();
144 case WaitingForSessionPath:
145 handle_waiting_for_session_path ();
148 fatal << string_compose (_("Programming error: %1"), string_compose (X_("impossible starting state in StartupFSM (%1)"), enum_2_string (_state))) << endmsg;
149 std::cerr << string_compose (_("Programming error: %1"), string_compose (X_("impossible starting state in StartupFSM (%1)"), enum_2_string (_state))) << std::endl;
154 DEBUG_TRACE (DEBUG::GuiStartup, string_compose (X_("State after startup: %1\n"), enum_2_string (_state)));
160 show_session_dialog (new_session_required);
164 StartupFSM::set_state (MainState ms)
166 DEBUG_TRACE (DEBUG::GuiStartup, string_compose (X_("new state: %1\n"), enum_2_string (ms)));
170 template<typename T> void
171 StartupFSM::end_dialog (T** d)
177 delete_when_idle (*d);
181 template<typename T> void
182 StartupFSM::end_dialog (T& d)
185 current_dialog_connection.disconnect ();
189 StartupFSM::dialog_response_handler (int response, StartupFSM::DialogID dialog_id)
191 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));
195 * 1) yes, a brand new user might have specified a command line
196 * argument naming a new session. We ignore it. You're new to Ardour?
197 * We want to guide you through the startup.
201 case WaitingForPreRelease:
203 case PreReleaseDialog:
205 /* any response value from the pre-release dialog means
208 end_dialog (&pre_release_dialog);
210 if (NewUserWizard::required()) {
211 show_new_user_dialog ();
213 handle_waiting_for_session_path ();
219 case WaitingForNewUser:
224 end_dialog (&new_user_dialog);
225 show_session_dialog (new_session_required);
228 _signal_response (ExitProgram);
236 case WaitingForSessionPath:
238 case NewSessionDialog:
241 case RESPONSE_ACCEPT:
242 switch (check_session_parameters (new_session_required)) {
244 /* Unrecoverable error */
245 _signal_response (ExitProgram);
248 end_dialog (&session_dialog);
249 start_audio_midi_setup ();
252 /* do nothing - keep dialog up for a
253 * retry. Errors were addressed by
254 * ::check_session_parameters()
261 _signal_response (ExitProgram);
272 case WaitingForEngineParams:
277 case RESPONSE_ACCEPT:
278 if (AudioEngine::instance()->running()) {
279 end_dialog (audiomidi_dialog);
282 /* just keep going */
286 _signal_response (ExitProgram);
295 case WaitingForPlugins:
298 end_dialog (&plugin_scan_dialog);
301 _signal_response (LoadSession);
304 _signal_response (ExitProgram);
315 StartupFSM::handle_waiting_for_session_path ()
317 if (ARDOUR_COMMAND_LINE::session_name.empty()) {
319 /* nothing given on the command line ... show new session dialog */
321 show_session_dialog (new_session_required);
325 if (get_session_parameters_from_command_line (new_session_required)) {
327 /* command line arguments all OK. Get engine parameters */
329 if (!new_session_required && session_existing_sample_rate > 0) {
330 audiomidi_dialog.set_desired_sample_rate (session_existing_sample_rate);
333 start_audio_midi_setup ();
337 /* command line arguments not good. Use
338 * dialog, but prime the dialog with
339 * the information we set up in
340 * get_session_parameters_from_command_line()
343 show_session_dialog (new_session_required);
349 StartupFSM::show_plugin_scan_dialog ()
351 set_state (WaitingForPlugins);
353 /* if the user does not ask to discover VSTs at startup, or if this is Mixbus, then the plugin scan
354 that we run here, during startup, should only use the existing plugin cache (if any).
357 const bool cache_only = (!Config->get_discover_vst_on_start() || Profile->get_mixbus());
358 const bool verbose = new_user;
360 plugin_scan_dialog = new PluginScanDialog (cache_only, verbose);
361 current_dialog_connection = plugin_scan_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), PluginDialog));
362 plugin_scan_dialog->set_position (WIN_POS_CENTER);
364 /* 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.
366 * See also comments in PluginScanDialog::start() to understand the absurd complexities behind this call.
369 DEBUG_TRACE (DEBUG::GuiStartup, string_compose ("starting plugin dialog, cache only ? %1\n", !Config->get_discover_vst_on_start()));
370 plugin_scan_dialog->start();
371 DEBUG_TRACE (DEBUG::GuiStartup, "plugin dialog done\n");
375 StartupFSM::show_new_user_dialog ()
377 set_state (WaitingForNewUser);
378 new_user_dialog = new NewUserWizard;
379 current_dialog_connection = new_user_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), NewUserDialog));
380 new_user_dialog->set_position (WIN_POS_CENTER);
381 new_user_dialog->present ();
385 StartupFSM::show_session_dialog (bool new_session_required)
387 set_state (WaitingForSessionPath);
388 session_dialog = new SessionDialog (new_session_required, session_name, session_path, session_template, false);
389 current_dialog_connection = session_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), NewSessionDialog));
390 session_dialog->set_position (WIN_POS_CENTER);
391 session_dialog->present ();
395 StartupFSM::show_audiomidi_dialog ()
397 set_state (WaitingForEngineParams);
398 current_dialog_connection = audiomidi_dialog.signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), AudioMIDISetup));
399 audiomidi_dialog.set_position (WIN_POS_CENTER);
400 audiomidi_dialog.present ();
404 StartupFSM::start_audio_midi_setup ()
406 bool setup_required = false;
408 if (AudioEngine::instance()->current_backend() == 0) {
409 /* backend is unknown ... */
410 setup_required = true;
412 } else if (session_is_new && AudioEngine::instance()->running() && AudioEngine::instance()->sample_rate () == session_existing_sample_rate) {
415 warning << "A running engine should not be possible at this point" << endmsg;
417 } else if (AudioEngine::instance()->setup_required()) {
418 /* backend is known, but setup is needed */
419 setup_required = true;
421 } else if (!AudioEngine::instance()->running()) {
422 /* should always be true during startup */
423 if (AudioEngine::instance()->start()) {
424 setup_required = true;
428 if (setup_required) {
430 if (!session_is_new && (Config->get_try_autostart_engine () || g_getenv ("ARDOUR_TRY_AUTOSTART_ENGINE"))) {
432 AudioEngine::instance()->set_sample_rate(session_existing_sample_rate);
433 if (!AudioEngine::instance()->start ()) {
434 if (ARDOUR::AudioEngine::instance()->running()) {
435 DEBUG_TRACE (DEBUG::GuiStartup, "autostart successful, audio/MIDI setup dialog not required\n");
442 if (!session_is_new && session_existing_sample_rate > 0) {
443 audiomidi_dialog.set_desired_sample_rate (session_existing_sample_rate);
446 show_audiomidi_dialog ();
447 DEBUG_TRACE (DEBUG::GuiStartup, "audiomidi shown and waiting\n");
451 DEBUG_TRACE (DEBUG::GuiStartup, "engine already running, audio/MIDI setup dialog not required\n");
457 StartupFSM::engine_running ()
459 DEBUG_TRACE (DEBUG::GuiStartup, "engine running, start plugin scan then attach UI to engine\n");
461 /* This may be very slow. See comments in PluginScanDialog::start() */
462 show_plugin_scan_dialog ();
464 DEBUG_TRACE (DEBUG::GuiStartup, "attach UI to engine\n");
466 /* This may be very slow: it will run the GUI's post-engine
467 initialization which is essentially unbounded in time/scope of what
471 ARDOUR_UI::instance()->attach_to_engine ();
473 /* now that we've done the plugin scan AND attached the UI to the engine, we can
474 proceed with the next (final) steps of startup. This uses the same response
475 signal mechanism we use for the other dialogs.
478 plugin_scan_dialog->response (RESPONSE_OK);
482 StartupFSM::get_session_parameters_from_command_line (bool new_session_required)
484 return get_session_parameters_from_path (ARDOUR_COMMAND_LINE::session_name, ARDOUR_COMMAND_LINE::load_template, new_session_required);
488 StartupFSM::get_session_parameters_from_path (string const & path, string const & template_name, bool new_session_required)
491 /* use GUI to ask the user */
495 if (Glib::file_test (path.c_str(), Glib::FILE_TEST_EXISTS)) {
497 session_is_new = false;
499 if (new_session_required) {
500 /* wait! it already exists */
502 if (!ask_about_loading_existing_session (path)) {
509 session_name = basename_nosuffix (path);
511 if (Glib::file_test (path.c_str(), Glib::FILE_TEST_IS_REGULAR)) {
512 /* session/snapshot file, change path to be dir */
513 session_path = Glib::path_get_dirname (path);
520 string program_version;
522 const string statefile_path = Glib::build_filename (session_path, session_name + ARDOUR::statefile_suffix);
523 if (Session::get_info_from_path (statefile_path, sr, fmt, program_version)) {
524 /* exists but we can't read it correctly */
525 error << string_compose (_("Cannot get existing session information from %1"), statefile_path) << endmsg;
529 session_existing_sample_rate = sr;
534 /* Everything after this involves a new session
536 * ... did the user give us a path or just a name?
539 if (path.find (G_DIR_SEPARATOR) == string::npos) {
540 /* user gave session name with no path info, use
541 default session folder.
543 session_name = ARDOUR_COMMAND_LINE::session_name;
544 session_path = Glib::build_filename (Config->get_default_session_parent_dir (), session_name);
546 session_name = basename_nosuffix (path);
551 if (!template_name.empty()) {
553 /* Allow the user to specify a template via path or name on the
557 bool have_resolved_template_name = false;
559 /* compare by name (path may or may not be UTF-8) */
561 vector<TemplateInfo> templates;
562 find_session_templates (templates, false);
563 for (vector<TemplateInfo>::iterator x = templates.begin(); x != templates.end(); ++x) {
564 if ((*x).name == template_name) {
565 session_template = (*x).path;
566 have_resolved_template_name = true;
571 /* look up script by name */
572 LuaScriptList scripts (LuaScripting::instance ().scripts (LuaScriptInfo::SessionInit));
573 LuaScriptList& as (LuaScripting::instance ().scripts (LuaScriptInfo::EditorAction));
574 for (LuaScriptList::const_iterator s = as.begin(); s != as.end(); ++s) {
575 if ((*s)->subtype & LuaScriptInfo::SessionSetup) {
576 scripts.push_back (*s);
579 std::sort (scripts.begin(), scripts.end(), LuaScripting::Sorter());
580 for (LuaScriptList::const_iterator s = scripts.begin(); s != scripts.end(); ++s) {
581 if ((*s)->name == template_name) {
582 session_template = "urn:ardour:" + (*s)->path;
583 have_resolved_template_name = true;
588 if (!have_resolved_template_name) {
589 /* this will produce a more or less meaninful error later:
590 * "ERROR: Could not open session template [abs-path to user-config dir]"
592 session_template = Glib::build_filename (ARDOUR::user_template_directory (), template_name);
596 /* We don't know what this is, because the session is new and the
597 * command line doesn't let us specify it. The user will get to decide
598 * in the audio/MIDI dialog.
601 session_existing_sample_rate = 0;
602 session_is_new = true;
604 /* this is an arbitrary default value but since the user insists on
605 * starting a new session from the command line, it will do as well as
606 * any other possible value. I mean, seriously, what else could it be
610 bus_profile.master_out_channels = 2;
617 * 1: failure but user can retry
618 * 0: success, seesion parameters ready for use
621 StartupFSM::check_session_parameters (bool must_be_new)
623 bool requested_new = false;
625 session_name = session_dialog->session_name (requested_new);
626 session_path = session_dialog->session_folder ();
629 assert (requested_new);
634 /* See if the specified session is a session archive */
636 int rv = ARDOUR::inflate_session (session_name, Config->get_default_session_parent_dir(), session_path, session_name);
638 ArdourMessageDialog msg (*session_dialog, string_compose (_("Extracting session-archive failed: %1"), inflate_error (rv)));
642 } else if (rv == 0) {
643 /* names are good (and session is unarchived/inflated */
648 /* check for ".ardour" in statefile name, because we don't want
651 * XXX Note this wierd conflation of a
652 * file-name-without-a-suffix and the session name. It's not
653 * really a session name at all, but rather the suffix-free
654 * name of a statefile (snapshot).
657 const string::size_type suffix_at = session_name.find (statefile_suffix);
659 if (suffix_at != string::npos) {
660 session_name = session_name.substr (0, suffix_at);
663 /* this shouldn't happen, but we catch it just in case it does */
665 if (session_name.empty()) {
666 return 1; /* keep running dialog */
669 if (session_dialog->use_session_template()) {
670 session_template = session_dialog->session_template_name();
673 if (session_name[0] == G_DIR_SEPARATOR ||
674 #ifdef PLATFORM_WINDOWS
675 // Windows file system .. detect absolute path
677 (session_name.length() > 3 && session_name[1] == ':' && session_name[2] == G_DIR_SEPARATOR)
679 // Sensible file systems
681 (session_name.length() > 2 && session_name[0] == '.' && session_name[1] == G_DIR_SEPARATOR) ||
682 (session_name.length() > 3 && session_name[0] == '.' && session_name[1] == '.' && session_name[2] == G_DIR_SEPARATOR)
687 /* user typed absolute path or cwd-relative path
688 specified into session name field. So ... infer
689 session path and name from what was given.
692 session_path = Glib::path_get_dirname (session_name);
693 session_name = Glib::path_get_basename (session_name);
697 /* session name is just a name */
700 /* check if name is legal */
702 const char illegal = Session::session_name_is_legal (session_name);
705 ArdourMessageDialog msg (*session_dialog,
706 string_compose (_("To ensure compatibility with various systems\n"
707 "session names may not contain a '%1' character"),
710 ARDOUR_COMMAND_LINE::session_name = ""; // cancel that
711 return 1; /* keep running dialog */
715 /* check if the currently-exists status matches whether or not
719 if (Glib::file_test (session_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
721 if (requested_new /*&& !nsm*/) {
723 std::string existing = Glib::build_filename (session_path, session_name);
725 if (!ask_about_loading_existing_session (existing)) {
726 session_dialog->clear_name ();
727 return 1; /* try again */
731 session_is_new = false;
735 /* does not exist at present */
737 if (!requested_new) {
738 ArdourMessageDialog msg (string_compose (_("There is no existing session at \"%1\""), session_path));
740 session_dialog->clear_name();
744 session_is_new = true;
749 string program_version;
750 const string statefile_path = Glib::build_filename (session_path, session_name + ARDOUR::statefile_suffix);
752 if (!session_is_new) {
754 if (Session::get_info_from_path (statefile_path, sr, fmt, program_version)) {
755 /* exists but we can't read it */
759 session_existing_sample_rate = sr;
763 bus_profile.master_out_channels = session_dialog->master_channel_count ();
770 StartupFSM::copy_demo_sessions ()
772 // TODO: maybe IFF brand_new_user
773 if (ARDOUR::Profile->get_mixbus () && Config->get_copy_demo_sessions ()) {
774 std::string dspd (Config->get_default_session_parent_dir());
775 Searchpath ds (ARDOUR::ardour_data_search_path());
776 ds.add_subdirectory_to_paths ("sessions");
777 vector<string> demos;
778 find_files_matching_pattern (demos, ds, ARDOUR::session_archive_suffix);
780 ARDOUR::RecentSessions rs;
781 ARDOUR::read_recent_sessions (rs);
783 for (vector<string>::iterator i = demos.begin(); i != demos.end (); ++i) {
784 /* "demo-session" must be inside "demo-session.<session_archive_suffix>" */
785 std::string name = basename_nosuffix (basename_nosuffix (*i));
786 std::string path = Glib::build_filename (dspd, name);
787 /* skip if session-dir already exists */
788 if (Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_DIR)) {
791 /* skip sessions that are already in 'recent'.
792 * eg. a new user changed <session-default-dir> shorly after installation
794 for (ARDOUR::RecentSessions::iterator r = rs.begin(); r != rs.end(); ++r) {
795 if ((*r).first == name) {
800 PBD::FileArchive ar (*i);
801 if (0 == ar.inflate (dspd)) {
802 store_recent_sessions (name, path);
803 info << string_compose (_("Copied Demo Session %1."), name) << endmsg;
813 StartupFSM::load_from_application_api (const std::string& path)
815 if (!ARDOUR_COMMAND_LINE::session_name.empty()) {
819 /* just set this as if it was given on the command line, rather than
820 * supplied via some desktop system (e.g. macOS application delegate
821 * and "openFile". Note that this relies on this being invoked before
822 * StartupFSM::start().
825 ARDOUR_COMMAND_LINE::session_name = path;
829 StartupFSM::ask_about_loading_existing_session (const std::string& session_path)
831 std::string str = string_compose (_("This session\n%1\nalready exists. Do you want to open it?"), session_path);
833 ArdourMessageDialog msg (str,
835 Gtk::MESSAGE_WARNING,
840 msg.set_name (X_("OpenExistingDialog"));
841 msg.set_title (_("Open Existing Session"));
842 msg.set_wmclass (X_("existing_session"), PROGRAM_NAME);
843 msg.set_position (Gtk::WIN_POS_CENTER);
854 StartupFSM::show_pre_release_dialog ()
856 pre_release_dialog = new ArdourDialog (_("Pre-Release Warning"), true, false);
857 pre_release_dialog->add_button (Gtk::Stock::OK, Gtk::RESPONSE_OK);
859 Label* label = manage (new Label);
860 label->set_markup (string_compose (_("<span size=\"x-large\" weight=\"bold\">Welcome to this pre-release build of %1 %2</span>\n\n\
861 <span size=\"large\">There are still several issues and bugs to be worked on,\n\
862 as well as general workflow improvements, before this can be considered\n\
863 release software. So, a few guidelines:\n\
865 1) Please do <b>NOT</b> use this software with the expectation that it is stable or reliable\n\
866 though it may be so, depending on your workflow.\n\
867 2) Please wait for a helpful writeup of new features.\n\
868 3) <b>Please do NOT use the forums at ardour.org to report issues</b>.\n\
869 4) <b>Please do NOT file bugs for this alpha-development versions at this point in time</b>.\n\
870 There is no bug triaging before the initial development concludes and\n\
871 reporting issue for incomplete, ongoing work-in-progress is mostly useless.\n\
872 5) Please <b>DO</b> join us on IRC for real time discussions about %1 %2. You\n\
873 can get there directly from within the program via the Help->Chat menu option.\n\
874 6) Please <b>DO</b> submit patches for issues after discussing them on IRC.\n\
876 Full information on all the above can be found on the support page at\n\
878 http://ardour.org/support</span>\n\
879 "), PROGRAM_NAME, VERSIONSTRING));
882 current_dialog_connection = pre_release_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (this, &StartupFSM::dialog_response_handler), PreReleaseDialog));
884 pre_release_dialog->get_vbox()->set_border_width (12);
885 pre_release_dialog->get_vbox()->pack_start (*label, false, false, 12);
886 pre_release_dialog->get_vbox()->show_all ();
887 pre_release_dialog->set_position (WIN_POS_CENTER);
888 pre_release_dialog->present ();