/* * Copyright (C) 2005-2019 Paul Davis * Copyright (C) 2012-2019 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifdef WAF_BUILD #include "gtk2ardour-config.h" #include "gtk2ardour-version.h" #endif #include #include "pbd/basename.h" #include "ardour/plugin_manager.h" #include "ardour_ui.h" #include "debug.h" #include "gui_thread.h" #include "plugin_scan_dialog.h" #include "ui_config.h" #include "pbd/i18n.h" using namespace ARDOUR; using namespace PBD; using namespace Gtk; using namespace std; PluginScanDialog::PluginScanDialog (bool just_cached, bool v, Gtk::Window* parent) : ArdourDialog (_("Scanning for plugins")) , timeout_button (_("Stop Timeout")) , all_timeout_button (_("Ignore all Timeouts")) , cancel_button (_("Cancel Plugin Scan")) , cache_only (just_cached) , verbose (v) , delayed_close (false) { VBox* vbox = get_vbox(); vbox->set_size_request(400,-1); message.set_padding (12, 12); timeout_info.set_padding (12, 12); vbox->pack_start (message); vbox->pack_start (timeout_info); timeout_info.set_markup (string_compose ("%1", _("Scan takes a long time, check for popup dialogs."))); timeout_info.set_no_show_all (); cancel_button.set_name ("EditorGTKButton"); cancel_button.signal_clicked().connect (sigc::mem_fun (*this, &PluginScanDialog::cancel_plugin_scan)); cancel_button.show(); vbox->pack_start (cancel_button, PACK_SHRINK); timeout_button.set_name ("EditorGTKButton"); timeout_button.signal_clicked().connect (sigc::mem_fun (*this, &PluginScanDialog::cancel_plugin_scan_timeout)); timeout_button.show(); all_timeout_button.set_name ("EditorGTKButton"); all_timeout_button.signal_clicked().connect (sigc::mem_fun (*this, &PluginScanDialog::cancel_plugin_all_scan_timeout)); all_timeout_button.show(); pbar.set_orientation(Gtk::PROGRESS_RIGHT_TO_LEFT); pbar.set_pulse_step (0.1); pbar.set_text(_("Scan Timeout")); pbar.show(); tbox.pack_start (pbar, PACK_EXPAND_WIDGET, 4); tbox.pack_start (timeout_button, PACK_SHRINK, 4); tbox.pack_start (all_timeout_button, PACK_SHRINK, 4); vbox->pack_start (tbox, PACK_SHRINK, 4); ARDOUR::PluginScanMessage.connect (connections, MISSING_INVALIDATOR, boost::bind(&PluginScanDialog::message_handler, this, _1, _2, _3), gui_context()); ARDOUR::PluginScanTimeout.connect (connections, MISSING_INVALIDATOR, boost::bind(&PluginScanDialog::plugin_scan_timeout, this, _1), gui_context()); vbox->show_all (); if (parent) { set_transient_for (*parent); set_position (Gtk::WIN_POS_CENTER_ON_PARENT); delayed_close = true; } } void PluginScanDialog::start () { /* OK, this is extremely hard to understand on first reading, so please * read this and think about it carefully if you are confused. * * Plugin discovery must take place in the main thread of the * process. This is not true for all plugin APIs but it is true for * VST. For AU, although plugins themselves do not care, Apple decided * that Cocoa must be "invoked" from the main thread. Since the plugin * might show a "registration" GUI, discovery must be done * in the main thread. * * This means that the PluginManager::refresh() call MUST be made from * the main thread (typically the GUI thread, but certainly the thread * running main()). Failure to do this will cause crashes, undefined * behavior and other undesirable stuff (because plugin APIs failed to * specify this aspect of the host behavior). * * The ::refresh call is likely to be slow, particularly in the case of * VST(2) plugins where we are forced to load the shared object do * discovery (there is no separate metadata as with LV2 for * example). This means that it will block the GUI event loop where we * are calling it from. This is a problem. * * Normally we would solve this by running it in a separate thread, but * we cannot do this for reasons described above regarding plugin * discovery. * * We "solve" this by making the PluginManager emit a signal as it * examines every new plugin. Our handler for this signal checks the * message, and then runs ARDOUR_UI::gui_idle_handler() which flushes * the GUI event loop of pending events. This effectively handles * redraws and event input and all the usual stuff, meaning that the * GUI event loop appears to continue running during the ::refresh() * call. In reality, it only runs at the start of each plugin * discovery, so if the discovery process for a particular plugin takes * a long time (e.g. because it displays a licensing window and sits * waiting for input from the user), there's nothing we can do - * control will not be returned to our GUI event loop until that is * finished. * * This is a horrible design. Truly, really horrible. But it is caused * by plugin APIs failing to mandate that discovery can happen from any * thread and that plugins should NOT display a GUI or interact with * the user during discovery/instantiation. Fundamentally, all plugin * APIs should allow discovery without instantiation, like LV2 does * (and to a very limited extent like AU does, if you play some games * with the lower level APIs). * * For now (October 2019) it is the best we can come up with that does * not break when some VST plugin decides to behave stupidly. */ DEBUG_TRACE (DEBUG::GuiStartup, "plugin refresh starting\n"); PluginManager::instance().refresh (cache_only); DEBUG_TRACE (DEBUG::GuiStartup, "plugin refresh complete\n"); /* scan is done at this point, return full control to main event loop */ } void PluginScanDialog::cancel_plugin_scan () { PluginManager::instance().cancel_plugin_scan(); } void PluginScanDialog::cancel_plugin_all_scan_timeout () { PluginManager::instance().cancel_plugin_all_scan_timeout (); all_timeout_button.set_sensitive (false); timeout_button.set_sensitive (false); } void PluginScanDialog::cancel_plugin_scan_timeout () { PluginManager::instance().cancel_plugin_scan_timeout (); timeout_button.set_sensitive (false); } void PluginScanDialog::plugin_scan_timeout (int timeout) { if (!is_mapped()) { return; } int scan_timeout = Config->get_vst_scan_timeout(); if (timeout > 0) { pbar.set_sensitive (true); if (timeout < scan_timeout / 2 || (scan_timeout - timeout) > 300) { timeout_info.show (); } if (timeout < scan_timeout) { pbar.set_text(_("Scan Timeout")); } else { pbar.set_text(_("Scanning")); timeout_info.hide (); } timeout_button.set_sensitive (timeout < scan_timeout); all_timeout_button.set_sensitive (timeout < scan_timeout); pbar.set_fraction ((float) timeout / (float) scan_timeout); tbox.show(); } else if (timeout < 0) { pbar.set_sensitive (true); pbar.set_text(_("Scanning")); pbar.pulse (); timeout_button.set_sensitive (false); tbox.show(); if (timeout <= -300) { timeout_info.show (); } } else { pbar.set_sensitive (false); timeout_button.set_sensitive (false); all_timeout_button.set_sensitive (false); tbox.hide(); timeout_info.hide (); } ARDOUR_UI::instance()->gui_idle_handler (); } void PluginScanDialog::message_handler (std::string type, std::string plugin, bool can_cancel) { DEBUG_TRACE (DEBUG::GuiStartup, string_compose (X_("plugin scan message: %1 cancel? %2\n"), type, can_cancel)); timeout_info.hide (); if (type == X_("closeme") && !is_mapped()) { return; } const bool cancelled = PluginManager::instance().cancelled(); if (type != X_("closeme") && !UIConfiguration::instance().get_show_plugin_scan_window() && !verbose) { if (is_mapped()) { hide (); connections.drop_connections(); ARDOUR_UI::instance()->gui_idle_handler (); return; } return; } if (type == X_("closeme")) { tbox.hide(); connections.drop_connections (); cancel_button.set_sensitive(false); queue_draw (); for (int i = 0; delayed_close && i < 30; ++i) { // 1.5 sec delay Glib::usleep (50000); ARDOUR_UI::instance()->gui_idle_handler (); } hide (); } else { message.set_text (type + ": " + PBD::basename_nosuffix (plugin)); show(); } if (!can_cancel || !cancelled) { timeout_button.set_sensitive(false); all_timeout_button.set_sensitive(false); } cancel_button.set_sensitive(can_cancel && !cancelled); ARDOUR_UI::instance()->gui_idle_handler (); }