| @@ -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,13 +380,21 @@ 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; | |||
| break; // no child process has ended this loop. Check again. | |||
| //One child process has stopped. Find which and figure out the stop-conditions | |||
| Client *c; | |||
| c = get_client_by_pid( pid ); | |||
| 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; | |||
| handle_client_process_death( pid ); | |||
| } | |||
| @@ -631,7 +654,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 | |||
| //This is code of the child process. It will be executed after launch() has finished | |||
| GUIMSG( "Launching %s", executable ); | |||
| char *args[] = { strdup( executable ), NULL }; | |||
| @@ -643,16 +666,24 @@ launch ( const char *executable, const char *client_id ) | |||
| sigset_t mask; | |||
| sigemptyset( &mask ); | |||
| sigaddset( &mask, SIGCHLD ); | |||
| sigprocmask(SIG_UNBLOCK, &mask, NULL ); | |||
| sigprocmask(SIG_UNBLOCK, &mask, NULL ); | |||
| 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; | |||
| @@ -663,6 +694,7 @@ launch ( const char *executable, const char *client_id ) | |||
| //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" ); | |||
| } | |||
| @@ -874,7 +906,7 @@ OSC_HANDLER( announce ) | |||
| 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 %s has pid: %i", c->name, pid ); | |||
| if ( ! expected_client ) | |||