summaryrefslogtreecommitdiff
path: root/gtk2_ardour/plugin_scan_dialog.cc
diff options
context:
space:
mode:
authorPaul Davis <paul@linuxaudiosystems.com>2019-10-24 21:08:24 -0600
committerPaul Davis <paul@linuxaudiosystems.com>2019-10-24 21:32:46 -0600
commitfc5076f926736354e2aaa098283b68a0e58a98eb (patch)
tree4a51e7d1f5f782241bbfe7184875ec65f8cbafe3 /gtk2_ardour/plugin_scan_dialog.cc
parentf40b859ff79d93a1d41ef31ec075a69aba39ca4e (diff)
refactor plugin scan dialog into its own object
Diffstat (limited to 'gtk2_ardour/plugin_scan_dialog.cc')
-rw-r--r--gtk2_ardour/plugin_scan_dialog.cc208
1 files changed, 208 insertions, 0 deletions
diff --git a/gtk2_ardour/plugin_scan_dialog.cc b/gtk2_ardour/plugin_scan_dialog.cc
new file mode 100644
index 0000000000..bddc5e5d91
--- /dev/null
+++ b/gtk2_ardour/plugin_scan_dialog.cc
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2005-2019 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
+ *
+ * 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 <gtkmm/progressbar.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)
+ : ArdourDialog (_("Scanning for plugins"))
+ , timeout_button (_("Stop Timeout"))
+ , cancel_button (_("Cancel Plugin Scan"))
+ , cache_only (just_cached)
+ , verbose (v)
+{
+ VBox* vbox = get_vbox();
+ vbox->set_size_request(400,-1);
+
+ message.set_padding (12, 12);
+ vbox->pack_start (message);
+
+ 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_timeout));
+ timeout_button.show();
+
+ pbar.set_orientation(Gtk::PROGRESS_RIGHT_TO_LEFT);
+ pbar.set_text(_("Scan Timeout"));
+ pbar.show();
+
+ tbox.pack_start (pbar, PACK_EXPAND_WIDGET, 4);
+ tbox.pack_start (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 ();
+}
+
+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
+ * and AU. 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_timeout ()
+{
+ PluginManager::instance().cancel_plugin_timeout();
+ timeout_button.set_sensitive (false);
+}
+
+void
+PluginScanDialog::plugin_scan_timeout (int timeout)
+{
+ if (!is_mapped()) {
+ return;
+ }
+
+ if (timeout > 0) {
+ pbar.set_sensitive (false);
+ timeout_button.set_sensitive (true);
+ pbar.set_fraction ((float) timeout / (float) Config->get_vst_scan_timeout());
+ tbox.show();
+ } else {
+ pbar.set_sensitive (false);
+ timeout_button.set_sensitive (false);
+ }
+
+ ARDOUR_UI::instance()->gui_idle_handler ();
+}
+
+void
+PluginScanDialog::message_handler (std::string type, std::string plugin, bool can_cancel)
+{
+ 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 (cancelled && is_mapped()) {
+ hide();
+ connections.drop_connections();
+ ARDOUR_UI::instance()->gui_idle_handler ();
+ return;
+ }
+ if (cancelled || !can_cancel) {
+ return;
+ }
+ }
+
+ if (type == X_("closeme")) {
+ tbox.hide();
+ hide();
+ connections.drop_connections ();
+ } else {
+ message.set_text (type + ": " + Glib::path_get_basename(plugin));
+ show();
+ }
+
+ if (!can_cancel || !cancelled) {
+ timeout_button.set_sensitive(false);
+ }
+
+ cancel_button.set_sensitive(can_cancel && !cancelled);
+
+ ARDOUR_UI::instance()->gui_idle_handler ();
+}