summaryrefslogtreecommitdiffstats
path: root/src/client/PluginUI.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/PluginUI.cpp')
-rw-r--r--src/client/PluginUI.cpp336
1 files changed, 336 insertions, 0 deletions
diff --git a/src/client/PluginUI.cpp b/src/client/PluginUI.cpp
new file mode 100644
index 00000000..2368ad63
--- /dev/null
+++ b/src/client/PluginUI.cpp
@@ -0,0 +1,336 @@
+/*
+ This file is part of Ingen.
+ Copyright 2007-2015 David Robillard <http://drobilla.net/>
+
+ Ingen is free software: you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or any later version.
+
+ Ingen 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 Affero General Public License for details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Ingen. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "ingen/Interface.hpp"
+#include "ingen/Log.hpp"
+#include "ingen/URIs.hpp"
+#include "ingen/client/BlockModel.hpp"
+#include "ingen/client/PluginUI.hpp"
+#include "ingen/client/PortModel.hpp"
+#include "lv2/atom/atom.h"
+#include "lv2/ui/ui.h"
+
+namespace ingen {
+namespace client {
+
+SuilHost* PluginUI::ui_host = nullptr;
+
+static SPtr<const PortModel>
+get_port(PluginUI* ui, uint32_t port_index)
+{
+ if (port_index >= ui->block()->ports().size()) {
+ ui->world()->log().error(
+ fmt("%1% UI tried to access invalid port %2%\n")
+ % ui->block()->plugin()->uri().c_str() % port_index);
+ return SPtr<const PortModel>();
+ }
+ return ui->block()->ports()[port_index];
+}
+
+static void
+lv2_ui_write(SuilController controller,
+ uint32_t port_index,
+ uint32_t buffer_size,
+ uint32_t format,
+ const void* buffer)
+{
+ PluginUI* const ui = (PluginUI*)controller;
+ const URIs& uris = ui->world()->uris();
+ SPtr<const PortModel> port = get_port(ui, port_index);
+ if (!port) {
+ return;
+ }
+
+ // float (special case, always 0)
+ if (format == 0) {
+ if (buffer_size != 4) {
+ ui->world()->log().error(
+ fmt("%1% UI wrote corrupt float with bad size\n")
+ % ui->block()->plugin()->uri().c_str());
+ return;
+ }
+ const float value = *(const float*)buffer;
+ if (port->value().type() == uris.atom_Float &&
+ value == port->value().get<float>()) {
+ return; // Ignore feedback
+ }
+
+ ui->signal_property_changed()(
+ port->uri(),
+ uris.ingen_value,
+ ui->world()->forge().make(value),
+ Resource::Graph::DEFAULT);
+
+ } else if (format == uris.atom_eventTransfer.urid.get<LV2_URID>()) {
+ const LV2_Atom* atom = (const LV2_Atom*)buffer;
+ Atom val = ui->world()->forge().alloc(
+ atom->size, atom->type, LV2_ATOM_BODY_CONST(atom));
+ ui->signal_property_changed()(port->uri(),
+ uris.ingen_activity,
+ val,
+ Resource::Graph::DEFAULT);
+ } else {
+ ui->world()->log().warn(
+ fmt("Unknown value format %1% from LV2 UI\n")
+ % format % ui->block()->plugin()->uri().c_str());
+ }
+}
+
+static uint32_t
+lv2_ui_port_index(SuilController controller, const char* port_symbol)
+{
+ PluginUI* const ui = (PluginUI*)controller;
+
+ const BlockModel::Ports& ports = ui->block()->ports();
+ for (uint32_t i = 0; i < ports.size(); ++i) {
+ if (ports[i]->symbol() == port_symbol) {
+ return i;
+ }
+ }
+ return LV2UI_INVALID_PORT_INDEX;
+}
+
+static uint32_t
+lv2_ui_subscribe(SuilController controller,
+ uint32_t port_index,
+ uint32_t protocol,
+ const LV2_Feature* const* features)
+{
+ PluginUI* const ui = (PluginUI*)controller;
+ SPtr<const PortModel> port = get_port(ui, port_index);
+ if (!port) {
+ return 1;
+ }
+
+ ui->signal_property_changed()(
+ ui->block()->ports()[port_index]->uri(),
+ ui->world()->uris().ingen_broadcast,
+ ui->world()->forge().make(true),
+ Resource::Graph::DEFAULT);
+
+ return 0;
+}
+
+static uint32_t
+lv2_ui_unsubscribe(SuilController controller,
+ uint32_t port_index,
+ uint32_t protocol,
+ const LV2_Feature* const* features)
+{
+ PluginUI* const ui = (PluginUI*)controller;
+ SPtr<const PortModel> port = get_port(ui, port_index);
+ if (!port) {
+ return 1;
+ }
+
+ ui->signal_property_changed()(
+ ui->block()->ports()[port_index]->uri(),
+ ui->world()->uris().ingen_broadcast,
+ ui->world()->forge().make(false),
+ Resource::Graph::DEFAULT);
+
+ return 0;
+}
+
+PluginUI::PluginUI(ingen::World* world,
+ SPtr<const BlockModel> block,
+ LilvUIs* uis,
+ const LilvUI* ui,
+ const LilvNode* ui_type)
+ : _world(world)
+ , _block(std::move(block))
+ , _instance(nullptr)
+ , _uis(uis)
+ , _ui(ui)
+ , _ui_node(lilv_node_duplicate(lilv_ui_get_uri(ui)))
+ , _ui_type(lilv_node_duplicate(ui_type))
+{
+}
+
+PluginUI::~PluginUI()
+{
+ for (uint32_t i : _subscribed_ports) {
+ lv2_ui_unsubscribe(this, i, 0, nullptr);
+ }
+ suil_instance_free(_instance);
+ lilv_node_free(_ui_node);
+ lilv_node_free(_ui_type);
+ lilv_uis_free(_uis);
+ lilv_world_unload_resource(_world->lilv_world(), lilv_ui_get_uri(_ui));
+}
+
+SPtr<PluginUI>
+PluginUI::create(ingen::World* world,
+ SPtr<const BlockModel> block,
+ const LilvPlugin* plugin)
+{
+ if (!PluginUI::ui_host) {
+ PluginUI::ui_host = suil_host_new(lv2_ui_write,
+ lv2_ui_port_index,
+ lv2_ui_subscribe,
+ lv2_ui_unsubscribe);
+ }
+
+ static const char* gtk_ui_uri = LV2_UI__GtkUI;
+
+ LilvNode* gtk_ui = lilv_new_uri(world->lilv_world(), gtk_ui_uri);
+
+ LilvUIs* uis = lilv_plugin_get_uis(plugin);
+ const LilvUI* ui = nullptr;
+ const LilvNode* ui_type = nullptr;
+ LILV_FOREACH(uis, u, uis) {
+ const LilvUI* this_ui = lilv_uis_get(uis, u);
+ if (lilv_ui_is_supported(this_ui,
+ suil_ui_supported,
+ gtk_ui,
+ &ui_type)) {
+ // TODO: Multiple UI support
+ ui = this_ui;
+ break;
+ }
+ }
+
+ if (!ui) {
+ lilv_node_free(gtk_ui);
+ return SPtr<PluginUI>();
+ }
+
+ // Create the PluginUI, but don't instantiate yet
+ SPtr<PluginUI> ret(new PluginUI(world, block, uis, ui, ui_type));
+ ret->_features = world->lv2_features().lv2_features(
+ world, const_cast<BlockModel*>(block.get()));
+
+ return ret;
+}
+
+bool
+PluginUI::instantiate()
+{
+ const URIs& uris = _world->uris();
+ const std::string plugin_uri = _block->plugin()->uri();
+ LilvWorld* lworld = _world->lilv_world();
+
+ // Load seeAlso files to access data like portNotification descriptions
+ lilv_world_load_resource(lworld, lilv_ui_get_uri(_ui));
+
+ /* Subscribe (enable broadcast) for any requested port notifications. This
+ must be done before instantiation so responses to any events sent by the
+ UI's init() will be sent back to this client. */
+ LilvNode* ui_portNotification = lilv_new_uri(lworld, LV2_UI__portNotification);
+ LilvNode* ui_plugin = lilv_new_uri(lworld, LV2_UI__plugin);
+ LilvNodes* notes = lilv_world_find_nodes(
+ lworld, lilv_ui_get_uri(_ui), ui_portNotification, nullptr);
+ LILV_FOREACH(nodes, n, notes) {
+ const LilvNode* note = lilv_nodes_get(notes, n);
+ const LilvNode* sym = lilv_world_get(lworld, note, uris.lv2_symbol, nullptr);
+ const LilvNode* plug = lilv_world_get(lworld, note, ui_plugin, nullptr);
+ if (!plug) {
+ _world->log().error(fmt("%1% UI %2% notification missing plugin\n")
+ % plugin_uri % lilv_node_as_string(_ui_node));
+ } else if (!sym) {
+ _world->log().error(fmt("%1% UI %2% notification missing symbol\n")
+ % plugin_uri % lilv_node_as_string(_ui_node));
+ } else if (!lilv_node_is_uri(plug)) {
+ _world->log().error(fmt("%1% UI %2% notification has non-URI plugin\n")
+ % plugin_uri % lilv_node_as_string(_ui_node));
+ } else if (!strcmp(lilv_node_as_uri(plug), plugin_uri.c_str())) {
+ // Notification is valid and for this plugin
+ uint32_t index = lv2_ui_port_index(this, lilv_node_as_string(sym));
+ if (index != LV2UI_INVALID_PORT_INDEX) {
+ lv2_ui_subscribe(this, index, 0, nullptr);
+ _subscribed_ports.insert(index);
+ }
+ }
+ }
+ lilv_nodes_free(notes);
+ lilv_node_free(ui_plugin);
+ lilv_node_free(ui_portNotification);
+
+ const char* bundle_uri = lilv_node_as_uri(lilv_ui_get_bundle_uri(_ui));
+ const char* binary_uri = lilv_node_as_uri(lilv_ui_get_binary_uri(_ui));
+ char* bundle_path = lilv_file_uri_parse(bundle_uri, nullptr);
+ char* binary_path = lilv_file_uri_parse(binary_uri, nullptr);
+
+ // Instantiate the actual plugin UI via Suil
+ _instance = suil_instance_new(
+ PluginUI::ui_host,
+ this,
+ LV2_UI__GtkUI,
+ plugin_uri.c_str(),
+ lilv_node_as_uri(lilv_ui_get_uri(_ui)),
+ lilv_node_as_uri(_ui_type),
+ bundle_path,
+ binary_path,
+ _features->array());
+
+ lilv_free(binary_path);
+ lilv_free(bundle_path);
+
+ if (!_instance) {
+ _world->log().error("Failed to instantiate LV2 UI\n");
+ // Cancel any subscriptions
+ for (uint32_t i : _subscribed_ports) {
+ lv2_ui_unsubscribe(this, i, 0, nullptr);
+ }
+ return false;
+ }
+
+ return true;
+}
+
+SuilWidget
+PluginUI::get_widget()
+{
+ return (SuilWidget*)suil_instance_get_widget(_instance);
+}
+
+void
+PluginUI::port_event(uint32_t port_index,
+ uint32_t buffer_size,
+ uint32_t format,
+ const void* buffer)
+{
+ if (_instance) {
+ suil_instance_port_event(
+ _instance, port_index, buffer_size, format, buffer);
+ } else {
+ _world->log().warn("LV2 UI port event with no instance\n");
+ }
+}
+
+bool
+PluginUI::is_resizable() const
+{
+ LilvWorld* w = _world->lilv_world();
+ const LilvNode* s = _ui_node;
+ LilvNode* p = lilv_new_uri(w, LV2_CORE__optionalFeature);
+ LilvNode* fs = lilv_new_uri(w, LV2_UI__fixedSize);
+ LilvNode* nrs = lilv_new_uri(w, LV2_UI__noUserResize);
+
+ LilvNodes* fs_matches = lilv_world_find_nodes(w, s, p, fs);
+ LilvNodes* nrs_matches = lilv_world_find_nodes(w, s, p, nrs);
+
+ lilv_nodes_free(nrs_matches);
+ lilv_nodes_free(fs_matches);
+ lilv_node_free(nrs);
+ lilv_node_free(fs);
+ lilv_node_free(p);
+
+ return !fs_matches && !nrs_matches;
+}
+
+} // namespace client
+} // namespace ingen