4 Recall to : disable XInitThreads in
5 doPlatformSpecificInitialisation/doPlatformSpecificShutdown with
6 also the signal redirection stuff, and the X11 io error stuff (this
7 is done in juce-git since 2010/10/28).
9 Recall also to export only a few symbols, in order to avoid
10 potential clashes with the host symbols. The best is to use a linker
13 g++ -shared -Wl,version-script=linux_dssi_symbols.map
15 with linux_dssi_symbols.map:
24 And build your sources with -fvisibility=hidden. The best is also to
25 check that nm -D plugin.so does not list a ton of exported symbols
26 that may clash, such as zlib functions / png stuff or md5_init ..
27 Stuff from libstdc++ should be safe, I think.
30 IMPORTANT: make sure that the .so file used for the VST plugin and
31 the DSSI are not symlinks to the same physical file, or that will
32 cause issues (if a host allows VST and DSSI to be loaded at the same
33 time, such as renoise, then in that case both vst and dssi will
34 share the same juce environment, but they both use a different
35 message thread, and they both call ShutdownJuce_GUI() without caring
36 for each other so horrible crashes happen).
41 #if defined(TARGET_LINUX)
43 #include <X11/Xutil.h>
44 #include <X11/Xatom.h>
50 #include <alsa/asoundlib.h>
51 #include "JucePluginCharacteristics.h"
58 #define OSCPKT_OSTREAM_OUTPUT
59 #include "oscpkt/oscpkt.hh"
60 #include "oscpkt/udp.hh"
63 /* conveniency functions for debugging ..*/
64 inline std::ostream
&operator<<(std::ostream
&os
, const juce::String
&s
) {
71 extern Display
* display
;
72 extern bool juce_postMessageToSystemQueue (void* message
);
74 class DssiSharedMessageThread
: public Thread
77 DssiSharedMessageThread()
78 : Thread (JUCE_T("DssiMessageThread")),
86 ~DssiSharedMessageThread()
88 signalThreadShouldExit();
89 JUCEApplication::quit();
90 waitForThreadToExit (5000);
91 clearSingletonInstance();
98 MessageManager::getInstance()->setCurrentThreadAsMessageThread();
100 while ((! threadShouldExit()) && MessageManager::getInstance()->runDispatchLoopUntil (250))
105 juce_DeclareSingleton (DssiSharedMessageThread
, false)
111 juce_ImplementSingleton (DssiSharedMessageThread
);
113 class JuceDSSIWrapper
;
115 class DssiEditorCompWrapper
: public DocumentWindow
117 JuceDSSIWrapper
* wrapper
;
120 DssiEditorCompWrapper (JuceDSSIWrapper
* const wrapper_
,
121 AudioProcessorEditor
* const editor
, const String
&title
,
123 : DocumentWindow(title
, Colours::white
, DocumentWindow::allButtons
, false)
127 setTitleBarHeight(0);
128 setUsingNativeTitleBar(true);
129 editor
->setOpaque (true);
130 setDropShadowEnabled(false);
131 setContentComponent(editor
, true, true);
133 if (xpos
!= -10000 && ypos
!= -10000) {
134 setTopLeftPosition(xpos
, ypos
-20 /* position bug? */ );
135 } else setCentreRelative(.5f
, .5f
);
137 Component::addToDesktop(getDesktopWindowStyleFlags());
141 AudioProcessorEditor
* getEditorComp() const
143 return dynamic_cast <AudioProcessorEditor
*> (getContentComponent());
146 BorderSize
getBorderThickness() const { return BorderSize(0); }
147 BorderSize
getContentComponentBorder() const { return BorderSize(0); }
149 void closeButtonPressed();
151 juce_UseDebuggingNewOperator
155 struct OscAction
: public juce::Message
{
159 class DssiMinimalOscServer
: public Thread
{
161 MessageListener
*listener
;
163 DssiMinimalOscServer() : Thread(JUCE_T("osc")), listener(0) {}
164 ~DssiMinimalOscServer() {
165 jassert(!isThreadRunning());
168 void setListener(MessageListener
*l
) { listener
= l
; }
169 void sendMessageTo(const String
&osc_url
, const String
&message_path
,
170 const String
&arg1
= String::empty
, const String
&arg2
= String::empty
) {
171 char *url_hostname
= lo_url_get_hostname(osc_url
.toUTF8());
172 char *url_port
= lo_url_get_port(osc_url
.toUTF8());
173 char *url_path
= lo_url_get_path(osc_url
.toUTF8());
176 path
<< url_path
<< message_path
; path
= path
.replace("//","/");
177 lo_address hostaddr
= lo_address_new(url_hostname
, url_port
);
179 if (!arg1
.isNotEmpty()) {
180 lo_send(hostaddr
, path
.toUTF8(), "");
181 } else if (!arg2
.isNotEmpty()) {
182 lo_send(hostaddr
, path
.toUTF8(), "s", arg1
.toUTF8());
184 lo_send(hostaddr
, path
.toUTF8(), "ss", arg1
.toUTF8(), arg2
.toUTF8());
186 lo_address_free(hostaddr
);
192 if (isThreadRunning()) return;
193 serv
= lo_server_new(NULL
, NULL
);
194 lo_server_add_method(serv
, NULL
, NULL
, &osc_callback
, this);
198 if (!isThreadRunning()) return;
199 signalThreadShouldExit();
201 lo_server_free(serv
);
204 char *s
= lo_server_get_url(serv
);
205 String url
; url
<< s
; free(s
);
209 while (!threadShouldExit()) {
210 lo_server_recv_noblock(serv
, 50);
213 static int osc_callback(const char *path
, const char *types
,
214 lo_arg
**argv
, int argc
, lo_message
, void *user_data
) {
215 OscAction
*a
= new OscAction
;
217 if (argc
> 0 && types
[0] == 's') {
218 a
->arg
<< &argv
[0]->s
;
220 ((DssiMinimalOscServer
*)user_data
)->listener
->postMessage(a
);
225 // ad-hoc server that implements only what we need for dssi..
226 class DssiMinimalOscServer
: public Thread
{
227 oscpkt::UdpSocket serv
;
228 MessageListener
*listener
;
230 DssiMinimalOscServer() : Thread(JUCE_T("osc")), listener(0) {}
231 void setListener(MessageListener
*l
) { listener
= l
; }
232 void sendMessageTo(const String
&osc_url
, const String
&message_path
,
233 const String
&arg1
= String::empty
, const String
&arg2
= String::empty
) {
234 oscpkt::Url
url(osc_url
.toUTF8());
235 if (!url
.isOk()) return;
237 oscpkt::PacketWriter pw
;
239 std::string path
= url
.path
;
240 if (!path
.empty() && path
[path
.size()-1] == '/') path
.resize(path
.size()-1);
241 path
+= message_path
.toUTF8();
244 if (arg1
.isNotEmpty()) msg
.pushStr(arg1
.toUTF8());
245 if (arg2
.isNotEmpty()) msg
.pushStr(arg2
.toUTF8());
248 oscpkt::UdpSocket sock
; sock
.connectTo(url
.hostname
, url
.port
);
249 bool ok
= sock
.sendPacket(pw
.packetData(), pw
.packetSize());
251 cerr
<< "Could not send " << msg
.addressPattern() << " message to "
252 << url
.hostname
<< ":" << url
.port
<< " '" << sock
.errorMessage() << "'\n";
256 if (isThreadRunning()) return;
257 serv
.bindTo(0 /* any port */);
259 cerr
<< "cannot start osc server: " << serv
.errorMessage() << "\n"; return;
264 if (!isThreadRunning()) return;
265 signalThreadShouldExit();
270 String s
; s
<< ("osc.udp://" + serv
.localHostNameWithPort() + "/").c_str();
274 while (!threadShouldExit() && serv
.isOk()) {
275 if (serv
.receiveNextPacket(50 /* timeout, in ms */)) {
276 oscpkt::PacketReader
pr(serv
.packetData(), serv
.packetSize());
278 oscpkt::Message
*msg
;
279 while ((msg
= pr
.popMessage())) {
280 OscAction
*a
= new OscAction
;
281 a
->msg
<< msg
->addressPattern().c_str();
282 if (msg
->arg().nbArgRemaining() && msg
->arg().isStr()) {
283 std::string s
; msg
->arg().popStr(s
);
286 listener
->postMessage(a
);
296 static Array
<void*> activePlugins
;
298 extern AudioProcessor
* JUCE_CALLTYPE
createPluginFilter();
300 static LADSPA_Descriptor
*ladspa_desc
= 0;
301 static DSSI_Descriptor
*dssi_desc
= 0;
303 struct JuceDSSIWrapper
: public Timer
, public MessageListener
{
305 static LADSPA_Descriptor
*getLadspaDescriptor() {
306 if (!ladspa_desc
) initialiseDescriptors();
310 static DSSI_Descriptor
*getDssiDescriptor() {
311 if (!dssi_desc
) initialiseDescriptors();
315 static void initialiseDescriptors();
317 static void destroyDescriptors() {
319 for (size_t i
=0; i
< ladspa_desc
->PortCount
; ++i
) {
320 free((void*)ladspa_desc
->PortNames
[i
]);
322 delete[] ladspa_desc
->PortDescriptors
;
323 delete[] ladspa_desc
->PortNames
;
324 delete[] ladspa_desc
->PortRangeHints
;
334 static void callbackCleanup(LADSPA_Handle instance
) {
336 MessageManagerLock mmLock
;
337 delete (JuceDSSIWrapper
*)instance
;
339 if (activePlugins
.size() == 0) {
340 DssiSharedMessageThread::deleteInstance();
345 static LADSPA_Handle
callbackInstantiate(const LADSPA_Descriptor
*,
346 unsigned long s_rate
) {
347 if (activePlugins
.size() == 0) {
348 DssiSharedMessageThread::getInstance();
350 MessageManagerLock mmLock
;
351 return new JuceDSSIWrapper(s_rate
);
355 static void callbackConnectPort(LADSPA_Handle instance
, unsigned long port
,
356 LADSPA_Data
* data
) {
357 MessageManagerLock mmLock
;
358 ((JuceDSSIWrapper
*)instance
)->connectPort(port
, data
);
361 static void callbackActivate(LADSPA_Handle instance
) {
362 MessageManagerLock mmLock
;
363 ((JuceDSSIWrapper
*)instance
)->activate();
366 static void callbackDeactivate(LADSPA_Handle instance
) {
367 MessageManagerLock mmLock
;
368 ((JuceDSSIWrapper
*)instance
)->deactivate();
371 static void callbackRunAsEffect(LADSPA_Handle instance
,
372 unsigned long sample_count
) {
373 ((JuceDSSIWrapper
*)instance
)->run(sample_count
, 0, 0);
376 static void callbackRun(LADSPA_Handle instance
, unsigned long sample_count
,
377 snd_seq_event_t
*events
, unsigned long event_count
) {
378 ((JuceDSSIWrapper
*)instance
)->run(sample_count
, events
, event_count
);
381 static char* callbackConfigure(LADSPA_Handle instance
,
382 const char *key
, const char *value
) {
383 MessageManagerLock mmLock
;
384 return ((JuceDSSIWrapper
*)instance
)->configure(key
, value
);
387 static const DSSI_Program_Descriptor
*callbackGetProgram(LADSPA_Handle instance
,
388 unsigned long index
) {
389 MessageManagerLock mmLock
;
390 return ((JuceDSSIWrapper
*)instance
)->getProgram(index
);
393 static void callbackSelectProgram(LADSPA_Handle instance
,
395 unsigned long program
) {
396 MessageManagerLock mmLock
;
397 return ((JuceDSSIWrapper
*)instance
)->selectProgram(bank
, program
);
402 MidiBuffer midi_buffer
;
404 float *output_port
[JucePlugin_MaxNumOutputChannels
];
406 #define UNSET_PARAMETER_VALUE 1e10
407 Array
<float*> param_port
;
408 Array
<float> param_saved
; // value of the parameters saved at previous callback, to detect param value change
410 AudioProcessor
*filter
;
412 DssiEditorCompWrapper
* editorComp
;
413 bool shouldDeleteEditor
;
416 int x_editor
, y_editor
;
418 DssiMinimalOscServer osc_server
; // used only for comminucation with the gui
422 JuceDSSIWrapper(unsigned long s_rate
) {
425 shouldDeleteEditor
= false;
426 x_editor
= y_editor
= -10000;
427 osc_server
.setListener(this);
429 for (int c
=0; c
< JucePlugin_MaxNumOutputChannels
; ++c
) output_port
[c
] = 0;
430 sample_rate
= s_rate
;
431 filter
= createPluginFilter();
432 param_port
.insertMultiple(0, 0, filter
->getNumParameters());
433 param_saved
.insertMultiple(0, UNSET_PARAMETER_VALUE
, filter
->getNumParameters());
434 activePlugins
.add (this);
439 osc_server
.stopServer();
443 deleteAndZero(filter
);
444 jassert (activePlugins
.contains (this));
445 activePlugins
.removeValue (this);
448 AudioProcessor
*getFilter() { return filter
; }
450 void connectPort(unsigned long port
, LADSPA_Data
*data
) {
451 if (port
< JucePlugin_MaxNumOutputChannels
) {
452 output_port
[port
] = data
;
454 int param
= (int)port
- JucePlugin_MaxNumOutputChannels
;
455 if (param
< param_port
.size()) {
456 param_port
.set(param
, (float*)data
);
457 param_saved
.set(param
, UNSET_PARAMETER_VALUE
);
463 unsigned block_size
= 512;
464 filter
->setNonRealtime(false);
465 filter
->setPlayConfigDetails (0, JucePlugin_MaxNumOutputChannels
,
466 sample_rate
, block_size
);
467 filter
->prepareToPlay(sample_rate
, block_size
);
470 snd_midi_event_new(sizeof midi_parser_buffer
, &midi_parser
);
474 filter
->releaseResources();
476 snd_midi_event_free(midi_parser
);
479 DSSI_Program_Descriptor latest_program_descriptor
;
480 std::string latest_program_descriptor_name
;
481 const DSSI_Program_Descriptor
*getProgram(unsigned long index
) {
482 if (index
< (unsigned long)filter
->getNumPrograms()) {
483 latest_program_descriptor
.Bank
= 0;
484 latest_program_descriptor
.Program
= index
;
485 latest_program_descriptor_name
= filter
->getProgramName((int)index
).toUTF8();
486 latest_program_descriptor
.Name
= latest_program_descriptor_name
.c_str();
487 return &latest_program_descriptor
;
492 void selectProgram(unsigned long bank
, unsigned long program
) {
493 if (bank
== 0) filter
->setCurrentProgram((int)program
);
497 // update the port values from the plugin parameter values
498 void updateParameters() {
499 for (int i
=0; i
< param_port
.size(); ++i
) {
501 float v
= filter
->getParameter(i
);
503 param_saved
.set(i
,v
);
508 void run(unsigned long sample_count
, snd_seq_event_t
*events
, unsigned long event_count
) {
509 /* handle incoming midi events */
511 for (size_t i
=0; i
< event_count
; ++i
) {
512 const int num_bytes
= snd_midi_event_decode(midi_parser
, midi_parser_buffer
, sizeof midi_parser_buffer
, &events
[i
]);
513 snd_midi_event_reset_decode(midi_parser
);
515 midi_buffer
.addEvent(midi_parser_buffer
, num_bytes
, events
[i
].time
.tick
);
520 /* handle parameter changes initiated by the host */
521 for (int i
=0; i
< param_port
.size(); ++i
) {
523 if (param_saved
[i
] != *param_port
[i
]) {
524 filter
->setParameter(i
, *param_port
[i
]);
530 const ScopedLock
sl (filter
->getCallbackLock());
531 if (filter
->isSuspended()) {
532 for (int i
= 0; i
< JucePlugin_MaxNumOutputChannels
; ++i
)
533 zeromem (output_port
[i
], sizeof (float) * sample_count
);
535 AudioSampleBuffer
chans (output_port
, JucePlugin_MaxNumOutputChannels
, sample_count
);
536 filter
->processBlock (chans
, midi_buffer
);
540 /* read back parameter values */
543 if (!midi_buffer
.isEmpty()) { midi_buffer
.clear(); }
546 struct FakeGuiConnectMessage
: public Message
{
548 FakeGuiConnectMessage(const String
&s
) { arg
= s
; }
549 ~FakeGuiConnectMessage() throw() {}
552 char *configure(const char *key
, const char *value
) {
553 if (strcmp(key
, "guiVisible") == 0) {
554 postMessage(new FakeGuiConnectMessage(String(value
)));
559 void handleMessage(const Message
&msg
) {
560 const FakeGuiConnectMessage
*fmsg
;
561 if ((fmsg
= dynamic_cast<const FakeGuiConnectMessage
*>(&msg
))) {
562 bool show
= fmsg
->arg
.isNotEmpty();
564 StringArray arg
; arg
.addTokens(fmsg
->arg
, JUCE_T("|"), JUCE_T(""));
565 if (arg
.size() == 2) {
566 gui_osc_url
= arg
[0];
567 String window_title
= arg
[1];
569 /* only 1 gui will be opened at once, request for new guis will automatically close the older ones */
571 createEditorComp(window_title
);
578 const OscAction
*osc
;
579 if ((osc
= dynamic_cast<const OscAction
*>(&msg
))) {
580 if (osc
->msg
== "/internal_gui_hide") {
582 } else if (osc
->msg
== "/exiting") {
584 gui_osc_url
= String::empty
;
589 static AudioProcessor
*initialiseAndCreateFilter() {
590 initialiseJuce_GUI();
591 AudioProcessor
* filter
= createPluginFilter();
595 void createEditorComp(const String
&title
);
596 void deleteEditor (bool canDeleteLaterIfModal
);
598 void notifyRemoteProcess(bool b
) {
600 osc_server
.startServer();
601 osc_server
.sendMessageTo(gui_osc_url
, JUCE_T("/internal_gui_status"), osc_server
.getOscUrl());
603 osc_server
.sendMessageTo(gui_osc_url
, JUCE_T("/internal_gui_status"), JUCE_T(""));
604 osc_server
.stopServer();
608 void timerCallback() {
609 if (shouldDeleteEditor
) {
610 shouldDeleteEditor
= false;
613 if (osc_server
.isThreadRunning() && gui_osc_url
.isNotEmpty()) {
614 // perdiodically ping the gui process, so that it now it has not been abandonned as an orphan process...
615 notifyRemoteProcess(editorComp
!=0 );
619 snd_midi_event_t
* midi_parser
;
620 uint8_t midi_parser_buffer
[16384];
622 }; // end of class JuceDSSIWrapper
624 void DssiEditorCompWrapper::closeButtonPressed() {
625 wrapper
->deleteEditor(true);
628 void JuceDSSIWrapper::createEditorComp(const String
&title
) {
629 if (hasShutdown
|| filter
== 0)
632 if (editorComp
== 0) {
633 AudioProcessorEditor
* const ed
= filter
->createEditorIfNeeded();
635 editorComp
= new DssiEditorCompWrapper(this, ed
, title
, x_editor
, y_editor
);
636 notifyRemoteProcess(true);
639 shouldDeleteEditor
= false;
642 void JuceDSSIWrapper::deleteEditor (bool canDeleteLaterIfModal
)
644 PopupMenu::dismissAllActiveMenus();
646 if (editorComp
!= 0) {
647 Component
* const modalComponent
= Component::getCurrentlyModalComponent();
648 if (modalComponent
!= 0) {
649 modalComponent
->exitModalState (0);
651 if (canDeleteLaterIfModal
) {
652 shouldDeleteEditor
= true;
657 filter
->editorBeingDeleted (editorComp
->getEditorComp());
658 x_editor
= editorComp
->getX();
659 y_editor
= editorComp
->getY();
660 deleteAndZero (editorComp
);
662 notifyRemoteProcess(false);
664 // there's some kind of component currently modal, but the host
665 // is trying to delete our plugin. You should try to avoid this happening..
666 jassert (Component::getCurrentlyModalComponent() == 0);
670 void JuceDSSIWrapper::initialiseDescriptors() {
671 initialiseJuce_GUI();
672 AudioProcessor
*plugin
= createPluginFilter();
675 LADSPA_PortDescriptor
*port_descriptors
;
676 LADSPA_PortRangeHint
*port_range_hints
;
678 ladspa_desc
= new LADSPA_Descriptor
; assert(ladspa_desc
);
679 ladspa_desc
->UniqueID
= JucePlugin_VSTUniqueID
; // not used by dssi hosts anyway..
680 ladspa_desc
->Label
= "Main"; // must not contain white spaces
681 ladspa_desc
->Properties
= LADSPA_PROPERTY_REALTIME
; //LADSPA_PROPERTY_HARD_RT_CAPABLE;
682 ladspa_desc
->Name
= JucePlugin_Name
" DSSI Synth";
683 ladspa_desc
->Maker
= JucePlugin_Manufacturer
;
684 ladspa_desc
->Copyright
= "Copyright (c) " JucePlugin_Manufacturer
" 2010";
685 ladspa_desc
->PortCount
= JucePlugin_MaxNumOutputChannels
+ plugin
->getNumParameters();
688 port_descriptors
= new LADSPA_PortDescriptor
[ladspa_desc
->PortCount
];
689 memset(port_descriptors
, 0, sizeof(LADSPA_PortDescriptor
)*ladspa_desc
->PortCount
);
690 ladspa_desc
->PortDescriptors
= port_descriptors
;
692 port_range_hints
= new LADSPA_PortRangeHint
[ladspa_desc
->PortCount
];
693 memset(port_range_hints
, 0, sizeof(LADSPA_PortRangeHint
)*ladspa_desc
->PortCount
);
694 ladspa_desc
->PortRangeHints
= port_range_hints
;
696 port_names
= new char *[ladspa_desc
->PortCount
];
697 ladspa_desc
->PortNames
= port_names
;
699 unsigned long port
= 0;
700 for (int channel
=0; channel
< JucePlugin_MaxNumOutputChannels
; ++channel
, ++port
) {
701 char s
[100]; snprintf(s
, 100, "Output%d", channel
+1);
702 port_names
[port
] = strdup(s
);
704 port_descriptors
[port
] = LADSPA_PORT_OUTPUT
|LADSPA_PORT_AUDIO
;
705 port_range_hints
[port
].HintDescriptor
= 0;
707 for (int param
=0; param
< plugin
->getNumParameters(); ++param
, ++port
) {
708 port_names
[port
] = strdup(plugin
->getParameterName(param
).toUTF8());
709 port_descriptors
[port
] = LADSPA_PORT_INPUT
| LADSPA_PORT_CONTROL
;
710 port_range_hints
[port
].HintDescriptor
= LADSPA_HINT_BOUNDED_BELOW
| LADSPA_HINT_BOUNDED_ABOVE
;
711 port_range_hints
[port
].LowerBound
= 0;
712 port_range_hints
[port
].UpperBound
= 1;
714 jassert(port
== ladspa_desc
->PortCount
);
716 ladspa_desc
->activate
= &callbackActivate
;
717 ladspa_desc
->cleanup
= &callbackCleanup
;
718 ladspa_desc
->connect_port
= &callbackConnectPort
;
719 ladspa_desc
->deactivate
= &callbackDeactivate
;
720 ladspa_desc
->instantiate
= &callbackInstantiate
;
721 ladspa_desc
->run
= &callbackRunAsEffect
;
722 ladspa_desc
->run_adding
= NULL
;
723 ladspa_desc
->set_run_adding_gain
= NULL
;
726 dssi_desc
= new DSSI_Descriptor
;
727 dssi_desc
->DSSI_API_Version
= 1;
728 dssi_desc
->LADSPA_Plugin
= ladspa_desc
;
729 dssi_desc
->configure
= &callbackConfigure
;
730 dssi_desc
->get_program
= callbackGetProgram
;
731 dssi_desc
->get_midi_controller_for_port
= NULL
;
732 dssi_desc
->select_program
= callbackSelectProgram
;
733 dssi_desc
->run_synth
= &callbackRun
;
734 dssi_desc
->run_synth_adding
= NULL
;
735 dssi_desc
->run_multiple_synths
= NULL
;
736 dssi_desc
->run_multiple_synths_adding
= NULL
;
742 __attribute__((destructor
)) void dssi_destructor()
744 jassert(activePlugins
.size() == 0);
745 JuceDSSIWrapper::destroyDescriptors();
748 extern "C" __attribute__ ((visibility("default"))) const LADSPA_Descriptor
*ladspa_descriptor(unsigned long index
)
750 return (index
== 0 ? JuceDSSIWrapper::getLadspaDescriptor() : 0);
753 extern "C" __attribute__ ((visibility("default"))) const DSSI_Descriptor
*dssi_descriptor(unsigned long index
)
755 return (index
== 0 ? JuceDSSIWrapper::getDssiDescriptor() : 0);
758 /* ---------- the fake gui process starts below ---------- */
760 struct FakeExternalGUI
: public MessageListener
, public Timer
{
763 String plugin_osc_url
;
764 DssiMinimalOscServer osc_server
;
765 juce::Time time_last_ping
;
767 FakeExternalGUI() { osc_server
.setListener(this); startTimer(1000); }
768 ~FakeExternalGUI() { osc_server
.stopServer(); }
770 // notify the plugin via the host, using the '/configure' callback
771 void show(bool do_show
) {
774 conf
<< osc_server
.getOscUrl() << "|" << window_title
;
776 osc_server
.sendMessageTo(host_osc_url
, "/configure", "guiVisible", conf
);
777 if (!do_show
&& plugin_osc_url
.isNotEmpty())
778 osc_server
.sendMessageTo(plugin_osc_url
, "/internal_gui_hide", "0");
782 MessageManager::getInstance()->stopDispatchLoop();
785 void init(const char *host_osc_url_
, const char *plugin_so_name
,
786 const char *label
, const char *friendlyname
) {
787 (void)plugin_so_name
;
788 host_osc_url
<< host_osc_url_
;
789 window_title
<< label
<< " - " << friendlyname
;
790 osc_server
.startServer();
791 osc_server
.sendMessageTo(host_osc_url
, "/update", osc_server
.getOscUrl() + "dssi");
794 void handleMessage(const Message
&msg
) {
795 const OscAction
*osc
;
796 if ((osc
= dynamic_cast<const OscAction
*>(&msg
))) {
797 if (osc
->msg
== "/dssi/hide") show(false);
798 else if (osc
->msg
== "/dssi/show") show(true);
799 else if (osc
->msg
== "/dssi/quit") quit();
800 else if (osc
->msg
== "/internal_gui_status") {
801 plugin_osc_url
= osc
->arg
;
802 time_last_ping
= juce::Time::getCurrentTime();
803 if (!plugin_osc_url
.isNotEmpty()) quit();
808 void timerCallback() {
809 juce::Time t
= juce::Time::getCurrentTime();
810 if (plugin_osc_url
.isNotEmpty() && (t
-time_last_ping
).inMilliseconds() > 5000) {
811 /* no ping for 5 seconds, the fake gui process kills itself.. */
817 osc_server
.sendMessageTo(host_osc_url
, "/exiting");
818 if (plugin_osc_url
) osc_server
.sendMessageTo(plugin_osc_url
, "/exiting");
822 FakeExternalGUI
*fake
= 0;
824 void handle_sigterm(int) {
825 static int count
= 0;
832 extern "C" __attribute__ ((visibility("default"))) int dssi_gui_main(const char *osc_host_url
, const char *plugin_so_name
,
833 const char *label
, const char *friendlyname
) {
834 initialiseJuce_GUI();
835 signal(SIGTERM
, &handle_sigterm
);
837 fake
= new FakeExternalGUI();
838 fake
->init(osc_host_url
, plugin_so_name
, label
, friendlyname
);
840 MessageManager::getInstance()->runDispatchLoop();