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.

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