2 * Copyright (C) 2005-2019 Paul Davis <paul@linuxaudiosystems.com>
3 * Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "gtk2ardour-config.h"
22 #include "gtk2ardour-version.h"
25 #include <gtkmm/progressbar.h>
27 #include "ardour/plugin_manager.h"
29 #include "ardour_ui.h"
31 #include "gui_thread.h"
32 #include "plugin_scan_dialog.h"
33 #include "ui_config.h"
37 using namespace ARDOUR;
42 PluginScanDialog::PluginScanDialog (bool just_cached, bool v)
43 : ArdourDialog (_("Scanning for plugins"))
44 , timeout_button (_("Stop Timeout"))
45 , cancel_button (_("Cancel Plugin Scan"))
46 , cache_only (just_cached)
49 VBox* vbox = get_vbox();
50 vbox->set_size_request(400,-1);
52 message.set_padding (12, 12);
53 vbox->pack_start (message);
55 cancel_button.set_name ("EditorGTKButton");
56 cancel_button.signal_clicked().connect (sigc::mem_fun (*this, &PluginScanDialog::cancel_plugin_scan));
59 vbox->pack_start (cancel_button, PACK_SHRINK);
61 timeout_button.set_name ("EditorGTKButton");
62 timeout_button.signal_clicked().connect (sigc::mem_fun (*this, &PluginScanDialog::cancel_plugin_timeout));
63 timeout_button.show();
65 pbar.set_orientation(Gtk::PROGRESS_RIGHT_TO_LEFT);
66 pbar.set_text(_("Scan Timeout"));
69 tbox.pack_start (pbar, PACK_EXPAND_WIDGET, 4);
70 tbox.pack_start (timeout_button, PACK_SHRINK, 4);
72 vbox->pack_start (tbox, PACK_SHRINK, 4);
74 ARDOUR::PluginScanMessage.connect (connections, MISSING_INVALIDATOR, boost::bind(&PluginScanDialog::message_handler, this, _1, _2, _3), gui_context());
75 ARDOUR::PluginScanTimeout.connect (connections, MISSING_INVALIDATOR, boost::bind(&PluginScanDialog::plugin_scan_timeout, this, _1), gui_context());
81 PluginScanDialog::start ()
83 /* OK, this is extremely hard to understand on first reading, so please
84 * read this and think about it carefully if you are confused.
86 * Plugin discovery must take place in the main thread of the
87 * process. This is not true for all plugin APIs but it is true for
88 * VST. For AU, although plugins themselves do not care, Apple decided
89 * that Cocoa must be "invoked" from the main thread. Since the plugin
90 * might show a "registration" GUI, discovery must be done
93 * This means that the PluginManager::refresh() call MUST be made from
94 * the main thread (typically the GUI thread, but certainly the thread
95 * running main()). Failure to do this will cause crashes, undefined
96 * behavior and other undesirable stuff (because plugin APIs failed to
97 * specify this aspect of the host behavior).
99 * The ::refresh call is likely to be slow, particularly in the case of
100 * VST(2) plugins where we are forced to load the shared object do
101 * discovery (there is no separate metadata as with LV2 for
102 * example). This means that it will block the GUI event loop where we
103 * are calling it from. This is a problem.
105 * Normally we would solve this by running it in a separate thread, but
106 * we cannot do this for reasons described above regarding plugin
109 * We "solve" this by making the PluginManager emit a signal as it
110 * examines every new plugin. Our handler for this signal checks the
111 * message, and then runs ARDOUR_UI::gui_idle_handler() which flushes
112 * the GUI event loop of pending events. This effectively handles
113 * redraws and event input and all the usual stuff, meaning that the
114 * GUI event loop appears to continue running during the ::refresh()
115 * call. In reality, it only runs at the start of each plugin
116 * discovery, so if the discovery process for a particular plugin takes
117 * a long time (e.g. because it displays a licensing window and sits
118 * waiting for input from the user), there's nothing we can do -
119 * control will not be returned to our GUI event loop until that is
122 * This is a horrible design. Truly, really horrible. But it is caused
123 * by plugin APIs failing to mandate that discovery can happen from any
124 * thread and that plugins should NOT display a GUI or interact with
125 * the user during discovery/instantiation. Fundamentally, all plugin
126 * APIs should allow discovery without instantiation, like LV2 does
127 * (and to a very limited extent like AU does, if you play some games
128 * with the lower level APIs).
130 * For now (October 2019) it is the best we can come up with that does
131 * not break when some VST plugin decides to behave stupidly.
135 DEBUG_TRACE (DEBUG::GuiStartup, "plugin refresh starting\n");
136 PluginManager::instance().refresh (cache_only);
137 DEBUG_TRACE (DEBUG::GuiStartup, "plugin refresh complete\n");
139 /* scan is done at this point, return full control to main event loop */
143 PluginScanDialog::cancel_plugin_scan ()
145 PluginManager::instance().cancel_plugin_scan();
149 PluginScanDialog::cancel_plugin_timeout ()
151 PluginManager::instance().cancel_plugin_timeout();
152 timeout_button.set_sensitive (false);
156 PluginScanDialog::plugin_scan_timeout (int timeout)
163 pbar.set_sensitive (false);
164 timeout_button.set_sensitive (true);
165 pbar.set_fraction ((float) timeout / (float) Config->get_vst_scan_timeout());
168 pbar.set_sensitive (false);
169 timeout_button.set_sensitive (false);
172 ARDOUR_UI::instance()->gui_idle_handler ();
176 PluginScanDialog::message_handler (std::string type, std::string plugin, bool can_cancel)
178 DEBUG_TRACE (DEBUG::GuiStartup, string_compose (X_("plugin scan message: %1 cancel? %2\n"), type, can_cancel));
180 if (type == X_("closeme") && !is_mapped()) {
184 const bool cancelled = PluginManager::instance().cancelled();
186 if (type != X_("closeme") && (!UIConfiguration::instance().get_show_plugin_scan_window()) && !verbose) {
188 if (cancelled && is_mapped()) {
190 connections.drop_connections();
191 ARDOUR_UI::instance()->gui_idle_handler ();
194 if (cancelled || !can_cancel) {
199 if (type == X_("closeme")) {
202 connections.drop_connections ();
204 message.set_text (type + ": " + Glib::path_get_basename(plugin));
208 if (!can_cancel || !cancelled) {
209 timeout_button.set_sensitive(false);
212 cancel_button.set_sensitive(can_cancel && !cancelled);
214 ARDOUR_UI::instance()->gui_idle_handler ();