fix up handling of audio/MIDI setup dialog and remove debug output
[ardour.git] / gtk2_ardour / startup_fsm.cc
1 /*
2  * Copyright (C) 2019 Paul Davis <paul@linuxaudiosystems.com>
3  *
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.
8  *
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.
13  *
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.
17  */
18
19 #include <vector>
20
21 #include <gtkmm/dialog.h>
22 #include <gtkmm/liststore.h>
23 #include <gtkmm/messagedialog.h>
24
25 #include "pbd/basename.h"
26 #include "pbd/file_archive.h"
27 #include "pbd/file_utils.h"
28 #include "pbd/i18n.h"
29
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"
40
41 #include "gtkmm2ext/application.h"
42 #include <gtkmm2ext/doi.h>
43
44 #include "engine_dialog.h"
45 #include "new_user_wizard.h"
46 #include "opts.h"
47 #include "session_dialog.h"
48 #include "startup_fsm.h"
49
50 using namespace ARDOUR;
51 using namespace Gtk;
52 using namespace Gtkmm2ext;
53 using namespace PBD;
54
55 using std::string;
56 using std::vector;
57
58 StartupFSM::StartupFSM (EngineControl& amd)
59         : session_existing_sample_rate (0)
60         , session_is_new (false)
61         , new_user (NewUserWizard::required())
62         , new_session (true)
63         , _state (new_user ? NeedWizard : NeedSessionPath)
64         , new_user_wizard (0)
65         , audiomidi_dialog (amd)
66         , session_dialog (0)
67 {
68         Application* app = Application::instance ();
69
70         app->ShouldQuit.connect (sigc::mem_fun (*this, &StartupFSM::queue_finish));
71         app->ShouldLoad.connect (sigc::mem_fun (*this, &StartupFSM::load_from_application_api));
72
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.
75          */
76
77         app->ready ();
78 }
79
80 StartupFSM::~StartupFSM ()
81 {
82         delete session_dialog;
83 }
84
85 void
86 StartupFSM::queue_finish ()
87 {
88         _signal_response (ExitProgram);
89 }
90
91 void
92 StartupFSM::start ()
93 {
94         if (new_user) {
95                 /* show new user wizard */
96                 _state = NeedSessionPath;
97                 show_new_user_wizard ();
98         } else {
99                 /* pretend we just showed the new user wizard and we're done
100                    with it
101                 */
102                 dialog_response_handler (RESPONSE_OK, NewUserDialog);
103         }
104 }
105
106
107 void
108 StartupFSM::end()
109 {
110
111 }
112
113 void
114 StartupFSM::dialog_response_handler (int response, StartupFSM::DialogID dialog_id)
115 {
116         const bool new_session_required = (ARDOUR_COMMAND_LINE::new_session || (!ARDOUR::Profile->get_mixbus() && new_user));
117
118         switch (_state) {
119         case NeedSessionPath:
120                 switch (dialog_id) {
121                 case NewUserDialog:
122
123                         current_dialog_connection.disconnect ();
124                         delete_when_idle (new_user_wizard);
125
126                         switch (response) {
127                         case RESPONSE_OK:
128                                 break;
129                         default:
130                                 exit (1);
131                         }
132
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
137                          */
138
139                         if (ARDOUR_COMMAND_LINE::session_name.empty()) {
140
141                                 /* nothing given on the command line ... show new session dialog */
142
143                                 session_path = string();
144                                 session_name = string();
145                                 session_template = string();
146
147                                 _state = NeedSessionPath;
148                                 session_dialog = new SessionDialog (new_session_required, string(), string(), string(), false);
149                                 show_session_dialog ();
150
151                         } else {
152
153                                 if (get_session_parameters_from_command_line (new_session_required)) {
154
155                                         /* command line arguments all OK. Get engine parameters */
156
157                                         _state = NeedEngineParams;
158
159                                         if (!new_session_required && session_existing_sample_rate > 0) {
160                                                 audiomidi_dialog.set_desired_sample_rate (session_existing_sample_rate);
161                                         }
162
163                                         show_audiomidi_dialog ();
164
165                                 } else {
166
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()
171                                          */
172
173                                         _state = NeedSessionPath;
174                                         session_dialog = new SessionDialog (new_session_required, session_name, session_path, session_template, false);
175                                         show_session_dialog ();
176                                 }
177                         }
178                         break;
179
180                 case NewSessionDialog:
181                         switch (response) {
182                         case RESPONSE_OK:
183                         case RESPONSE_ACCEPT:
184                                 switch (check_session_parameters (new_session_required)) {
185                                 case -1:
186                                         /* Unrecoverable error */
187                                         _signal_response (ExitProgram);
188                                         break;
189                                 case 1:
190                                         /* do nothing - keep dialog up for a
191                                          * retry. Errors were addressed by
192                                          * ::check_session_parameters()
193                                          */
194                                         break;
195                                 case 0:
196                                         _state = NeedEngineParams;
197                                         session_dialog->hide ();
198                                         delete session_dialog;
199                                         session_dialog = 0;
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);
203                                         }
204                                         show_audiomidi_dialog ();
205                                         break;
206                                 }
207                                 break;
208
209                         default:
210                                 _signal_response (ExitProgram);
211                                 break;
212                         }
213                         break;
214
215                 default:
216                         /* ERROR */
217                         break;
218                 }
219                 break;
220
221         case NeedEngineParams:
222                 switch (dialog_id) {
223                 case AudioMIDISetup:
224                         switch (response) {
225                         case RESPONSE_OK:
226                         case RESPONSE_ACCEPT:
227                                 if (AudioEngine::instance()->running()) {
228                                         audiomidi_dialog.hide ();
229                                         current_dialog_connection.disconnect();
230                                         _signal_response (LoadSession);
231                                 } else {
232                                         /* just keep going */
233                                 }
234                                 break;
235                         default:
236                                 _signal_response (ExitProgram);
237                         }
238                         break;
239                 default:
240                         /* ERROR */
241                         break;
242                 }
243
244         case NeedWizard:
245                 /* ERROR */
246                 break;
247         }
248 }
249
250 void
251 StartupFSM::show_new_user_wizard ()
252 {
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->present ();
256 }
257
258 void
259 StartupFSM::show_session_dialog ()
260 {
261         current_dialog_connection = session_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), NewSessionDialog));
262         session_dialog->present ();
263 }
264
265 void
266 StartupFSM::show_audiomidi_dialog ()
267 {
268         current_dialog_connection = audiomidi_dialog.signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), AudioMIDISetup));
269         audiomidi_dialog.present ();
270 }
271
272 bool
273 StartupFSM::get_session_parameters_from_command_line (bool new_session_required)
274 {
275         return get_session_parameters_from_path (ARDOUR_COMMAND_LINE::session_name, ARDOUR_COMMAND_LINE::load_template, new_session_required);
276 }
277
278 bool
279 StartupFSM::get_session_parameters_from_path (string const & path, string const & template_name, bool new_session_required)
280 {
281         if (path.empty()) {
282                 /* use GUI to ask the user */
283                 return false;
284         }
285
286         if (Glib::file_test (path.c_str(), Glib::FILE_TEST_EXISTS)) {
287
288                 session_is_new = false;
289
290                 if (new_session_required) {
291                         /* wait! it already exists */
292
293                         if (!ask_about_loading_existing_session (path)) {
294                                 return false;
295                         } else {
296                                 /* load it anyway */
297                         }
298                 }
299
300                 session_name = basename_nosuffix (path);
301
302                 if (Glib::file_test (path.c_str(), Glib::FILE_TEST_IS_REGULAR)) {
303                         /* session/snapshot file, change path to be dir */
304                         session_path = Glib::path_get_dirname (path);
305                 } else {
306                         session_path = path;
307                 }
308
309                 float sr;
310                 SampleFormat fmt;
311                 string program_version;
312
313                 const string statefile_path = Glib::build_filename (session_path, session_name + ARDOUR::statefile_suffix);
314                 if (Session::get_info_from_path (statefile_path, sr, fmt, program_version)) {
315                         /* exists but we can't read it correctly */
316                         error << string_compose (_("Cannot get existing session information from %1"), statefile_path) << endmsg;
317                         return false;
318                 }
319
320                 session_existing_sample_rate = sr;
321                 return true;
322
323         }
324
325         /*  Everything after this involves a new session
326          *
327          *  ... did the  user give us a path or just a name?
328          */
329
330         if (path.find (G_DIR_SEPARATOR) == string::npos) {
331                 /* user gave session name with no path info, use
332                    default session folder.
333                 */
334                 session_name = ARDOUR_COMMAND_LINE::session_name;
335                 session_path = Glib::build_filename (Config->get_default_session_parent_dir (), session_name);
336         } else {
337                 session_name = basename_nosuffix (path);
338                 session_path = path;
339         }
340
341
342         if (!template_name.empty()) {
343
344                 /* Allow the user to specify a template via path or name on the
345                  * command line
346                  */
347
348                 bool have_resolved_template_name = false;
349
350                 /* compare by name (path may or may not be UTF-8) */
351
352                 vector<TemplateInfo> templates;
353                 find_session_templates (templates, false);
354                 for (vector<TemplateInfo>::iterator x = templates.begin(); x != templates.end(); ++x) {
355                         if ((*x).name == template_name) {
356                                 session_template = (*x).path;
357                                 have_resolved_template_name = true;
358                                 break;
359                         }
360                 }
361
362                 /* look up script by name */
363                 LuaScriptList scripts (LuaScripting::instance ().scripts (LuaScriptInfo::SessionInit));
364                 LuaScriptList& as (LuaScripting::instance ().scripts (LuaScriptInfo::EditorAction));
365                 for (LuaScriptList::const_iterator s = as.begin(); s != as.end(); ++s) {
366                         if ((*s)->subtype & LuaScriptInfo::SessionSetup) {
367                                 scripts.push_back (*s);
368                         }
369                 }
370                 std::sort (scripts.begin(), scripts.end(), LuaScripting::Sorter());
371                 for (LuaScriptList::const_iterator s = scripts.begin(); s != scripts.end(); ++s) {
372                         if ((*s)->name == template_name) {
373                                 session_template = "urn:ardour:" + (*s)->path;
374                                 have_resolved_template_name = true;
375                                 break;
376                         }
377                 }
378
379                 if (!have_resolved_template_name) {
380                         /* this will produce a more or less meaninful error later:
381                          * "ERROR: Could not open session template [abs-path to user-config dir]"
382                          */
383                         session_template = Glib::build_filename (ARDOUR::user_template_directory (), template_name);
384                 }
385         }
386
387         /* We don't know what this is, because the session is new and the
388          * command line doesn't let us specify it. The user will get to decide
389          * in the audio/MIDI dialog.
390          */
391
392         session_existing_sample_rate = 0;
393         session_is_new = true;
394
395         /* this is an arbitrary default value but since the user insists on
396          * starting a new session from the command line, it will do as well as
397          * any other possible value. I mean, seriously, what else could it be
398          * by default?
399          */
400
401         bus_profile.master_out_channels = 2;
402
403         return true;
404 }
405
406 /** return values:
407  * -1: failure
408  *  1: failure but user can retry
409  *  0: success, seesion parameters ready for use
410  */
411 int
412 StartupFSM::check_session_parameters (bool must_be_new)
413 {
414         bool requested_new = false;
415
416         session_name = session_dialog->session_name (requested_new);
417         session_path = session_dialog->session_folder ();
418
419         if (must_be_new) {
420                 assert (requested_new);
421         }
422
423         if (!must_be_new) {
424
425                 /* See if the specified session is a session archive */
426
427                 int rv = ARDOUR::inflate_session (session_name, Config->get_default_session_parent_dir(), session_path, session_name);
428                 if (rv < 0) {
429                         MessageDialog msg (*session_dialog, string_compose (_("Extracting session-archive failed: %1"), inflate_error (rv)));
430                         msg.run ();
431
432                         return 1;
433                 } else if (rv == 0) {
434                         /* names are good (and session is unarchived/inflated */
435                         return 0;
436                 }
437         }
438
439         /* check for ".ardour" in statefile name, because we don't want
440          * it
441          *
442          * XXX Note this wierd conflation of a
443          * file-name-without-a-suffix and the session name. It's not
444          * really a session name at all, but rather the suffix-free
445          * name of a statefile (snapshot).
446          */
447
448         const string::size_type suffix_at = session_name.find (statefile_suffix);
449
450         if (suffix_at != string::npos) {
451                 session_name = session_name.substr (0, suffix_at);
452         }
453
454         /* this shouldn't happen, but we catch it just in case it does */
455
456         if (session_name.empty()) {
457                 return 1; /* keep running dialog */
458         }
459
460         if (session_dialog->use_session_template()) {
461                 session_template = session_dialog->session_template_name();
462         }
463
464         if (session_name[0] == G_DIR_SEPARATOR ||
465 #ifdef PLATFORM_WINDOWS
466             // Windows file system .. detect absolute path
467             // C:/*
468             (session_name.length() > 3 && session_name[1] == ':' && session_name[2] == G_DIR_SEPARATOR)
469 #else
470             // Sensible file systems
471             // /* or ./* or ../*
472             (session_name.length() > 2 && session_name[0] == '.' && session_name[1] == G_DIR_SEPARATOR) ||
473             (session_name.length() > 3 && session_name[0] == '.' && session_name[1] == '.' && session_name[2] == G_DIR_SEPARATOR)
474 #endif
475                 )
476         {
477
478                 /* user typed absolute path or cwd-relative path
479                    specified into session name field. So ... infer
480                    session path and name from what was given.
481                 */
482
483                 session_path = Glib::path_get_dirname (session_name);
484                 session_name = Glib::path_get_basename (session_name);
485
486         } else {
487
488                 /* session name is just a name */
489         }
490
491         /* check if name is legal */
492
493         const char illegal = Session::session_name_is_legal (session_name);
494
495         if (illegal) {
496                 MessageDialog msg (*session_dialog,
497                                    string_compose (_("To ensure compatibility with various systems\n"
498                                                      "session names may not contain a '%1' character"),
499                                                    illegal));
500                 msg.run ();
501                 ARDOUR_COMMAND_LINE::session_name = ""; // cancel that
502                 return 1; /* keep running dialog */
503         }
504
505
506         /* check if the currently-exists status matches whether or not
507          * it should be new
508          */
509
510         if (Glib::file_test (session_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
511
512                 if (requested_new /*&& !nsm*/) {
513
514                         std::string existing = Glib::build_filename (session_path, session_name);
515
516                         if (!ask_about_loading_existing_session (existing)) {
517                                 session_dialog->clear_name ();
518                                 return 1; /* try again */
519                         }
520                 }
521
522                 session_is_new = false;
523
524         } else {
525
526                 /* does not exist at present */
527
528                 if (!requested_new) {
529                         // pop_back_splash (session_dialog);
530                         MessageDialog msg (string_compose (_("There is no existing session at \"%1\""), session_path));
531                         msg.run ();
532                         session_dialog->clear_name();
533                         return 1;
534                 }
535
536                 session_is_new = true;
537         }
538
539         float sr;
540         SampleFormat fmt;
541         string program_version;
542         const string statefile_path = Glib::build_filename (session_path, session_name + ARDOUR::statefile_suffix);
543
544         if (!session_is_new) {
545
546                 if (Session::get_info_from_path (statefile_path, sr, fmt, program_version)) {
547                         /* exists but we can't read it */
548                         return -1;
549                 }
550
551                 session_existing_sample_rate = sr;
552
553         } else {
554
555                 bus_profile.master_out_channels = session_dialog->master_channel_count ();
556         }
557
558         return 0;
559 }
560
561 void
562 StartupFSM::copy_demo_sessions ()
563 {
564         // TODO: maybe IFF brand_new_user
565         if (ARDOUR::Profile->get_mixbus () && Config->get_copy_demo_sessions ()) {
566                 std::string dspd (Config->get_default_session_parent_dir());
567                 Searchpath ds (ARDOUR::ardour_data_search_path());
568                 ds.add_subdirectory_to_paths ("sessions");
569                 vector<string> demos;
570                 find_files_matching_pattern (demos, ds, ARDOUR::session_archive_suffix);
571
572                 ARDOUR::RecentSessions rs;
573                 ARDOUR::read_recent_sessions (rs);
574
575                 for (vector<string>::iterator i = demos.begin(); i != demos.end (); ++i) {
576                         /* "demo-session" must be inside "demo-session.<session_archive_suffix>" */
577                         std::string name = basename_nosuffix (basename_nosuffix (*i));
578                         std::string path = Glib::build_filename (dspd, name);
579                         /* skip if session-dir already exists */
580                         if (Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_DIR)) {
581                                 continue;
582                         }
583                         /* skip sessions that are already in 'recent'.
584                          * eg. a new user changed <session-default-dir> shorly after installation
585                          */
586                         for (ARDOUR::RecentSessions::iterator r = rs.begin(); r != rs.end(); ++r) {
587                                 if ((*r).first == name) {
588                                         continue;
589                                 }
590                         }
591                         try {
592                                 PBD::FileArchive ar (*i);
593                                 if (0 == ar.inflate (dspd)) {
594                                         store_recent_sessions (name, path);
595                                         info << string_compose (_("Copied Demo Session %1."), name) << endmsg;
596                                 }
597                         } catch (...) {
598                                 /* relax ? */
599                         }
600                 }
601         }
602 }
603
604 void
605 StartupFSM::load_from_application_api (const std::string& path)
606 {
607         /* macOS El Capitan (and probably later) now somehow passes the command
608            line arguments to an app via the openFile delegate protocol. Ardour
609            already does its own command line processing, and having both
610            pathways active causes crashes. So, if the command line was already
611            set, do nothing here.
612         */
613
614         if (!ARDOUR_COMMAND_LINE::session_name.empty()) {
615                 return;
616         }
617
618         /* Cancel SessionDialog if it's visible to make macOS delegates work.
619          *
620          * there's a race condition here: we connect to app->ShouldLoad
621          * and then at some point (might) show a session dialog. The race is
622          * caused by the non-deterministic interaction between the macOS event
623          * loop(s) and the GDK one(s).
624          *
625          *  - ShouldLoad does not arrive before we show the session dialog
626          *          -> here we should hide the session dialog, then use the
627          *             supplied path as if it was provided on the command line
628          *  - ShouldLoad signal arrives before we show a session dialog
629          *          -> don't bother showing the session dialog, just use the
630          *             supplied path as if it was provided on the command line
631          *
632          */
633
634         if (session_dialog) {
635                 session_dialog->hide ();
636                 delete_when_idle (session_dialog);
637                 session_dialog = 0;
638         }
639
640         /* no command line argument given ... must just be via
641          * desktop/finder/window manager API (e.g. double click on "foo.ardour"
642          * icon)
643          */
644
645         if (get_session_parameters_from_path (path, string(), false)) {
646                 _signal_response (LoadSession);
647                 return;
648         }
649
650         /* given parameters failed for some reason. This is probably true
651          * anyway, but force it to be true and then carry on with whatever the
652          * main event loop is doing.
653          */
654
655         _state = NeedSessionPath;
656 }
657
658 bool
659 StartupFSM::ask_about_loading_existing_session (const std::string& session_path)
660 {
661         std::string str = string_compose (_("This session\n%1\nalready exists. Do you want to open it?"), session_path);
662
663         MessageDialog msg (str,
664                            false,
665                            Gtk::MESSAGE_WARNING,
666                            Gtk::BUTTONS_YES_NO,
667                            true);
668
669
670         msg.set_name (X_("OpenExistingDialog"));
671         msg.set_title (_("Open Existing Session"));
672         msg.set_wmclass (X_("existing_session"), PROGRAM_NAME);
673         msg.set_position (Gtk::WIN_POS_CENTER);
674         // pop_back_splash (msg);
675
676         switch (msg.run()) {
677         case RESPONSE_YES:
678                 return true;
679                 break;
680         }
681         return false;
682 }