Assists music production by grouping standalone programs into sessions. Community version of "Non Session Manager".
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.

1447 lines
43KB

  1. /*******************************************************************************/
  2. /* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager") */
  3. /* Copyright (C) 2020- Nils Hilbricht */
  4. /* */
  5. /* This file is part of New-Session-Manager */
  6. /* */
  7. /* New-Session-Manager is free software: you can redistribute it and/or modify */
  8. /* it under the terms of the GNU General Public License as published by */
  9. /* the Free Software Foundation, either version 3 of the License, or */
  10. /* (at your option) any later version. */
  11. /* */
  12. /* New-Session-Manager is distributed in the hope that it will be useful, */
  13. /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
  14. /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
  15. /* GNU General Public License for more details. */
  16. /* */
  17. /* You should have received a copy of the GNU General Public License */
  18. /* along with New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
  19. /*******************************************************************************/
  20. #include "Endpoint.hpp"
  21. #include <FL/Fl.H>
  22. #include <FL/Fl_Window.H>
  23. #include <FL/Fl_Double_Window.H>
  24. #include <FL/Fl_Widget.H>
  25. #include <FL/Fl.H>
  26. #include <FL/Fl_File_Chooser.H>
  27. #include <FL/Fl_Box.H>
  28. #include <FL/Fl_Pack.H>
  29. #include <FL/Fl_File_Chooser.H>
  30. #include <FL/Fl_Progress.H>
  31. #include "debug.h"
  32. #include <FL/Fl_Browser.H>
  33. #include <FL/Fl_Select_Browser.H>
  34. #include <FL/Fl_Tree.H>
  35. #include <FL/Fl_Hold_Browser.H>
  36. #include <FL/Fl_Tile.H>
  37. #include <FL/Fl_Shared_Image.H>
  38. #include <FL/Fl_Box.H>
  39. #include <FL/Fl_Text_Display.H>
  40. #include "FL/Fl_Packscroller.H"
  41. #include "FL/Fl_Scalepack.H"
  42. #include <unistd.h>
  43. #include <errno.h>
  44. #include <time.h>
  45. #include <getopt.h>
  46. #define APP_NAME "NSM Legacy GUI"
  47. #define APP_TITLE "NSM Legacy GUI"
  48. #pragma GCC diagnostic ignored "-Wunused-result"
  49. // static lo_address nsm_addr = NULL;
  50. static time_t last_ping_response;
  51. static OSC::Endpoint *osc;
  52. struct Daemon
  53. {
  54. const char *url;
  55. lo_address addr;
  56. bool is_child;
  57. Daemon ( )
  58. {
  59. url = NULL;
  60. addr = NULL;
  61. is_child = false;
  62. }
  63. };
  64. static std::list<Daemon*> daemon_list; /* list of all connected daemons */
  65. #define foreach_daemon( _it ) for ( std::list<Daemon*>::iterator _it = daemon_list.begin(); _it != daemon_list.end(); ++ _it )
  66. static
  67. Fl_Image *
  68. get_program_icon ( const char *name )
  69. {
  70. const char *tries[] =
  71. {
  72. "/usr/local/share/icons/hicolor/32x32/apps/%s.png",
  73. "/usr/local/share/pixmaps/%s.png",
  74. "/usr/local/share/pixmaps/%s.xpm",
  75. "/usr/share/icons/hicolor/32x32/apps/%s.png",
  76. "/usr/share/pixmaps/%s.png",
  77. "/usr/share/pixmaps/%s.xpm",
  78. };
  79. for ( unsigned int i = 0; i < 6; i++ )
  80. {
  81. char *icon_p;
  82. asprintf( &icon_p, tries[i], name );
  83. Fl_Image *img = Fl_Shared_Image::get( icon_p );
  84. free( icon_p );
  85. if ( img )
  86. return img;
  87. }
  88. return NULL;
  89. }
  90. class NSM_Client : public Fl_Group
  91. {
  92. char *_client_id;
  93. char *_client_label;
  94. char *_client_name;
  95. Fl_Box *client_name;
  96. Fl_Box *icon_box;
  97. Fl_Progress *_progress;
  98. Fl_Light_Button *_dirty;
  99. Fl_Light_Button *_gui;
  100. Fl_Button *_remove_button;
  101. Fl_Button *_restart_button;
  102. Fl_Button *_kill_button;
  103. void
  104. set_label ( void )
  105. {
  106. char *l;
  107. if ( _client_label )
  108. asprintf( &l, "%s (%s)", _client_name, _client_label );
  109. else
  110. l = strdup( _client_name );
  111. if ( ! icon_box->image() )
  112. {
  113. Fl_Image *img = get_program_icon( _client_name );
  114. if ( img )
  115. {
  116. icon_box->image( img );
  117. }
  118. }
  119. client_name->copy_label( l );
  120. free(l);
  121. redraw();
  122. }
  123. public:
  124. void
  125. name ( const char *v )
  126. {
  127. if ( _client_name )
  128. free( _client_name );
  129. _client_name = strdup( v );
  130. set_label();
  131. }
  132. void
  133. client_label ( const char *s )
  134. {
  135. if ( _client_label )
  136. free( _client_label );
  137. _client_label = strdup( s );
  138. set_label();
  139. }
  140. void
  141. client_id ( const char *v )
  142. {
  143. if ( _client_id )
  144. free( _client_id );
  145. _client_id = strdup( v );
  146. }
  147. void
  148. progress ( float f )
  149. {
  150. _progress->value( f );
  151. _progress->redraw();
  152. }
  153. void
  154. dirty ( bool b )
  155. {
  156. _dirty->value( b );
  157. _dirty->redraw();
  158. }
  159. void
  160. gui_visible ( bool b )
  161. {
  162. _gui->value( b );
  163. _gui->redraw();
  164. }
  165. void
  166. has_optional_gui ( void )
  167. {
  168. _gui->show();
  169. _gui->redraw();
  170. }
  171. void
  172. stopped ( bool b )
  173. {
  174. if ( b )
  175. {
  176. _remove_button->show();
  177. _restart_button->show();
  178. _kill_button->hide();
  179. _gui->deactivate();
  180. _dirty->deactivate();
  181. color( fl_color_average( FL_BLACK, FL_RED, 0.50 ) );
  182. redraw();
  183. }
  184. else
  185. {
  186. _gui->activate();
  187. _dirty->activate();
  188. _kill_button->show();
  189. _restart_button->hide();
  190. _remove_button->hide();
  191. }
  192. /* _restart_button->redraw(); */
  193. /* _remove_button->redraw(); */
  194. }
  195. void
  196. pending_command ( const char *command )
  197. {
  198. _progress->copy_label( command );
  199. stopped( 0 );
  200. if ( ! strcmp( command, "ready" ) )
  201. {
  202. color( fl_color_average( FL_BLACK, FL_GREEN, 0.50 ) );
  203. _progress->value( 0.0f );
  204. }
  205. else if ( ! strcmp( command, "quit" ) ||
  206. ! strcmp( command, "kill" ) ||
  207. ! strcmp( command, "error" ) )
  208. {
  209. color( fl_color_average( FL_BLACK, FL_RED, 0.50 ) );
  210. }
  211. else if ( ! strcmp( command, "stopped" ) )
  212. {
  213. stopped( 1 );
  214. }
  215. else
  216. {
  217. color( fl_color_average( FL_BLACK, FL_YELLOW, 0.50 ) );
  218. }
  219. redraw();
  220. }
  221. static void
  222. cb_button ( Fl_Widget *o, void * v )
  223. {
  224. ((NSM_Client*)v)->cb_button( o );
  225. }
  226. void
  227. cb_button ( Fl_Widget *o )
  228. {
  229. if ( o == _dirty )
  230. {
  231. MESSAGE( "Sending save.");
  232. foreach_daemon ( d )
  233. {
  234. osc->send( (*d)->addr, "/nsm/gui/client/save", _client_id );
  235. }
  236. }
  237. else if ( o == _gui )
  238. {
  239. MESSAGE( "Sending hide/show GUI.");
  240. foreach_daemon ( d )
  241. {
  242. if ( !_gui->value() )
  243. osc->send( (*d)->addr, "/nsm/gui/client/show_optional_gui", _client_id );
  244. else
  245. osc->send( (*d)->addr, "/nsm/gui/client/hide_optional_gui", _client_id );
  246. }
  247. }
  248. else if ( o == _remove_button )
  249. {
  250. MESSAGE( "Sending remove.");
  251. foreach_daemon ( d )
  252. {
  253. osc->send( (*d)->addr, "/nsm/gui/client/remove", _client_id );
  254. }
  255. }
  256. else if ( o == _restart_button )
  257. {
  258. MESSAGE( "Sending resume" );
  259. foreach_daemon ( d )
  260. {
  261. osc->send( (*d)->addr, "/nsm/gui/client/resume", _client_id );
  262. }
  263. }
  264. else if ( o == _kill_button )
  265. {
  266. MESSAGE( "Sending stop" );
  267. foreach_daemon ( d )
  268. {
  269. osc->send( (*d)->addr, "/nsm/gui/client/stop", _client_id );
  270. }
  271. }
  272. }
  273. const char *
  274. client_id ( void )
  275. { return _client_id; }
  276. NSM_Client ( int X, int Y, int W, int H, const char *L ) :
  277. Fl_Group( X, Y, W, H, L )
  278. {
  279. _client_id = NULL;
  280. _client_name = NULL;
  281. _client_label = NULL;
  282. align( FL_ALIGN_LEFT | FL_ALIGN_INSIDE );
  283. color( fl_darker( FL_RED ) );
  284. box( FL_UP_FRAME );
  285. int yy = Y + H * 0.25;
  286. int hh = H * 0.50;
  287. int xx = X + W - ( 75 + Fl::box_dw( box() ) );
  288. int ss = 2;
  289. /* /\* dummy group *\/ */
  290. /* { Fl_Group *o = new Fl_Group( X, Y, W, H ); */
  291. /* o->end(); */
  292. /* resizable( o ); */
  293. /* } */
  294. { Fl_Pack *o = new Fl_Pack( X + 15, Y, 300 - 5, H );
  295. o->type( FL_HORIZONTAL );
  296. o->spacing( 10 );
  297. { icon_box = new Fl_Box( 0, 0, 32, 32 );
  298. }
  299. { Fl_Box *o = client_name = new Fl_Box( 0, 0, 300, 48 );
  300. /* o->color( FL_BLUE ); */
  301. o->align( FL_ALIGN_INSIDE | FL_ALIGN_LEFT );
  302. o->labeltype( FL_NORMAL_LABEL );
  303. }
  304. o->end();
  305. }
  306. { Fl_Box *o = new Fl_Box( X + 300, Y, 100, h() );
  307. Fl_Group::current()->resizable(o);
  308. }
  309. { Fl_Progress *o = _progress = new Fl_Progress( xx, Y + H * 0.25, 75, H * 0.50, NULL );
  310. o->box( FL_FLAT_BOX );
  311. o->color( FL_DARK1, FL_LIGHT1 );
  312. o->copy_label( "launch" );
  313. o->labelsize( 12 );
  314. o->minimum( 0.0f );
  315. o->maximum( 1.0f );
  316. }
  317. { Fl_Group *o = new Fl_Group( X + W - 400, Y, 400, H );
  318. xx -= 50 + ss;
  319. { Fl_Light_Button *o = _dirty = new Fl_Light_Button( xx, yy, 50, hh, "SAVE" );
  320. o->align( FL_ALIGN_LEFT | FL_ALIGN_INSIDE );
  321. o->labelsize( 9 );
  322. o->box( FL_UP_BOX );
  323. o->type(0);
  324. o->color();
  325. o->selection_color( FL_YELLOW );
  326. o->value( 0 );
  327. o->callback( cb_button, this );
  328. }
  329. xx -= 40 + ss;
  330. { Fl_Light_Button *o = _gui = new Fl_Light_Button( xx, yy, 40, hh, "GUI" );
  331. o->align( FL_ALIGN_LEFT | FL_ALIGN_INSIDE );
  332. o->labelsize( 9 );
  333. o->box( FL_UP_BOX );
  334. o->type(0);
  335. o->color();
  336. o->selection_color( FL_YELLOW );
  337. o->value( 0 );
  338. o->hide();
  339. o->callback( cb_button, this );
  340. }
  341. xx -= 25 + ss;
  342. { Fl_Button *o = _kill_button = new Fl_Button( xx, yy, 25, hh, "@square" );
  343. o->labelsize( 9 );
  344. o->box( FL_UP_BOX );
  345. o->type(0);
  346. // o->color( FL_RED );
  347. o->value( 0 );
  348. o->tooltip( "Stop" );
  349. o->callback( cb_button, this );
  350. }
  351. xx -= 25 + ss;
  352. { Fl_Button *o = _restart_button = new Fl_Button( xx, yy, 25, hh );
  353. o->box( FL_UP_BOX );
  354. o->type(0);
  355. // o->color( FL_GREEN );
  356. o->value( 0 );
  357. o->label( "@>" );
  358. o->tooltip( "Resume" );
  359. o->hide();
  360. o->callback( cb_button, this );
  361. }
  362. xx -= 25 + ss;
  363. { Fl_Button *o = _remove_button = new Fl_Button( xx, yy, 25, hh );
  364. o->box( FL_UP_BOX );
  365. o->type(0);
  366. // o->color( FL_RED );
  367. o->value( 0 );
  368. o->label( "X" );
  369. o->tooltip( "Remove" );
  370. o->hide();
  371. o->callback( cb_button, this );
  372. }
  373. o->end();
  374. }
  375. end();
  376. }
  377. ~NSM_Client ( )
  378. {
  379. if ( _client_id )
  380. {
  381. free( _client_id );
  382. _client_id = NULL;
  383. }
  384. if ( _client_name )
  385. {
  386. free( _client_name );
  387. _client_name = NULL;
  388. }
  389. if ( _client_label )
  390. {
  391. free( _client_label );
  392. _client_label = NULL;
  393. }
  394. if ( label() )
  395. {
  396. free( (char*)label() );
  397. label( NULL );
  398. }
  399. }
  400. };
  401. static
  402. void
  403. fl_awake_alert( void *v )
  404. {
  405. if ( v )
  406. {
  407. fl_alert( "%s", (char*)v );
  408. free( v );
  409. }
  410. }
  411. void
  412. browser_callback ( Fl_Widget *w, void * )
  413. {
  414. w->window()->hide();
  415. }
  416. class NSM_Controller : public Fl_Group
  417. {
  418. Fl_Text_Display *status_display;
  419. public:
  420. Fl_Pack *clients_pack;
  421. Fl_Pack *buttons_pack;
  422. Fl_Button *close_button;
  423. Fl_Button *abort_button;
  424. Fl_Button *save_button;
  425. Fl_Button *open_button;
  426. Fl_Button *new_button;
  427. Fl_Button *add_button;
  428. Fl_Button *duplicate_button;
  429. Fl_Button *quit_button;
  430. Fl_Button *refresh_button;
  431. Fl_Box *session_name_box;
  432. Fl_Tree *session_browser;
  433. int status_lines;
  434. static void cb_handle ( Fl_Widget *w, void *v )
  435. {
  436. ((NSM_Controller*)v)->cb_handle( w );
  437. }
  438. void log_status ( const char *s )
  439. {
  440. time_t now;
  441. now = time( NULL );
  442. struct tm * tm = localtime( &now );
  443. char *ts;
  444. asprintf( &ts, "%02i:%02i:%02i ", tm->tm_hour, tm->tm_min, tm->tm_sec );
  445. status_display->buffer()->append( ts );
  446. free( ts );
  447. status_display->buffer()->append( s );
  448. status_display->scroll( ++status_lines, 0 );
  449. status_display->buffer()->append( "\n" );
  450. }
  451. void
  452. cb_handle ( Fl_Widget *w )
  453. {
  454. if ( w == abort_button )
  455. {
  456. if ( 0 == fl_choice( "Are you sure you want to abort this session? Unsaved changes will be lost.", "Abort", "Cancel", NULL ) )
  457. {
  458. MESSAGE( "Sending abort." );
  459. foreach_daemon ( d )
  460. {
  461. osc->send( (*d)->addr, "/nsm/server/abort" );
  462. }
  463. }
  464. }
  465. if ( w == close_button )
  466. {
  467. MESSAGE( "Sending close." );
  468. foreach_daemon ( d )
  469. {
  470. osc->send( (*d)->addr, "/nsm/server/close" );
  471. }
  472. }
  473. else if ( w == save_button )
  474. {
  475. MESSAGE( "Sending save." );
  476. foreach_daemon ( d )
  477. {
  478. osc->send( (*d)->addr, "/nsm/server/save" );
  479. }
  480. }
  481. else if ( w == open_button )
  482. {
  483. const char *name = fl_input( "Open Session", NULL );
  484. if ( ! name )
  485. return;
  486. Fl_Tree_Item *item = session_browser->find_item( name );
  487. if ( item )
  488. session_browser->select_only( item, 1 );
  489. }
  490. else if ( w == duplicate_button )
  491. {
  492. const char *name = fl_input( "New Session", NULL );
  493. if ( ! name )
  494. return;
  495. MESSAGE( "Sending duplicate for: %s", name );
  496. foreach_daemon ( d )
  497. {
  498. osc->send( (*d)->addr, "/nsm/server/duplicate", name );
  499. }
  500. }
  501. else if ( w == quit_button )
  502. {
  503. window()->do_callback( window(), this );
  504. }
  505. else if ( w == refresh_button )
  506. {
  507. session_browser->clear();
  508. session_browser->redraw();
  509. MESSAGE( "Refreshing session list." );
  510. foreach_daemon ( d )
  511. {
  512. osc->send( (*d)->addr, "/nsm/server/list" );
  513. }
  514. }
  515. else if ( w == session_browser )
  516. {
  517. if ( session_browser->callback_reason() != FL_TREE_REASON_SELECTED )
  518. return;
  519. Fl_Tree_Item *item = session_browser->callback_item();
  520. // session_browser->deselect( item, 0 );
  521. if ( item->children() )
  522. return;
  523. char name[1024];
  524. session_browser->item_pathname( name, sizeof(name), item );
  525. foreach_daemon ( d )
  526. {
  527. osc->send( (*d)->addr, "/nsm/server/open", name );
  528. }
  529. }
  530. else if ( w == new_button )
  531. {
  532. const char *name = fl_input( "New Session", NULL );
  533. if ( !name )
  534. return;
  535. MESSAGE( "Sending new for: %s", name );
  536. foreach_daemon ( d )
  537. {
  538. osc->send( (*d)->addr, "/nsm/server/new", name );
  539. }
  540. }
  541. else if ( w == add_button )
  542. {
  543. Fl_Select_Browser *browser;
  544. if ( daemon_list.size() > 1 )
  545. {
  546. Fl_Window* win = new Fl_Window( window()->x(), window()->y(), 300, 400, "Choose Server" );
  547. {
  548. {
  549. Fl_Box *o = new Fl_Box( 0,0, 300, 100 );
  550. o->label( "Connected to multiple NSM servers, please select which one to add a client to." );
  551. o->align( FL_ALIGN_CENTER | FL_ALIGN_INSIDE | FL_ALIGN_WRAP );
  552. }
  553. {
  554. Fl_Select_Browser *o = browser = new Fl_Select_Browser( 0, 100, 300, 300 );
  555. o->box( FL_ROUNDED_BOX );
  556. o->color( FL_BLACK );
  557. o->callback( browser_callback, win );
  558. foreach_daemon( d )
  559. {
  560. o->add( (*d)->url );
  561. }
  562. }
  563. }
  564. win->end();
  565. win->show();
  566. while ( win->visible() )
  567. {
  568. Fl::wait();
  569. }
  570. if ( ! browser->value() )
  571. return;
  572. const char *n = fl_input( "Enter executable name" );
  573. if ( !n )
  574. return;
  575. char *name = strdup( n );
  576. if ( index( name, ' ' ) )
  577. {
  578. free( name );
  579. name = strdup( "nsm-proxy" );
  580. }
  581. lo_address nsm_addr = lo_address_new_from_url( browser->text( browser->value() ) );
  582. osc->send( nsm_addr, "/nsm/server/add", name );
  583. free( name );
  584. delete win;
  585. }
  586. else
  587. {
  588. const char *n = fl_input( "Enter executable name" );
  589. if ( !n )
  590. return;
  591. char *name = strdup( n );
  592. if ( index( name, ' ' ) )
  593. {
  594. free( name );
  595. name = strdup( "nsm-proxy" );
  596. }
  597. MESSAGE( "Sending add for: %s", name );
  598. /* FIXME: user should get to choose which system to do the add on */
  599. foreach_daemon ( d )
  600. {
  601. osc->send( (*d)->addr, "/nsm/server/add", name );
  602. }
  603. free( name );
  604. }
  605. }
  606. }
  607. NSM_Client *
  608. client_by_id ( const char *id )
  609. {
  610. for ( int i = clients_pack->children(); i--; )
  611. {
  612. NSM_Client *c = (NSM_Client*)clients_pack->child( i );
  613. if ( ! strcmp( c->client_id(), id ) )
  614. {
  615. return c;
  616. }
  617. }
  618. return NULL;
  619. }
  620. const char *session_name ( void ) const
  621. {
  622. return session_name_box->label();
  623. }
  624. void
  625. session_name ( const char *name )
  626. {
  627. session_name_box->copy_label( name );
  628. if ( strlen( name ) )
  629. {
  630. save_button->activate();
  631. add_button->activate();
  632. duplicate_button->activate();
  633. abort_button->activate();
  634. close_button->activate();
  635. }
  636. else
  637. {
  638. save_button->deactivate();
  639. add_button->deactivate();
  640. duplicate_button->deactivate();
  641. abort_button->deactivate();
  642. close_button->deactivate();
  643. }
  644. redraw();
  645. }
  646. void
  647. client_stopped ( const char *client_id )
  648. {
  649. NSM_Client *c = client_by_id( client_id );
  650. if ( c )
  651. {
  652. c->stopped( 1 );
  653. }
  654. }
  655. void
  656. client_quit ( const char *client_id )
  657. {
  658. NSM_Client *c = client_by_id( client_id );
  659. if ( c )
  660. {
  661. clients_pack->remove( c );
  662. delete c;
  663. }
  664. if ( clients_pack->children() == 0 )
  665. {
  666. ((Fl_Packscroller*)clients_pack->parent())->yposition( 0 );
  667. }
  668. parent()->redraw();
  669. }
  670. void
  671. client_new ( const char *client_id, const char *client_name )
  672. {
  673. NSM_Client *c;
  674. c = client_by_id( client_id );
  675. if ( c )
  676. {
  677. c->name( client_name );
  678. return;
  679. }
  680. c = new NSM_Client( 0, 0, w(), 40, NULL );
  681. c->name( client_name );
  682. c->client_id( client_id );
  683. c->stopped( 0 );
  684. clients_pack->add( c );
  685. redraw();
  686. }
  687. void client_pending_command ( NSM_Client *c, const char *command )
  688. {
  689. if ( c )
  690. {
  691. if ( ! strcmp( command, "removed" ) )
  692. {
  693. clients_pack->remove( c );
  694. delete c;
  695. parent()->redraw();
  696. }
  697. else
  698. c->pending_command( command );
  699. }
  700. }
  701. void add_session_to_list ( const char *name )
  702. {
  703. session_browser->add( name );
  704. session_browser->redraw();
  705. }
  706. NSM_Controller ( int X, int Y, int W, int H, const char *L ) :
  707. Fl_Group( X, Y, W, H, L )
  708. {
  709. status_lines = 0;
  710. align( FL_ALIGN_RIGHT | FL_ALIGN_CENTER | FL_ALIGN_INSIDE );
  711. { Fl_Pack *o = buttons_pack = new Fl_Pack( X, Y, W, 30 );
  712. o->type( Fl_Pack::HORIZONTAL );
  713. o->box( FL_NO_BOX );
  714. { Fl_Button *o = quit_button = new Fl_Button( 0, 0, 50, 50, "&Quit" );
  715. o->shortcut( FL_CTRL | 'q' );
  716. o->box( FL_UP_BOX );
  717. o->callback( cb_handle, (void*)this );
  718. }
  719. { Fl_Button *o = refresh_button = new Fl_Button( 0, 0, 70, 50, "&Refresh" );
  720. o->shortcut( FL_CTRL | 'r' );
  721. o->box( FL_UP_BOX );
  722. o->callback( cb_handle, (void*)this );
  723. }
  724. { Fl_Button *o = new_button = new Fl_Button( 0, 0, 100, 50, "&New Session" );
  725. o->shortcut( FL_CTRL | 'n' );
  726. o->box( FL_UP_BOX );
  727. o->callback( cb_handle, (void*)this );
  728. }
  729. { Fl_Button *o = save_button = new Fl_Button( 0, 0, 105, 50, "&Save" );
  730. o->shortcut( FL_CTRL | 's' );
  731. o->box( FL_UP_BOX );
  732. o->callback( cb_handle, (void*)this );
  733. }
  734. { Fl_Button *o = close_button = new Fl_Button( 0, 0, 105, 50, "Save && Close" );
  735. o->shortcut( FL_CTRL | 'e' ); // is this a good key?
  736. o->box( FL_UP_BOX );
  737. o->callback( cb_handle, (void*)this );
  738. }
  739. { Fl_Button *o = duplicate_button = new Fl_Button( 0, 0, 105, 50, "Save && &Dupl." );
  740. o->shortcut( FL_CTRL | 'd' );
  741. o->box( FL_UP_BOX );
  742. o->callback( cb_handle, (void*)this );
  743. }
  744. { Fl_Button *o = open_button = new Fl_Button( 0, 0, 105, 50, "Save && &Open" );
  745. o->shortcut( FL_CTRL | 'o' );
  746. o->box( FL_UP_BOX );
  747. o->callback( cb_handle, (void*)this );
  748. }
  749. { Fl_Button *o = abort_button = new Fl_Button( 0, 0, 160, 50, "Close &without Saving" );
  750. o->shortcut( FL_CTRL | 'w' );
  751. o->box( FL_UP_BOX );
  752. o->color( fl_color_average( FL_RED, fl_rgb_color(10,10,10), 0.5f ) );
  753. o->callback( cb_handle, (void*)this );
  754. }
  755. o->end();
  756. }
  757. int SH = 14;
  758. { Fl_Tile *o = new Fl_Tile( X, Y + 30, W, H - 30 );
  759. { Fl_Scalepack *o = new Fl_Scalepack( X, Y + 30, 300, H - ( 30 + SH ) );
  760. o->type( FL_VERTICAL );
  761. o->spacing( 2 );
  762. { new Fl_Box( 0,0,100, 24, "Sessions" );
  763. }
  764. {
  765. Fl_Tree *o = session_browser = new Fl_Tree( X, Y + 50, W / 3, H - ( 50 + SH ) );
  766. o->callback( cb_handle, (void *)this );
  767. o->color( FL_DARK1 );
  768. o->item_labelbgcolor( o->color() );
  769. o->item_labelfgcolor( FL_FOREGROUND_COLOR );
  770. o->sortorder( FL_TREE_SORT_ASCENDING );
  771. o->showroot( 0 );
  772. o->selection_color( fl_darker( FL_GREEN ) );
  773. o->selectbox( FL_UP_FRAME );
  774. o->box( FL_FLAT_BOX );
  775. /* o->label( "Sessions" ); */
  776. o->end();
  777. Fl_Group::current()->resizable( o );
  778. } // Fl_Tree
  779. o->end();
  780. }
  781. Fl_Scalepack *scalepack;
  782. { Fl_Scalepack *o = scalepack = new Fl_Scalepack( X + 300, Y + 30, W - 300, H - ( 30 + SH ) );
  783. o->type( FL_VERTICAL );
  784. o->spacing( 2 );
  785. { session_name_box = new Fl_Box( 0, 0, 100, 25, "" );
  786. }
  787. { Fl_Button *o = add_button = new Fl_Button( 0, 0, 100, 25, "&Add Client to Session" );
  788. o->shortcut( FL_CTRL | 'a' );
  789. o->box( FL_UP_BOX );
  790. o->align( FL_ALIGN_CLIP );
  791. o->callback( cb_handle, (void*)this );
  792. }
  793. {
  794. Fl_Packscroller *o = new Fl_Packscroller( 0, 0, 100, H - ( 30 + SH ) );
  795. o->align( FL_ALIGN_TOP );
  796. o->labeltype( FL_SHADOW_LABEL );
  797. {
  798. Fl_Pack *o = clients_pack = new Fl_Pack( 0, 0, 100, 100 );
  799. o->align( FL_ALIGN_TOP );
  800. o->spacing( 4 );
  801. o->type( Fl_Pack::VERTICAL );
  802. o->end();
  803. }
  804. o->end();
  805. Fl_Group::current()->resizable( o );
  806. } // Fl_Packscroller
  807. o->end();
  808. /* Fl_Group::current()->resizable( o ); */
  809. } // Fl_Scalepack
  810. { Fl_Box *o = new Fl_Box( X + 300, Y + 30, 100, H - ( 30 + SH ));
  811. Fl_Group::current()->resizable(o);
  812. }
  813. { Fl_Text_Display *o = status_display = new Fl_Text_Display( X, Y + H - SH, W, SH );
  814. o->color( FL_DARK1 );
  815. o->textcolor( FL_FOREGROUND_COLOR );
  816. o->box( FL_UP_BOX );
  817. o->textfont( FL_COURIER );
  818. o->textsize( 10 );
  819. Fl_Text_Buffer *b = new Fl_Text_Buffer();
  820. o->buffer(b);
  821. }
  822. o->end();
  823. resizable( o );
  824. } // Fl_tile
  825. end();
  826. deactivate();
  827. }
  828. int min_h ( void )
  829. {
  830. return 500;
  831. }
  832. void
  833. ping ( void )
  834. {
  835. if ( daemon_list.size() )
  836. {
  837. foreach_daemon( d )
  838. {
  839. osc->send( (*d)->addr, "/osc/ping" );
  840. }
  841. }
  842. if ( last_ping_response )
  843. {
  844. if ( time(NULL) - last_ping_response > 10 )
  845. {
  846. if ( active() )
  847. {
  848. deactivate();
  849. log_status( "Server is not responding..." );
  850. }
  851. }
  852. else
  853. {
  854. if ( !active() )
  855. {
  856. log_status( "Server is back." );
  857. activate();
  858. }
  859. }
  860. }
  861. }
  862. int init_osc ( void )
  863. {
  864. osc = new OSC::Endpoint();
  865. if ( int r = osc->init( LO_UDP ) )
  866. return r;
  867. osc->owner = this;
  868. osc->add_method( "/error", "sis", osc_handler, osc, "msg" );
  869. osc->add_method( "/reply", "ss", osc_handler, osc, "msg" );
  870. osc->add_method( "/reply", "s", osc_handler, osc, "" );
  871. osc->add_method( "/nsm/server/broadcast", NULL, osc_broadcast_handler, osc, "msg" );
  872. osc->add_method( "/nsm/gui/server_announce", "s", osc_handler, osc, "msg" );
  873. osc->add_method( "/nsm/gui/server/message", "s", osc_handler, osc, "msg" );
  874. osc->add_method( "/nsm/gui/gui_announce", "s", osc_handler, osc, "msg" );
  875. osc->add_method( "/nsm/gui/session/session", "s", osc_handler, osc, "path,display_name" );
  876. osc->add_method( "/nsm/gui/session/name", "ss", osc_handler, osc, "path,display_name" );
  877. osc->add_method( "/nsm/gui/client/new", "ss", osc_handler, osc, "path,display_name" );
  878. osc->add_method( "/nsm/gui/client/status", "ss", osc_handler, osc, "path,display_name" );
  879. osc->add_method( "/nsm/gui/client/switch", "ss", osc_handler, osc, "path,display_name" );
  880. osc->add_method( "/nsm/gui/client/progress", "sf", osc_handler, osc, "path,display_name" );
  881. osc->add_method( "/nsm/gui/client/dirty", "si", osc_handler, osc, "path,display_name" );
  882. osc->add_method( "/nsm/gui/client/has_optional_gui", "s", osc_handler, osc, "path,display_name" );
  883. osc->add_method( "/nsm/gui/client/gui_visible", "si", osc_handler, osc, "path,display_name" );
  884. osc->add_method( "/nsm/gui/client/label", "ss", osc_handler, osc, "path,display_name" );
  885. osc->start();
  886. return 0;
  887. }
  888. void announce ( const char *nsm_url )
  889. {
  890. /* Daemon *d = new Daemon; */
  891. /* d->url = nsm_url; */
  892. lo_address nsm_addr = lo_address_new_from_url( nsm_url );
  893. // d->is_child = true;
  894. /* daemon_list.push_back( d ); */
  895. osc->send( nsm_addr, "/nsm/gui/gui_announce" );
  896. }
  897. private:
  898. static int osc_broadcast_handler ( const char *path, const char *, lo_arg **, int argc, lo_message msg, void * )
  899. {
  900. if ( ! argc )
  901. /* need at least one argument... */
  902. return 0;
  903. DMESSAGE( "Relaying broadcast" );
  904. foreach_daemon( d )
  905. {
  906. char *u1 = lo_address_get_url( (*d)->addr );
  907. char *u2 = lo_address_get_url( lo_message_get_source( msg ) );
  908. if ( strcmp( u1, u2 ) )
  909. {
  910. osc->send( (*d)->addr, path, msg );
  911. }
  912. free( u1 );
  913. free( u2 );
  914. }
  915. return 0;
  916. }
  917. static int osc_handler ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  918. {
  919. // OSC_DMSG();
  920. NSM_Controller *controller = (NSM_Controller*)((OSC::Endpoint*)user_data)->owner;
  921. Fl::lock();
  922. if ( !strcmp( path, "/nsm/gui/server/message" ) && !strcmp( types, "s" ) )
  923. {
  924. controller->log_status( &argv[0]->s );
  925. }
  926. else if ( !strcmp( path, "/nsm/gui/session/session" ) &&
  927. ! strcmp( types, "s" ) )
  928. {
  929. controller->add_session_to_list( &argv[0]->s );
  930. }
  931. else if ( !strcmp( path, "/nsm/gui/gui_announce" ) )
  932. {
  933. /* pre-existing server is replying to our announce message */
  934. controller->activate();
  935. lo_address nsm_addr = lo_message_get_source( msg );
  936. osc->send( nsm_addr, "/nsm/server/list" );
  937. }
  938. else if ( !strcmp( path, "/nsm/gui/server_announce" ) )
  939. {
  940. /* must be a server we launched */
  941. controller->activate();
  942. Daemon *d = new Daemon;
  943. d->url = lo_address_get_url( lo_message_get_source( msg ) );
  944. d->addr = lo_address_new_from_url( d->url );
  945. d->is_child = true;
  946. daemon_list.push_back( d );
  947. osc->send( d->addr, "/nsm/server/list" );
  948. }
  949. else if ( !strcmp( path, "/nsm/gui/session/name" ) &&
  950. !strcmp( types, "ss" ))
  951. {
  952. controller->session_name( &argv[0]->s );
  953. if ( !strcmp( &argv[0]->s, "" ) )
  954. {
  955. controller->session_browser->deselect_all();
  956. }
  957. else
  958. {
  959. Fl_Tree_Item *o = controller->session_browser->find_item( &argv[1]->s );
  960. if ( o )
  961. {
  962. controller->session_browser->select_only( o, 0 );
  963. controller->session_browser->show_item( o, 0 );
  964. }
  965. }
  966. }
  967. else if (!strcmp( path, "/error" ) &&
  968. !strcmp( types, "sis" ) )
  969. {
  970. int err = argv[1]->i;
  971. if ( err != 0 )
  972. {
  973. char *s;
  974. asprintf( &s, "Command %s failed with:\n\n%s", &argv[0]->s, &argv[2]->s );
  975. Fl::awake(fl_awake_alert, s);
  976. }
  977. }
  978. else if (!strcmp( path, "/reply" ) && argc && 's' == *types )
  979. {
  980. if ( !strcmp( &argv[0]->s, "/nsm/server/list" ) )
  981. {
  982. controller->add_session_to_list( &argv[1]->s );
  983. }
  984. else if ( !strcmp( &argv[0]->s, "/osc/ping" ) )
  985. {
  986. last_ping_response = time( NULL );
  987. }
  988. else if ( ! strcmp( types, "ss" ) )
  989. {
  990. MESSAGE( "%s says %s", &argv[0]->s, &argv[1]->s);
  991. controller->log_status( &argv[1]->s );
  992. }
  993. }
  994. if ( !strncmp( path, "/nsm/gui/client/", strlen( "/nsm/gui/client/" ) ) )
  995. {
  996. if ( !strcmp( path, "/nsm/gui/client/new" ) &&
  997. !strcmp( types, "ss" ) )
  998. {
  999. controller->client_new( &argv[0]->s, &argv[1]->s );
  1000. }
  1001. else
  1002. {
  1003. NSM_Client *c = controller->client_by_id( &argv[0]->s );
  1004. if ( c )
  1005. {
  1006. if ( !strcmp( path, "/nsm/gui/client/status" ) &&
  1007. !strcmp( types, "ss" ))
  1008. {
  1009. controller->client_pending_command( c, &argv[1]->s );
  1010. }
  1011. else if ( !strcmp( path, "/nsm/gui/client/progress" ) &&
  1012. !strcmp( types, "sf" ))
  1013. {
  1014. c->progress( argv[1]->f );
  1015. }
  1016. else if ( !strcmp( path, "/nsm/gui/client/dirty" ) &&
  1017. !strcmp( types, "si" ))
  1018. {
  1019. c->dirty( argv[1]->i );
  1020. }
  1021. else if ( !strcmp( path, "/nsm/gui/client/gui_visible" ) &&
  1022. !strcmp( types, "si" ))
  1023. {
  1024. c->gui_visible( argv[1]->i );
  1025. }
  1026. else if ( !strcmp( path, "/nsm/gui/client/label" ) &&
  1027. !strcmp( types, "ss" ))
  1028. {
  1029. c->client_label( &argv[1]->s );
  1030. }
  1031. else if ( !strcmp( path, "/nsm/gui/client/has_optional_gui" ) &&
  1032. !strcmp( types, "s" ))
  1033. {
  1034. c->has_optional_gui();
  1035. }
  1036. else if ( !strcmp( path, "/nsm/gui/client/switch" ) &&
  1037. !strcmp( types, "ss" ))
  1038. {
  1039. c->client_id( &argv[1]->s );
  1040. }
  1041. }
  1042. else
  1043. MESSAGE( "Got message %s from unknown client", path );
  1044. }
  1045. }
  1046. Fl::unlock();
  1047. Fl::awake();
  1048. return 0;
  1049. }
  1050. };
  1051. static NSM_Controller *controller;
  1052. void
  1053. ping ( void * )
  1054. {
  1055. controller->ping();
  1056. Fl::repeat_timeout( 1.0, ping, NULL );
  1057. }
  1058. void
  1059. cb_main ( Fl_Widget *, void * )
  1060. {
  1061. if ( Fl::event_key() != FL_Escape )
  1062. {
  1063. int children = 0;
  1064. foreach_daemon ( d )
  1065. {
  1066. if ( (*d)->is_child )
  1067. ++children;
  1068. }
  1069. if ( children )
  1070. {
  1071. if ( strlen( controller->session_name() ) )
  1072. {
  1073. fl_message( "%s", "You have to close the session before you can quit." );
  1074. return;
  1075. }
  1076. }
  1077. while ( Fl::first_window() ) Fl::first_window()->hide();
  1078. }
  1079. }
  1080. int
  1081. main (int argc, char **argv )
  1082. {
  1083. fl_register_images();
  1084. Fl::lock();
  1085. Fl_Double_Window *main_window;
  1086. {
  1087. Fl_Double_Window *o = main_window = new Fl_Double_Window( 800, 600, APP_TITLE );
  1088. {
  1089. main_window->xclass( APP_NAME );
  1090. Fl_Widget *o = controller = new NSM_Controller( 0, 0, main_window->w(), main_window->h(), NULL );
  1091. controller->session_name( "" );
  1092. Fl_Group::current()->resizable(o);
  1093. }
  1094. o->end();
  1095. o->size_range( main_window->w(), controller->min_h(), 0, 0 );
  1096. o->callback( (Fl_Callback*)cb_main, main_window );
  1097. o->show( 0, NULL );
  1098. }
  1099. static struct option long_options[] =
  1100. {
  1101. { "nsm-url", required_argument, 0, 'n' },
  1102. { "help", no_argument, 0, 'h' },
  1103. { 0, 0, 0, 0 }
  1104. };
  1105. int option_index = 0;
  1106. int c = 0;
  1107. while ( ( c = getopt_long_only( argc, argv, "", long_options, &option_index ) ) != -1 )
  1108. {
  1109. switch ( c )
  1110. {
  1111. case 'n':
  1112. {
  1113. DMESSAGE( "Adding %s to daemon list", optarg );
  1114. Daemon *d = new Daemon;
  1115. d->url = optarg;
  1116. d->addr = lo_address_new_from_url( optarg );
  1117. daemon_list.push_back( d );
  1118. break;
  1119. }
  1120. case 'h':
  1121. printf( "Usage: %s [--nsm-url...] [-- server options ]\n\n", argv[0] );
  1122. exit(0);
  1123. break;
  1124. }
  1125. }
  1126. const char *nsm_url = getenv( "NSM_URL" );
  1127. if ( nsm_url )
  1128. {
  1129. MESSAGE( "Found NSM URL of \"%s\" in environment, attempting to connect.", nsm_url );
  1130. Daemon *d = new Daemon;
  1131. d->url = nsm_url;
  1132. d->addr = lo_address_new_from_url( nsm_url );
  1133. daemon_list.push_back( d );
  1134. }
  1135. if ( controller->init_osc() )
  1136. FATAL( "Could not create OSC server" );
  1137. if ( daemon_list.size() )
  1138. {
  1139. foreach_daemon ( d )
  1140. {
  1141. controller->announce( (*d)->url );
  1142. }
  1143. }
  1144. else
  1145. {
  1146. /* start a new daemon... */
  1147. MESSAGE( "Starting daemon..." );
  1148. char *url = osc->url();
  1149. if ( ! fork() )
  1150. {
  1151. /* pass non-option arguments on to daemon */
  1152. char *args[4 + argc - optind];
  1153. int i = 0;
  1154. args[i++] = strdup("nsmd");
  1155. args[i++] = strdup("--gui-url");
  1156. args[i++] = url;
  1157. for ( ; optind < argc; i++, optind++ )
  1158. {
  1159. DMESSAGE( "Passing argument: %s", argv[optind] );
  1160. args[i] = argv[optind];
  1161. }
  1162. args[i] = 0;
  1163. if ( -1 == execvp( "nsmd", args ) )
  1164. {
  1165. FATAL( "Error starting process: %s", strerror( errno ) );
  1166. }
  1167. }
  1168. free(url);
  1169. }
  1170. Fl::add_timeout( 1.0, ping, NULL );
  1171. Fl::run();
  1172. foreach_daemon ( d )
  1173. {
  1174. if ( (*d)->is_child )
  1175. {
  1176. MESSAGE( "Telling server to quit" );
  1177. osc->send( (*d)->addr, "/nsm/server/quit" );
  1178. }
  1179. }
  1180. return 0;
  1181. }