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.

709 lines
18KB

  1. /*******************************************************************************/
  2. /* Copyright (C) 2012 Jonathan Moore Liles */
  3. /* */
  4. /* This program is free software; you can redistribute it and/or modify it */
  5. /* under the terms of the GNU General Public License as published by the */
  6. /* Free Software Foundation; either version 2 of the License, or (at your */
  7. /* option) any later version. */
  8. /* */
  9. /* This program is distributed in the hope that it will be useful, but WITHOUT */
  10. /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
  11. /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */
  12. /* more details. */
  13. /* */
  14. /* You should have received a copy of the GNU General Public License along */
  15. /* with This program; see the file COPYING. If not,write to the Free Software */
  16. /* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
  17. /*******************************************************************************/
  18. #pragma GCC diagnostic ignored "-Wunused-parameter"
  19. #define _MODULE_ "nsm-proxy"
  20. #define APP_NAME "NSM Proxy"
  21. #define APP_TITLE "NSM Proxy"
  22. #include "debug.h"
  23. #include <lo/lo.h>
  24. #include <stdio.h>
  25. #include <stdlib.h>
  26. #include <unistd.h>
  27. #include <errno.h>
  28. #include <signal.h>
  29. #include <string.h>
  30. #include <sys/types.h>
  31. #include <sys/stat.h>
  32. #include <sys/signalfd.h>
  33. #include <sys/stat.h>
  34. #include <sys/wait.h>
  35. static lo_server losrv;
  36. static lo_address nsm_addr;
  37. static lo_address gui_addr;
  38. static int nsm_is_active;
  39. static char *project_file;
  40. static int die_now = 0;
  41. static int signal_fd;
  42. static char *nsm_client_id;
  43. static char *nsm_display_name;
  44. #define CONFIG_FILE_NAME "nsm-proxy.config"
  45. class NSM_Proxy {
  46. char *_label;
  47. char *_executable;
  48. char *_config_file;
  49. char *_arguments;
  50. int _save_signal;
  51. int _stop_signal;
  52. int _pid;
  53. public:
  54. NSM_Proxy ( )
  55. {
  56. _label = _executable = _arguments = _config_file = 0;
  57. _save_signal = 0;
  58. _stop_signal = SIGTERM;
  59. _pid = 0;
  60. }
  61. ~NSM_Proxy ( )
  62. {
  63. }
  64. void kill ( void )
  65. {
  66. if ( _pid )
  67. ::kill( _pid, _stop_signal );
  68. }
  69. bool start ( const char *executable, const char *arguments, const char *config_file )
  70. {
  71. if ( _executable )
  72. free( _executable );
  73. if ( _arguments )
  74. free( _arguments );
  75. if ( _config_file )
  76. free( _config_file );
  77. _executable = strdup( executable );
  78. if ( arguments )
  79. _arguments = strdup( arguments );
  80. else
  81. _arguments = NULL;
  82. if ( config_file )
  83. _config_file = strdup( config_file );
  84. else
  85. _config_file = NULL;
  86. return start();
  87. }
  88. bool start ( void )
  89. {
  90. dump( project_file );
  91. if ( _pid )
  92. /* already running */
  93. return true;
  94. if ( !_executable )
  95. {
  96. WARNING( "Executable is null." );
  97. return false;
  98. }
  99. int pid;
  100. if ( ! (pid = fork()) )
  101. {
  102. MESSAGE( "Launching %s\n", _executable );
  103. // char *args[] = { strdup( executable ), NULL };
  104. char *cmd;
  105. if ( _arguments )
  106. asprintf( &cmd, "exec %s %s", _executable, _arguments );
  107. else
  108. asprintf( &cmd, "exec %s", _executable );
  109. char *args[] = { _executable, strdup( "-c" ), cmd, NULL };
  110. setenv( "NSM_CLIENT_ID", nsm_client_id, 1 );
  111. setenv( "NSM_SESSION_NAME", nsm_display_name, 1 );
  112. if ( _config_file )
  113. setenv( "CONFIG_FILE", _config_file, 1 );
  114. unsetenv( "NSM_URL" );
  115. if ( -1 == execvp( "/bin/sh", args ) )
  116. {
  117. WARNING( "Error starting process: %s", strerror( errno ) );
  118. exit(-1);
  119. }
  120. }
  121. _pid = pid;
  122. return _pid > 0;
  123. }
  124. void save_signal ( int s )
  125. {
  126. _save_signal = s;
  127. }
  128. void stop_signal ( int s )
  129. {
  130. _stop_signal = s;
  131. }
  132. void label ( const char *s )
  133. {
  134. if ( _label )
  135. free( _label );
  136. _label = strdup( s );
  137. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/label", "s", _label );
  138. }
  139. void save ( void )
  140. {
  141. DMESSAGE( "Sending process save signal" );
  142. if ( _pid )
  143. ::kill( _pid, _save_signal );
  144. }
  145. bool dump ( const char *path )
  146. {
  147. char *fname;
  148. asprintf( &fname, "%s/%s", path, CONFIG_FILE_NAME );
  149. FILE *fp = fopen( fname, "w" );
  150. free( fname );
  151. if ( !fp )
  152. {
  153. WARNING( "Error opening file for saving: %s", strerror( errno ) );
  154. return false;
  155. }
  156. if ( _executable && strlen(_executable) )
  157. fprintf( fp, "executable\n\t%s\n", _executable );
  158. if ( _arguments && strlen(_arguments) )
  159. fprintf( fp, "arguments\n\t%s\n", _arguments );
  160. if ( _config_file && strlen(_config_file) )
  161. fprintf( fp, "config file\n\t%s\n", _config_file );
  162. fprintf( fp, "save signal\n\t%i\n", _save_signal );
  163. fprintf( fp, "stop signal\n\t%i\n", _stop_signal );
  164. if ( _label && strlen(_label) )
  165. fprintf( fp, "label\n\t%s\n", _label );
  166. fclose( fp );
  167. return true;
  168. }
  169. bool restore ( const char *path )
  170. {
  171. FILE *fp = fopen( path, "r" );
  172. if ( ! fp )
  173. {
  174. WARNING( "Error opening file for restore: %s", strerror( errno ) );
  175. return false;
  176. }
  177. char *name;
  178. char *value;
  179. MESSAGE( "Loading file config \"%s\"", path );
  180. while ( 2 == fscanf( fp, "%a[^\n]\n\t%a[^\n]\n", &name, &value ) )
  181. {
  182. DMESSAGE( "%s=%s", name, value );
  183. if ( !strcmp( name, "executable" ) )
  184. _executable = value;
  185. else if (!strcmp( name, "arguments" ) )
  186. _arguments = value;
  187. else if (!strcmp( name, "config file" ) )
  188. _config_file = value;
  189. else if ( !strcmp( name, "save signal" ) )
  190. {
  191. _save_signal = atoi( value );
  192. free( value );
  193. }
  194. else if ( !strcmp( name, "stop signal" ) )
  195. {
  196. _stop_signal = atoi( value );
  197. free( value );
  198. }
  199. else if ( !strcmp( name, "label" ) )
  200. {
  201. label( value );
  202. free( value );
  203. }
  204. else
  205. {
  206. WARNING( "Unknown option \"%s\" in config file", name );
  207. }
  208. free( name );
  209. }
  210. fclose( fp );
  211. start();
  212. return true;
  213. }
  214. void update ( lo_address to )
  215. {
  216. DMESSAGE( "Sending update" );
  217. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/save_signal", "i", _save_signal );
  218. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/label", "s", _label ? _label : "" );
  219. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/executable", "s", _executable ? _executable : "" );
  220. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/arguments", "s", _arguments ? _arguments : "" );
  221. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/config_file", "s", _config_file ? _config_file : "" );
  222. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/stop_signal", "i", _stop_signal );
  223. }
  224. };
  225. NSM_Proxy *nsm_proxy;
  226. bool
  227. snapshot ( const char *file )
  228. {
  229. return nsm_proxy->dump(file);
  230. }
  231. void
  232. announce ( const char *nsm_url, const char *client_name, const char *process_name )
  233. {
  234. printf( "Announcing to NSM\n" );
  235. lo_address to = lo_address_new_from_url( nsm_url );
  236. int pid = (int)getpid();
  237. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/server/announce", "sssiii",
  238. client_name,
  239. ":optional-gui:",
  240. process_name,
  241. 1, /* api_major_version */
  242. 0, /* api_minor_version */
  243. pid );
  244. lo_address_free( to );
  245. }
  246. bool
  247. open ( const char *file )
  248. {
  249. char *path;
  250. asprintf( &path, "%s/%s", file, CONFIG_FILE_NAME );
  251. bool r = nsm_proxy->restore( path );
  252. free( path );
  253. return r;
  254. }
  255. /****************/
  256. /* OSC HANDLERS */
  257. /****************/
  258. /* NSM */
  259. int
  260. osc_announce_error ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  261. {
  262. if ( strcmp( types, "sis" ) )
  263. return -1;
  264. if ( strcmp( "/nsm/server/announce", &argv[0]->s ) )
  265. return -1;
  266. printf( "Failed to register with NSM: %s\n", &argv[2]->s );
  267. nsm_is_active = 0;
  268. return 0;
  269. }
  270. int
  271. osc_announce_reply ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  272. {
  273. if ( strcmp( "/nsm/server/announce", &argv[0]->s ) )
  274. return -1;
  275. printf( "Successfully registered. NSM says: %s", &argv[1]->s );
  276. nsm_is_active = 1;
  277. nsm_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ) );
  278. return 0;
  279. }
  280. int
  281. osc_save ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  282. {
  283. bool r = snapshot( project_file );
  284. nsm_proxy->save();
  285. if ( r )
  286. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );
  287. else
  288. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/error", "sis", path, -1, "Error saving project file" );
  289. return 0;
  290. }
  291. static int gui_pid;
  292. void
  293. show_gui ( void )
  294. {
  295. int pid;
  296. if ( ! (pid = fork()) )
  297. {
  298. char executable[] = "nsm-proxy-gui";
  299. MESSAGE( "Launching %s\n", executable );
  300. char *url = lo_server_get_url( losrv );
  301. char *args[] = { executable, strdup( "--connect-to" ), url, NULL };
  302. if ( -1 == execvp( executable, args ) )
  303. {
  304. WARNING( "Error starting process: %s", strerror( errno ) );
  305. exit(-1);
  306. }
  307. }
  308. gui_pid = pid;
  309. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_shown", "" );
  310. }
  311. int
  312. osc_show_gui ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  313. {
  314. show_gui();
  315. /* FIXME: detect errors */
  316. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );
  317. return 0;
  318. }
  319. void
  320. hide_gui ( void )
  321. {
  322. if ( gui_pid )
  323. {
  324. kill( gui_pid, SIGTERM );
  325. }
  326. }
  327. int
  328. osc_hide_gui ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  329. {
  330. hide_gui();
  331. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" );
  332. /* FIXME: detect errors */
  333. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );
  334. return 0;
  335. }
  336. int
  337. osc_open ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  338. {
  339. const char *new_path = &argv[0]->s;
  340. const char *display_name = &argv[1]->s;
  341. const char *client_id = &argv[2]->s;
  342. if ( nsm_client_id )
  343. free(nsm_client_id);
  344. nsm_client_id = strdup( client_id );
  345. if ( nsm_display_name )
  346. free( nsm_display_name );
  347. nsm_display_name = strdup( display_name );
  348. char *new_filename;
  349. mkdir( new_path, 0777 );
  350. chdir( new_path );
  351. asprintf( &new_filename, "%s/%s", new_path, CONFIG_FILE_NAME );
  352. struct stat st;
  353. if ( 0 == stat( new_filename, &st ) )
  354. {
  355. if ( open( new_path ) )
  356. {
  357. }
  358. else
  359. {
  360. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/error", "sis", path, -1, "Could not open file" );
  361. return 0;
  362. }
  363. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" );
  364. }
  365. else
  366. {
  367. show_gui();
  368. }
  369. if ( project_file )
  370. free( project_file );
  371. project_file = strdup( new_path );
  372. // new_filename;
  373. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );
  374. if ( gui_addr )
  375. nsm_proxy->update( gui_addr );
  376. return 0;
  377. }
  378. /* GUI */
  379. int
  380. osc_label ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  381. {
  382. nsm_proxy->label( &argv[0]->s );
  383. return 0;
  384. }
  385. int
  386. osc_save_signal ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  387. {
  388. nsm_proxy->save_signal( argv[0]->i );
  389. return 0;
  390. }
  391. int
  392. osc_stop_signal ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  393. {
  394. nsm_proxy->stop_signal( argv[0]->i );
  395. return 0;
  396. }
  397. int
  398. osc_start ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  399. {
  400. snapshot( project_file );
  401. if ( nsm_proxy->start( &argv[0]->s, &argv[1]->s, &argv[2]->s ) )
  402. {
  403. hide_gui();
  404. }
  405. return 0;
  406. }
  407. int
  408. osc_kill ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  409. {
  410. nsm_proxy->kill();
  411. return 0;
  412. }
  413. int
  414. osc_update ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  415. {
  416. lo_address to = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));
  417. nsm_proxy->update( to );
  418. gui_addr = to;
  419. return 0;
  420. }
  421. void
  422. signal_handler ( int x )
  423. {
  424. die_now = 1;
  425. }
  426. void
  427. set_traps ( void )
  428. {
  429. signal( SIGHUP, signal_handler );
  430. signal( SIGINT, signal_handler );
  431. // signal( SIGQUIT, signal_handler );
  432. // signal( SIGSEGV, signal_handler );
  433. // signal( SIGPIPE, signal_handler );
  434. signal( SIGTERM, signal_handler );
  435. }
  436. void
  437. init_osc ( const char *osc_port )
  438. {
  439. losrv = lo_server_new( osc_port, NULL );
  440. //error_handler );
  441. char *url = lo_server_get_url(losrv);
  442. printf("OSC: %s\n",url);
  443. free(url);
  444. /* NSM */
  445. lo_server_add_method( losrv, "/nsm/client/save", "", osc_save, NULL );
  446. lo_server_add_method( losrv, "/nsm/client/open", "sss", osc_open, NULL );
  447. lo_server_add_method( losrv, "/nsm/client/show_optional_gui", "", osc_show_gui, NULL );
  448. lo_server_add_method( losrv, "/nsm/client/hide_optional_gui", "", osc_hide_gui, NULL );
  449. lo_server_add_method( losrv, "/error", "sis", osc_announce_error, NULL );
  450. lo_server_add_method( losrv, "/reply", "ssss", osc_announce_reply, NULL );
  451. /* GUI */
  452. lo_server_add_method( losrv, "/nsm/proxy/label", "s", osc_label, NULL );
  453. lo_server_add_method( losrv, "/nsm/proxy/save_signal", "i", osc_save_signal, NULL );
  454. lo_server_add_method( losrv, "/nsm/proxy/stop_signal", "i", osc_stop_signal, NULL );
  455. lo_server_add_method( losrv, "/nsm/proxy/kill", "", osc_kill, NULL );
  456. lo_server_add_method( losrv, "/nsm/proxy/start", "sss", osc_start, NULL );
  457. lo_server_add_method( losrv, "/nsm/proxy/update", "", osc_update, NULL );
  458. }
  459. void
  460. die ( void )
  461. {
  462. if ( gui_pid )
  463. {
  464. DMESSAGE( "Killing GUI" );
  465. kill( gui_pid, SIGTERM );
  466. }
  467. nsm_proxy->kill();
  468. exit(0);
  469. }
  470. void handle_sigchld ( )
  471. {
  472. for ( ;; )
  473. {
  474. int status;
  475. pid_t pid = waitpid(-1, &status, WNOHANG);
  476. if (pid <= 0)
  477. break;
  478. if ( pid == gui_pid )
  479. {
  480. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" );
  481. gui_pid = 0;
  482. /* don't care... */
  483. continue;
  484. }
  485. /* otherwise, it was our proxied process that died, so we should die too */
  486. printf( "proxied process died... nsm-proxy dying too\n" );
  487. die_now = 1;
  488. }
  489. }
  490. int
  491. main ( int argc, char **argv )
  492. {
  493. set_traps();
  494. sigset_t mask;
  495. sigemptyset( &mask );
  496. sigaddset( &mask, SIGCHLD );
  497. sigprocmask(SIG_BLOCK, &mask, NULL );
  498. signal_fd = signalfd( -1, &mask, SFD_NONBLOCK );
  499. nsm_proxy = new NSM_Proxy();
  500. init_osc( NULL );
  501. const char *nsm_url = getenv( "NSM_URL" );
  502. if ( nsm_url )
  503. {
  504. announce( nsm_url, APP_TITLE, argv[0] );
  505. }
  506. else
  507. {
  508. fprintf( stderr, "Could not register as NSM client.\n" );
  509. exit(1);
  510. }
  511. struct signalfd_siginfo fdsi;
  512. /* listen for sigchld signals and process OSC messages forever */
  513. for ( ;; )
  514. {
  515. ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));
  516. if (s == sizeof(struct signalfd_siginfo))
  517. {
  518. if (fdsi.ssi_signo == SIGCHLD)
  519. handle_sigchld();
  520. }
  521. lo_server_recv_noblock( losrv, 500 );
  522. if ( die_now )
  523. die();
  524. }
  525. }