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>
25 #include "pbd/basename.h"
26 #include "pbd/file_archive.h"
27 #include "pbd/file_utils.h"
30 #include "ardour/audioengine.h"
31 #include "ardour/filename_extensions.h"
32 #include "ardour/filesystem_paths.h"
33 #include "ardour/profile.h"
34 #include "ardour/recent_sessions.h"
35 #include "ardour/rc_configuration.h"
36 #include "ardour/search_paths.h"
37 #include "ardour/session.h"
38 #include "ardour/session_utils.h"
39 #include "ardour/template_utils.h"
41 #include "gtkmm2ext/application.h"
42 #include <gtkmm2ext/doi.h>
44 #include "engine_dialog.h"
45 #include "new_user_wizard.h"
47 #include "session_dialog.h"
48 #include "startup_fsm.h"
50 using namespace ARDOUR;
52 using namespace Gtkmm2ext;
58 StartupFSM::StartupFSM (EngineControl& amd)
59 : session_existing_sample_rate (0)
60 , session_is_new (false)
61 , new_user (NewUserWizard::required())
63 , _state (new_user ? NeedWizard : NeedSessionPath)
65 , audiomidi_dialog (amd)
68 Application* app = Application::instance ();
70 app->ShouldQuit.connect (sigc::mem_fun (*this, &StartupFSM::queue_finish));
71 app->ShouldLoad.connect (sigc::mem_fun (*this, &StartupFSM::load_from_application_api));
73 /* this may cause the delivery of ShouldLoad etc if we were invoked in
74 * particular ways. It will happen when the event loop runs again.
80 StartupFSM::~StartupFSM ()
82 delete session_dialog;
86 StartupFSM::queue_finish ()
88 _signal_response (ExitProgram);
95 /* show new user wizard */
96 _state = NeedSessionPath;
97 show_new_user_wizard ();
99 /* pretend we just showed the new user wizard and we're done
102 dialog_response_handler (RESPONSE_OK, NewUserDialog);
114 StartupFSM::dialog_response_handler (int response, StartupFSM::DialogID dialog_id)
116 const bool new_session_required = (ARDOUR_COMMAND_LINE::new_session || (!ARDOUR::Profile->get_mixbus() && new_user));
119 case NeedSessionPath:
123 current_dialog_connection.disconnect ();
124 delete_when_idle (new_user_wizard);
133 /* new user wizard done, now lets get session params
134 * either from the command line (if given) or a dialog
135 * (if nothing given on the command line or if the
136 * command line arguments don't work for some reason
139 if (ARDOUR_COMMAND_LINE::session_name.empty()) {
141 /* nothing given on the command line ... show new session dialog */
143 session_path = string();
144 session_name = string();
145 session_template = string();
147 _state = NeedSessionPath;
148 session_dialog = new SessionDialog (new_session_required, string(), string(), string(), false);
149 show_session_dialog ();
153 if (get_session_parameters_from_command_line (new_session_required)) {
155 /* command line arguments all OK. Get engine parameters */
157 _state = NeedEngineParams;
159 if (!new_session_required && session_existing_sample_rate > 0) {
160 audiomidi_dialog.set_desired_sample_rate (session_existing_sample_rate);
163 show_audiomidi_dialog ();
167 /* command line arguments not good. Use
168 * dialog, but prime the dialog with
169 * the information we set up in
170 * get_session_parameters_from_command_line()
173 _state = NeedSessionPath;
174 session_dialog = new SessionDialog (new_session_required, session_name, session_path, session_template, false);
175 show_session_dialog ();
180 case NewSessionDialog:
183 case RESPONSE_ACCEPT:
184 switch (check_session_parameters (new_session_required)) {
186 /* Unrecoverable error */
187 _signal_response (ExitProgram);
190 /* do nothing - keep dialog up for a
191 * retry. Errors were addressed by
192 * ::check_session_parameters()
196 _state = NeedEngineParams;
197 session_dialog->hide ();
198 delete session_dialog;
200 current_dialog_connection.disconnect();
201 if (!session_is_new && session_existing_sample_rate > 0) {
202 audiomidi_dialog.set_desired_sample_rate (session_existing_sample_rate);
204 show_audiomidi_dialog ();
210 _signal_response (ExitProgram);
221 case NeedEngineParams:
226 case RESPONSE_ACCEPT:
227 if (AudioEngine::instance()->running()) {
228 audiomidi_dialog.hide ();
229 current_dialog_connection.disconnect();
230 _signal_response (LoadSession);
232 /* just keep going */
236 _signal_response (ExitProgram);
251 StartupFSM::show_new_user_wizard ()
253 new_user_wizard = new NewUserWizard;
254 current_dialog_connection = new_user_wizard->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), NewUserDialog));
255 new_user_wizard->set_position (WIN_POS_CENTER);
256 new_user_wizard->present ();
260 StartupFSM::show_session_dialog ()
262 current_dialog_connection = session_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), NewSessionDialog));
263 session_dialog->set_position (WIN_POS_CENTER);
264 session_dialog->present ();
268 StartupFSM::show_audiomidi_dialog ()
270 current_dialog_connection = audiomidi_dialog.signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), AudioMIDISetup));
271 audiomidi_dialog.set_position (WIN_POS_CENTER);
272 audiomidi_dialog.present ();
276 StartupFSM::get_session_parameters_from_command_line (bool new_session_required)
278 return get_session_parameters_from_path (ARDOUR_COMMAND_LINE::session_name, ARDOUR_COMMAND_LINE::load_template, new_session_required);
282 StartupFSM::get_session_parameters_from_path (string const & path, string const & template_name, bool new_session_required)
285 /* use GUI to ask the user */
289 if (Glib::file_test (path.c_str(), Glib::FILE_TEST_EXISTS)) {
291 session_is_new = false;
293 if (new_session_required) {
294 /* wait! it already exists */
296 if (!ask_about_loading_existing_session (path)) {
303 session_name = basename_nosuffix (path);
305 if (Glib::file_test (path.c_str(), Glib::FILE_TEST_IS_REGULAR)) {
306 /* session/snapshot file, change path to be dir */
307 session_path = Glib::path_get_dirname (path);
314 string program_version;
316 const string statefile_path = Glib::build_filename (session_path, session_name + ARDOUR::statefile_suffix);
317 if (Session::get_info_from_path (statefile_path, sr, fmt, program_version)) {
318 /* exists but we can't read it correctly */
319 error << string_compose (_("Cannot get existing session information from %1"), statefile_path) << endmsg;
323 session_existing_sample_rate = sr;
328 /* Everything after this involves a new session
330 * ... did the user give us a path or just a name?
333 if (path.find (G_DIR_SEPARATOR) == string::npos) {
334 /* user gave session name with no path info, use
335 default session folder.
337 session_name = ARDOUR_COMMAND_LINE::session_name;
338 session_path = Glib::build_filename (Config->get_default_session_parent_dir (), session_name);
340 session_name = basename_nosuffix (path);
345 if (!template_name.empty()) {
347 /* Allow the user to specify a template via path or name on the
351 bool have_resolved_template_name = false;
353 /* compare by name (path may or may not be UTF-8) */
355 vector<TemplateInfo> templates;
356 find_session_templates (templates, false);
357 for (vector<TemplateInfo>::iterator x = templates.begin(); x != templates.end(); ++x) {
358 if ((*x).name == template_name) {
359 session_template = (*x).path;
360 have_resolved_template_name = true;
365 /* look up script by name */
366 LuaScriptList scripts (LuaScripting::instance ().scripts (LuaScriptInfo::SessionInit));
367 LuaScriptList& as (LuaScripting::instance ().scripts (LuaScriptInfo::EditorAction));
368 for (LuaScriptList::const_iterator s = as.begin(); s != as.end(); ++s) {
369 if ((*s)->subtype & LuaScriptInfo::SessionSetup) {
370 scripts.push_back (*s);
373 std::sort (scripts.begin(), scripts.end(), LuaScripting::Sorter());
374 for (LuaScriptList::const_iterator s = scripts.begin(); s != scripts.end(); ++s) {
375 if ((*s)->name == template_name) {
376 session_template = "urn:ardour:" + (*s)->path;
377 have_resolved_template_name = true;
382 if (!have_resolved_template_name) {
383 /* this will produce a more or less meaninful error later:
384 * "ERROR: Could not open session template [abs-path to user-config dir]"
386 session_template = Glib::build_filename (ARDOUR::user_template_directory (), template_name);
390 /* We don't know what this is, because the session is new and the
391 * command line doesn't let us specify it. The user will get to decide
392 * in the audio/MIDI dialog.
395 session_existing_sample_rate = 0;
396 session_is_new = true;
398 /* this is an arbitrary default value but since the user insists on
399 * starting a new session from the command line, it will do as well as
400 * any other possible value. I mean, seriously, what else could it be
404 bus_profile.master_out_channels = 2;
411 * 1: failure but user can retry
412 * 0: success, seesion parameters ready for use
415 StartupFSM::check_session_parameters (bool must_be_new)
417 bool requested_new = false;
419 session_name = session_dialog->session_name (requested_new);
420 session_path = session_dialog->session_folder ();
423 assert (requested_new);
428 /* See if the specified session is a session archive */
430 int rv = ARDOUR::inflate_session (session_name, Config->get_default_session_parent_dir(), session_path, session_name);
432 MessageDialog msg (*session_dialog, string_compose (_("Extracting session-archive failed: %1"), inflate_error (rv)));
436 } else if (rv == 0) {
437 /* names are good (and session is unarchived/inflated */
442 /* check for ".ardour" in statefile name, because we don't want
445 * XXX Note this wierd conflation of a
446 * file-name-without-a-suffix and the session name. It's not
447 * really a session name at all, but rather the suffix-free
448 * name of a statefile (snapshot).
451 const string::size_type suffix_at = session_name.find (statefile_suffix);
453 if (suffix_at != string::npos) {
454 session_name = session_name.substr (0, suffix_at);
457 /* this shouldn't happen, but we catch it just in case it does */
459 if (session_name.empty()) {
460 return 1; /* keep running dialog */
463 if (session_dialog->use_session_template()) {
464 session_template = session_dialog->session_template_name();
467 if (session_name[0] == G_DIR_SEPARATOR ||
468 #ifdef PLATFORM_WINDOWS
469 // Windows file system .. detect absolute path
471 (session_name.length() > 3 && session_name[1] == ':' && session_name[2] == G_DIR_SEPARATOR)
473 // Sensible file systems
475 (session_name.length() > 2 && session_name[0] == '.' && session_name[1] == G_DIR_SEPARATOR) ||
476 (session_name.length() > 3 && session_name[0] == '.' && session_name[1] == '.' && session_name[2] == G_DIR_SEPARATOR)
481 /* user typed absolute path or cwd-relative path
482 specified into session name field. So ... infer
483 session path and name from what was given.
486 session_path = Glib::path_get_dirname (session_name);
487 session_name = Glib::path_get_basename (session_name);
491 /* session name is just a name */
494 /* check if name is legal */
496 const char illegal = Session::session_name_is_legal (session_name);
499 MessageDialog msg (*session_dialog,
500 string_compose (_("To ensure compatibility with various systems\n"
501 "session names may not contain a '%1' character"),
504 ARDOUR_COMMAND_LINE::session_name = ""; // cancel that
505 return 1; /* keep running dialog */
509 /* check if the currently-exists status matches whether or not
513 if (Glib::file_test (session_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
515 if (requested_new /*&& !nsm*/) {
517 std::string existing = Glib::build_filename (session_path, session_name);
519 if (!ask_about_loading_existing_session (existing)) {
520 session_dialog->clear_name ();
521 return 1; /* try again */
525 session_is_new = false;
529 /* does not exist at present */
531 if (!requested_new) {
532 // pop_back_splash (session_dialog);
533 MessageDialog msg (string_compose (_("There is no existing session at \"%1\""), session_path));
535 session_dialog->clear_name();
539 session_is_new = true;
544 string program_version;
545 const string statefile_path = Glib::build_filename (session_path, session_name + ARDOUR::statefile_suffix);
547 if (!session_is_new) {
549 if (Session::get_info_from_path (statefile_path, sr, fmt, program_version)) {
550 /* exists but we can't read it */
554 session_existing_sample_rate = sr;
558 bus_profile.master_out_channels = session_dialog->master_channel_count ();
565 StartupFSM::copy_demo_sessions ()
567 // TODO: maybe IFF brand_new_user
568 if (ARDOUR::Profile->get_mixbus () && Config->get_copy_demo_sessions ()) {
569 std::string dspd (Config->get_default_session_parent_dir());
570 Searchpath ds (ARDOUR::ardour_data_search_path());
571 ds.add_subdirectory_to_paths ("sessions");
572 vector<string> demos;
573 find_files_matching_pattern (demos, ds, ARDOUR::session_archive_suffix);
575 ARDOUR::RecentSessions rs;
576 ARDOUR::read_recent_sessions (rs);
578 for (vector<string>::iterator i = demos.begin(); i != demos.end (); ++i) {
579 /* "demo-session" must be inside "demo-session.<session_archive_suffix>" */
580 std::string name = basename_nosuffix (basename_nosuffix (*i));
581 std::string path = Glib::build_filename (dspd, name);
582 /* skip if session-dir already exists */
583 if (Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_DIR)) {
586 /* skip sessions that are already in 'recent'.
587 * eg. a new user changed <session-default-dir> shorly after installation
589 for (ARDOUR::RecentSessions::iterator r = rs.begin(); r != rs.end(); ++r) {
590 if ((*r).first == name) {
595 PBD::FileArchive ar (*i);
596 if (0 == ar.inflate (dspd)) {
597 store_recent_sessions (name, path);
598 info << string_compose (_("Copied Demo Session %1."), name) << endmsg;
608 StartupFSM::load_from_application_api (const std::string& path)
610 /* macOS El Capitan (and probably later) now somehow passes the command
611 line arguments to an app via the openFile delegate protocol. Ardour
612 already does its own command line processing, and having both
613 pathways active causes crashes. So, if the command line was already
614 set, do nothing here.
617 if (!ARDOUR_COMMAND_LINE::session_name.empty()) {
621 /* Cancel SessionDialog if it's visible to make macOS delegates work.
623 * there's a race condition here: we connect to app->ShouldLoad
624 * and then at some point (might) show a session dialog. The race is
625 * caused by the non-deterministic interaction between the macOS event
626 * loop(s) and the GDK one(s).
628 * - ShouldLoad does not arrive before we show the session dialog
629 * -> here we should hide the session dialog, then use the
630 * supplied path as if it was provided on the command line
631 * - ShouldLoad signal arrives before we show a session dialog
632 * -> don't bother showing the session dialog, just use the
633 * supplied path as if it was provided on the command line
637 if (session_dialog) {
638 session_dialog->hide ();
639 delete_when_idle (session_dialog);
643 /* no command line argument given ... must just be via
644 * desktop/finder/window manager API (e.g. double click on "foo.ardour"
648 if (get_session_parameters_from_path (path, string(), false)) {
649 _signal_response (LoadSession);
653 /* given parameters failed for some reason. This is probably true
654 * anyway, but force it to be true and then carry on with whatever the
655 * main event loop is doing.
658 _state = NeedSessionPath;
662 StartupFSM::ask_about_loading_existing_session (const std::string& session_path)
664 std::string str = string_compose (_("This session\n%1\nalready exists. Do you want to open it?"), session_path);
666 MessageDialog msg (str,
668 Gtk::MESSAGE_WARNING,
673 msg.set_name (X_("OpenExistingDialog"));
674 msg.set_title (_("Open Existing Session"));
675 msg.set_wmclass (X_("existing_session"), PROGRAM_NAME);
676 msg.set_position (Gtk::WIN_POS_CENTER);
677 // pop_back_splash (msg);