non/session-manager/src/nsmd.C

2358 lines
56 KiB
C
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*******************************************************************************/
/* Copyright (C) 2010 Jonathan Moore Liles */
/* */
/* This program is free software; you can redistribute it and/or modify it */
/* under the terms of the GNU General Public License as published by the */
/* Free Software Foundation; either version 2 of the License, or (at your */
/* option) any later version. */
/* */
/* This program is distributed in the hope that it will be useful, but WITHOUT */
/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */
/* more details. */
/* */
/* You should have received a copy of the GNU General Public License along */
/* with This program; see the file COPYING. If not,write to the Free Software */
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
/*******************************************************************************/
#define __MODULE__ "nsmd"
#include "debug.h"
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <errno.h>
#include <string.h>
#include <list>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/signalfd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <time.h>
#include <libgen.h>
#include <dirent.h>
#include <ftw.h>
#include <list>
#include <getopt.h>
#include <sys/time.h>
#include <OSC/Endpoint.H>
/* for locking */
#include "file.h"
#include <map>
#include <string>
#include <algorithm>
#pragma GCC diagnostic ignored "-Wunused-parameter"
static OSC::Endpoint *osc_server;
static lo_address gui_addr;
static bool gui_is_active = false;
static int signal_fd;
static int session_lock_fd = 0;
static char *session_root;
#define NSM_API_VERSION_MAJOR 1
#define NSM_API_VERSION_MINOR 0
#define ERR_OK 0
#define ERR_GENERAL_ERROR -1
#define ERR_INCOMPATIBLE_API -2
#define ERR_BLACKLISTED -3
#define ERR_LAUNCH_FAILED -4
#define ERR_NO_SUCH_FILE -5
#define ERR_NO_SESSION_OPEN -6
#define ERR_UNSAVED_CHANGES -7
#define ERR_NOT_NOW -8
#define ERR_BAD_PROJECT -9
#define ERR_CREATE_FAILED -10
#define ERR_SESSION_LOCKED -11
#define ERR_OPERATION_PENDING -12
#define APP_TITLE "Non Session Manager"
enum {
COMMAND_NONE = 0,
COMMAND_QUIT,
COMMAND_KILL,
COMMAND_SAVE,
COMMAND_OPEN,
COMMAND_START,
COMMAND_CLOSE,
COMMAND_DUPLICATE,
COMMAND_NEW
};
static int pending_operation = COMMAND_NONE;
static void wait ( long );
#define GUIMSG( fmt, args... ) \
{ \
MESSAGE( fmt, ## args ); \
if ( gui_is_active ) \
{ \
char *s;\
asprintf( &s, fmt, ## args );\
osc_server->send( gui_addr, "/nsm/gui/server/message", s);\
free(s);\
}\
}
struct Client
{
private:
int _reply_errcode;
char *_reply_message;
int _pending_command; /* */
struct timeval _command_sent_time;
bool _gui_visible;
char *_label;
public:
lo_address addr; /* */
char *name; /* client application name */
char *executable_path; /* path to client executable */
int pid; /* PID of client process */
float progress; /* */
bool active; /* client has registered via announce */
// bool stopped; /* the client quit, but not because we told it to--user still has to decide to remove it from the session */
char *client_id; /* short part of client ID */
char *capabilities; /* client capabilities... will be null for dumb clients */
bool dirty; /* flag for client self-reported dirtiness */
bool pre_existing;
const char *status;
const char *label ( void ) const { return _label; }
void label ( const char *l )
{
if ( _label )
free( _label );
if ( l )
_label = strdup( l );
else
_label = NULL;
}
bool gui_visible ( void ) const
{
return _gui_visible;
}
void gui_visible ( bool b )
{
_gui_visible = b;
}
bool
has_error ( void ) const
{
return _reply_errcode != 0;
}
int
error_code ( void ) const
{
return _reply_errcode;
}
const char * message ( void )
{
return _reply_message;
}
void
set_reply ( int errcode, const char *message )
{
if ( _reply_message )
free( _reply_message );
_reply_message = strdup( message );
_reply_errcode = errcode;
}
bool reply_pending ( void )
{
return _pending_command != COMMAND_NONE;
}
bool is_dumb_client ( void )
{
return capabilities == NULL;
}
void pending_command ( int command )
{
gettimeofday( &_command_sent_time, NULL );
_pending_command = command;
}
double milliseconds_since_last_command ( void ) const
{
struct timeval now;
gettimeofday( &now, NULL );
double elapsedms = ( now.tv_sec - _command_sent_time.tv_sec ) * 1000.0;
elapsedms += ( now.tv_usec - _command_sent_time.tv_usec ) / 1000.0;
return elapsedms;
}
int pending_command ( void )
{
return _pending_command;
}
// capability should be enclosed in colons. I.e. ":switch:"
bool
is_capable_of ( const char *capability ) const
{
return capabilities &&
strstr( capabilities, capability );
}
Client ( )
{
_label = 0;
_gui_visible = true;
addr = 0;
_reply_errcode = 0;
_reply_message = 0;
pid = 0;
progress = -0;
_pending_command = 0;
active = false;
client_id = 0;
capabilities = 0;
name = 0;
executable_path = 0;
pre_existing = false;
}
~Client ( )
{
if ( name )
free(name);
if (executable_path)
free(executable_path);
if (client_id)
free(client_id);
if (capabilities)
free(capabilities);
name = executable_path = client_id = capabilities = NULL;
}
};
static std::list< Client* > client;
/* helper macros for defining OSC handlers */
#define OSC_NAME( name ) osc_ ## name
// #define OSCDMSG() DMESSAGE( "Got OSC message: %s", path );
#define OSC_HANDLER( name ) static int OSC_NAME( name ) ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
static char *session_path = NULL;
static char *session_name = NULL;
bool
clients_have_errors ( )
{
for ( std::list<Client*>::const_iterator i = client.begin();
i != client.end();
++i )
if ( (*i)->active && (*i)->has_error() )
return true;
return false;
}
Client *
get_client_by_pid ( int pid )
{
std::list<Client*> *cl = &client;
for ( std::list<Client*>::const_iterator i = cl->begin();
i != cl->end();
++i )
if ( (*i)->pid == pid )
return *i;
return NULL;
}
void clear_clients ( void )
{
std::list<Client*> *cl = &client;
for ( std::list<Client*>::iterator i = cl->begin();
i != cl->end();
++i )
{
delete *i;
i = cl->erase( i );
}
}
void
handle_client_process_death ( int pid )
{
Client *c = get_client_by_pid( (int)pid );
if ( c )
{
bool dead_because_we_said = ( c->pending_command() == COMMAND_KILL ||
c->pending_command() == COMMAND_QUIT );
if ( dead_because_we_said )
{
GUIMSG( "Client %s terminated because we told it to.", c->name );
}
else
{
GUIMSG( "Client %s died unexpectedly.", c->name );
}
if ( c->pending_command() == COMMAND_QUIT )
{
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "removed" );
client.remove(c);
delete c;
}
else
{
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "stopped" );
}
c->pending_command( COMMAND_NONE );
c->active = false;
c->pid = 0;
}
}
void handle_sigchld ( )
{
for ( ;; )
{
int status;
pid_t pid = waitpid(-1, &status, WNOHANG);
if (pid <= 0)
break;
handle_client_process_death( pid );
}
}
int
path_is_valid ( const char *path )
{
char *s;
asprintf( &s, "/%s/", path );
int r = strstr( s, "/../" ) == NULL;
free( s );
return r;
}
int
mkpath ( const char *path, bool create_final_directory )
{
char *p = strdup( path );
char *i = p + 1;
while ( ( i = index( i, '/' ) ) )
{
*i = 0;
struct stat st;
if ( stat( p, &st ) )
{
if ( mkdir( p, 0711 ) )
{
free( p );
return -1;
}
}
*i = '/';
i++;
}
if ( create_final_directory )
{
if ( mkdir( p, 0711 ) )
{
free( p );
return -1;
}
}
free( p );
return 0;
}
void
set_name ( const char *name )
{
if ( session_name )
free( session_name );
char *s = strdup( name );
session_name = strdup( basename( s ) );
free( s );
}
bool
address_matches ( lo_address addr1, lo_address addr2 )
{
/* char *url1 = lo_address_get_url( addr1 ); */
/* char *url2 = lo_address_get_url( addr2 ); */
char *url1 = strdup( lo_address_get_port( addr1 ) );
char *url2 = strdup(lo_address_get_port( addr2 ) );
bool r = !strcmp( url1, url2 );
free( url1 );
free( url2 );
return r;
}
Client *
get_client_by_id ( std::list<Client*> *cl, const char *id )
{
for ( std::list<Client*>::const_iterator i = cl->begin();
i != cl->end();
++i )
if ( !strcmp( (*i)->client_id, id ) )
return *i;
return NULL;
}
Client *
get_client_by_name_and_id ( std::list<Client*> *cl, const char *name, const char *id )
{
for ( std::list<Client*>::const_iterator i = cl->begin();
i != cl->end();
++i )
if ( !strcmp( (*i)->client_id, id ) &&
! strcmp( (*i)->name, name ) )
return *i;
return NULL;
}
Client *
get_client_by_address ( lo_address addr )
{
for ( std::list<Client*>::iterator i = client.begin();
i != client.end();
++i )
if ( (*i)->addr && address_matches( (*i)->addr, addr ) )
return *i;
return NULL;
}
char *
generate_client_id ( Client *c )
{
char id_str[6];
id_str[0] = 'n';
id_str[5] = 0;
for ( int i = 1; i < 5; i++)
id_str[i] = 'A' + (rand() % 25);
return strdup(id_str);
}
bool
replies_still_pending ( void )
{
for ( std::list<Client*>::const_iterator i = client.begin();
i != client.end();
++i )
if ( (*i)->active && (*i)->reply_pending() )
return true;
return false;
}
int
number_of_active_clients ( void )
{
int active = 0;
for ( std::list<Client*>::const_iterator i = client.begin(); i != client.end(); i++ )
{
if ( (*i)->active )
active++;
}
return active;
}
void
wait_for_announce ( void )
{
GUIMSG( "Waiting for announce messages from clients" );
int n = 5 * 1000;
long unsigned int active;
while ( n > 0 )
{
n -= 100;
wait(100);
active = number_of_active_clients();
if ( client.size() == active )
break;
}
GUIMSG( "Done. %lu out of %lu clients announced within the initialization grace period", active, client.size() );
}
void
wait_for_replies ( void )
{
GUIMSG( "Waiting for clients to reply to commands" );
int n = 60 * 1000; /* 60 seconds */
while ( n )
{
n -= 100;
wait(100);
if ( ! replies_still_pending() )
break;
}
GUIMSG( "Done waiting" );
/* FIXME: do something about unresponsive clients */
}
char *
get_client_project_path ( const char *session_path, Client *c )
{
char *client_project_path;
asprintf( &client_project_path, "%s/%s.%s", session_path, c->name, c->client_id );
return client_project_path;
}
bool
launch ( const char *executable, const char *client_id )
{
Client *c;
if ( !client_id || !( c = get_client_by_id( &client, client_id ) ) )
{
c = new Client();
c->executable_path = strdup( executable );
{
char *s = strdup( c->executable_path );
c->name = strdup( basename( s ) );
free( s );
}
if ( client_id )
c->client_id = strdup( client_id );
else
c->client_id = generate_client_id( c );
client.push_back( c );
}
char * url = osc_server->url();
int pid;
if ( ! (pid = fork()) )
{
GUIMSG( "Launching %s", executable );
char *args[] = { strdup( executable ), NULL };
setenv( "NSM_URL", url, 1 );
/* Ensure the launched process can receive SIGCHLD */
/* Unblocking SIGCHLD here does NOT unblock it for nsmd itself */
sigset_t mask;
sigemptyset( &mask );
sigaddset( &mask, SIGCHLD );
sigprocmask(SIG_UNBLOCK, &mask, NULL );
if ( -1 == execvp( executable, args ) )
{
WARNING( "Error starting process: %s", strerror( errno ) );
exit(-1);
}
}
c->pending_command( COMMAND_START );
c->pid = pid;
MESSAGE( "Process has pid: %i", pid );
if ( gui_is_active )
{
osc_server->send( gui_addr, "/nsm/gui/client/new", c->client_id, c->name );
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "launch" );
}
return true;
}
void
command_client_to_save ( Client *c )
{
if ( c->active )
{
MESSAGE( "Telling %s to save", c->name );
osc_server->send( c->addr, "/nsm/client/save" );
c->pending_command( COMMAND_SAVE );
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "save" );
}
else if ( c->is_dumb_client() && c->pid )
{
// this is a dumb client...
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "noop" );
}
}
void command_client_to_switch ( Client *c, const char *new_client_id )
{
char *old_client_id = c->client_id;
c->client_id = strdup( new_client_id );
char *client_project_path = get_client_project_path( session_path, c );
MESSAGE( "Commanding %s to switch \"%s\"", c->name, client_project_path );
char *full_client_id;
asprintf( &full_client_id, "%s.%s", c->name, c->client_id );
osc_server->send( c->addr, "/nsm/client/open", client_project_path, session_name, full_client_id );
free( full_client_id );
free( client_project_path );
c->pending_command( COMMAND_OPEN );
if ( gui_is_active )
{
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "switch" );
osc_server->send( gui_addr, "/nsm/gui/client/switch", old_client_id, c->client_id );
}
free( old_client_id );
}
void
purge_inactive_clients ( )
{
for ( std::list<Client*>::iterator i = client.begin();
i != client.end();
++i )
{
if ( ! (*i)->active )
{
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/status", (*i)->client_id, (*i)->status = "removed" );
delete *i;
i = client.erase( i );
}
}
}
bool
process_is_running ( int pid )
{
if ( 0 == kill( pid, 0 ) )
{
return true;
}
else if ( ESRCH == errno )
{
return false;
}
return false;
}
void
purge_dead_clients ( )
{
std::list<Client*> tmp( client );
for ( std::list<Client*>::const_iterator i = tmp.begin();
i != tmp.end();
++i )
{
const Client *c = *i;
if ( c->pid )
{
if ( ! process_is_running( c->pid ) )
handle_client_process_death( c->pid );
}
}
}
/************************/
/* OSC Message Handlers */
/************************/
OSC_HANDLER( add )
{
if ( ! session_path )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_NO_SESSION_OPEN,
"Cannot add to session because no session is loaded." );
return 0;
}
if ( strchr( &argv[0]->s, '/' ) )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_LAUNCH_FAILED,
"Absolute paths are not permitted. Clients must be in $PATH" );
return 0;
}
if ( ! launch( &argv[0]->s, NULL ) )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_LAUNCH_FAILED,
"Failed to launch process!" );
}
else
{
osc_server->send( lo_message_get_source( msg ), "/reply", path,
ERR_OK,
"Launched." );
}
return 0;
}
OSC_HANDLER( announce )
{
const char *client_name = &argv[0]->s;
const char *capabilities = &argv[1]->s;
const char *executable_path = &argv[2]->s;
int major = argv[3]->i;
int minor = argv[4]->i;
int pid = argv[5]->i;
GUIMSG( "Got announce from %s", client_name );
if ( ! session_path )
{
osc_server->send( lo_message_get_source( msg ), "/error",
path,
ERR_NO_SESSION_OPEN,
"Sorry, but there's no session open for this application to join." );
return 0;
}
bool expected_client = false;
Client *c = NULL;
for ( std::list<Client*>::iterator i = client.begin();
i != client.end();
++i )
{
if ( ! strcmp( (*i)->executable_path, executable_path ) &&
! (*i)->active &&
(*i)->pending_command() == COMMAND_START )
{
// I think we've found the slot we were looking for.
MESSAGE( "Client was expected." );
c = *i;
break;
}
}
if ( ! c )
{
c = new Client();
c->executable_path = strdup( executable_path );
c->client_id = generate_client_id( c );
}
else
expected_client = true;
if ( major > NSM_API_VERSION_MAJOR )
{
MESSAGE( "Client is using incompatible and more recent API version %i.%i", major, minor );
osc_server->send( lo_message_get_source( msg ), "/error",
path,
ERR_INCOMPATIBLE_API,
"Server is using an incompatible API version." );
return 0;
}
c->pid = pid;
c->capabilities = strdup( capabilities );
c->addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));
c->name = strdup( client_name );
c->active = true;
MESSAGE( "Process has pid: %i", pid );
if ( ! expected_client )
client.push_back( c );
MESSAGE( "The client \"%s\" at \"%s\" informs us it's ready to receive commands.", &argv[0]->s, lo_address_get_url( c->addr ) );
osc_server->send( lo_message_get_source( msg ), "/reply",
path,
expected_client ?
"Howdy, what took you so long?" :
"Well hello, stranger. Welcome to the party.",
APP_TITLE,
":server-control:broadcast:optional-gui:" );
if ( gui_is_active )
{
osc_server->send( gui_addr, "/nsm/gui/client/new", c->client_id, c->name );
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "open" );
if ( c->is_capable_of( ":optional-gui:" ) )
osc_server->send( gui_addr, "/nsm/gui/client/has_optional_gui", c->client_id );
}
{
char *full_client_id;
asprintf( &full_client_id, "%s.%s", c->name, c->client_id );
char *client_project_path = get_client_project_path( session_path, c );
osc_server->send( lo_message_get_source( msg ), "/nsm/client/open", client_project_path, session_name, full_client_id );
c->pending_command( COMMAND_OPEN );
free( full_client_id );
free( client_project_path );
}
return 0;
}
void
save_session_file ( )
{
char *session_file = NULL;
asprintf( &session_file, "%s/session.nsm", session_path );
FILE *fp = fopen( session_file, "w+" );
free( session_file );
/* FIXME: handle errors. */
for ( std::list<Client*>::iterator i = client.begin();
i != client.end();
++i )
{
fprintf( fp, "%s:%s:%s\n", (*i)->name, (*i)->executable_path, (*i)->client_id );
}
fclose( fp );
}
Client *
client_by_name ( const char *name,
std::list<Client*> *cl )
{
for ( std::list<Client*>::iterator i = cl->begin();
i != cl->end();
++i )
{
if ( !strcmp( name, (*i)->name ) )
return *i;
}
return NULL;
}
bool
dumb_clients_are_alive ( )
{
std::list<Client*> *cl = &client;
for ( std::list<Client*>::iterator i = cl->begin();
i != cl->end();
++i )
{
if ( (*i)->is_dumb_client() && (*i)->pid > 0 )
return true;
}
return false;
}
void
wait_for_dumb_clients_to_die ( )
{
struct signalfd_siginfo fdsi;
GUIMSG( "Waiting for any dumb clients to die." );
for ( int i = 0; i < 6; i++ )
{
MESSAGE( "Loop %i", i );
if ( ! dumb_clients_are_alive() )
break;
ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));
if (s == sizeof(struct signalfd_siginfo))
{
if (fdsi.ssi_signo == SIGCHLD)
handle_sigchld();
}
usleep( 50000 );
}
GUIMSG( "Done waiting" );
/* FIXME: give up on remaining clients and purge them */
}
bool
killed_clients_are_alive ( )
{
std::list<Client*> *cl = &client;
for ( std::list<Client*>::iterator i = cl->begin();
i != cl->end();
++i )
{
if ( ( (*i)->pending_command() == COMMAND_QUIT ||
(*i)->pending_command() == COMMAND_KILL ) &&
(*i)->pid > 0 )
return true;
}
return false;
}
void
wait_for_killed_clients_to_die ( )
{
struct signalfd_siginfo fdsi;
MESSAGE( "Waiting for killed clients to die." );
for ( int i = 0; i < 60; i++ )
{
MESSAGE( "Loop %i", i );
if ( ! killed_clients_are_alive() )
goto done;
ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));
if (s == sizeof(struct signalfd_siginfo))
{
if (fdsi.ssi_signo == SIGCHLD)
handle_sigchld();
}
purge_dead_clients();
/* check OSC so we can get /progress messages. */
osc_server->check();
sleep(1);
}
WARNING( "Killed clients are still alive" );
return;
done:
MESSAGE( "All clients have died." );
}
void
command_all_clients_to_save ( )
{
if ( session_path )
{
GUIMSG( "Commanding attached clients to save." );
for ( std::list<Client*>::iterator i = client.begin();
i != client.end();
++i )
{
command_client_to_save( *i );
}
wait_for_replies();
save_session_file();
}
}
void
command_client_to_stop ( Client *c )
{
GUIMSG( "Stopping client %s", c->name );
if ( c->pid > 0 )
{
c->pending_command( COMMAND_KILL );
kill( c->pid, SIGTERM );
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "stopped" );
}
}
void
command_client_to_quit ( Client *c )
{
MESSAGE( "Commanding %s to quit", c->name );
if ( c->active )
{
c->pending_command( COMMAND_QUIT );
kill( c->pid, SIGTERM );
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "quit" );
}
else if ( c->is_dumb_client() )
{
if ( c->pid > 0 )
{
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "quit" );
/* should be kill? */
c->pending_command( COMMAND_QUIT );
// this is a dumb client... try and kill it
kill( c->pid, SIGTERM );
}
else
{
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "removed" );
}
}
}
void
close_session ( )
{
if ( ! session_path )
return;
for ( std::list<Client*>::iterator i = client.begin();
i != client.end();
++i )
{
command_client_to_quit( *i );
}
wait_for_killed_clients_to_die();
purge_inactive_clients();
clear_clients();
if ( session_path )
{
char *session_lock;
asprintf( &session_lock, "%s/.lock", session_path );
release_lock( &session_lock_fd, session_lock );
free(session_lock);
free(session_path);
session_path = NULL;
free(session_name);
session_name = NULL;
}
if ( gui_is_active )
{
osc_server->send( gui_addr, "/nsm/gui/session/name", "", "" );
}
}
void
tell_client_session_is_loaded( Client *c )
{
if ( c->active )
//!c->is_dumb_client() )
{
MESSAGE( "Telling client %s that session is loaded.", c->name );
osc_server->send( c->addr, "/nsm/client/session_is_loaded" );
}
}
void
tell_all_clients_session_is_loaded ( void )
{
MESSAGE( "Telling all clients that session is loaded..." );
for ( std::list<Client*>::iterator i = client.begin();
i != client.end();
++i )
{
tell_client_session_is_loaded( *i );
}
}
int
load_session_file ( const char * path )
{
char *session_file = NULL;
asprintf( &session_file, "%s/session.nsm", path );
char *session_lock = NULL;
asprintf( &session_lock, "%s/.lock", path );
if ( ! acquire_lock( &session_lock_fd, session_lock ) )
{
free( session_file );
free( session_lock );
WARNING( "Session is locked by another process" );
return ERR_SESSION_LOCKED;
}
FILE *fp;
if ( ! ( fp = fopen( session_file, "r" ) ) )
{
free( session_file );
return ERR_CREATE_FAILED;
}
free( session_file );
session_path = strdup( path );
set_name( path );
std::list<Client*> new_clients;
{
char * client_name = NULL;
char * client_executable = NULL;
char * client_id = NULL;
// load the client list
while ( fscanf( fp, "%m[^:]:%m[^:]:%m[^:\n]\n", &client_name, &client_executable, &client_id ) > 0 )
{
Client *c = new Client();
c->name = client_name;
c->executable_path = client_executable;
c->client_id = client_id;
new_clients.push_back( c );
}
}
fclose(fp);
MESSAGE( "Commanding unneeded and dumb clients to quit" );
std::map<std::string,int> client_map;
/* count how many instances of each client are needed in the new session */
for ( std::list<Client*>::iterator i = new_clients.begin();
i != new_clients.end();
++i )
{
if ( client_map.find( (*i)->name) != client_map.end() )
client_map[(*i)->name]++;
else
client_map[(*i)->name] = 1;
}
for ( std::list<Client*>::iterator i = client.begin();
i != client.end();
++i )
{
if ( ! (*i)->is_capable_of( ":switch:" ) || client_map.find((*i)->name ) == client_map.end() )
{
/* client is not capable of switch, or is not wanted in the new session */
command_client_to_quit( *i );
}
else
{
/* client is switch capable and may be wanted in the new session */
if ( client_map[ (*i)->name ]-- <= 0 )
/* nope,, we already have as many as we need, stop this one */
command_client_to_quit( *i );
}
}
// wait_for_replies();
wait_for_killed_clients_to_die();
// wait_for_dumb_clients_to_die();
purge_inactive_clients();
for ( std::list<Client*>::iterator i = client.begin();
i != client.end();
++i )
{
(*i)->pre_existing = true;
}
MESSAGE( "Commanding smart clients to switch" );
for ( std::list<Client*>::iterator i = new_clients.begin();
i != new_clients.end();
++i )
{
Client *c = NULL;
/* in a duplicated session, clients will have the same
* IDs, so be sure to pick the right one to avoid race
* conditions in JACK name registration. */
c = get_client_by_name_and_id( &client, (*i)->name, (*i)->client_id );
if ( ! c )
c = client_by_name( (*i)->name, &client );
if ( c && c->pre_existing && !c->reply_pending() )
{
// since we already shutdown clients not capable of 'switch', we can assume that these are.
command_client_to_switch( c, (*i)->client_id );
}
else
{
/* sleep a little bit because liblo derives its sequence
* of port numbers from the system time (second
* resolution) and if too many clients start at once they
* won't be able to find a free port. */
usleep( 100 * 1000 );
launch( (*i)->executable_path, (*i)->client_id );
}
}
/* this part is a little tricky... the clients need some time to
* send their 'announce' messages before we can send them 'open'
* and know that a reply is pending and we should continue waiting
* until they finish. wait_for_replies() must check for OSC
* messages immediately, even if no replies seem to be pending
* yet. */
/* dumb clients will never send an 'announce message', so we need
* to give up waiting on them fairly soon. */
wait_for_announce();
wait_for_replies();
tell_all_clients_session_is_loaded();
MESSAGE( "Loaded." );
new_clients.clear();
if ( gui_is_active )
{
osc_server->send( gui_addr, "/nsm/gui/session/name", session_name, session_path + strlen( session_root ));
}
return ERR_OK;
}
OSC_HANDLER( save )
{
if ( pending_operation != COMMAND_NONE )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_OPERATION_PENDING,
"An operation pending." );
return 0;
}
if ( ! session_path )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_NO_SESSION_OPEN,
"No session to save.");
goto done;
}
command_all_clients_to_save();
MESSAGE( "Done." );
osc_server->send( lo_message_get_source( msg ), "/reply", path, "Saved." );
done:
pending_operation = COMMAND_NONE;
return 0;
}
OSC_HANDLER( duplicate )
{
if ( pending_operation != COMMAND_NONE )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_OPERATION_PENDING,
"An operation pending." );
return 0;
}
pending_operation = COMMAND_DUPLICATE;
if ( ! session_path )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_NO_SESSION_OPEN,
"No session to duplicate.");
goto done;
}
if ( ! path_is_valid( &argv[0]->s ) )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_CREATE_FAILED,
"Invalid session name." );
goto done;
}
command_all_clients_to_save();
if ( clients_have_errors() )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_GENERAL_ERROR,
"Some clients could not save" );
goto done;
}
// save_session_file();
char *spath;
asprintf( &spath, "%s/%s", session_root, &argv[0]->s );
mkpath( spath, false );
/* FIXME: code a recursive copy instead of calling the shell */
char *cmd;
asprintf( &cmd, "cp -R \"%s\" \"%s\"", session_path, spath);
system( cmd );
free( cmd );
osc_server->send( gui_addr, "/nsm/gui/session/session", &argv[0]->s );
MESSAGE( "Attempting to open %s", spath );
if ( !load_session_file( spath ) )
{
MESSAGE( "Loaded" );
osc_server->send( lo_message_get_source( msg ), "/reply", path,
"Loaded." );
}
else
{
MESSAGE( "Failed" );
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_NO_SUCH_FILE,
"No such file." );
free(spath);
return -1;
}
free( spath );
MESSAGE( "Done" );
osc_server->send( lo_message_get_source( msg ), "/reply", path, "Duplicated." );
done:
pending_operation = COMMAND_NONE;
return 0;
}
OSC_HANDLER( new )
{
if ( pending_operation != COMMAND_NONE )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_OPERATION_PENDING,
"An operation pending." );
return 0;
}
pending_operation = COMMAND_NEW;
if ( ! path_is_valid( &argv[0]->s ) )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_CREATE_FAILED,
"Invalid session name." );
pending_operation = COMMAND_NONE;
return 0;
}
if ( session_path )
{
command_all_clients_to_save();
close_session();
}
GUIMSG( "Creating new session \"%s\"", &argv[0]->s );
char *spath;
asprintf( &spath, "%s/%s", session_root, &argv[0]->s );
if ( mkpath( spath, true ) )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_CREATE_FAILED,
"Could not create the session directory" );
free(spath);
pending_operation = COMMAND_NONE;
return 0;
}
session_path = strdup( spath );
set_name( session_path );
osc_server->send( lo_message_get_source( msg ), "/reply", path, "Created." );
if ( gui_is_active )
{
osc_server->send( gui_addr, "/nsm/gui/session/session", &argv[0]->s );
osc_server->send( gui_addr, "/nsm/gui/session/name", &argv[0]->s, &argv[0]->s );
}
save_session_file();
free( spath );
osc_server->send( lo_message_get_source( msg ), "/reply", path,
"Session created" );
pending_operation = COMMAND_NONE;
return 0;
}
static lo_address list_response_address;
int
list_file ( const char *fpath, const struct stat *sb, int tflag )
{
char *s;
if ( tflag == FTW_F )
{
s = strdup( fpath );
if ( ! strcmp( "session.nsm", basename( s ) ) )
{
free( s );
s = strdup( fpath );
s = dirname( s );
memmove( s, s + strlen( session_root ) + 1, (strlen( s ) - strlen( session_root )) + 1);
osc_server->send( list_response_address, "/reply", "/nsm/server/list", s );
free( s );
}
else
free( s );
}
return 0;
}
OSC_HANDLER( list )
{
GUIMSG( "Listing sessions" );
list_response_address = lo_message_get_source( msg );
ftw( session_root, list_file, 20 );
osc_server->send( lo_message_get_source( msg ), path,
ERR_OK,
"Done." );
return 0;
}
OSC_HANDLER( open )
{
GUIMSG( "Opening session %s", &argv[0]->s );
if ( pending_operation != COMMAND_NONE )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_OPERATION_PENDING,
"An operation pending." );
return 0;
}
pending_operation = COMMAND_OPEN;
if ( session_path )
{
command_all_clients_to_save();
if ( clients_have_errors() )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_GENERAL_ERROR,
"Some clients could not save" );
pending_operation = COMMAND_NONE;
return 0;
}
// save_session_file();
}
char *spath;
asprintf( &spath, "%s/%s", session_root, &argv[0]->s );
MESSAGE( "Attempting to open %s", spath );
int err = load_session_file( spath );
if ( ! err )
{
MESSAGE( "Loaded" );
osc_server->send( lo_message_get_source( msg ), "/reply", path,
"Loaded." );
}
else
{
MESSAGE( "Failed" );
const char *m = NULL;
switch ( err )
{
case ERR_CREATE_FAILED:
m = "Could not create session file!";
break;
case ERR_SESSION_LOCKED:
m = "Session is locked by another process!";
break;
case ERR_NO_SUCH_FILE:
m = "The named session does not exist.";
break;
default:
m = "Unknown error";
}
osc_server->send( lo_message_get_source( msg ), "/error", path,
err,
m );
}
free( spath );
MESSAGE( "Done" );
pending_operation = COMMAND_NONE;
return 0;
}
OSC_HANDLER( quit )
{
close_session();
exit(0);
return 0;
}
OSC_HANDLER( abort )
{
if ( pending_operation != COMMAND_NONE )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_OPERATION_PENDING,
"An operation pending." );
return 0;
}
pending_operation = COMMAND_CLOSE;
if ( ! session_path )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_NO_SESSION_OPEN,
"No session to abort." );
goto done;
}
GUIMSG( "Commanding attached clients to quit." );
close_session();
osc_server->send( lo_message_get_source( msg ), "/reply", path,
"Aborted." );
MESSAGE( "Done" );
done:
pending_operation = COMMAND_NONE;
return 0;
}
OSC_HANDLER( close )
{
if ( pending_operation != COMMAND_NONE )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_OPERATION_PENDING,
"An operation pending." );
return 0;
}
pending_operation = COMMAND_CLOSE;
if ( ! session_path )
{
osc_server->send( lo_message_get_source( msg ), "/error", path,
ERR_NO_SESSION_OPEN,
"No session to close." );
goto done;
}
command_all_clients_to_save();
GUIMSG( "Commanding attached clients to quit." );
close_session();
osc_server->send( lo_message_get_source( msg ), "/reply", path,
"Closed." );
MESSAGE( "Done" );
done:
pending_operation = COMMAND_NONE;
return 0;
}
OSC_HANDLER( broadcast )
{
const char *to_path = &argv[0]->s;
/* don't allow clients to broadcast NSM commands */
if ( ! strncmp( to_path, "/nsm/", strlen( "/nsm/" ) ) )
return 0;
std::list<OSC::OSC_Value> new_args;
for ( int i = 1; i < argc; ++i )
{
switch ( types[i] )
{
case 's':
new_args.push_back( OSC::OSC_String( &argv[i]->s ) );
break;
case 'i':
new_args.push_back( OSC::OSC_Int( argv[i]->i ) );
break;
case 'f':
new_args.push_back( OSC::OSC_Float( argv[i]->f ) );
break;
}
}
char *sender_url = lo_address_get_url( lo_message_get_source( msg ) );
for ( std::list<Client*>::iterator i = client.begin();
i != client.end();
++i )
{
if ( ! (*i)->addr )
continue;
char *url = lo_address_get_url( (*i)->addr );
if ( strcmp( sender_url, url ) )
{
osc_server->send( (*i)->addr, to_path, new_args );
}
free( url );
}
/* also relay to attached GUI so that the broadcast can be
* propagated to another NSMD instance */
if ( gui_is_active )
{
char *u1 = lo_address_get_url( gui_addr );
if ( strcmp( u1, sender_url ) )
{
new_args.push_front( OSC::OSC_String( to_path ) );
osc_server->send( gui_addr, path, new_args );
}
free(u1);
}
free( sender_url );
return 0;
}
/*********************************/
/* Client Informational Messages */
/*********************************/
OSC_HANDLER( progress )
{
Client *c = get_client_by_address( lo_message_get_source( msg ) );
if ( c )
{
c->progress = argv[0]->f;
/* MESSAGE( "%s progress: %i%%", c->name, (int)(c->progress * 100.0f) ); */
if ( gui_is_active )
{
osc_server->send( gui_addr, "/nsm/gui/client/progress", c->client_id, (float)c->progress );
}
}
return 0;
}
OSC_HANDLER( is_dirty )
{
MESSAGE( "Client sends dirty" );
Client *c = get_client_by_address( lo_message_get_source( msg ) );
if ( ! c )
return 0;
c->dirty = 1;
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/dirty", c->client_id, c->dirty );
return 0;
}
OSC_HANDLER( is_clean )
{
MESSAGE( "Client sends clean" );
Client *c = get_client_by_address( lo_message_get_source( msg ) );
if ( ! c )
return 0;
c->dirty = 0;
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/dirty", c->client_id, c->dirty );
return 0;
}
OSC_HANDLER( gui_is_hidden )
{
MESSAGE( "Client sends gui hidden" );
Client *c = get_client_by_address( lo_message_get_source( msg ) );
if ( ! c )
return 0;
c->gui_visible( false );
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/gui_visible", c->client_id, c->gui_visible() );
return 0;
}
OSC_HANDLER( gui_is_shown )
{
MESSAGE( "Client sends gui shown" );
Client *c = get_client_by_address( lo_message_get_source( msg ) );
if ( ! c )
return 0;
c->gui_visible( true );
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/gui_visible", c->client_id, c->gui_visible() );
return 0;
}
OSC_HANDLER( message )
{
Client *c = get_client_by_address( lo_message_get_source( msg ) );
if ( ! c )
return 0;
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/message", c->client_id, argv[0]->i, &argv[1]->s );
return 0;
}
OSC_HANDLER( label )
{
Client *c = get_client_by_address( lo_message_get_source( msg ) );
if ( ! c )
return 0;
if ( strcmp( types, "s" ) )
return -1;
c->label( &argv[0]->s );
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/label", c->client_id, &argv[0]->s );
return 0;
}
/**********************/
/* Response Handlers */
/**********************/
OSC_HANDLER( error )
{
Client *c = get_client_by_address( lo_message_get_source( msg ) );
if ( ! c )
{
WARNING( "Error from unknown client" );
return 0;
}
// const char *rpath = &argv[0]->s;
int err_code = argv[1]->i;
const char *message = &argv[2]->s;
c->set_reply( err_code, message );
MESSAGE( "Client \"%s\" replied with error: %s (%i) in %fms", c->name, message, err_code, c->milliseconds_since_last_command() );
c->pending_command( COMMAND_NONE );
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "error" );
return 0;
}
OSC_HANDLER( reply )
{
Client *c = get_client_by_address( lo_message_get_source( msg ) );
// const char *rpath = &argv[0]->s;
const char *message = &argv[1]->s;
if ( c )
{
c->set_reply( ERR_OK, message );
MESSAGE( "Client \"%s\" replied with: %s in %fms", c->name, message, c->milliseconds_since_last_command() );
c->pending_command( COMMAND_NONE );
if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "ready" );
}
else
MESSAGE( "Reply from unknown client" );
return 0;
}
/******************/
/* GUI operations */
/******************/
OSC_HANDLER( stop )
{
Client *c = get_client_by_id( &client, &argv[0]->s );
if ( c )
{
command_client_to_stop( c );
if ( gui_is_active )
osc_server->send( gui_addr, "/reply", "Client stopped." );
}
else
{
if ( gui_is_active )
osc_server->send( gui_addr, "/error", -10, "No such client." );
}
return 0;
}
OSC_HANDLER( remove )
{
Client *c = get_client_by_id( &client, &argv[0]->s );
if ( c )
{
if ( c->pid == 0 &&
! c->active )
{
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "removed" );
client.remove( c );
delete c;
if ( gui_is_active )
osc_server->send( gui_addr, "/reply", "Client removed." );
}
}
else
{
if ( gui_is_active )
osc_server->send( gui_addr, "/error", -10, "No such client." );
}
return 0;
}
OSC_HANDLER( resume )
{
Client *c = get_client_by_id( &client, &argv[0]->s );
/* FIXME: return error if no such client? */
if ( c )
{
if ( c->pid == 0 &&
! c->active )
{
if ( ! launch( c->executable_path, c->client_id ) )
{
}
}
}
return 0;
}
OSC_HANDLER( client_save )
{
Client *c = get_client_by_id( &client, &argv[0]->s );
/* FIXME: return error if no such client? */
if ( c )
{
if ( c->active )
{
command_client_to_save( c );
}
}
return 0;
}
OSC_HANDLER( client_show_optional_gui )
{
Client *c = get_client_by_id( &client, &argv[0]->s );
/* FIXME: return error if no such client? */
if ( c )
{
if ( c->active )
{
osc_server->send( c->addr, "/nsm/client/show_optional_gui" );
}
}
return 0;
}
OSC_HANDLER( client_hide_optional_gui )
{
Client *c = get_client_by_id( &client, &argv[0]->s );
/* FIXME: return error if no such client? */
if ( c )
{
if ( c->active )
{
osc_server->send( c->addr, "/nsm/client/hide_optional_gui" );
}
}
return 0;
}
void
announce_gui( const char *url, bool is_reply )
{
gui_addr = lo_address_new_from_url( url );
gui_is_active = true;
if ( is_reply )
osc_server->send( gui_addr, "/nsm/gui/gui_announce", "hi" );
else
osc_server->send( gui_addr, "/nsm/gui/server_announce", "hi" );
for ( std::list<Client*>::iterator i = client.begin();
i != client.end();
++i )
{
Client *c = *i;
osc_server->send( gui_addr, "/nsm/gui/client/new", c->client_id, c->name );
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );
}
osc_server->send( gui_addr, "/nsm/gui/session/name", session_name ? session_name : "", session_path ? session_path : "" );
DMESSAGE( "Registered with GUI" );
}
OSC_HANDLER( gui_announce )
{
announce_gui( lo_address_get_url( lo_message_get_source( msg ) ), true );
return 0;
}
OSC_HANDLER( ping )
{
osc_server->send( lo_message_get_source( msg ), "/reply", path );
return 0;
}
OSC_HANDLER( null )
{
WARNING( "Unrecognized message with type signature \"%s\" at path \"%s\"", types, path );
return 0;
}
static void
wait ( long timeout )
{
struct signalfd_siginfo fdsi;
ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));
if (s == sizeof(struct signalfd_siginfo))
{
if (fdsi.ssi_signo == SIGCHLD)
handle_sigchld();
}
osc_server->wait( timeout );
purge_dead_clients();
}
int main(int argc, char *argv[])
{
sigset_t mask;
sigemptyset( &mask );
sigaddset( &mask, SIGCHLD );
sigprocmask(SIG_BLOCK, &mask, NULL );
signal_fd = signalfd( -1, &mask, SFD_NONBLOCK );
/* generate random seed for client ids */
{
time_t seconds;
time(&seconds);
srand( (unsigned int) seconds );
}
// char *osc_port = "6666";
char *osc_port = NULL;
const char *gui_url = NULL;
static struct option long_options[] =
{
{ "detach", no_argument, 0, 'd' },
{ "session-root", required_argument, 0, 's' },
{ "osc-port", required_argument, 0, 'p' },
{ "gui-url", required_argument, 0, 'g' },
{ "help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
int option_index = 0;
int c = 0;
bool detach = false;
while ( ( c = getopt_long_only( argc, argv, "", long_options, &option_index ) ) != -1 )
{
switch ( c )
{
case 'd':
detach = true;
break;
case 's':
{
session_root = optarg;
/* get rid of trailing slash */
char *s = rindex(session_root,'/');
if ( s == &session_root[strlen(session_root) - 1] )
*s = '\0';
break;
}
case 'p':
DMESSAGE( "Using OSC port %s", optarg );
osc_port = optarg;
break;
case 'g':
DMESSAGE( "Going to connect to GUI at: %s", optarg );
gui_url = optarg;
break;
case 'h':
printf( "Usage: %s [--osc-port portnum] [--session-root path]\n\n", argv[0] );
exit(0);
break;
}
}
if ( !session_root )
asprintf( &session_root, "%s/%s", getenv( "HOME" ), "NSM Sessions" );
struct stat st;
if ( stat( session_root, &st ) )
{
if ( mkdir( session_root, 0771 ) )
{
FATAL( "Failed to create session directory: %s", strerror( errno ) );
}
}
MESSAGE( "Session root is: %s", session_root );
osc_server = new OSC::Endpoint();
if ( osc_server->init( LO_UDP, osc_port ) )
{
FATAL( "Failed to create OSC server." );
}
printf( "NSM_URL=%s\n", osc_server->url() );
if ( gui_url )
{
announce_gui( gui_url, false );
}
/* */
osc_server->add_method( "/nsm/server/announce", "sssiii", OSC_NAME( announce ), NULL, "client_name,capabilities,executable,api_version_major,api_version_minor,client_pid" );
/* response handlers */
osc_server->add_method( "/reply", "ss", OSC_NAME( reply ), NULL, "err_code,msg" );
osc_server->add_method( "/error", "sis", OSC_NAME( error ), NULL, "err_code,msg" );
osc_server->add_method( "/nsm/client/progress", "f", OSC_NAME( progress ), NULL, "progress" );
osc_server->add_method( "/nsm/client/is_dirty", "", OSC_NAME( is_dirty ), NULL, "dirtiness" );
osc_server->add_method( "/nsm/client/is_clean", "", OSC_NAME( is_clean ), NULL, "dirtiness" );
osc_server->add_method( "/nsm/client/message", "is", OSC_NAME( message ), NULL, "message" );
osc_server->add_method( "/nsm/client/gui_is_hidden", "", OSC_NAME( gui_is_hidden ), NULL, "message" );
osc_server->add_method( "/nsm/client/gui_is_shown", "", OSC_NAME( gui_is_shown ), NULL, "message" );
osc_server->add_method( "/nsm/client/label", "s", OSC_NAME( label ), NULL, "message" );
/* */
osc_server->add_method( "/nsm/gui/gui_announce", "", OSC_NAME( gui_announce ), NULL, "" );
osc_server->add_method( "/nsm/gui/client/stop", "s", OSC_NAME( stop ), NULL, "client_id" );
osc_server->add_method( "/nsm/gui/client/remove", "s", OSC_NAME( remove ), NULL, "client_id" );
osc_server->add_method( "/nsm/gui/client/resume", "s", OSC_NAME( resume ), NULL, "client_id" );
osc_server->add_method( "/nsm/gui/client/save", "s", OSC_NAME( client_save ), NULL, "client_id" );
osc_server->add_method( "/nsm/gui/client/show_optional_gui", "s", OSC_NAME( client_show_optional_gui ), NULL, "client_id" );
osc_server->add_method( "/nsm/gui/client/hide_optional_gui", "s", OSC_NAME( client_hide_optional_gui ), NULL, "client_id" );
osc_server->add_method( "/osc/ping", "", OSC_NAME( ping ), NULL, "" );
osc_server->add_method( "/nsm/server/broadcast", NULL, OSC_NAME( broadcast ), NULL, "" );
osc_server->add_method( "/nsm/server/duplicate", "s", OSC_NAME( duplicate ), NULL, "" );
osc_server->add_method( "/nsm/server/abort", "", OSC_NAME( abort ), NULL, "" );
osc_server->add_method( "/nsm/server/list", "", OSC_NAME( list ), NULL, "" );
osc_server->add_method( "/nsm/server/add", "s", OSC_NAME( add ), NULL, "executable_name" );
osc_server->add_method( "/nsm/server/new", "s", OSC_NAME( new ), NULL, "name" );
osc_server->add_method( "/nsm/server/save", "", OSC_NAME( save ), NULL, "" );
osc_server->add_method( "/nsm/server/open", "s", OSC_NAME( open ), NULL, "name" );
osc_server->add_method( "/nsm/server/close", "", OSC_NAME( close ), NULL, "" );
osc_server->add_method( "/nsm/server/quit", "", OSC_NAME( quit ), NULL, "" );
osc_server->add_method( NULL, NULL, OSC_NAME( null ),NULL, "" );
if ( detach )
{
MESSAGE( "Detaching from console" );
if ( fork() )
{
exit( 0 );
}
else
{
fclose( stdin );
fclose( stdout );
fclose( stderr );
}
}
/* listen for sigchld signals and process OSC messages forever */
for ( ;; )
{
wait( 1000 );
}
// osc_server->run();
return 0;
}