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