diff --git a/src/nsmd.cpp b/src/nsmd.cpp index b67bb34..6d4fdb9 100644 --- a/src/nsmd.cpp +++ b/src/nsmd.cpp @@ -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 );