1 /*  SpiralPlugin
2  *  Copyleft (C) 2000 David Griffiths <dave@pawfal.org>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 */
18 
19 #include "JackPlugin.h"
20 #include "JackPluginGUI.h"
21 #include <FL/fl_draw.H>
22 #include <FL/Fl_File_Chooser.H>
23 #include <FL/Fl_Hold_Browser.H>
24 #include <algorithm>
25 
26 using namespace std;
27 
28 ////////////////////////////////////////////////////////////////////////
29 
OptionsList(const std::vector<string> & List)30 int OptionsList(const std::vector<string> &List)
31 {
32 	Fl_Double_Window *Win    = new Fl_Double_Window(300,300);
33 	Fl_Button *Ok            = new Fl_Button(10,275,40,20,"Ok");
34 	Ok->labelsize(10);
35 	Fl_Button *Cancel        = new Fl_Button(50,275,40,20,"Cancel");
36 	Cancel->labelsize(10);
37 	Fl_Hold_Browser* Browser = new Fl_Hold_Browser(5,5,290,265,"");
38 
39 	for (std::vector<string>::const_iterator i = List.begin();
40 		 i!=List.end(); i++)
41 	{
42 		Browser->add(i->c_str());
43 	}
44 
45 	Win->show();
46 
47 	int Choice=-1;
48 
49 	for (;;)
50 	{
51     	Fl::wait();
52     	Fl_Widget* o = Fl::readqueue();
53     	if (o==Ok || o==Browser)
54 		{
55 			Choice=Browser->value();
56 			Win->hide();
57 			delete Win;
58 			break;
59 		}
60 		if (o==Cancel)
61 		{
62 			Choice=-1;
63 			Win->hide();
64 			delete Win;
65 			break;
66 		}
67 
68 		if (o==Win) break;
69   	}
70 
71 	return Choice;
72 }
73 
74 ////////////////////////////////////////////////////////////////////////
75 
JackPluginGUI(int w,int h,JackPlugin * o,ChannelHandler * ch,const HostInfo * Info)76 JackPluginGUI::JackPluginGUI(int w, int h,JackPlugin *o,ChannelHandler *ch,const HostInfo *Info) :
77 SpiralPluginGUI(w,h,o,ch)
78 {
79         m_GUIColour = (Fl_Color)Info->GUI_COLOUR;
80         m_JackPlugin =o;
81         m_JackClient = o->GetJackClient();
82 
83 	m_Indicator = new Fl_LED_Button(w/2 - 15,15,30,30,"");
84 	m_Indicator->value(0);
85 	m_Indicator->color(FL_RED);
86         add(m_Indicator);
87 
88 	m_Remove = new Fl_Button(5,15,25,25,"-");
89         m_Remove->box (FL_PLASTIC_UP_BOX);
90         m_Remove->color (m_GUIColour);
91 	m_Remove->type(0);
92 	m_Remove->labelsize(2);
93         m_Remove->selection_color (m_GUIColour);
94 	m_Remove->callback((Fl_Callback*)cb_Remove, this);
95         add(m_Remove);
96 
97 	m_Add = new Fl_Button(30,15,25,25,"+");
98         m_Add->box (FL_PLASTIC_UP_BOX);
99         m_Add->color (m_GUIColour);
100 	m_Add->type(0);
101 	m_Add->labelsize(2);
102         m_Add->selection_color (m_GUIColour);
103 	m_Add->callback((Fl_Callback*)cb_Add, this);
104         add(m_Add);
105 
106 	m_Attach = new Fl_Button(5,45,w - 10,20,"Attach");
107         m_Attach->box (FL_PLASTIC_UP_BOX);
108         m_Attach->color (m_GUIColour);
109 	m_Attach->type(0);
110 	m_Attach->labelsize(10);
111         m_Attach->selection_color (m_GUIColour);
112 	m_Attach->callback((Fl_Callback*)cb_Attach, this);
113         add(m_Attach);
114 
115 	m_Detach = new Fl_Button(5,65,w - 10,20,"Detach");
116         m_Detach->box (FL_PLASTIC_UP_BOX);
117         m_Detach->color (m_GUIColour);
118 	m_Detach->type(0);
119 	m_Detach->labelsize(10);
120         m_Detach->selection_color (m_GUIColour);
121 	m_Detach->callback((Fl_Callback*)cb_Detach, this);
122         add(m_Detach);
123 
124 	m_Scroll = new Fl_Scroll(5, 90, w - 10, h - 102);
125 	m_Scroll->box(FL_PLASTIC_DOWN_BOX);
126 	m_Scroll->type(Fl_Scroll::VERTICAL_ALWAYS);
127         m_Scroll->position(0, 0);
128         add(m_Scroll);
129 
130         m_OutputPack = new Fl_Pack(15, 90, 85, h - 102);
131 	m_Scroll->add(m_OutputPack);
132 
133         m_InputPack = new Fl_Pack(110, 90, 85, h - 102);
134 	m_Scroll->add(m_InputPack);
135 
136 	for (int n=0; n<m_JackClient->GetJackInputCount(); n++)
137 	{
138 		AddOutput();
139 		AddInput();
140 	}
141 
142 	end();
143 }
144 
UpdateValues(SpiralPlugin * o)145 void JackPluginGUI::UpdateValues(SpiralPlugin *o)
146 {
147 	//To make sure buttons match ports on loading a patch
148 	if (! m_GUICH->GetBool("Connected"))
149 	{
150 		int i, numbuttons = (int) m_InputName.size(), numports = m_JackClient->GetJackInputCount();
151 
152 		if (numbuttons > numports)
153 		{
154 			for (int i=numbuttons-numports; i > 0; i--)
155 			{
156 		        	RemoveOutput() ;
157 		        	RemoveInput() ;
158 		        }
159 		}
160 
161 		if (numbuttons < numports)
162 		{
163 			for (int i=0; i < numports-numbuttons; i++)
164 			{
165 		        	AddOutput() ;
166 		        	AddInput() ;
167 		        }
168 		}
169 	}
170 }
171 
Update()172 void JackPluginGUI::Update()
173 {
174 	if (m_GUICH->GetBool("Connected")) {
175 		m_JackClient->CheckingPortChanges = true;
176 
177 		for (unsigned int n=0; n<m_JackClient->m_OutputPortsChanged.size(); n++) {
178 			m_JackClient->m_OutputPortsChanged[n]->Connected = jack_port_connected(m_JackClient->m_OutputPortsChanged[n]->Port);
179 
180 			if (jack_port_connected(m_JackClient->m_OutputPortsChanged[n]->Port)) {
181 				if (m_JackClient->m_OutputPortsChanged[n]->ConnectedTo!="") {
182 					m_OutputButton[n]->label(m_JackClient->m_OutputPortsChanged[n]->ConnectedTo.c_str());
183 				}
184 				else
185 				{
186 					const char** connections = jack_port_get_all_connections(m_JackClient->m_Client,m_JackClient->m_OutputPortsChanged[n]->Port);
187   					if (connections) {
188 						m_OutputButton[m_JackClient->m_OutputPortsChanged[n]->PortNo]->label(connections[0]);
189 						free(connections);
190 					}
191 				}
192 				m_OutputButton[m_JackClient->m_OutputPortsChanged[n]->PortNo]->value(1);
193 			}
194 			else
195 			{
196 				m_OutputButton[m_JackClient->m_OutputPortsChanged[n]->PortNo]->value(0);
197 				m_OutputButton[m_JackClient->m_OutputPortsChanged[n]->PortNo]->label("None");
198 			}
199 		}
200 
201 		m_JackClient->m_OutputPortsChanged.clear();
202 
203 		for (unsigned int n=0; n<m_JackClient->m_InputPortsChanged.size(); n++) {
204 			m_JackClient->m_InputPortsChanged[n]->Connected = jack_port_connected(m_JackClient->m_InputPortsChanged[n]->Port);
205 
206 			if (m_JackClient->m_InputPortsChanged[n]->Connected) {
207 				if (m_JackClient->m_InputPortsChanged[n]->ConnectedTo!="") {
208 					m_InputButton[n]->label(m_JackClient->m_InputPortsChanged[n]->ConnectedTo.c_str());
209 				}
210 				else
211 				{
212 					const char** connections = jack_port_get_all_connections(m_JackClient->m_Client,m_JackClient->m_InputPortsChanged[n]->Port);
213   					if (connections) {
214 						m_InputButton[m_JackClient->m_InputPortsChanged[n]->PortNo]->label(connections[0]);
215 						free(connections);
216 					}
217 				}
218 				m_InputButton[m_JackClient->m_InputPortsChanged[n]->PortNo]->value(1);
219 			}
220 			else
221 			{
222 				m_InputButton[m_JackClient->m_InputPortsChanged[n]->PortNo]->value(0);
223 				m_InputButton[m_JackClient->m_InputPortsChanged[n]->PortNo]->label("None");
224 			}
225 
226 		}
227 
228 		m_JackClient->m_InputPortsChanged.clear();
229 
230 		m_JackClient->CheckingPortChanges = false;
231 	}
232 
233 	m_GUICH->SetCommand (JackPlugin::CHECK_PORT_CHANGES);
234 
235 	m_Indicator->value(m_GUICH->GetBool("Connected"));
236 	redraw();
237 }
238 
239 //// Callbacks ////
RemoveOutput()240 void JackPluginGUI::RemoveOutput() {
241         int n =(int) m_InputName.size() - 1;
242 
243 	if (m_OutputName[n])
244 	{
245 		delete(m_OutputName[n]);
246 		m_OutputName[n] = NULL;
247 		m_OutputName.pop_back();
248 	}
249 
250 	if (m_OutputLabel[n])
251 	{
252 		m_OutputPack->remove(m_OutputLabel[n]);
253 		delete(m_OutputLabel[n]);
254                 m_OutputLabel[n] = NULL;
255 		m_OutputLabel.pop_back();
256 		m_OutputPack->redraw();
257 		m_Scroll->redraw();
258 	}
259 
260 	if (m_OutputButton[n])
261 	{
262 		m_OutputPack->remove(m_OutputButton[n]);
263 		delete(m_OutputButton[n]);
264 		m_OutputButton[n] = NULL;
265 		m_OutputButton.pop_back();
266 		m_OutputPack->redraw();
267 		m_Scroll->redraw();
268 	}
269 }
270 
RemoveInput()271 void JackPluginGUI::RemoveInput() {
272         int n = (int) m_InputName.size() - 1;
273 
274 	if (m_InputName[n])
275 	{
276 		delete(m_InputName[n]);
277 		m_InputName[n] = NULL;
278 		m_InputName.pop_back();
279 	}
280 
281 	if (m_InputLabel[n])
282 	{
283 		m_InputPack->remove(m_InputLabel[n]);
284 		delete(m_InputLabel[n]);
285                 m_InputLabel[n] = NULL;
286                 m_InputLabel.pop_back();
287 		m_InputPack->redraw();
288 		m_Scroll->redraw();
289 	}
290 
291 	if (m_InputButton[n])
292 	{
293 		m_InputPack->remove(m_InputButton[n]);
294 		delete(m_InputButton[n]);
295 		m_InputButton[n] = NULL;
296 		m_InputButton.pop_back();
297 		m_InputPack->redraw();
298 		m_Scroll->redraw();
299 
300 	}
301 }
302 
AddOutput()303 void JackPluginGUI::AddOutput() {
304         int n = (int) m_OutputName.size();
305         char *NewName = new char [256];
306 
307         sprintf(NewName,"Output %d",n);
308         m_OutputName.push_back(NewName);
309 
310         m_OutputLabel.push_back(new Fl_Box(0,n*30,90,10,m_OutputName[n]));
311         m_OutputLabel[n]->labelsize(8);
312         m_OutputPack->add(m_OutputLabel[n]);
313 
314         m_OutputButton.push_back(new Fl_Button(0,n*30+10,90,20,"None"));
315         m_OutputButton[n]->type(1);
316         m_OutputButton[n]->labelsize(8);
317         m_OutputButton[n]->callback((Fl_Callback*)cb_OutputConnect,this);
318         m_OutputPack->add(m_OutputButton[n]);
319 
320 	redraw();
321 	Fl::check();
322 }
323 
AddInput()324 void JackPluginGUI::AddInput() {
325         int n = (int) m_InputName.size();
326         char *NewName = new char [256];
327 
328         sprintf(NewName,"Input %d",n);
329         m_InputName.push_back(NewName);
330 
331         m_InputLabel.push_back(new Fl_Box(95,n*30,90,10, m_InputName[n]));
332         m_InputLabel[n]->labelsize(8);
333         m_InputPack->add(m_InputLabel[n]);
334 
335         m_InputButton.push_back(new Fl_Button(95,n*30+10,90,20,"None"));
336         m_InputButton[n]->type(1);
337         m_InputButton[n]->labelsize(8);
338         m_InputButton[n]->callback((Fl_Callback*)cb_InputConnect,this);
339         m_InputPack->add(m_InputButton[n]);
340 
341 	redraw();
342 	Fl::check();
343 }
344 
cb_Remove_i(Fl_Button * o)345 inline void  JackPluginGUI::cb_Remove_i(Fl_Button* o)
346 {
347         int n = (int) m_InputName.size();
348 
349         if (n > MIN_PORTS)
350         {
351 	        RemoveOutput() ;
352 	        RemoveInput() ;
353 
354 		m_GUICH->Set ("NumInputs", n-1);
355 		m_GUICH->Set ("NumOutputs", n-1);
356 		m_GUICH->SetCommand (JackPlugin::SET_PORT_COUNT);
357                 m_GUICH->Wait ();
358 
359 		/* removing connections live must be called directly from here in the GUI thread */
360                 if (m_GUICH->GetBool("Connected")) {
361                 	m_JackClient->RemoveInputPort(n-1);
362                 	m_JackClient->RemoveOutputPort(n-1);
363                 }
364 
365                 if (n > 19) {
366 			resize (x(), y(), w(), h()-7);
367 
368 			m_Indicator->resize (x()+w()/2 - 15,y()+15,30,30);
369 			m_Remove->resize (x()+5,y()+15,25,25);
370 			m_Add->resize (x()+30,y()+15,25,25);
371 			m_Attach->resize (x()+5,y()+45,w() - 10,20);
372 			m_Detach->resize (x()+5,y()+65,w() - 10,20);
373 			m_Scroll->resize (x()+5, y()+90, w() - 10, h() - 102);
374 			m_OutputPack->resize (x()+15, y()+90, 85, h() - 102);
375 			m_InputPack->resize (x()+110, y()+90, 85, h() - 102);
376                }
377         }
378 }
379 
cb_Add_i(Fl_Button * o)380 inline void  JackPluginGUI::cb_Add_i(Fl_Button* o)
381 {
382         int n = (int) m_OutputName.size();
383 
384        if (n <= MAX_PORTS)
385        {
386 		/* Adding connections live must be called directly from here in the GUI thread */
387                 if (m_GUICH->GetBool("Connected")) {
388                 	m_JackClient->AddInputPort(n);
389                 	m_JackClient->AddOutputPort(n);
390                 }
391 
392 		m_GUICH->Set ("NumInputs", n+1);
393 		m_GUICH->Set ("NumOutputs", n+1);
394 		m_GUICH->SetCommand (JackPlugin::SET_PORT_COUNT);
395                 m_GUICH->Wait ();
396 
397 	        AddOutput() ;
398 	        AddInput() ;
399 
400 		if (n > 20) {
401 			resize (x(), y(), w(), h()+12);
402 
403 			m_Indicator->resize (x()+w()/2 - 15,y()+15,30,30);
404 			m_Remove->resize (x()+5,y()+15,25,25);
405 			m_Add->resize (x()+30,y()+15,25,25);
406 			m_Attach->resize (x()+5,y()+45,w() - 10,20);
407 			m_Detach->resize (x()+5,y()+65,w() - 10,20);
408 			m_Scroll->resize (x()+5, y()+90, w() - 10, h() - 102);
409 			m_OutputPack->resize (x()+15, y()+90, 85, h() - 102);
410 			m_InputPack->resize (x()+110, y()+90, 85, h() - 102);
411 		}
412        }
413 }
414 
cb_Attach_i(Fl_Button * o)415 inline void  JackPluginGUI::cb_Attach_i(Fl_Button* o)
416 {
417 	m_JackPlugin->Attach();
418 }
419 
cb_Detach_i(Fl_Button * o)420 inline void  JackPluginGUI::cb_Detach_i(Fl_Button* o)
421 {
422 	for (int n=0; n<(int)m_OutputName.size(); n++)
423 	{
424 		m_OutputButton[n]->value(false);
425 		m_OutputButton[n]->label("None");
426 	}
427 
428 	for (int n=0; n<(int)m_InputName.size(); n++)
429 	{
430 		m_InputButton[n]->value(false);
431 		m_InputButton[n]->label("None");
432 	}
433 
434 	m_JackPlugin->Detach();
435 }
436 
cb_OutputConnect_i(Fl_Button * o)437 inline void JackPluginGUI::cb_OutputConnect_i(Fl_Button* o)
438 {
439 	int index=0;
440 	std::vector<Fl_Button *>::iterator it = std::find( m_OutputButton.begin(), m_OutputButton.end(), o );
441 
442 	if ( it != m_OutputButton.end() )
443 		index = std::distance( m_OutputButton.begin(), it );
444 
445 	if ((o->value()) && m_GUICH->GetBool("Connected"))
446 	{
447 		m_GUICH->SetCommand(JackPlugin::UPDATE_NAMES);
448 		m_GUICH->Wait();
449 
450 		// bit of a hack for multithreaded safety
451 		int ninputs=m_GUICH->GetInt("NumOutputPortNames");
452 		char inputs[MAX_PORTS][256];
453 		m_GUICH->GetData("InputPortNames",inputs);
454 
455 		vector<string> Inputs;
456 		for (int n=0; n<ninputs; n++) Inputs.push_back(inputs[n]);
457 		int choice=OptionsList(Inputs);
458 
459 		// connect this plugin's output to a jack input
460 		if (choice>0)
461 		{
462 			m_JackClient->ConnectOutput(index,inputs[choice-1]);
463 
464 			o->label(inputs[choice-1]);
465 			o->redraw();
466 		}
467 		else {
468 			o->label("None");
469 			o->value(0);
470 			o->redraw();
471 		}
472 	}
473 	else
474 	{
475 		m_JackClient->DisconnectOutput(index);
476 		o->label("None");
477 		o->value(0);
478 		o->redraw();
479 	}
480 }
481 
cb_InputConnect_i(Fl_Button * o)482 inline void JackPluginGUI::cb_InputConnect_i(Fl_Button* o)
483 {
484 	int index=0;
485 	std::vector<Fl_Button *>::iterator it = std::find( m_InputButton.begin(), m_InputButton.end(), o );
486 
487 	if ( it != m_InputButton.end() )
488 		index = std::distance( m_InputButton.begin(), it );
489 
490 	if ((o->value()) && (m_JackClient) && (m_JackClient->IsAttached()))
491 	{
492 		m_GUICH->SetCommand(JackPlugin::UPDATE_NAMES);
493 		m_GUICH->Wait();
494 
495 		// bit of a hack for multithreaded safety
496 		int noutputs=m_GUICH->GetInt("NumOutputPortNames");
497 		char outputs[MAX_PORTS][256];
498 		m_GUICH->GetData("OutputPortNames",outputs);
499 
500 		vector<string> Outputs;
501 		for (int n=0; n<noutputs; n++) Outputs.push_back(outputs[n]);
502 		int choice=OptionsList(Outputs);
503 
504 		// connect this plugin's input to a jack output
505 		if (choice>0)
506 		{
507 			m_JackClient->ConnectInput(index,outputs[choice-1]);
508 
509 			o->label(outputs[choice-1]);
510 			o->redraw();
511 		}
512 		else {
513 			o->label("None");
514 			o->value(0);
515 			o->redraw();
516 		}
517 	}
518 	else
519 	{
520 		m_JackClient->DisconnectInput(index);
521 		o->label("None");
522 		o->value(0);
523 		o->redraw();
524 	}
525 }
526 
GetHelpText(const string & loc)527 const string JackPluginGUI::GetHelpText(const string &loc)
528 {
529 	return string("")
530 		+ "JACK is the Jack Audio Connection Kit, and allows multiple Linux audio\n"
531 		+ "apps to be connected together and run simultaneously in a low latency.\n"
532 		+ "environment.\n\n"
533 		+ "This plugin allows you to connect up to 64 inputs and outputs to other\n"
534 		+ "JACK apps (providing a server is running and your system can handle it)\n"
535 		+ "You can use the JackPlugin to connect the ports, or an external program\n"
536 		+ "such as the excellent qjackconnect app.\n\n"
537 		+ "When using JACK, make sure the buffer size and samplerate are set to\n"
538 		+ "match the JACK server, otherwise glitchy playback, and/or crashes may\n"
539 		+ "result";
540 }
541