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.

1445 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. { new Fl_Box( X + 1000, Y + 30, 100, H - ( 30 + SH ));
  784. }
  785. { Fl_Text_Display *o = status_display = new Fl_Text_Display( X, Y + H - SH, W, SH );
  786. o->box( FL_UP_BOX );
  787. Fl_Text_Buffer *b = new Fl_Text_Buffer();
  788. o->textfont( FL_COURIER ); // Create the "technical log" look&feel
  789. o->textsize( 12 );
  790. o->buffer(b);
  791. }
  792. o->end();
  793. resizable( o );
  794. } // Fl_tile
  795. end();
  796. deactivate();
  797. }
  798. int min_h ( void )
  799. {
  800. return 500;
  801. }
  802. void
  803. ping ( void )
  804. {
  805. if ( daemon_list.size() )
  806. {
  807. foreach_daemon( d )
  808. {
  809. osc->send( (*d)->addr, "/osc/ping" );
  810. }
  811. }
  812. if ( last_ping_response )
  813. {
  814. if ( time(NULL) - last_ping_response > 10 )
  815. {
  816. if ( active() )
  817. {
  818. deactivate();
  819. log_status( "Server is not responding..." );
  820. }
  821. }
  822. else
  823. {
  824. if ( !active() )
  825. {
  826. log_status( "Server is back." );
  827. activate();
  828. }
  829. }
  830. }
  831. }
  832. int init_osc ( void )
  833. {
  834. osc = new OSC::Endpoint();
  835. if ( int r = osc->init( LO_UDP ) )
  836. return r;
  837. osc->owner = this;
  838. osc->add_method( "/error", "sis", osc_handler, osc, "msg" );
  839. osc->add_method( "/reply", "ss", osc_handler, osc, "msg" );
  840. osc->add_method( "/reply", "s", osc_handler, osc, "" );
  841. osc->add_method( "/nsm/server/broadcast", NULL, osc_broadcast_handler, osc, "msg" );
  842. osc->add_method( "/nsm/gui/server_announce", "s", osc_handler, osc, "msg" );
  843. osc->add_method( "/nsm/gui/server/message", "s", osc_handler, osc, "msg" );
  844. osc->add_method( "/nsm/gui/gui_announce", "s", osc_handler, osc, "msg" );
  845. osc->add_method( "/nsm/gui/session/session", "s", osc_handler, osc, "path,display_name" );
  846. osc->add_method( "/nsm/gui/session/name", "ss", osc_handler, osc, "path,display_name" );
  847. osc->add_method( "/nsm/gui/client/new", "ss", osc_handler, osc, "path,display_name" );
  848. osc->add_method( "/nsm/gui/client/status", "ss", osc_handler, osc, "path,display_name" );
  849. osc->add_method( "/nsm/gui/client/switch", "ss", osc_handler, osc, "path,display_name" );
  850. osc->add_method( "/nsm/gui/client/progress", "sf", osc_handler, osc, "path,display_name" );
  851. osc->add_method( "/nsm/gui/client/dirty", "si", osc_handler, osc, "path,display_name" );
  852. osc->add_method( "/nsm/gui/client/has_optional_gui", "s", osc_handler, osc, "path,display_name" );
  853. osc->add_method( "/nsm/gui/client/gui_visible", "si", osc_handler, osc, "path,display_name" );
  854. osc->add_method( "/nsm/gui/client/label", "ss", osc_handler, osc, "path,display_name" );
  855. osc->start();
  856. return 0;
  857. }
  858. void announce ( const char *nsm_url )
  859. {
  860. /* Daemon *d = new Daemon; */
  861. /* d->url = nsm_url; */
  862. lo_address nsm_addr = lo_address_new_from_url( nsm_url );
  863. // d->is_child = true;
  864. /* daemon_list.push_back( d ); */
  865. osc->send( nsm_addr, "/nsm/gui/gui_announce" );
  866. }
  867. private:
  868. static int osc_broadcast_handler ( const char *path, const char *, lo_arg **, int argc, lo_message msg, void * )
  869. {
  870. if ( ! argc )
  871. /* need at least one argument... */
  872. return 0;
  873. DMESSAGE( "Relaying broadcast" );
  874. foreach_daemon( d )
  875. {
  876. char *u1 = lo_address_get_url( (*d)->addr );
  877. char *u2 = lo_address_get_url( lo_message_get_source( msg ) );
  878. if ( strcmp( u1, u2 ) )
  879. {
  880. osc->send( (*d)->addr, path, msg );
  881. }
  882. free( u1 );
  883. free( u2 );
  884. }
  885. return 0;
  886. }
  887. static int osc_handler ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  888. {
  889. // OSC_DMSG();
  890. NSM_Controller *controller = (NSM_Controller*)((OSC::Endpoint*)user_data)->owner;
  891. Fl::lock();
  892. if ( !strcmp( path, "/nsm/gui/server/message" ) && !strcmp( types, "s" ) )
  893. {
  894. controller->log_status( &argv[0]->s );
  895. }
  896. else if ( !strcmp( path, "/nsm/gui/session/session" ) &&
  897. ! strcmp( types, "s" ) )
  898. {
  899. controller->add_session_to_list( &argv[0]->s );
  900. }
  901. else if ( !strcmp( path, "/nsm/gui/gui_announce" ) )
  902. {
  903. /* pre-existing server is replying to our announce message */
  904. controller->activate();
  905. lo_address nsm_addr = lo_message_get_source( msg );
  906. osc->send( nsm_addr, "/nsm/server/list" );
  907. }
  908. else if ( !strcmp( path, "/nsm/gui/server_announce" ) )
  909. {
  910. /* must be a server we launched */
  911. controller->activate();
  912. Daemon *d = new Daemon;
  913. d->url = lo_address_get_url( lo_message_get_source( msg ) );
  914. d->addr = lo_address_new_from_url( d->url );
  915. d->is_child = true;
  916. daemon_list.push_back( d );
  917. osc->send( d->addr, "/nsm/server/list" );
  918. }
  919. else if ( !strcmp( path, "/nsm/gui/session/name" ) &&
  920. !strcmp( types, "ss" ))
  921. {
  922. controller->session_name( &argv[0]->s );
  923. if ( !strcmp( &argv[0]->s, "" ) )
  924. {
  925. controller->session_browser->deselect_all();
  926. }
  927. else
  928. {
  929. Fl_Tree_Item *o = controller->session_browser->find_item( &argv[1]->s );
  930. if ( o )
  931. {
  932. controller->session_browser->select_only( o, 0 );
  933. controller->session_browser->show_item( o, 0 );
  934. }
  935. }
  936. }
  937. else if (!strcmp( path, "/error" ) &&
  938. !strcmp( types, "sis" ) )
  939. {
  940. int err = argv[1]->i;
  941. if ( err != 0 )
  942. {
  943. char *s;
  944. asprintf( &s, "Command %s failed with:\n\n%s", &argv[0]->s, &argv[2]->s );
  945. Fl::awake(fl_awake_alert, s);
  946. }
  947. }
  948. else if (!strcmp( path, "/reply" ) && argc && 's' == *types )
  949. {
  950. if ( !strcmp( &argv[0]->s, "/nsm/server/list" ) )
  951. {
  952. controller->add_session_to_list( &argv[1]->s );
  953. }
  954. else if ( !strcmp( &argv[0]->s, "/osc/ping" ) )
  955. {
  956. last_ping_response = time( NULL );
  957. }
  958. else if ( ! strcmp( types, "ss" ) )
  959. {
  960. MESSAGE( "%s says %s", &argv[0]->s, &argv[1]->s);
  961. controller->log_status( &argv[1]->s );
  962. }
  963. }
  964. if ( !strncmp( path, "/nsm/gui/client/", strlen( "/nsm/gui/client/" ) ) )
  965. {
  966. if ( !strcmp( path, "/nsm/gui/client/new" ) &&
  967. !strcmp( types, "ss" ) )
  968. {
  969. controller->client_new( &argv[0]->s, &argv[1]->s );
  970. }
  971. else
  972. {
  973. NSM_Client *c = controller->client_by_id( &argv[0]->s );
  974. if ( c )
  975. {
  976. if ( !strcmp( path, "/nsm/gui/client/status" ) &&
  977. !strcmp( types, "ss" ))
  978. {
  979. controller->client_pending_command( c, &argv[1]->s );
  980. }
  981. else if ( !strcmp( path, "/nsm/gui/client/progress" ) &&
  982. !strcmp( types, "sf" ))
  983. {
  984. c->progress( argv[1]->f );
  985. }
  986. else if ( !strcmp( path, "/nsm/gui/client/dirty" ) &&
  987. !strcmp( types, "si" ))
  988. {
  989. c->dirty( argv[1]->i );
  990. }
  991. else if ( !strcmp( path, "/nsm/gui/client/gui_visible" ) &&
  992. !strcmp( types, "si" ))
  993. {
  994. c->gui_visible( argv[1]->i );
  995. }
  996. else if ( !strcmp( path, "/nsm/gui/client/label" ) &&
  997. !strcmp( types, "ss" ))
  998. {
  999. c->client_label( &argv[1]->s );
  1000. }
  1001. else if ( !strcmp( path, "/nsm/gui/client/has_optional_gui" ) &&
  1002. !strcmp( types, "s" ))
  1003. {
  1004. c->has_optional_gui();
  1005. }
  1006. else if ( !strcmp( path, "/nsm/gui/client/switch" ) &&
  1007. !strcmp( types, "ss" ))
  1008. {
  1009. c->client_id( &argv[1]->s );
  1010. }
  1011. }
  1012. else
  1013. MESSAGE( "Got message %s from unknown client", path );
  1014. }
  1015. }
  1016. Fl::unlock();
  1017. Fl::awake();
  1018. return 0;
  1019. }
  1020. };
  1021. static NSM_Controller *controller;
  1022. void
  1023. ping ( void * )
  1024. {
  1025. controller->ping();
  1026. Fl::repeat_timeout( 1.0, ping, NULL );
  1027. }
  1028. void
  1029. cb_main ( Fl_Widget *, void * )
  1030. {
  1031. if ( Fl::event_key() != FL_Escape )
  1032. {
  1033. int children = 0;
  1034. foreach_daemon ( d )
  1035. {
  1036. if ( (*d)->is_child )
  1037. ++children;
  1038. }
  1039. if ( children )
  1040. {
  1041. if ( strlen( controller->session_name() ) )
  1042. {
  1043. fl_message( "%s", "You have to close the session before you can quit." );
  1044. return;
  1045. }
  1046. }
  1047. while ( Fl::first_window() ) Fl::first_window()->hide();
  1048. }
  1049. }
  1050. int
  1051. main (int argc, char **argv )
  1052. {
  1053. Fl::scheme( "gtk+" );
  1054. Fl::visual(FL_DOUBLE|FL_INDEX); // FLKT Double_Window: "higly recommended […] put before the first show() of any window in your program"
  1055. fl_register_images();
  1056. Fl::lock();
  1057. Fl_Double_Window *main_window;
  1058. {
  1059. Fl_Double_Window *o = main_window = new Fl_Double_Window( 800, 600, APP_TITLE );
  1060. {
  1061. main_window->xclass( APP_NAME );
  1062. Fl_Widget *o = controller = new NSM_Controller( 0, 0, main_window->w(), main_window->h(), NULL );
  1063. controller->session_name( "" );
  1064. Fl_Group::current()->resizable(o);
  1065. }
  1066. o->end();
  1067. o->size_range( main_window->w(), controller->min_h(), 0, 0 );
  1068. o->callback( (Fl_Callback*)cb_main, main_window );
  1069. o->show( 0, NULL );
  1070. }
  1071. //Setting colors only after main window creation.
  1072. //We keep them all in once place instead of setting them in the widgets
  1073. 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.
  1074. Fl::set_color( FL_BACKGROUND2_COLOR, 55, 61, 69 ); //This color is used as a background by Fl_Input and other text widgets.
  1075. Fl::set_color( FL_FOREGROUND_COLOR, 223, 237, 255 );
  1076. Fl::set_color( FL_INACTIVE_COLOR, 255, 0, 0 ); // Not used
  1077. Fl::set_color( FL_SELECTION_COLOR, 80, 84, 92 ); // e.g. the currently selected session
  1078. Fl::reload_scheme();
  1079. controller->session_browser->item_labelfgcolor( fl_rgb_color (213, 227, 245 ) ); // a bit darker than foreground
  1080. static struct option long_options[] =
  1081. {
  1082. { "nsm-url", required_argument, 0, 'n' },
  1083. { "help", no_argument, 0, 'h' },
  1084. { 0, 0, 0, 0 }
  1085. };
  1086. int option_index = 0;
  1087. int c = 0;
  1088. while ( ( c = getopt_long_only( argc, argv, "", long_options, &option_index ) ) != -1 )
  1089. {
  1090. switch ( c )
  1091. {
  1092. case 'n':
  1093. {
  1094. DMESSAGE( "Adding %s to daemon list", optarg );
  1095. Daemon *d = new Daemon;
  1096. d->url = optarg;
  1097. d->addr = lo_address_new_from_url( optarg );
  1098. daemon_list.push_back( d );
  1099. break;
  1100. }
  1101. case 'h':
  1102. //Print usage message according to POSIX.1-2017
  1103. const char *usage =
  1104. "legacy-gui - FLTK GUI for the 'New Session Manager'\n\n"
  1105. "Usage:\n"
  1106. " legacy-gui\n"
  1107. " legacy-gui --help\n"
  1108. "\n"
  1109. "Options:\n"
  1110. " --help Show this screen\n"
  1111. " --nsm-url url Connect to a running nsmd [Example: osc.udp://mycomputer.localdomain:38356/].\n"
  1112. " -- Everything after -- will be given to nsmd as server options. See nsmd --help .\n"
  1113. "\n"
  1114. "For backwards compatibility this executable also exist as symlink 'non-session-manager'\n"
  1115. "\n"
  1116. "";
  1117. puts ( usage );
  1118. exit(0);
  1119. break;
  1120. }
  1121. }
  1122. const char *nsm_url = getenv( "NSM_URL" );
  1123. if ( nsm_url )
  1124. {
  1125. MESSAGE( "Found NSM URL of \"%s\" in environment, attempting to connect.", nsm_url );
  1126. Daemon *d = new Daemon;
  1127. d->url = nsm_url;
  1128. d->addr = lo_address_new_from_url( nsm_url );
  1129. daemon_list.push_back( d );
  1130. }
  1131. if ( controller->init_osc() )
  1132. FATAL( "Could not create OSC server" );
  1133. if ( daemon_list.size() )
  1134. {
  1135. foreach_daemon ( d )
  1136. {
  1137. controller->announce( (*d)->url );
  1138. }
  1139. }
  1140. else
  1141. {
  1142. /* start a new daemon... */
  1143. MESSAGE( "Starting daemon..." );
  1144. char *url = osc->url();
  1145. if ( ! fork() )
  1146. {
  1147. /* pass non-option arguments on to daemon */
  1148. char *args[4 + argc - optind];
  1149. int i = 0;
  1150. args[i++] = strdup("nsmd");
  1151. args[i++] = strdup("--gui-url");
  1152. args[i++] = url;
  1153. for ( ; optind < argc; i++, optind++ )
  1154. {
  1155. DMESSAGE( "Passing argument: %s", argv[optind] );
  1156. args[i] = argv[optind];
  1157. }
  1158. args[i] = 0;
  1159. if ( -1 == execvp( "nsmd", args ) )
  1160. {
  1161. FATAL( "Error starting process: %s", strerror( errno ) );
  1162. }
  1163. }
  1164. free(url);
  1165. }
  1166. Fl::add_timeout( 1.0, ping, NULL );
  1167. Fl::run();
  1168. foreach_daemon ( d )
  1169. {
  1170. if ( (*d)->is_child )
  1171. {
  1172. MESSAGE( "Telling server to quit" );
  1173. osc->send( (*d)->addr, "/nsm/server/quit" );
  1174. }
  1175. }
  1176. return 0;
  1177. }