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.

664 lines
16KB

  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 *_arguments;
  49. int _save_signal;
  50. int _pid;
  51. public:
  52. NSM_Proxy ( )
  53. {
  54. _label = _executable = _arguments = 0;
  55. _save_signal = 0;
  56. _pid = 0;
  57. }
  58. ~NSM_Proxy ( )
  59. {
  60. }
  61. void kill ( void )
  62. {
  63. if ( _pid )
  64. ::kill( _pid, SIGTERM );
  65. }
  66. bool start ( const char *executable, const char *arguments )
  67. {
  68. if ( _executable )
  69. free( _executable );
  70. if ( _arguments )
  71. free( _arguments );
  72. _executable = strdup( executable );
  73. if ( arguments )
  74. _arguments = strdup( arguments );
  75. else
  76. _arguments = NULL;
  77. return start();
  78. }
  79. bool start ( void )
  80. {
  81. if ( _pid )
  82. /* already running */
  83. return true;
  84. int pid;
  85. if ( ! (pid = fork()) )
  86. {
  87. MESSAGE( "Launching %s\n", _executable );
  88. // char *args[] = { strdup( executable ), NULL };
  89. char *cmd;
  90. if ( _arguments )
  91. asprintf( &cmd, "exec %s %s", _executable, _arguments );
  92. else
  93. asprintf( &cmd, "exec %s", _executable );
  94. char *args[] = { _executable, strdup( "-c" ), cmd, NULL };
  95. setenv( "NSM_CLIENT_ID", nsm_client_id, 1 );
  96. setenv( "NSM_SESSION_NAME", nsm_display_name, 1 );
  97. if ( -1 == execvp( "/bin/sh", args ) )
  98. {
  99. WARNING( "Error starting process: %s", strerror( errno ) );
  100. exit(-1);
  101. }
  102. }
  103. _pid = pid;
  104. return _pid > 0;
  105. }
  106. void save_signal ( int s )
  107. {
  108. _save_signal = s;
  109. }
  110. void label ( const char *s )
  111. {
  112. if ( _label )
  113. free( _label );
  114. _label = strdup( s );
  115. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/label", "s", _label );
  116. }
  117. void save ( void )
  118. {
  119. DMESSAGE( "Sending process save signal" );
  120. if ( _pid )
  121. ::kill( _pid, _save_signal );
  122. }
  123. bool dump ( const char *path )
  124. {
  125. FILE *fp = fopen( path, "w" );
  126. if ( !fp )
  127. {
  128. WARNING( "Error opening file for saving: %s", strerror( errno ) );
  129. return false;
  130. }
  131. if ( _executable && strlen(_executable) )
  132. fprintf( fp, "executable\n\t%s\n", _executable );
  133. if ( _arguments && strlen(_arguments) )
  134. fprintf( fp, "arguments\n\t%s\n", _arguments );
  135. fprintf( fp, "save signal\n\t%i\n", _save_signal );
  136. if ( _label && strlen(_label) )
  137. fprintf( fp, "label\n\t%s\n", _label );
  138. fclose( fp );
  139. return true;
  140. }
  141. bool restore ( const char *path )
  142. {
  143. FILE *fp = fopen( path, "r" );
  144. if ( ! fp )
  145. {
  146. WARNING( "Error opening file for restore: %s", strerror( errno ) );
  147. return false;
  148. }
  149. char *name;
  150. char *value;
  151. MESSAGE( "Loading file config \"%s\"", path );
  152. while ( 2 == fscanf( fp, "%a[^\n]\n\t%a[^\n]\n", &name, &value ) )
  153. {
  154. DMESSAGE( "%s=%s", name, value );
  155. if ( !strcmp( name, "executable" ) )
  156. _executable = value;
  157. else if (!strcmp( name, "arguments" ) )
  158. _arguments = value;
  159. else if ( !strcmp( name, "save signal" ) )
  160. {
  161. _save_signal = atoi( value );
  162. free( value );
  163. }
  164. else if ( !strcmp( name, "label" ) )
  165. {
  166. label( value );
  167. free( value );
  168. }
  169. else
  170. {
  171. WARNING( "Unknown option \"%s\" in config file", name );
  172. }
  173. free( name );
  174. }
  175. fclose( fp );
  176. start();
  177. return true;
  178. }
  179. void update ( lo_address to )
  180. {
  181. DMESSAGE( "Sending update" );
  182. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/save_signal", "i", _save_signal );
  183. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/label", "s", _label ? _label : "" );
  184. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/executable", "s", _executable ? _executable : "" );
  185. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/arguments", "s", _arguments ? _arguments : "" );
  186. }
  187. };
  188. NSM_Proxy *nsm_proxy;
  189. void
  190. announce ( const char *nsm_url, const char *client_name, const char *process_name )
  191. {
  192. printf( "Announcing to NSM\n" );
  193. lo_address to = lo_address_new_from_url( nsm_url );
  194. int pid = (int)getpid();
  195. lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/server/announce", "sssiii",
  196. client_name,
  197. ":optional-gui:",
  198. process_name,
  199. 1, /* api_major_version */
  200. 0, /* api_minor_version */
  201. pid );
  202. lo_address_free( to );
  203. }
  204. bool
  205. snapshot ( const char *file )
  206. {
  207. /* mkdir( file, 0777 ); */
  208. char *path;
  209. asprintf( &path, "%s/%s", file, CONFIG_FILE_NAME );
  210. bool r = nsm_proxy->dump( path );
  211. free( path );
  212. return r;
  213. }
  214. bool
  215. open ( const char *file )
  216. {
  217. char *path;
  218. asprintf( &path, "%s/%s", file, CONFIG_FILE_NAME );
  219. bool r = nsm_proxy->restore( path );
  220. free( path );
  221. return r;
  222. }
  223. /****************/
  224. /* OSC HANDLERS */
  225. /****************/
  226. /* NSM */
  227. int
  228. osc_announce_error ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  229. {
  230. if ( strcmp( types, "sis" ) )
  231. return -1;
  232. if ( strcmp( "/nsm/server/announce", &argv[0]->s ) )
  233. return -1;
  234. printf( "Failed to register with NSM: %s\n", &argv[2]->s );
  235. nsm_is_active = 0;
  236. return 0;
  237. }
  238. int
  239. osc_announce_reply ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  240. {
  241. if ( strcmp( "/nsm/server/announce", &argv[0]->s ) )
  242. return -1;
  243. printf( "Successfully registered. NSM says: %s", &argv[1]->s );
  244. nsm_is_active = 1;
  245. nsm_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ) );
  246. return 0;
  247. }
  248. int
  249. osc_save ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  250. {
  251. bool r = snapshot( project_file );
  252. nsm_proxy->save();
  253. if ( r )
  254. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );
  255. else
  256. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/error", "sis", path, -1, "Error saving project file" );
  257. return 0;
  258. }
  259. static int gui_pid;
  260. void
  261. show_gui ( void )
  262. {
  263. int pid;
  264. if ( ! (pid = fork()) )
  265. {
  266. char executable[] = "nsm-proxy-gui";
  267. MESSAGE( "Launching %s\n", executable );
  268. char *url = lo_server_get_url( losrv );
  269. char *args[] = { executable, strdup( "--connect-to" ), url, NULL };
  270. if ( -1 == execvp( executable, args ) )
  271. {
  272. WARNING( "Error starting process: %s", strerror( errno ) );
  273. exit(-1);
  274. }
  275. }
  276. gui_pid = pid;
  277. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_shown", "" );
  278. }
  279. int
  280. osc_show_gui ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  281. {
  282. show_gui();
  283. /* FIXME: detect errors */
  284. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );
  285. return 0;
  286. }
  287. void
  288. hide_gui ( void )
  289. {
  290. if ( gui_pid )
  291. {
  292. kill( gui_pid, SIGTERM );
  293. }
  294. }
  295. int
  296. osc_hide_gui ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  297. {
  298. hide_gui();
  299. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" );
  300. /* FIXME: detect errors */
  301. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );
  302. return 0;
  303. }
  304. int
  305. osc_open ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  306. {
  307. const char *new_path = &argv[0]->s;
  308. const char *display_name = &argv[1]->s;
  309. const char *client_id = &argv[2]->s;
  310. if ( nsm_client_id )
  311. free(nsm_client_id);
  312. nsm_client_id = strdup( client_id );
  313. if ( nsm_display_name )
  314. free( nsm_display_name );
  315. nsm_display_name = strdup( display_name );
  316. char *new_filename;
  317. mkdir( new_path, 0777 );
  318. chdir( new_path );
  319. asprintf( &new_filename, "%s/%s", new_path, CONFIG_FILE_NAME );
  320. struct stat st;
  321. if ( 0 == stat( new_filename, &st ) )
  322. {
  323. if ( open( new_path ) )
  324. {
  325. }
  326. else
  327. {
  328. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/error", "sis", path, -1, "Could not open file" );
  329. return 0;
  330. }
  331. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" );
  332. }
  333. else
  334. {
  335. show_gui();
  336. }
  337. if ( project_file )
  338. free( project_file );
  339. project_file = strdup( new_path );
  340. // new_filename;
  341. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );
  342. if ( gui_addr )
  343. nsm_proxy->update( gui_addr );
  344. return 0;
  345. }
  346. /* GUI */
  347. int
  348. osc_label ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  349. {
  350. nsm_proxy->label( &argv[0]->s );
  351. return 0;
  352. }
  353. int
  354. osc_save_signal ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  355. {
  356. nsm_proxy->save_signal( argv[0]->i );
  357. return 0;
  358. }
  359. int
  360. osc_start ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  361. {
  362. snapshot( project_file );
  363. if ( nsm_proxy->start( &argv[0]->s, &argv[1]->s ) )
  364. {
  365. hide_gui();
  366. }
  367. return 0;
  368. }
  369. int
  370. osc_kill ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  371. {
  372. nsm_proxy->kill();
  373. return 0;
  374. }
  375. int
  376. osc_update ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
  377. {
  378. lo_address to = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));
  379. nsm_proxy->update( to );
  380. gui_addr = to;
  381. return 0;
  382. }
  383. void
  384. signal_handler ( int x )
  385. {
  386. die_now = 1;
  387. }
  388. void
  389. set_traps ( void )
  390. {
  391. signal( SIGHUP, signal_handler );
  392. signal( SIGINT, signal_handler );
  393. // signal( SIGQUIT, signal_handler );
  394. // signal( SIGSEGV, signal_handler );
  395. // signal( SIGPIPE, signal_handler );
  396. signal( SIGTERM, signal_handler );
  397. }
  398. void
  399. init_osc ( const char *osc_port )
  400. {
  401. losrv = lo_server_new( osc_port, NULL );
  402. //error_handler );
  403. char *url = lo_server_get_url(losrv);
  404. printf("OSC: %s\n",url);
  405. free(url);
  406. /* NSM */
  407. lo_server_add_method( losrv, "/nsm/client/save", "", osc_save, NULL );
  408. lo_server_add_method( losrv, "/nsm/client/open", "sss", osc_open, NULL );
  409. lo_server_add_method( losrv, "/nsm/client/show_optional_gui", "", osc_show_gui, NULL );
  410. lo_server_add_method( losrv, "/nsm/client/hide_optional_gui", "", osc_hide_gui, NULL );
  411. lo_server_add_method( losrv, "/error", "sis", osc_announce_error, NULL );
  412. lo_server_add_method( losrv, "/reply", "ssss", osc_announce_reply, NULL );
  413. /* GUI */
  414. lo_server_add_method( losrv, "/nsm/proxy/label", "s", osc_label, NULL );
  415. lo_server_add_method( losrv, "/nsm/proxy/save_signal", "i", osc_save_signal, NULL );
  416. lo_server_add_method( losrv, "/nsm/proxy/kill", "", osc_kill, NULL );
  417. lo_server_add_method( losrv, "/nsm/proxy/start", "ss", osc_start, NULL );
  418. lo_server_add_method( losrv, "/nsm/proxy/update", "", osc_update, NULL );
  419. }
  420. void
  421. die ( void )
  422. {
  423. if ( gui_pid )
  424. {
  425. DMESSAGE( "Killing GUI" );
  426. kill( gui_pid, SIGTERM );
  427. }
  428. nsm_proxy->kill();
  429. exit(0);
  430. }
  431. void handle_sigchld ( )
  432. {
  433. for ( ;; )
  434. {
  435. int status;
  436. pid_t pid = waitpid(-1, &status, WNOHANG);
  437. if (pid <= 0)
  438. break;
  439. if ( pid == gui_pid )
  440. {
  441. lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" );
  442. gui_pid = 0;
  443. /* don't care... */
  444. continue;
  445. }
  446. /* otherwise, it was our proxied process that died, so we should die too */
  447. printf( "proxied process died... nsm-proxy dying too\n" );
  448. die_now = 1;
  449. }
  450. }
  451. int
  452. main ( int argc, char **argv )
  453. {
  454. set_traps();
  455. sigset_t mask;
  456. sigemptyset( &mask );
  457. sigaddset( &mask, SIGCHLD );
  458. sigprocmask(SIG_BLOCK, &mask, NULL );
  459. signal_fd = signalfd( -1, &mask, SFD_NONBLOCK );
  460. nsm_proxy = new NSM_Proxy();
  461. init_osc( NULL );
  462. const char *nsm_url = getenv( "NSM_URL" );
  463. if ( nsm_url )
  464. {
  465. announce( nsm_url, APP_TITLE, argv[0] );
  466. }
  467. else
  468. {
  469. fprintf( stderr, "Could not register as NSM client.\n" );
  470. exit(1);
  471. }
  472. struct signalfd_siginfo fdsi;
  473. /* listen for sigchld signals and process OSC messages forever */
  474. for ( ;; )
  475. {
  476. ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));
  477. if (s == sizeof(struct signalfd_siginfo))
  478. {
  479. if (fdsi.ssi_signo == SIGCHLD)
  480. handle_sigchld();
  481. }
  482. lo_server_recv_noblock( losrv, 500 );
  483. if ( die_now )
  484. die();
  485. }
  486. }