Collection of tools useful for audio production
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

824 lines
21KB

  1. /*
  2. * Carla Plugin bridge code
  3. * Copyright (C) 2012 Filipe Coelho <falktx@falktx.com>
  4. *
  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. * any later version.
  9. *
  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.
  14. *
  15. * For a full copy of the GNU General Public License see the COPYING file
  16. */
  17. #ifdef BUILD_BRIDGE_PLUGIN
  18. #include "carla_bridge_client.h"
  19. #include "carla_plugin.h"
  20. #include <set>
  21. #include <QtCore/QDir>
  22. #include <QtCore/QFile>
  23. #include <QtCore/QTextStream>
  24. #include <QtCore/QTimerEvent>
  25. #include <QtGui/QApplication>
  26. #include <QtGui/QMainWindow>
  27. #include <QtGui/QtEvents>
  28. #ifdef Q_OS_UNIX
  29. # include <signal.h>
  30. #endif
  31. static int qargc = 0;
  32. static char** qargv = nullptr;
  33. static bool qCloseNow = false;
  34. #if defined(Q_OS_UNIX)
  35. void closeSignalHandler(int)
  36. {
  37. qCloseNow = true;
  38. }
  39. #elif defined(Q_OS_WIN)
  40. BOOL WINAPI closeSignalHandler(DWORD dwCtrlType)
  41. {
  42. if (dwCtrlType == CTRL_C_EVENT)
  43. {
  44. qCloseNow = true;
  45. return TRUE;
  46. }
  47. return FALSE;
  48. }
  49. #endif
  50. void initSignalHandler()
  51. {
  52. #if defined(Q_OS_UNIX)
  53. struct sigaction sint;
  54. struct sigaction sterm;
  55. sint.sa_handler = closeSignalHandler;
  56. sint.sa_flags = SA_RESTART;
  57. sint.sa_restorer = nullptr;
  58. sigemptyset(&sint.sa_mask);
  59. sigaction(SIGINT, &sint, nullptr);
  60. sterm.sa_handler = closeSignalHandler;
  61. sterm.sa_flags = SA_RESTART;
  62. sterm.sa_restorer = nullptr;
  63. sigemptyset(&sterm.sa_mask);
  64. sigaction(SIGTERM, &sterm, nullptr);
  65. #elif defined(Q_OS_WIN)
  66. SetConsoleCtrlHandler(closeSignalHandler, TRUE);
  67. #endif
  68. }
  69. CARLA_BRIDGE_START_NAMESPACE
  70. // -------------------------------------------------------------------------
  71. class BridgePluginGUI : public QMainWindow
  72. {
  73. public:
  74. class Callback
  75. {
  76. public:
  77. virtual ~Callback() {}
  78. virtual void guiClosedCallback() = 0;
  79. };
  80. BridgePluginGUI(QWidget* const parent, Callback* const callback_)
  81. : QMainWindow(parent),
  82. callback(callback_)
  83. {
  84. qDebug("BridgePluginGUI::BridgePluginGUI(%p, %p", parent, callback);
  85. CARLA_ASSERT(callback);
  86. m_firstShow = true;
  87. m_resizable = true;
  88. container = new GuiContainer(this);
  89. setCentralWidget(container);
  90. setNewSize(50, 50);
  91. }
  92. ~BridgePluginGUI()
  93. {
  94. qDebug("BridgePluginGUI::~BridgePluginGUI()");
  95. CARLA_ASSERT(container);
  96. delete container;
  97. }
  98. GuiContainer* getContainer()
  99. {
  100. return container;
  101. }
  102. void setResizable(bool resizable)
  103. {
  104. m_resizable = resizable;
  105. setNewSize(width(), height());
  106. #ifdef Q_OS_WIN
  107. if (! resizable)
  108. setWindowFlags(windowFlags() | Qt::MSWindowsFixedSizeDialogHint);
  109. #endif
  110. }
  111. void setTitle(const char* title)
  112. {
  113. CARLA_ASSERT(title);
  114. setWindowTitle(QString("%1 (GUI)").arg(title));
  115. }
  116. void setNewSize(int width, int height)
  117. {
  118. qDebug("BridgePluginGUI::setNewSize(%i, %i)", width, height);
  119. if (width < 30)
  120. width = 30;
  121. if (height < 30)
  122. height = 30;
  123. if (m_resizable)
  124. {
  125. resize(width, height);
  126. }
  127. else
  128. {
  129. setFixedSize(width, height);
  130. container->setFixedSize(width, height);
  131. }
  132. }
  133. void setVisible(const bool yesNo)
  134. {
  135. qDebug("BridgePluginGUI::setVisible(%s)", bool2str(yesNo));
  136. if (yesNo)
  137. {
  138. if (m_firstShow)
  139. {
  140. m_firstShow = false;
  141. restoreGeometry(QByteArray());
  142. }
  143. else if (! m_geometry.isNull())
  144. restoreGeometry(m_geometry);
  145. }
  146. else
  147. m_geometry = saveGeometry();
  148. QMainWindow::setVisible(yesNo);
  149. }
  150. protected:
  151. void hideEvent(QHideEvent* const event)
  152. {
  153. qDebug("BridgePluginGUI::hideEvent(%p)", event);
  154. event->accept();
  155. close();
  156. }
  157. void closeEvent(QCloseEvent* const event)
  158. {
  159. qDebug("BridgePluginGUI::closeEvent(%p)", event);
  160. if (event->spontaneous())
  161. callback->guiClosedCallback();
  162. QMainWindow::closeEvent(event);
  163. }
  164. private:
  165. Callback* const callback;
  166. GuiContainer* container;
  167. bool m_firstShow;
  168. bool m_resizable;
  169. QByteArray m_geometry;
  170. };
  171. // -------------------------------------------------------------------------
  172. class BridgePluginClient : public CarlaToolkit,
  173. public CarlaClient,
  174. public BridgePluginGUI::Callback,
  175. public QApplication
  176. {
  177. public:
  178. BridgePluginClient()
  179. : CarlaToolkit("carla-bridge-plugin"),
  180. CarlaClient(this),
  181. QApplication(qargc, qargv, true)
  182. {
  183. qDebug("BridgePluginClient::BridgePluginClient()");
  184. hasUI = false;
  185. msgTimerGUI = 0;
  186. msgTimerOSC = 0;
  187. engine = nullptr;
  188. plugin = nullptr;
  189. pluginGui = nullptr;
  190. m_client = this;
  191. }
  192. ~BridgePluginClient()
  193. {
  194. qDebug("BridgePluginClient::~BridgePluginClient()");
  195. CARLA_ASSERT(msgTimerGUI == 0);
  196. CARLA_ASSERT(msgTimerOSC == 0);
  197. CARLA_ASSERT(! pluginGui);
  198. }
  199. void setStuff(CarlaBackend::CarlaEngine* const engine, CarlaBackend::CarlaPlugin* const plugin)
  200. {
  201. qDebug("BridgePluginClient::setStuff(%p, %p)", engine, plugin);
  202. CARLA_ASSERT(engine);
  203. CARLA_ASSERT(plugin);
  204. this->engine = engine;
  205. this->plugin = plugin;
  206. }
  207. // ---------------------------------------------------------------------
  208. // toolkit
  209. void init()
  210. {
  211. qDebug("BridgePluginClient::init()");
  212. pluginGui = new BridgePluginGUI(nullptr, this);
  213. pluginGui->hide();
  214. }
  215. void exec(CarlaClient* const, const bool showGui)
  216. {
  217. qDebug("BridgePluginClient::exec()");
  218. if (showGui)
  219. {
  220. if (hasUI)
  221. show();
  222. }
  223. else
  224. {
  225. CarlaClient::sendOscUpdate();
  226. CarlaClient::sendOscBridgeUpdate();
  227. QApplication::setQuitOnLastWindowClosed(false);
  228. }
  229. msgTimerGUI = startTimer(50);
  230. msgTimerOSC = startTimer(25);
  231. QApplication::exec();
  232. }
  233. void quit()
  234. {
  235. qDebug("BridgePluginClient::quit()");
  236. if (msgTimerGUI != 0)
  237. {
  238. QApplication::killTimer(msgTimerGUI);
  239. msgTimerGUI = 0;
  240. }
  241. if (msgTimerOSC != 0)
  242. {
  243. QApplication::killTimer(msgTimerOSC);
  244. msgTimerOSC = 0;
  245. }
  246. if (pluginGui)
  247. {
  248. if (pluginGui->isVisible())
  249. hide();
  250. pluginGui->close();
  251. delete pluginGui;
  252. pluginGui = nullptr;
  253. }
  254. if (! QApplication::closingDown())
  255. QApplication::quit();
  256. }
  257. void show()
  258. {
  259. qDebug("BridgePluginClient::show()");
  260. CARLA_ASSERT(pluginGui);
  261. if (plugin)
  262. plugin->showGui(true);
  263. if (pluginGui)
  264. pluginGui->show();
  265. }
  266. void hide()
  267. {
  268. qDebug("BridgePluginClient::hide()");
  269. CARLA_ASSERT(pluginGui);
  270. if (pluginGui)
  271. pluginGui->hide();
  272. if (plugin)
  273. plugin->showGui(false);
  274. }
  275. void resize(int width, int height)
  276. {
  277. qDebug("BridgePluginClient::resize(%i, %i)", width, height);
  278. CARLA_ASSERT(pluginGui);
  279. if (pluginGui)
  280. pluginGui->setNewSize(width, height);
  281. }
  282. // ---------------------------------------------------------------------
  283. void createWindow(const bool resizable)
  284. {
  285. qDebug("BridgePluginClient::createWindow(%s)", bool2str(resizable));
  286. CARLA_ASSERT(plugin);
  287. CARLA_ASSERT(pluginGui);
  288. if (! (plugin && pluginGui))
  289. return;
  290. hasUI = true;
  291. pluginGui->setResizable(resizable);
  292. pluginGui->setTitle(plugin->name());
  293. plugin->setGuiContainer(pluginGui->getContainer());
  294. }
  295. // ---------------------------------------------------------------------
  296. // processing
  297. void setParameter(const int32_t rindex, const double value)
  298. {
  299. qDebug("CarlaPluginClient::setParameter(%i, %g)", rindex, value);
  300. CARLA_ASSERT(plugin);
  301. if (! plugin)
  302. return;
  303. plugin->setParameterValueByRIndex(rindex, value, true, true, false);
  304. }
  305. void setProgram(const uint32_t index)
  306. {
  307. qDebug("CarlaPluginClient::setProgram(%i)", index);
  308. CARLA_ASSERT(engine);
  309. CARLA_ASSERT(plugin);
  310. CARLA_ASSERT(index < plugin->programCount());
  311. if (! (plugin && engine))
  312. return;
  313. if (index >= plugin->programCount())
  314. return;
  315. plugin->setProgram(index, true, true, false, true);
  316. double value;
  317. for (uint32_t i=0; i < plugin->parameterCount(); i++)
  318. {
  319. value = plugin->getParameterValue(i);
  320. engine->osc_send_bridge_set_parameter_value(i, value);
  321. engine->osc_send_bridge_set_default_value(i, value);
  322. }
  323. }
  324. void setMidiProgram(const uint32_t index)
  325. {
  326. qDebug("CarlaPluginClient::setMidiProgram(%i)", index);
  327. CARLA_ASSERT(engine);
  328. CARLA_ASSERT(plugin);
  329. if (! (plugin && engine))
  330. return;
  331. plugin->setMidiProgram(index, true, true, false, true);
  332. double value;
  333. for (uint32_t i=0; i < plugin->parameterCount(); i++)
  334. {
  335. value = plugin->getParameterValue(i);
  336. engine->osc_send_bridge_set_parameter_value(i, value);
  337. engine->osc_send_bridge_set_default_value(i, value);
  338. }
  339. }
  340. void noteOn(const uint8_t channel, const uint8_t note, const uint8_t velo)
  341. {
  342. qDebug("CarlaPluginClient::noteOn(%i, %i, %i)", channel, note, velo);
  343. CARLA_ASSERT(plugin);
  344. CARLA_ASSERT(velo > 0);
  345. if (! plugin)
  346. return;
  347. plugin->sendMidiSingleNote(channel, note, velo, true, true, false);
  348. }
  349. void noteOff(const uint8_t channel, const uint8_t note)
  350. {
  351. qDebug("CarlaPluginClient::noteOff(%i, %i)", channel, note);
  352. CARLA_ASSERT(plugin);
  353. if (! plugin)
  354. return;
  355. plugin->sendMidiSingleNote(channel, note, 0, true, true, false);
  356. }
  357. // ---------------------------------------------------------------------
  358. // plugin
  359. void saveNow()
  360. {
  361. qDebug("CarlaPluginClient::saveNow()");
  362. CARLA_ASSERT(plugin);
  363. CARLA_ASSERT(engine);
  364. if (! (plugin && engine))
  365. return;
  366. plugin->prepareForSave();
  367. for (uint32_t i=0; i < plugin->customDataCount(); i++)
  368. {
  369. const CarlaBackend::CustomData* const cdata = plugin->customData(i);
  370. engine->osc_send_bridge_set_custom_data(CarlaBackend::getCustomDataTypeString(cdata->type), cdata->key, cdata->value);
  371. }
  372. if (plugin->hints() & CarlaBackend::PLUGIN_USES_CHUNKS)
  373. {
  374. void* data = nullptr;
  375. int32_t dataSize = plugin->chunkData(&data);
  376. if (data && dataSize >= 4)
  377. {
  378. QString filePath;
  379. filePath = QDir::tempPath();
  380. #ifdef Q_OS_WIN
  381. filePath += "\\.CarlaChunk_";
  382. #else
  383. filePath += "/.CarlaChunk_";
  384. #endif
  385. filePath += plugin->name();
  386. QFile file(filePath);
  387. if (file.open(QIODevice::WriteOnly))
  388. {
  389. QByteArray chunk((const char*)data, dataSize);
  390. file.write(chunk);
  391. file.close();
  392. engine->osc_send_bridge_set_chunk_data(filePath.toUtf8().constData());
  393. }
  394. }
  395. }
  396. engine->osc_send_bridge_configure(CarlaBackend::CARLA_BRIDGE_MSG_SAVED, "");
  397. }
  398. void setCustomData(const char* const type, const char* const key, const char* const value)
  399. {
  400. qDebug("CarlaPluginClient::setCustomData(\"%s\", \"%s\", \"%s\")", type, key, value);
  401. CARLA_ASSERT(plugin);
  402. if (! plugin)
  403. return;
  404. plugin->setCustomData(CarlaBackend::getCustomDataStringType(type), key, value, true);
  405. }
  406. void setChunkData(const char* const filePath)
  407. {
  408. qDebug("CarlaPluginClient::setChunkData(\"%s\")", filePath);
  409. CARLA_ASSERT(plugin);
  410. if (! plugin)
  411. return;
  412. QString chunkFilePath(filePath);
  413. #ifdef Q_OS_WIN
  414. if (chunkFilePath.startsWith("/"))
  415. {
  416. // running under Wine, posix host
  417. chunkFilePath = chunkFilePath.replace(0, 1, "Z:/");
  418. chunkFilePath = QDir::toNativeSeparators(chunkFilePath);
  419. }
  420. #endif
  421. QFile chunkFile(chunkFilePath);
  422. if (plugin && chunkFile.open(QIODevice::ReadOnly | QIODevice::Text))
  423. {
  424. QTextStream in(&chunkFile);
  425. QString stringData(in.readAll());
  426. chunkFile.close();
  427. chunkFile.remove();
  428. plugin->setChunkData(stringData.toUtf8().constData());
  429. }
  430. }
  431. // ---------------------------------------------------------------------
  432. // callback
  433. void handleCallback(const CarlaBackend::CallbackType action, const int value1, const int value2, const double value3)
  434. {
  435. qDebug("CarlaPluginClient::handleCallback(%s, %i, %i, %g)", CarlaBackend::CallbackType2str(action), value1, value2, value3);
  436. if (! engine)
  437. return;
  438. switch (action)
  439. {
  440. case CarlaBackend::CALLBACK_PARAMETER_VALUE_CHANGED:
  441. #if 0
  442. parametersToUpdate.insert(value1);
  443. #endif
  444. engine->osc_send_bridge_set_parameter_value(value1, value3);
  445. break;
  446. case CarlaBackend::CALLBACK_PROGRAM_CHANGED:
  447. engine->osc_send_bridge_set_program(value1);
  448. break;
  449. case CarlaBackend::CALLBACK_MIDI_PROGRAM_CHANGED:
  450. engine->osc_send_bridge_set_midi_program(value1);
  451. break;
  452. case CarlaBackend::CALLBACK_NOTE_ON:
  453. {
  454. //uint8_t mdata[4] = { 0, MIDI_STATUS_NOTE_ON, (uint8_t)value1, (uint8_t)value2 };
  455. //osc_send_midi(mdata);
  456. break;
  457. }
  458. case CarlaBackend::CALLBACK_NOTE_OFF:
  459. {
  460. //uint8_t mdata[4] = { 0, MIDI_STATUS_NOTE_OFF, (uint8_t)value1, (uint8_t)value2 };
  461. //osc_send_midi(mdata);
  462. break;
  463. }
  464. case CarlaBackend::CALLBACK_SHOW_GUI:
  465. if (value1 == 0)
  466. engine->osc_send_bridge_configure(CarlaBackend::CARLA_BRIDGE_MSG_HIDE_GUI, "");
  467. break;
  468. case CarlaBackend::CALLBACK_RESIZE_GUI:
  469. CARLA_ASSERT(value1 > 0 && value2 > 0);
  470. CARLA_ASSERT(pluginGui);
  471. if (value1 > 0 && value2 > 0 && pluginGui)
  472. pluginGui->setNewSize(value1, value2);
  473. break;
  474. case CarlaBackend::CALLBACK_RELOAD_PARAMETERS:
  475. //if (CARLA_PLUGIN)
  476. //{
  477. // for (uint32_t i=0; i < CARLA_PLUGIN->parameterCount(); i++)
  478. // {
  479. // osc_send_control(i, CARLA_PLUGIN->getParameterValue(i));
  480. // }
  481. //}
  482. break;
  483. case CarlaBackend::CALLBACK_QUIT:
  484. //QApplication::quit();
  485. break;
  486. default:
  487. break;
  488. }
  489. }
  490. // ---------------------------------------------------------------------
  491. static void callback(void* const ptr, CarlaBackend::CallbackType const action, const unsigned short, const int value1, const int value2, const double value3)
  492. {
  493. CARLA_ASSERT(ptr);
  494. if (! ptr)
  495. return;
  496. BridgePluginClient* const _this_ = (BridgePluginClient*)ptr;
  497. _this_->handleCallback(action, value1, value2, value3);
  498. }
  499. protected:
  500. void guiClosedCallback()
  501. {
  502. if (engine)
  503. engine->osc_send_bridge_configure(CarlaBackend::CARLA_BRIDGE_MSG_HIDE_GUI, "");
  504. }
  505. void timerEvent(QTimerEvent* const event)
  506. {
  507. if (qCloseNow)
  508. return quit();
  509. if (event->timerId() == msgTimerGUI)
  510. {
  511. #if 0
  512. if (parametersToUpdate.size() > 0)
  513. {
  514. for (auto it = parametersToUpdate.begin(); it != parametersToUpdate.end(); it++)
  515. {
  516. const int32_t paramId(*it);
  517. engine->osc_send_bridge_set_parameter_value(paramId, plugin->getParameterValue(paramId));
  518. }
  519. parametersToUpdate.clear();
  520. }
  521. #endif
  522. if (plugin)
  523. plugin->idleGui();
  524. }
  525. else if (event->timerId() == msgTimerOSC)
  526. {
  527. if (! CarlaClient::oscIdle())
  528. {
  529. CARLA_ASSERT(msgTimerOSC == 0);
  530. msgTimerOSC = 0;
  531. return;
  532. }
  533. }
  534. QApplication::timerEvent(event);
  535. }
  536. // ---------------------------------------------------------------------
  537. private:
  538. bool hasUI;
  539. int msgTimerGUI, msgTimerOSC;
  540. #if 0
  541. std::set<int32_t> parametersToUpdate;
  542. #endif
  543. CarlaBackend::CarlaEngine* engine;
  544. CarlaBackend::CarlaPlugin* plugin;
  545. BridgePluginGUI* pluginGui;
  546. };
  547. // -------------------------------------------------------------------------
  548. CARLA_BRIDGE_END_NAMESPACE
  549. int main(int argc, char* argv[])
  550. {
  551. if (argc != 6)
  552. {
  553. qWarning("usage: %s <osc-url|\"null\"> <type> <filename> <name|\"(none)\"> <label>", argv[0]);
  554. return 1;
  555. }
  556. qargc = argc;
  557. qargv = argv;
  558. const char* const oscUrl = argv[1];
  559. const char* const stype = argv[2];
  560. const char* const filename = argv[3];
  561. const char* name = argv[4];
  562. const char* const label = argv[5];
  563. const bool useOsc = strcmp(oscUrl, "null");
  564. if (strcmp(name, "(none)") == 0)
  565. name = nullptr;
  566. CarlaBackend::PluginType itype;
  567. if (strcmp(stype, "LADSPA") == 0)
  568. itype = CarlaBackend::PLUGIN_LADSPA;
  569. else if (strcmp(stype, "DSSI") == 0)
  570. itype = CarlaBackend::PLUGIN_DSSI;
  571. else if (strcmp(stype, "LV2") == 0)
  572. itype = CarlaBackend::PLUGIN_LV2;
  573. else if (strcmp(stype, "VST") == 0)
  574. itype = CarlaBackend::PLUGIN_VST;
  575. else
  576. {
  577. itype = CarlaBackend::PLUGIN_NONE;
  578. qWarning("Invalid plugin type '%s'", stype);
  579. return 1;
  580. }
  581. // Init bridge client
  582. CarlaBridge::BridgePluginClient client;
  583. client.init();
  584. // Init OSC
  585. if (useOsc && ! client.oscInit(oscUrl))
  586. {
  587. client.quit();
  588. return -1;
  589. }
  590. // Listen for ctrl+c or sigint/sigterm events
  591. initSignalHandler();
  592. // Init backend engine
  593. CarlaBackend::CarlaEngine* engine = CarlaBackend::CarlaEngine::newDriverByName("JACK");
  594. engine->setCallback(client.callback, &client);
  595. // bridge client <-> engine
  596. client.registerOscEngine(engine);
  597. // Init engine
  598. QString engName = QString("%1 (master)").arg(name ? name : label);
  599. engName.truncate(engine->maxClientNameSize());
  600. if (! engine->init(engName.toUtf8().constData()))
  601. {
  602. const char* const lastError = CarlaBackend::getLastError();
  603. qWarning("Bridge engine failed to start, error was:\n%s", lastError);
  604. engine->close();
  605. delete engine;
  606. client.sendOscBridgeError(lastError);
  607. client.quit();
  608. return 2;
  609. }
  610. // Init plugin
  611. short id = engine->addPlugin(itype, filename, name, label);
  612. int ret;
  613. if (id >= 0 && id < CarlaBackend::MAX_PLUGINS)
  614. {
  615. CarlaBackend::CarlaPlugin* const plugin = engine->getPlugin(id);
  616. client.setStuff(engine, plugin);
  617. // create window if needed
  618. bool guiResizable;
  619. CarlaBackend::GuiType guiType;
  620. plugin->getGuiInfo(&guiType, &guiResizable);
  621. if (guiType == CarlaBackend::GUI_INTERNAL_QT4 || guiType == CarlaBackend::GUI_INTERNAL_COCOA || guiType == CarlaBackend::GUI_INTERNAL_HWND || guiType == CarlaBackend::GUI_INTERNAL_X11)
  622. {
  623. client.createWindow(guiResizable);
  624. }
  625. if (! useOsc)
  626. plugin->setActive(true, false, false);
  627. client.exec(nullptr, !useOsc);
  628. ret = 0;
  629. }
  630. else
  631. {
  632. const char* const lastError = CarlaBackend::getLastError();
  633. qWarning("Plugin failed to load, error was:\n%s", lastError);
  634. if (useOsc)
  635. client.sendOscBridgeError(lastError);
  636. ret = 1;
  637. }
  638. engine->removeAllPlugins();
  639. engine->close();
  640. delete engine;
  641. if (useOsc)
  642. {
  643. // Close OSC
  644. client.oscClose();
  645. // bridge client can't be closed manually, only by host
  646. }
  647. else
  648. {
  649. // Close bridge client
  650. client.quit();
  651. }
  652. return ret;
  653. }
  654. #endif // BUILD_BRIDGE_PLUGIN