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.

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