Implement LV2 request-value UI

* Handle cross-thread request from DSP context
* Allow calls without plugin GUI
* Implement boolean value requests, show dialog
This commit is contained in:
Robin Gareus 2021-10-06 01:34:01 +02:00
parent eadd98efec
commit 532fbc41cb
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
5 changed files with 124 additions and 46 deletions

View File

@ -89,6 +89,7 @@
#include "gtkmm2ext/application.h"
#include "gtkmm2ext/bindings.h"
#include "gtkmm2ext/doi.h"
#include "gtkmm2ext/gtk_ui.h"
#include "gtkmm2ext/utils.h"
#include "gtkmm2ext/window_title.h"
@ -108,6 +109,7 @@
#include "ardour/filename_extensions.h"
#include "ardour/filesystem_paths.h"
#include "ardour/ltc_file_reader.h"
#include "ardour/lv2_plugin.h"
#include "ardour/monitor_control.h"
#include "ardour/midi_track.h"
#include "ardour/port.h"
@ -174,6 +176,7 @@
#include "location_ui.h"
#include "lua_script_manager.h"
#include "luawindow.h"
#include "lv2_plugin_ui.h"
#include "main_clock.h"
#include "missing_file_dialog.h"
#include "missing_plugin_dialog.h"
@ -437,6 +440,11 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir)
ARDOUR::Session::Dialog.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::session_dialog, this, _1), gui_context());
#ifdef HAVE_LV2_1_17_2
ARDOUR::LV2Plugin::Request.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::lv2_plugin_request, this, _1, _2, _3, _4), gui_context());
LV2PluginUI::CatchDeletion.connect_same_thread (forever_connections, boost::bind (&delete_when_idle<LV2PluginUI>, _1));
#endif
/* handle pending state with a dialog (PROBLEM: needs to return a value and thus cannot be x-thread) */
ARDOUR::Session::AskAboutPendingState.connect_same_thread (forever_connections, boost::bind (&ARDOUR_UI::pending_state_dialog, this));

View File

@ -72,6 +72,9 @@
#include "gtkmm2ext/bindings.h"
#include "gtkmm2ext/visibility_tracker.h"
#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
#include "ardour/ardour.h"
#include "ardour/types.h"
#include "ardour/utils.h"
@ -177,6 +180,7 @@ namespace ARDOUR {
class Route;
class RouteGroup;
class Location;
class PluginInsert;
class ProcessThread;
}
@ -236,6 +240,8 @@ public:
bool ask_about_loading_existing_session (const std::string& session_path);
int load_session_from_startup_fsm ();
void lv2_plugin_request (boost::weak_ptr<ARDOUR::PluginInsert>, LV2_URID, LV2_URID, LV2_Feature const* const*);
/// @return true if session was successfully unloaded.
int unload_session (bool hide_stuff = false);
void close_session();

View File

@ -40,6 +40,8 @@
#include "ardour/audioengine.h"
#include "ardour/automation_watch.h"
#include "ardour/control_protocol_manager.h"
#include "ardour/lv2_plugin.h"
#include "ardour/plugin_insert.h"
#include "ardour/profile.h"
#include "ardour/session.h"
@ -63,6 +65,7 @@
#include "location_ui.h"
#include "lua_script_manager.h"
#include "luawindow.h"
#include "lv2_plugin_ui.h"
#include "main_clock.h"
#include "meterbridge.h"
#include "meter_patterns.h"
@ -1034,6 +1037,25 @@ ARDOUR_UI::show_plugin_manager ()
tact->set_active();
}
void
ARDOUR_UI::lv2_plugin_request (boost::weak_ptr<PluginInsert> wi, LV2_URID key, LV2_URID type, LV2_Feature const* const* features)
{
#ifdef HAVE_LV2_1_17_2
boost::shared_ptr<PluginInsert> insert = wi.lock ();
if (!insert) {
return;
}
boost::shared_ptr<LV2Plugin> vp = boost::dynamic_pointer_cast<LV2Plugin> (insert->plugin());
if (!vp) {
return;
}
LV2PluginUI* lpu = new LV2PluginUI (insert, vp);
if (LV2PluginUI::request_value (lpu, key, type, features) != LV2UI_REQUEST_VALUE_SUCCESS) {
delete lpu;
}
#endif
}
bool
ARDOUR_UI::timecode_button_press (GdkEventButton* ev)
{

View File

@ -25,17 +25,18 @@
#include <gtkmm/stock.h>
#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
#include "ardour/lv2_plugin.h"
#include "ardour/session.h"
#include "pbd/error.h"
#include "gtkmm2ext/utils.h"
#include "gui_thread.h"
#include "lv2_plugin_ui.h"
#include "timers.h"
#include "gtkmm2ext/utils.h"
#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
#include "ardour_message.h"
#include <lilv/lilv.h>
#include <suil/suil.h>
@ -48,6 +49,10 @@ using namespace PBD;
#define NS_UI "http://lv2plug.in/ns/extensions/ui#"
#ifdef HAVE_LV2_1_17_2
PBD::Signal1<void, LV2PluginUI*> LV2PluginUI::CatchDeletion;
#endif
static SuilHost* ui_host = NULL;
void
@ -121,24 +126,31 @@ LV2PluginUI::touch(void* controller,
}
}
#ifdef HAVE_LV2_1_17_2
void
LV2PluginUI::set_path_property (int response,
const ParameterDescriptor& desc,
Gtk::FileChooserDialog* widget)
LV2PluginUI::request_response (int response,
const ParameterDescriptor& desc,
Gtk::Widget* widget)
{
if (response == Gtk::RESPONSE_ACCEPT) {
plugin->set_property (desc.key, Variant (Variant::PATH, widget->get_filename()));
switch (desc.datatype) {
case Variant::PATH:
if (response == Gtk::RESPONSE_ACCEPT) {
set_path_property (desc.key, Variant (Variant::PATH, dynamic_cast<Gtk::FileChooserDialog*> (widget)->get_filename ()));
}
break;
case Variant::BOOL:
set_path_property (desc.key, Variant (Variant::BOOL, response == Gtk::RESPONSE_YES));
break;
default:
break;
}
#if 0
widget->hide ();
delete_when_idle (widget);
#else
delete widget;
#endif
active_parameter_requests.erase (desc.key);
_active_parameter_requests.erase (desc.key);
CatchDeletion (this); /* EMIT SIGNAL */
}
#ifdef HAVE_LV2_1_17_2
LV2UI_Request_Value_Status
LV2PluginUI::request_value(void* handle,
LV2_URID key,
@ -150,39 +162,62 @@ LV2PluginUI::request_value(void* handle,
const ParameterDescriptor& desc (me->_lv2->get_property_descriptor(key));
if (desc.key == (uint32_t)-1) {
return LV2UI_REQUEST_VALUE_ERR_UNKNOWN;
} else if (desc.datatype != Variant::PATH) {
return LV2UI_REQUEST_VALUE_ERR_UNSUPPORTED;
} else if (me->active_parameter_requests.count (key)) {
} else if (me->_active_parameter_requests.count (key)) {
return LV2UI_REQUEST_VALUE_BUSY;
}
me->active_parameter_requests.insert (key);
Gtk::FileChooserDialog* lv2ui_file_dialog = new Gtk::FileChooserDialog(desc.label, FILE_CHOOSER_ACTION_OPEN);
Gtkmm2ext::add_volume_shortcuts (*lv2ui_file_dialog);
lv2ui_file_dialog->add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
lv2ui_file_dialog->add_button (Gtk::Stock::OPEN, Gtk::RESPONSE_ACCEPT);
lv2ui_file_dialog->set_default_response(Gtk::RESPONSE_ACCEPT);
me->_active_parameter_requests.insert (key);
/* this assumes announce_property_values() was called, or
* the plugin has previously sent a patch:Set */
const Variant& value = me->_lv2->get_property_value (desc.key);
if (value.type() == Variant::PATH) {
lv2ui_file_dialog->set_filename (value.get_path());
}
switch (desc.datatype) {
case Variant::PATH:
{
Gtk::FileChooserDialog* lv2ui_file_dialog = new Gtk::FileChooserDialog(desc.label, FILE_CHOOSER_ACTION_OPEN);
Gtkmm2ext::add_volume_shortcuts (*lv2ui_file_dialog);
lv2ui_file_dialog->add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
lv2ui_file_dialog->add_button (Gtk::Stock::OPEN, Gtk::RESPONSE_ACCEPT);
lv2ui_file_dialog->set_default_response(Gtk::RESPONSE_ACCEPT);
/* this assumes announce_property_values() was called, or
* the plugin has previously sent a patch:Set */
const Variant& value = me->_lv2->get_property_value (desc.key);
if (value.type() == Variant::PATH) {
lv2ui_file_dialog->set_filename (value.get_path());
}
#if 0 // TODO mime-type, file-extension filter, get from LV2 Parameter Property
FileFilter file_ext_filter;
file_ext_filter.add_pattern ("*.foo");
file_ext_filter.set_name ("Foo File");
lv2ui_file_dialog.add_filter (file_ext_filter);
FileFilter file_ext_filter;
file_ext_filter.add_pattern ("*.foo");
file_ext_filter.set_name ("Foo File");
lv2ui_file_dialog.add_filter (file_ext_filter);
#endif
lv2ui_file_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*me, &LV2PluginUI::request_response), desc, lv2ui_file_dialog));
lv2ui_file_dialog->present();
}
break;
case Variant::BOOL:
{
ArdourMessageDialog* msg = new ArdourMessageDialog (desc.label, false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO, false);
msg->signal_response().connect (sigc::bind (sigc::mem_fun (*me, &LV2PluginUI::request_response), desc, msg));
msg->present();
}
break;
default:
me->_active_parameter_requests.erase (key);
return LV2UI_REQUEST_VALUE_ERR_UNSUPPORTED;
}
lv2ui_file_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*me, &LV2PluginUI::set_path_property), desc, lv2ui_file_dialog));
lv2ui_file_dialog->present();
return LV2UI_REQUEST_VALUE_SUCCESS;
}
#endif
void
LV2PluginUI::set_path_property (const uint32_t key, ARDOUR::Variant const& val)
{
plugin->set_property (key, val);
}
void
LV2PluginUI::update_timeout()
{

View File

@ -66,6 +66,16 @@ public:
int package (Gtk::Window&);
void grab_focus ();
#ifdef HAVE_LV2_1_17_2
static LV2UI_Request_Value_Status
request_value(void* handle,
LV2_URID key,
LV2_URID type,
const LV2_Feature* const* features);
static PBD::Signal1<void, LV2PluginUI*> CatchDeletion;
#endif
private:
void control_changed (uint32_t);
@ -88,6 +98,7 @@ private:
#ifdef HAVE_LV2_1_17_2
LV2UI_Request_Value _lv2ui_request_value;
LV2_Feature _lv2ui_request_feature;
std::set<uint32_t> _active_parameter_requests;
#endif
struct lv2_external_ui* _external_ui_ptr;
LV2_Feature _parent_feature;
@ -115,18 +126,14 @@ private:
uint32_t port_index,
bool grabbed);
void set_path_property (const uint32_t key, ARDOUR::Variant const&);
#ifdef HAVE_LV2_1_17_2
static LV2UI_Request_Value_Status
request_value(void* handle,
LV2_URID key,
LV2_URID type,
const LV2_Feature* const* features);
void request_response (int,
ARDOUR::ParameterDescriptor const&,
Gtk::Widget*);
#endif
void set_path_property (int,
const ARDOUR::ParameterDescriptor&,
Gtk::FileChooserDialog*);
std::set<uint32_t> active_parameter_requests;
void update_timeout();