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"
46 #include "engine_dialog.h"
47 #include "new_user_wizard.h"
49 #include "session_dialog.h"
50 #include "startup_fsm.h"
53 #include "gtk2ardour-version.h"
56 using namespace ARDOUR;
58 using namespace Gtkmm2ext;
64 StartupFSM::StartupFSM (EngineControl& amd)
65 : session_existing_sample_rate (0)
66 , session_is_new (false)
67 , new_user (NewUserWizard::required())
69 , _state (new_user ? NeedWizard : NeedSessionPath)
71 , audiomidi_dialog (amd)
73 , pre_release_dialog (0)
75 if (string (VERSIONSTRING).find (".pre") != string::npos) {
76 string fn = Glib::build_filename (user_config_directory(), ".i_swear_that_i_will_heed_the_guidelines_stated_in_the_pre_release_dialog");
77 if (!Glib::file_test (fn, Glib::FILE_TEST_EXISTS)) {
78 _state = NeedPreRelease;
82 Application* app = Application::instance ();
84 app->ShouldQuit.connect (sigc::mem_fun (*this, &StartupFSM::queue_finish));
85 app->ShouldLoad.connect (sigc::mem_fun (*this, &StartupFSM::load_from_application_api));
87 /* this may cause the delivery of ShouldLoad etc if we were invoked in
88 * particular ways. It will happen when the event loop runs again.
94 StartupFSM::~StartupFSM ()
96 delete session_dialog;
100 StartupFSM::queue_finish ()
102 _signal_response (ExitProgram);
108 if (_state == NeedPreRelease) {
109 show_pre_release_dialog ();
110 } else if (new_user) {
111 show_new_user_wizard ();
113 /* pretend we just showed the new user wizard and we're done
116 dialog_response_handler (RESPONSE_OK, NewUserDialog);
128 StartupFSM::dialog_response_handler (int response, StartupFSM::DialogID dialog_id)
130 const bool new_session_required = (ARDOUR_COMMAND_LINE::new_session || (!ARDOUR::Profile->get_mixbus() && new_user));
137 case PreReleaseDialog:
139 /* any response value from the pre-release dialog means
142 delete_when_idle (pre_release_dialog);
143 pre_release_dialog = 0;
144 _state = NeedSessionPath;
145 if (NewUserWizard::required()) {
146 show_new_user_wizard ();
148 /* act as if we had just finished with the new
149 user wizard. goto preferred over reentrancy.
151 dialog_id = NewUserDialog;
152 response = RESPONSE_OK;
159 case NeedSessionPath:
163 current_dialog_connection.disconnect ();
164 delete_when_idle (new_user_wizard);
173 /* new user wizard done, now lets get session params
174 * either from the command line (if given) or a dialog
175 * (if nothing given on the command line or if the
176 * command line arguments don't work for some reason
179 if (ARDOUR_COMMAND_LINE::session_name.empty()) {
181 /* nothing given on the command line ... show new session dialog */
183 session_path = string();
184 session_name = string();
185 session_template = string();
187 _state = NeedSessionPath;
188 show_session_dialog (new_session_required);
192 if (get_session_parameters_from_command_line (new_session_required)) {
194 /* command line arguments all OK. Get engine parameters */
196 _state = NeedEngineParams;
198 if (!new_session_required && session_existing_sample_rate > 0) {
199 audiomidi_dialog.set_desired_sample_rate (session_existing_sample_rate);
202 start_audio_midi_setup ();
206 /* command line arguments not good. Use
207 * dialog, but prime the dialog with
208 * the information we set up in
209 * get_session_parameters_from_command_line()
212 _state = NeedSessionPath;
213 show_session_dialog (new_session_required);
218 case NewSessionDialog:
221 case RESPONSE_ACCEPT:
222 switch (check_session_parameters (new_session_required)) {
224 /* Unrecoverable error */
225 _signal_response (ExitProgram);
228 /* do nothing - keep dialog up for a
229 * retry. Errors were addressed by
230 * ::check_session_parameters()
234 start_audio_midi_setup ();
240 _signal_response (ExitProgram);
251 case NeedEngineParams:
256 case RESPONSE_ACCEPT:
257 if (AudioEngine::instance()->running()) {
258 audiomidi_dialog.hide ();
259 current_dialog_connection.disconnect();
260 _signal_response (LoadSession);
262 /* just keep going */
266 _signal_response (ExitProgram);
276 show_new_user_wizard ();
277 _state = NeedSessionPath;
283 StartupFSM::show_new_user_wizard ()
285 new_user_wizard = new NewUserWizard;
286 current_dialog_connection = new_user_wizard->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), NewUserDialog));
287 new_user_wizard->set_position (WIN_POS_CENTER);
288 new_user_wizard->present ();
292 StartupFSM::show_session_dialog (bool new_session_required)
294 session_dialog = new SessionDialog (new_session_required, session_name, session_path, session_template, false);
295 current_dialog_connection = session_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), NewSessionDialog));
296 session_dialog->set_position (WIN_POS_CENTER);
297 session_dialog->present ();
301 StartupFSM::show_audiomidi_dialog ()
303 current_dialog_connection = audiomidi_dialog.signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), AudioMIDISetup));
304 audiomidi_dialog.set_position (WIN_POS_CENTER);
305 audiomidi_dialog.present ();
309 StartupFSM::start_audio_midi_setup ()
311 bool setup_required = false;
313 if (AudioEngine::instance()->current_backend() == 0) {
314 /* backend is unknown ... */
315 setup_required = true;
317 } else if (session_is_new && AudioEngine::instance()->running() && AudioEngine::instance()->sample_rate () == session_existing_sample_rate) {
320 warning << "A running engine should not be possible at this point" << endmsg;
322 } else if (AudioEngine::instance()->setup_required()) {
323 /* backend is known, but setup is needed */
324 setup_required = true;
326 } else if (!AudioEngine::instance()->running()) {
327 /* should always be true during startup */
328 if (AudioEngine::instance()->start()) {
329 setup_required = true;
333 if (setup_required) {
334 _state = NeedEngineParams;
335 if (session_dialog) {
336 session_dialog->hide ();
337 delete_when_idle (session_dialog);
340 current_dialog_connection.disconnect();
341 if (!session_is_new && session_existing_sample_rate > 0) {
342 audiomidi_dialog.set_desired_sample_rate (session_existing_sample_rate);
344 show_audiomidi_dialog ();
346 /* XXX should we reset _state to something meaningul here (e.g. "Done")? */
348 if (session_dialog) {
349 session_dialog->hide ();
350 delete_when_idle (session_dialog);
354 current_dialog_connection.disconnect ();
355 _signal_response (LoadSession);
360 StartupFSM::get_session_parameters_from_command_line (bool new_session_required)
362 return get_session_parameters_from_path (ARDOUR_COMMAND_LINE::session_name, ARDOUR_COMMAND_LINE::load_template, new_session_required);
366 StartupFSM::get_session_parameters_from_path (string const & path, string const & template_name, bool new_session_required)
369 /* use GUI to ask the user */
373 if (Glib::file_test (path.c_str(), Glib::FILE_TEST_EXISTS)) {
375 session_is_new = false;
377 if (new_session_required) {
378 /* wait! it already exists */
380 if (!ask_about_loading_existing_session (path)) {
387 session_name = basename_nosuffix (path);
389 if (Glib::file_test (path.c_str(), Glib::FILE_TEST_IS_REGULAR)) {
390 /* session/snapshot file, change path to be dir */
391 session_path = Glib::path_get_dirname (path);
398 string program_version;
400 const string statefile_path = Glib::build_filename (session_path, session_name + ARDOUR::statefile_suffix);
401 if (Session::get_info_from_path (statefile_path, sr, fmt, program_version)) {
402 /* exists but we can't read it correctly */
403 error << string_compose (_("Cannot get existing session information from %1"), statefile_path) << endmsg;
407 session_existing_sample_rate = sr;
412 /* Everything after this involves a new session
414 * ... did the user give us a path or just a name?
417 if (path.find (G_DIR_SEPARATOR) == string::npos) {
418 /* user gave session name with no path info, use
419 default session folder.
421 session_name = ARDOUR_COMMAND_LINE::session_name;
422 session_path = Glib::build_filename (Config->get_default_session_parent_dir (), session_name);
424 session_name = basename_nosuffix (path);
429 if (!template_name.empty()) {
431 /* Allow the user to specify a template via path or name on the
435 bool have_resolved_template_name = false;
437 /* compare by name (path may or may not be UTF-8) */
439 vector<TemplateInfo> templates;
440 find_session_templates (templates, false);
441 for (vector<TemplateInfo>::iterator x = templates.begin(); x != templates.end(); ++x) {
442 if ((*x).name == template_name) {
443 session_template = (*x).path;
444 have_resolved_template_name = true;
449 /* look up script by name */
450 LuaScriptList scripts (LuaScripting::instance ().scripts (LuaScriptInfo::SessionInit));
451 LuaScriptList& as (LuaScripting::instance ().scripts (LuaScriptInfo::EditorAction));
452 for (LuaScriptList::const_iterator s = as.begin(); s != as.end(); ++s) {
453 if ((*s)->subtype & LuaScriptInfo::SessionSetup) {
454 scripts.push_back (*s);
457 std::sort (scripts.begin(), scripts.end(), LuaScripting::Sorter());
458 for (LuaScriptList::const_iterator s = scripts.begin(); s != scripts.end(); ++s) {
459 if ((*s)->name == template_name) {
460 session_template = "urn:ardour:" + (*s)->path;
461 have_resolved_template_name = true;
466 if (!have_resolved_template_name) {
467 /* this will produce a more or less meaninful error later:
468 * "ERROR: Could not open session template [abs-path to user-config dir]"
470 session_template = Glib::build_filename (ARDOUR::user_template_directory (), template_name);
474 /* We don't know what this is, because the session is new and the
475 * command line doesn't let us specify it. The user will get to decide
476 * in the audio/MIDI dialog.
479 session_existing_sample_rate = 0;
480 session_is_new = true;
482 /* this is an arbitrary default value but since the user insists on
483 * starting a new session from the command line, it will do as well as
484 * any other possible value. I mean, seriously, what else could it be
488 bus_profile.master_out_channels = 2;
495 * 1: failure but user can retry
496 * 0: success, seesion parameters ready for use
499 StartupFSM::check_session_parameters (bool must_be_new)
501 bool requested_new = false;
503 session_name = session_dialog->session_name (requested_new);
504 session_path = session_dialog->session_folder ();
507 assert (requested_new);
512 /* See if the specified session is a session archive */
514 int rv = ARDOUR::inflate_session (session_name, Config->get_default_session_parent_dir(), session_path, session_name);
516 MessageDialog msg (*session_dialog, string_compose (_("Extracting session-archive failed: %1"), inflate_error (rv)));
520 } else if (rv == 0) {
521 /* names are good (and session is unarchived/inflated */
526 /* check for ".ardour" in statefile name, because we don't want
529 * XXX Note this wierd conflation of a
530 * file-name-without-a-suffix and the session name. It's not
531 * really a session name at all, but rather the suffix-free
532 * name of a statefile (snapshot).
535 const string::size_type suffix_at = session_name.find (statefile_suffix);
537 if (suffix_at != string::npos) {
538 session_name = session_name.substr (0, suffix_at);
541 /* this shouldn't happen, but we catch it just in case it does */
543 if (session_name.empty()) {
544 return 1; /* keep running dialog */
547 if (session_dialog->use_session_template()) {
548 session_template = session_dialog->session_template_name();
551 if (session_name[0] == G_DIR_SEPARATOR ||
552 #ifdef PLATFORM_WINDOWS
553 // Windows file system .. detect absolute path
555 (session_name.length() > 3 && session_name[1] == ':' && session_name[2] == G_DIR_SEPARATOR)
557 // Sensible file systems
559 (session_name.length() > 2 && session_name[0] == '.' && session_name[1] == G_DIR_SEPARATOR) ||
560 (session_name.length() > 3 && session_name[0] == '.' && session_name[1] == '.' && session_name[2] == G_DIR_SEPARATOR)
565 /* user typed absolute path or cwd-relative path
566 specified into session name field. So ... infer
567 session path and name from what was given.
570 session_path = Glib::path_get_dirname (session_name);
571 session_name = Glib::path_get_basename (session_name);
575 /* session name is just a name */
578 /* check if name is legal */
580 const char illegal = Session::session_name_is_legal (session_name);
583 MessageDialog msg (*session_dialog,
584 string_compose (_("To ensure compatibility with various systems\n"
585 "session names may not contain a '%1' character"),
588 ARDOUR_COMMAND_LINE::session_name = ""; // cancel that
589 return 1; /* keep running dialog */
593 /* check if the currently-exists status matches whether or not
597 if (Glib::file_test (session_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
599 if (requested_new /*&& !nsm*/) {
601 std::string existing = Glib::build_filename (session_path, session_name);
603 if (!ask_about_loading_existing_session (existing)) {
604 session_dialog->clear_name ();
605 return 1; /* try again */
609 session_is_new = false;
613 /* does not exist at present */
615 if (!requested_new) {
616 ARDOUR_UI::pop_back_splash (*session_dialog);
617 MessageDialog msg (string_compose (_("There is no existing session at \"%1\""), session_path));
619 session_dialog->clear_name();
623 session_is_new = true;
628 string program_version;
629 const string statefile_path = Glib::build_filename (session_path, session_name + ARDOUR::statefile_suffix);
631 if (!session_is_new) {
633 if (Session::get_info_from_path (statefile_path, sr, fmt, program_version)) {
634 /* exists but we can't read it */
638 session_existing_sample_rate = sr;
642 bus_profile.master_out_channels = session_dialog->master_channel_count ();
649 StartupFSM::copy_demo_sessions ()
651 // TODO: maybe IFF brand_new_user
652 if (ARDOUR::Profile->get_mixbus () && Config->get_copy_demo_sessions ()) {
653 std::string dspd (Config->get_default_session_parent_dir());
654 Searchpath ds (ARDOUR::ardour_data_search_path());
655 ds.add_subdirectory_to_paths ("sessions");
656 vector<string> demos;
657 find_files_matching_pattern (demos, ds, ARDOUR::session_archive_suffix);
659 ARDOUR::RecentSessions rs;
660 ARDOUR::read_recent_sessions (rs);
662 for (vector<string>::iterator i = demos.begin(); i != demos.end (); ++i) {
663 /* "demo-session" must be inside "demo-session.<session_archive_suffix>" */
664 std::string name = basename_nosuffix (basename_nosuffix (*i));
665 std::string path = Glib::build_filename (dspd, name);
666 /* skip if session-dir already exists */
667 if (Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_DIR)) {
670 /* skip sessions that are already in 'recent'.
671 * eg. a new user changed <session-default-dir> shorly after installation
673 for (ARDOUR::RecentSessions::iterator r = rs.begin(); r != rs.end(); ++r) {
674 if ((*r).first == name) {
679 PBD::FileArchive ar (*i);
680 if (0 == ar.inflate (dspd)) {
681 store_recent_sessions (name, path);
682 info << string_compose (_("Copied Demo Session %1."), name) << endmsg;
692 StartupFSM::load_from_application_api (const std::string& path)
694 /* macOS El Capitan (and probably later) now somehow passes the command
695 line arguments to an app via the openFile delegate protocol. Ardour
696 already does its own command line processing, and having both
697 pathways active causes crashes. So, if the command line was already
698 set, do nothing here.
701 if (!ARDOUR_COMMAND_LINE::session_name.empty()) {
705 /* Cancel SessionDialog if it's visible to make macOS delegates work.
707 * there's a race condition here: we connect to app->ShouldLoad
708 * and then at some point (might) show a session dialog. The race is
709 * caused by the non-deterministic interaction between the macOS event
710 * loop(s) and the GDK one(s).
712 * - ShouldLoad does not arrive before we show the session dialog
713 * -> here we should hide the session dialog, then use the
714 * supplied path as if it was provided on the command line
715 * - ShouldLoad signal arrives before we show a session dialog
716 * -> don't bother showing the session dialog, just use the
717 * supplied path as if it was provided on the command line
721 if (session_dialog) {
722 session_dialog->hide ();
723 delete_when_idle (session_dialog);
727 /* no command line argument given ... must just be via
728 * desktop/finder/window manager API (e.g. double click on "foo.ardour"
732 if (get_session_parameters_from_path (path, string(), false)) {
733 _signal_response (LoadSession);
737 /* given parameters failed for some reason. This is probably true
738 * anyway, but force it to be true and then carry on with whatever the
739 * main event loop is doing.
742 _state = NeedSessionPath;
746 StartupFSM::ask_about_loading_existing_session (const std::string& session_path)
748 std::string str = string_compose (_("This session\n%1\nalready exists. Do you want to open it?"), session_path);
750 MessageDialog msg (str,
752 Gtk::MESSAGE_WARNING,
757 msg.set_name (X_("OpenExistingDialog"));
758 msg.set_title (_("Open Existing Session"));
759 msg.set_wmclass (X_("existing_session"), PROGRAM_NAME);
760 msg.set_position (Gtk::WIN_POS_CENTER);
761 ARDOUR_UI::pop_back_splash (msg);
772 StartupFSM::show_pre_release_dialog ()
774 pre_release_dialog = new ArdourDialog (_("Pre-Release Warning"), true, false);
775 pre_release_dialog->add_button (Gtk::Stock::OK, Gtk::RESPONSE_OK);
777 Label* label = manage (new Label);
778 label->set_markup (string_compose (_("<span size=\"x-large\" weight=\"bold\">Welcome to this pre-release build of %1 %2</span>\n\n\
779 <span size=\"large\">There are still several issues and bugs to be worked on,\n\
780 as well as general workflow improvements, before this can be considered\n\
781 release software. So, a few guidelines:\n\
783 1) Please do <b>NOT</b> use this software with the expectation that it is stable or reliable\n\
784 though it may be so, depending on your workflow.\n\
785 2) Please wait for a helpful writeup of new features.\n\
786 3) <b>Please do NOT use the forums at ardour.org to report issues</b>.\n\
787 4) <b>Please do NOT file bugs for this alpha-development versions at this point in time</b>.\n\
788 There is no bug triaging before the initial development concludes and\n\
789 reporting issue for incomplete, ongoing work-in-progress is mostly useless.\n\
790 5) Please <b>DO</b> join us on IRC for real time discussions about %1 %2. You\n\
791 can get there directly from within the program via the Help->Chat menu option.\n\
792 6) Please <b>DO</b> submit patches for issues after discussing them on IRC.\n\
794 Full information on all the above can be found on the support page at\n\
796 http://ardour.org/support</span>\n\
797 "), PROGRAM_NAME, VERSIONSTRING));
800 pre_release_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (this, &StartupFSM::dialog_response_handler), PreReleaseDialog));
802 pre_release_dialog->get_vbox()->set_border_width (12);
803 pre_release_dialog->get_vbox()->pack_start (*label, false, false, 12);
804 pre_release_dialog->get_vbox()->show_all ();
805 pre_release_dialog->set_position (WIN_POS_CENTER);
806 pre_release_dialog->present ();