|  |  | @@ -123,7 +123,7 @@ private: | 
		
	
		
			
			|  |  |  | int _reply_errcode; | 
		
	
		
			
			|  |  |  | char *_reply_message; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | int _pending_command;                /*  */ | 
		
	
		
			
			|  |  |  | int _pending_command; | 
		
	
		
			
			|  |  |  | struct timeval _command_sent_time; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | bool _gui_visible; | 
		
	
	
		
			
				|  |  | @@ -137,13 +137,14 @@ public: | 
		
	
		
			
			|  |  |  | 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 active;                       /* NSM capable: 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; | 
		
	
		
			
			|  |  |  | int launch_error;                   /* APIv1.2, leads to status for executable not found, permission denied etc. */ | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | const char *label ( void ) const { return _label; } | 
		
	
		
			
			|  |  |  | void label ( const char *l ) | 
		
	
	
		
			
				|  |  | @@ -250,6 +251,7 @@ public: | 
		
	
		
			
			|  |  |  | name = 0; | 
		
	
		
			
			|  |  |  | executable_path = 0; | 
		
	
		
			
			|  |  |  | pre_existing = false; | 
		
	
		
			
			|  |  |  | launch_error = 0; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | ~Client ( ) | 
		
	
	
		
			
				|  |  | @@ -353,7 +355,20 @@ handle_client_process_death ( int pid ) | 
		
	
		
			
			|  |  |  | else | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | if ( gui_is_active ) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "stopped" ); | 
		
	
		
			
			|  |  |  | if ( c->launch_error ) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | /* NSM API treats the stopped status as switch. You can only remove stopped. | 
		
	
		
			
			|  |  |  | * Furthermore the GUI will change its client-buttons. | 
		
	
		
			
			|  |  |  | * In consequence we cannot add an arbitrary "launch-error" status. | 
		
	
		
			
			|  |  |  | * Compatible compromise is to use the label field to relay info the user, | 
		
	
		
			
			|  |  |  | * which was the goal. There is nothing we can do about a failed launch anyway. | 
		
	
		
			
			|  |  |  | */ | 
		
	
		
			
			|  |  |  | GUIMSG("Client %s had a launch error.", c->name); | 
		
	
		
			
			|  |  |  | osc_server->send( gui_addr, "/nsm/gui/client/label", c->client_id, "launch error!" ); //do not set the client objects label. | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | c->pending_command( COMMAND_NONE ); | 
		
	
	
		
			
				|  |  | @@ -365,15 +380,32 @@ handle_client_process_death ( int pid ) | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void handle_sigchld ( ) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | // compare waitpid(2) | 
		
	
		
			
			|  |  |  | for ( ;; ) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | int status; | 
		
	
		
			
			|  |  |  | pid_t pid = waitpid(-1, &status, WNOHANG); | 
		
	
		
			
			|  |  |  | int status = 1; // make it not NULL to enable information storage in status | 
		
	
		
			
			|  |  |  | pid_t pid = waitpid(-1, &status, WNOHANG); //-1 meaning wait for any child process. pid_t is signed integer | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | if (pid <= 0) | 
		
	
		
			
			|  |  |  | break; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | handle_client_process_death( pid ); | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | break; // no child process has ended this loop. Check again. | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | else | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | //One child process has stopped. Find which and figure out the stop-conditions | 
		
	
		
			
			|  |  |  | Client *c; | 
		
	
		
			
			|  |  |  | c = get_client_by_pid( pid ); | 
		
	
		
			
			|  |  |  | if ( c ) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | //The following will not trigger with normal crashes, e.g. segfaults or python tracebacks | 
		
	
		
			
			|  |  |  | if ( WIFEXITED( status ) ) // returns true if the child terminated normally | 
		
	
		
			
			|  |  |  | if ( WEXITSTATUS( status ) == 255 ) // as given by exit(-1) in launch() | 
		
	
		
			
			|  |  |  | c->launch_error = true; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | // Call even if Client was already null. This will check itself again and was expected | 
		
	
		
			
			|  |  |  | // to be called for the majority of nsmds development | 
		
	
		
			
			|  |  |  | handle_client_process_death( pid ); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
	
		
			
				|  |  | @@ -631,6 +663,7 @@ launch ( const char *executable, const char *client_id ) | 
		
	
		
			
			|  |  |  | int pid; | 
		
	
		
			
			|  |  |  | if ( ! (pid = fork()) ) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | //This is code of the child process. It will be executed after launch() has finished | 
		
	
		
			
			|  |  |  | GUIMSG( "Launching %s", executable ); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | char *args[] = { strdup( executable ), NULL }; | 
		
	
	
		
			
				|  |  | @@ -646,20 +679,31 @@ launch ( const char *executable, const char *client_id ) | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | if ( -1 == execvp( executable, args ) ) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | WARNING( "Error starting process: %s", strerror( errno ) ); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | exit(-1); | 
		
	
		
			
			|  |  |  | /* The program was not started. Causes: not installed on the current system, and the | 
		
	
		
			
			|  |  |  | * session was transferred from another system, or permission denied (no executable flag) | 
		
	
		
			
			|  |  |  | * Since we are running in a forked child process Client c does exist, but points to | 
		
	
		
			
			|  |  |  | * a memory copy, not the real client. So we can't set any error code or status in the | 
		
	
		
			
			|  |  |  | * client object. Instead we check the exit return code in handle_sigchld() and set the | 
		
	
		
			
			|  |  |  | *  bool client->launch_error to true. | 
		
	
		
			
			|  |  |  | */ | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | WARNING( "Error starting process %s: %s", executable, strerror( errno ) ); | 
		
	
		
			
			|  |  |  | exit(-1); //-1 later parsed as 255 | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | //This is code of the parent process. It is executed right at this point, before the child. | 
		
	
		
			
			|  |  |  | c->pending_command( COMMAND_START ); | 
		
	
		
			
			|  |  |  | c->pid = pid; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | MESSAGE( "Process has pid: %i", pid ); | 
		
	
		
			
			|  |  |  | MESSAGE( "Process %s has pid: %i", executable, pid ); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | if ( gui_is_active ) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | //At this point we do not know if launched program will start or fail | 
		
	
		
			
			|  |  |  | //And we do not know if it has nsm-support or not. This will be decided if it announces. | 
		
	
		
			
			|  |  |  | osc_server->send( gui_addr, "/nsm/gui/client/new", c->client_id, c->name ); | 
		
	
		
			
			|  |  |  | osc_server->send( gui_addr, "/nsm/gui/client/label", c->client_id, "" ); //clear label from potential previous-and-fixed launch error | 
		
	
		
			
			|  |  |  | osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "launch" ); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
	
		
			
				|  |  | @@ -872,7 +916,7 @@ OSC_HANDLER( announce ) | 
		
	
		
			
			|  |  |  | c->name = strdup( client_name ); | 
		
	
		
			
			|  |  |  | c->active = true; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | MESSAGE( "Process has pid: %i", pid ); | 
		
	
		
			
			|  |  |  | MESSAGE( "Process %s has pid: %i", c->name, pid ); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | if ( ! expected_client ) | 
		
	
		
			
			|  |  |  | client.push_back( c ); | 
		
	
	
		
			
				|  |  | 
 |