summaryrefslogtreecommitdiff
path: root/gtk2_ardour/plugin_scan_dialog.cc
blob: a3ba2c16c886d7cecd43812b06a53cb7c708dd00 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/*
 * 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.  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_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)
{
	DEBUG_TRACE (DEBUG::GuiStartup, string_compose (X_("plugin scan message: %1 cancel? %2\n"), type, 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 ();
}