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.

799 lines
20KB

  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. #pragma GCC diagnostic ignored "-Wunused-parameter"
  21. #pragma GCC diagnostic ignored "-Wunused-result"
  22. #define _MODULE_ "nsm-proxy"
  23. #define APP_NAME "NSM Proxy"
  24. #define APP_TITLE "NSM Proxy"
  25. #include "debug.h"
  26. #include <lo/lo.h>
  27. #include <stdio.h>
  28. #include <stdlib.h>
  29. #include <unistd.h>
  30. #include <errno.h>
  31. #include <signal.h>
  32. #include <string.h>
  33. #include <sys/types.h>
  34. #include <sys/stat.h>
  35. #include <sys/signalfd.h>
  36. #include <sys/stat.h>
  37. #include <sys/wait.h>
  38. #include <getopt.h>
  39. static lo_server losrv;
  40. static lo_address nsm_addr;
  41. static lo_address gui_addr;
  42. static int nsm_is_active;
  43. static char *project_file;
  44. static int die_now = 0;
  45. static int signal_fd;
  46. static char *nsm_client_id;
  47. static char *nsm_display_name;
  48. #define CONFIG_FILE_NAME "nsm-proxy.config"
  49. void show_gui ( void );
  50. class NSM_Proxy {
  51. char *_label;
  52. char *_executable;
  53. char *_config_file;
  54. char *_arguments;
  55. int _save_signal;
  56. int _stop_signal;
  57. int _pid;
  58. char *_client_error;
  59. public:
  60. int stop_signal ( void ) {return _stop_signal;}
  61. NSM_Proxy ( )
  62. {
  63. _label = _executable = _arguments = _config_file = 0;
  64. _save_signal = 0;
  65. _stop_signal = SIGTERM;
  66. _pid = 0;
  67. _client_error = 0;
  68. }
  69. ~NSM_Proxy ( )
  70. {
  71. }
  72. void handle_client_death ( int status )
  73. {
  74. printf( "proxied process died unexpectedly... not dying\n" );
  75. /* proxied process died unexpectedly */
  76. if ( _client_error != NULL )
  77. free(_client_error);
  78. asprintf(&_client_error, "The proxied process terminated abnormally during invocation. Exit status: %i.", status );
  79. show_gui();
  80. _pid = 0;
  81. }
  82. void kill ( void )
  83. {
  84. if ( _pid )
  85. {
  86. ::kill( _pid, _stop_signal );
  87. }
  88. }
  89. bool start ( const char *executable, const char *arguments, const char *config_file )
  90. {
  91. if ( _executable )
  92. free( _executable );
  93. if ( _arguments )
  94. free( _arguments );
  95. if ( _config_file )
  96. free( _config_file );
  97. _executable = strdup( executable );
  98. if ( arguments )
  99. _arguments = strdup( arguments );
  100. else
  101. _arguments = NULL;
  102. if ( config_file )
  103. _config_file = strdup( config_file );
  104. else
  105. _config_file = NULL;
  106. return start();
  107. }
  108. bool start ( void )
  109. {
  110. dump( project_file );
  111. if ( _pid )
  112. /* already running */
  113. return true;
  114. if ( !_executable )
  115. {
  116. WARNING( "Executable is null." );
  117. return false;
  118. }
  119. int pid;
  120. if ( ! (pid = fork()) )
  121. {
  122. MESSAGE( "Launching %s\n", _executable );
  123. // char *args[] = { strdup( executable ), NULL };
  124. char *cmd;
  125. if ( _arguments )
  126. asprintf( &cmd, "exec %s %s >error.log 2>&1", _executable, _arguments );
  127. else
  128. asprintf( &cmd, "exec %s >error.log 2>&1", _executable );
  129. char *args[] = { strdup("/bin/sh"), strdup( "-c" ), cmd, NULL };
  130. setenv( "NSM_CLIENT_ID", nsm_client_id, 1 );
  131. setenv( "NSM_SESSION_NAME", nsm_display_name, 1 );
  132. if ( _config_file )
  133. setenv( "CONFIG_FILE", _config_file, 1 );
  134. unsetenv( "NSM_URL" );
  135. if ( -1 == execvp( "/bin/sh", args ) )
  136. {
  137. WARNING( "Error starting process: %s", strerror( errno ) );
  138. exit(1);
  139. }
  140. }
  141. _pid = pid;
  142. return _pid > 0;
  143. }
  144. void save_signal ( int s )
  145. {
  146. _save_signal = s;
  147. }
  148. void stop_signal ( int s )
  149. {
  150. _stop_signal = s;
  151. }
  152. void label ( const char *s )
  153. {
  154. if ( _label )
  155. free( _label );
  156. _label = strdup( s );
  157. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/label", "s", _label );
  158. }
  159. void save ( void )
  160. {
  161. DMESSAGE( "Sending process save signal" );
  162. if ( _pid )
  163. ::kill( _pid, _save_signal );
  164. }
  165. bool dump ( const char *path )
  166. {
  167. char *fname;
  168. asprintf( &fname, "%s/%s", path, CONFIG_FILE_NAME );
  169. FILE *fp = fopen( fname, "w" );
  170. free( fname );
  171. if ( !fp )
  172. {
  173. WARNING( "Error opening file for saving: %s", strerror( errno ) );
  174. return false;
  175. }
  176. if ( _executable && strlen(_executable) )
  177. fprintf( fp, "executable\n\t%s\n", _executable );
  178. if ( _arguments && strlen(_arguments) )
  179. fprintf( fp, "arguments\n\t%s\n", _arguments );
  180. if ( _config_file && strlen(_config_file) )
  181. fprintf( fp, "config file\n\t%s\n", _config_file );
  182. fprintf( fp, "save signal\n\t%i\n", _save_signal );
  183. fprintf( fp, "stop signal\n\t%i\n", _stop_signal );
  184. if ( _label && strlen(_label) )
  185. fprintf( fp, "label\n\t%s\n", _label );
  186. fclose( fp );
  187. return true;
  188. }
  189. bool restore ( const char *path )
  190. {
  191. FILE *fp = fopen( path, "r" );
  192. if ( ! fp )
  193. {
  194. WARNING( "Error opening file for restore: %s", strerror( errno ) );
  195. return false;
  196. }
  197. char *name;
  198. char *value;
  199. MESSAGE( "Loading file config \"%s\"", path );
  200. while ( 2 == fscanf( fp, "%m[^\n]\n\t%m[^\n]\n", &name, &value ) )
  201. {
  202. DMESSAGE( "%s=%s", name, value );
  203. if ( !strcmp( name, "executable" ) )
  204. _executable = value;
  205. else if (!strcmp( name, "arguments" ) )
  206. _arguments = value;
  207. else if (!strcmp( name, "config file" ) )
  208. _config_file = value;
  209. else if ( !strcmp( name, "save signal" ) )
  210. {
  211. _save_signal = atoi( value );
  212. free( value );
  213. }
  214. else if ( !strcmp( name, "stop signal" ) )
  215. {
  216. _stop_signal = atoi( value );
  217. free( value );
  218. }
  219. else if ( !strcmp( name, "label" ) )
  220. {
  221. label( value );
  222. free( value );
  223. }
  224. else
  225. {
  226. WARNING( "Unknown option \"%s\" in config file", name );
  227. }
  228. free( name );
  229. }
  230. fclose( fp );
  231. start();
  232. return true;
  233. }
  234. void update ( lo_address to )
  235. {
  236. DMESSAGE( "Sending update" );
  237. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/save_signal", "i", _save_signal );
  238. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/label", "s", _label ? _label : "" );
  239. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/executable", "s", _executable ? _executable : "" );
  240. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/arguments", "s", _arguments ? _arguments : "" );
  241. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/config_file", "s", _config_file ? _config_file : "" );
  242. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/stop_signal", "i", _stop_signal );
  243. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/client_error", "s", _client_error ? _client_error : "" );
  244. }
  245. };
  246. NSM_Proxy *nsm_proxy;
  247. bool
  248. snapshot ( const char *file )
  249. {
  250. return nsm_proxy->dump(file);
  251. }
  252. void
  253. announce ( const char *nsm_url, const char *client_name, const char *process_name )
  254. {
  255. printf( "Announcing to NSM\n" );
  256. lo_address to = lo_address_new_from_url( nsm_url );
  257. int pid = (int)getpid();
  258. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/server/announce", "sssiii",
  259. client_name,
  260. ":optional-gui:",
  261. process_name,
  262. 1, /* api_major_version */
  263. 0, /* api_minor_version */
  264. pid );
  265. lo_address_free( to );
  266. }
  267. bool
  268. open ( const char *file )
  269. {
  270. char *path;
  271. asprintf( &path, "%s/%s", file, CONFIG_FILE_NAME );
  272. bool r = nsm_proxy->restore( path );
  273. free( path );
  274. return r;
  275. }
  276. /****************/
  277. /* OSC HANDLERS */
  278. /****************/
  279. /* NSM */
  280. int
  281. osc_announce_error ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  282. {
  283. if ( strcmp( types, "sis" ) )
  284. return -1;
  285. if ( strcmp( "/nsm/server/announce", &argv[0]->s ) )
  286. return -1;
  287. printf( "Failed to register with NSM: %s\n", &argv[2]->s );
  288. nsm_is_active = 0;
  289. return 0;
  290. }
  291. int
  292. osc_announce_reply ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  293. {
  294. if ( strcmp( "/nsm/server/announce", &argv[0]->s ) )
  295. return -1;
  296. printf( "Successfully registered. NSM says: %s", &argv[1]->s );
  297. nsm_is_active = 1;
  298. nsm_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ) );
  299. return 0;
  300. }
  301. int
  302. osc_save ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  303. {
  304. bool r = snapshot( project_file );
  305. nsm_proxy->save();
  306. if ( r )
  307. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );
  308. else
  309. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/error", "sis", path, -1, "Error saving project file" );
  310. return 0;
  311. }
  312. static int gui_pid;
  313. void
  314. show_gui ( void )
  315. {
  316. int pid;
  317. if ( ! (pid = fork()) )
  318. {
  319. char executable[] = "nsm-proxy-gui";
  320. MESSAGE( "Launching %s\n", executable );
  321. char *url = lo_server_get_url( losrv );
  322. char *args[] = { executable, strdup( "--connect-to" ), url, NULL };
  323. if ( -1 == execvp( executable, args ) )
  324. {
  325. WARNING( "Error starting process: %s", strerror( errno ) );
  326. exit(1);
  327. }
  328. }
  329. gui_pid = pid;
  330. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_shown", "" );
  331. }
  332. int
  333. osc_show_gui ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  334. {
  335. show_gui();
  336. /* FIXME: detect errors */
  337. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );
  338. return 0;
  339. }
  340. void
  341. hide_gui ( void )
  342. {
  343. if ( gui_pid )
  344. {
  345. kill( gui_pid, SIGTERM );
  346. }
  347. }
  348. int
  349. osc_hide_gui ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  350. {
  351. hide_gui();
  352. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" );
  353. /* FIXME: detect errors */
  354. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );
  355. return 0;
  356. }
  357. int
  358. osc_open ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  359. {
  360. const char *new_path = &argv[0]->s;
  361. const char *display_name = &argv[1]->s;
  362. const char *client_id = &argv[2]->s;
  363. if ( nsm_client_id )
  364. free(nsm_client_id);
  365. nsm_client_id = strdup( client_id );
  366. if ( nsm_display_name )
  367. free( nsm_display_name );
  368. nsm_display_name = strdup( display_name );
  369. char *new_filename;
  370. mkdir( new_path, 0777 );
  371. chdir( new_path );
  372. asprintf( &new_filename, "%s/%s", new_path, CONFIG_FILE_NAME );
  373. struct stat st;
  374. if ( 0 == stat( new_filename, &st ) )
  375. {
  376. if ( open( new_path ) )
  377. {
  378. }
  379. else
  380. {
  381. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/error", "sis", path, -1, "Could not open file" );
  382. return 0;
  383. }
  384. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" );
  385. }
  386. else
  387. {
  388. show_gui();
  389. }
  390. if ( project_file )
  391. free( project_file );
  392. project_file = strdup( new_path );
  393. // new_filename;
  394. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );
  395. if ( gui_addr )
  396. nsm_proxy->update( gui_addr );
  397. return 0;
  398. }
  399. /* GUI */
  400. int
  401. osc_label ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  402. {
  403. nsm_proxy->label( &argv[0]->s );
  404. return 0;
  405. }
  406. int
  407. osc_save_signal ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  408. {
  409. nsm_proxy->save_signal( argv[0]->i );
  410. return 0;
  411. }
  412. int
  413. osc_stop_signal ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  414. {
  415. nsm_proxy->stop_signal( argv[0]->i );
  416. return 0;
  417. }
  418. int
  419. osc_start ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  420. {
  421. snapshot( project_file );
  422. if ( nsm_proxy->start( &argv[0]->s, &argv[1]->s, &argv[2]->s ) )
  423. {
  424. hide_gui();
  425. }
  426. return 0;
  427. }
  428. int
  429. osc_kill ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  430. {
  431. nsm_proxy->kill();
  432. return 0;
  433. }
  434. int
  435. osc_update ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  436. {
  437. lo_address to = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));
  438. nsm_proxy->update( to );
  439. gui_addr = to;
  440. return 0;
  441. }
  442. void
  443. signal_handler ( int x )
  444. {
  445. die_now = 1;
  446. }
  447. void
  448. set_traps ( void )
  449. {
  450. signal( SIGHUP, signal_handler );
  451. signal( SIGINT, signal_handler );
  452. // signal( SIGQUIT, signal_handler );
  453. // signal( SIGSEGV, signal_handler );
  454. // signal( SIGPIPE, signal_handler );
  455. signal( SIGTERM, signal_handler );
  456. }
  457. void
  458. init_osc ( const char *osc_port )
  459. {
  460. losrv = lo_server_new( osc_port, NULL );
  461. //error_handler );
  462. char *url = lo_server_get_url(losrv);
  463. printf("OSC: %s\n",url);
  464. free(url);
  465. /* NSM */
  466. lo_server_add_method( losrv, "/nsm/client/save", "", osc_save, NULL );
  467. lo_server_add_method( losrv, "/nsm/client/open", "sss", osc_open, NULL );
  468. lo_server_add_method( losrv, "/nsm/client/show_optional_gui", "", osc_show_gui, NULL );
  469. lo_server_add_method( losrv, "/nsm/client/hide_optional_gui", "", osc_hide_gui, NULL );
  470. lo_server_add_method( losrv, "/error", "sis", osc_announce_error, NULL );
  471. lo_server_add_method( losrv, "/reply", "ssss", osc_announce_reply, NULL );
  472. /* GUI */
  473. lo_server_add_method( losrv, "/nsm/proxy/label", "s", osc_label, NULL );
  474. lo_server_add_method( losrv, "/nsm/proxy/save_signal", "i", osc_save_signal, NULL );
  475. lo_server_add_method( losrv, "/nsm/proxy/stop_signal", "i", osc_stop_signal, NULL );
  476. lo_server_add_method( losrv, "/nsm/proxy/kill", "", osc_kill, NULL );
  477. lo_server_add_method( losrv, "/nsm/proxy/start", "sss", osc_start, NULL );
  478. lo_server_add_method( losrv, "/nsm/proxy/update", "", osc_update, NULL );
  479. }
  480. void
  481. die ( void )
  482. {
  483. if ( gui_pid )
  484. {
  485. DMESSAGE( "Killing GUI" );
  486. kill( gui_pid, SIGTERM );
  487. }
  488. nsm_proxy->kill();
  489. exit(0);
  490. }
  491. void handle_sigchld ( )
  492. {
  493. for ( ;; )
  494. {
  495. int status;
  496. pid_t pid = waitpid(-1, &status, WNOHANG);
  497. if (pid <= 0)
  498. break;
  499. if ( pid == gui_pid )
  500. {
  501. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" );
  502. gui_pid = 0;
  503. /* don't care... */
  504. continue;
  505. }
  506. if ( WIFSIGNALED(status) )
  507. {
  508. /* process was killed via signal */
  509. if (WTERMSIG(status) == SIGTERM ||
  510. WTERMSIG(status) == SIGHUP ||
  511. WTERMSIG(status) == SIGINT ||
  512. WTERMSIG(status) == SIGKILL )
  513. {
  514. /* process was killed via an appropriate signal */
  515. MESSAGE( "child was killed (maybe by us)\n" );
  516. die_now = 1;
  517. continue;
  518. }
  519. }
  520. else if ( WIFEXITED(status) )
  521. {
  522. /* child called exit() or returned from main() */
  523. MESSAGE( "child exit status: %i", WEXITSTATUS(status) );
  524. if ( WEXITSTATUS(status) == 0 )
  525. {
  526. /* apparently normal termination */
  527. MESSAGE( "child exited without error.");
  528. die_now = 1;
  529. continue;
  530. }
  531. else
  532. {
  533. MESSAGE("child exited abnormally.");
  534. nsm_proxy->handle_client_death(WEXITSTATUS(status));
  535. }
  536. }
  537. }
  538. }
  539. int
  540. main ( int argc, char **argv )
  541. {
  542. set_traps();
  543. sigset_t mask;
  544. sigemptyset( &mask );
  545. sigaddset( &mask, SIGCHLD );
  546. sigprocmask(SIG_BLOCK, &mask, NULL );
  547. signal_fd = signalfd( -1, &mask, SFD_NONBLOCK );
  548. //Command line parameters
  549. static struct option long_options[] =
  550. {
  551. { "help", no_argument, 0, 'h' },
  552. { 0, 0, 0, 0 }
  553. };
  554. int option_index = 0;
  555. int c = 0;
  556. while ( ( c = getopt_long_only( argc, argv, "", long_options, &option_index ) ) != -1 )
  557. {
  558. switch ( c )
  559. {
  560. case 'h':
  561. {
  562. const char *usage =
  563. "nsm-proxy - Wrapper for executables without direct NSM-Support.\n\n"
  564. "It is a module for the 'New Session Manager' and only communicates\n"
  565. "over OSC in an NSM-Session and has no standalone functionality.\n"
  566. "\n"
  567. "Usage:\n"
  568. " nsm-proxy --help\n"
  569. "\n"
  570. "Options:\n"
  571. " --help Show this screen\n"
  572. "";
  573. printf ( usage );
  574. exit(0);
  575. break;
  576. }
  577. }
  578. }
  579. nsm_proxy = new NSM_Proxy();
  580. init_osc( NULL );
  581. const char *nsm_url = getenv( "NSM_URL" );
  582. if ( nsm_url )
  583. {
  584. announce( nsm_url, APP_TITLE, argv[0] );
  585. }
  586. else
  587. {
  588. fprintf( stderr, "Could not register as NSM client.\n" );
  589. exit(1);
  590. }
  591. struct signalfd_siginfo fdsi;
  592. /* listen for sigchld signals and process OSC messages forever */
  593. for ( ;; )
  594. {
  595. ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));
  596. if (s == sizeof(struct signalfd_siginfo))
  597. {
  598. if (fdsi.ssi_signo == SIGCHLD)
  599. handle_sigchld();
  600. }
  601. lo_server_recv_noblock( losrv, 500 );
  602. if ( die_now )
  603. die();
  604. }
  605. }