| @@ -2,7 +2,7 @@ | |||
| ! title Non Session Management API | |||
| ! author Jonathan Moore Liles #(email,male@tuxfamily.org) | |||
| ! date August 1, 2010 | |||
| ! revision Version 1.0 | |||
| ! revision Version 1.1 | |||
| ! extra #(image,logo,icon.png) | |||
| -- Table Of Contents | |||
| @@ -156,6 +156,7 @@ | |||
| [[ dirty, client knows when it has unsaved changes | |||
| [[ progress, client can send progress updates during time-consuming operations | |||
| [[ message, client can send textual status updates | |||
| [[ optional-gui, client has an optional GUI | |||
| :::: Response | |||
| @@ -179,6 +180,7 @@ | |||
| [[ Name, Description | |||
| [[ server_control, client-to-server control | |||
| [[ broadcast, server responds to /nsm/server/broadcast message | |||
| [[ optional-gui, server responds to optional-gui messages--if this capability is not present then clients with optional-guis MUST always keep them visible | |||
| A client should not consider itself to be under session management | |||
| until it receives this response. For example, the Non applications | |||
| @@ -366,6 +368,22 @@ | |||
| This message does not require a response. | |||
| :::: Show Optional Gui | |||
| If the client has specified the `optional-gui` capability, then it | |||
| may receive this message from the server when the user wishes to | |||
| change the visibility state of the GUI. It doesn't matter if the | |||
| optional GUI is integrated with the program or if it is a separate | |||
| program \(as is the case with SooperLooper\). When the GUI is | |||
| hidden, there should be no window mapped and if the GUI is a | |||
| separate program, it should be killed. | |||
| > /nsm/client/show_optional_gui | |||
| > /nsm/client/hide_optional_gui | |||
| No response is message is required. | |||
| ::: Client to Server Informational Messages | |||
| These are optional messages which a client can send to the NSM | |||
| @@ -375,6 +393,20 @@ | |||
| appropriate value to its `capabilities` string when composing the | |||
| `announce` message. | |||
| :::: Optional GUI | |||
| If the client has specified the `optional-gui` capability, then it | |||
| *MUST* send this message whenever the state of visibility of the | |||
| optional GUI has changed. It also *MUST* send this message after | |||
| it's announce message to indicate the initial visibility state of | |||
| the optional GUI. | |||
| > /nsm/client/gui_is_hidden | |||
| > /nsm/client/gui_is_shown | |||
| No response will be delivered. | |||
| :::: Progress | |||
| > /nsm/client/progress f:progress | |||
| @@ -36,9 +36,7 @@ | |||
| #include <sys/signalfd.h> | |||
| #include <sys/stat.h> | |||
| #include <sys/wait.h> | |||
| #include <uuid/uuid.h> | |||
| #include <unistd.h> | |||
| #include <uuid/uuid.h> | |||
| #include <time.h> | |||
| #include <libgen.h> | |||
| #include <dirent.h> | |||
| @@ -97,6 +95,10 @@ private: | |||
| int _pending_command; /* */ | |||
| struct timeval _command_sent_time; | |||
| bool _gui_visible; | |||
| char *_label; | |||
| public: | |||
| lo_address addr; /* */ | |||
| @@ -105,7 +107,6 @@ public: | |||
| int pid; /* PID of client process */ | |||
| float progress; /* */ | |||
| bool active; /* client has registered via announce */ | |||
| bool dead_because_we_said; | |||
| // 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 */ | |||
| @@ -113,14 +114,32 @@ public: | |||
| bool pre_existing; | |||
| const char *status; | |||
| const char *label ( void ) const { return _label; } | |||
| void label ( const char *l ) | |||
| { | |||
| if ( _label ) | |||
| free( _label ); | |||
| _label = strdup( l ); | |||
| } | |||
| bool gui_visible ( void ) const | |||
| { | |||
| return _gui_visible; | |||
| } | |||
| void gui_visible ( bool b ) | |||
| { | |||
| _gui_visible = b; | |||
| } | |||
| bool | |||
| has_error ( void ) | |||
| has_error ( void ) const | |||
| { | |||
| return _reply_errcode != 0; | |||
| } | |||
| int | |||
| error_code ( void ) | |||
| error_code ( void ) const | |||
| { | |||
| return _reply_errcode; | |||
| } | |||
| @@ -173,12 +192,20 @@ public: | |||
| 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 ( ) | |||
| { | |||
| _gui_visible = true; | |||
| addr = 0; | |||
| _reply_errcode = 0; | |||
| _reply_message = 0; | |||
| dead_because_we_said = false; | |||
| pid = 0; | |||
| progress = -0; | |||
| _pending_command = 0; | |||
| @@ -265,10 +292,12 @@ handle_client_process_death ( int pid ) | |||
| { | |||
| MESSAGE( "Client %s died.", c->name ); | |||
| bool dead_because_we_said = false; | |||
| if ( c->pending_command() == COMMAND_KILL || | |||
| c->pending_command() == COMMAND_QUIT ) | |||
| { | |||
| c->dead_because_we_said = true; | |||
| dead_because_we_said = true; | |||
| } | |||
| c->pending_command( COMMAND_NONE ); | |||
| @@ -276,7 +305,7 @@ handle_client_process_death ( int pid ) | |||
| c->active = false; | |||
| c->pid = 0; | |||
| if ( c->dead_because_we_said ) | |||
| if ( dead_because_we_said ) | |||
| { | |||
| if ( gui_is_active ) | |||
| osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "removed" ); | |||
| @@ -770,6 +799,9 @@ OSC_HANDLER( announce ) | |||
| { | |||
| 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 ); | |||
| } | |||
| { | |||
| @@ -824,13 +856,6 @@ client_by_name ( const char *name, | |||
| return NULL; | |||
| } | |||
| // capability should be enclosed in colons. I.e. ":switch:" | |||
| bool | |||
| client_is_capable_of ( Client *c, const char *capability ) | |||
| { | |||
| return c->capabilities && | |||
| strstr( c->capabilities, capability ); | |||
| } | |||
| bool | |||
| dumb_clients_are_alive ( ) | |||
| @@ -1109,7 +1134,7 @@ load_session_file ( const char * path ) | |||
| i != client.end(); | |||
| ++i ) | |||
| { | |||
| if ( ! client_is_capable_of( *i, ":switch:" ) | |||
| if ( ! (*i)->is_capable_of( ":switch:" ) | |||
| || | |||
| ! client_by_name( (*i)->name, &new_clients ) ) | |||
| { | |||
| @@ -1636,6 +1661,39 @@ OSC_HANDLER( is_clean ) | |||
| 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 ) | |||
| { | |||
| @@ -1650,6 +1708,24 @@ OSC_HANDLER( message ) | |||
| 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 */ | |||
| /**********************/ | |||
| @@ -1779,6 +1855,38 @@ OSC_HANDLER( client_save ) | |||
| 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 ) | |||
| { | |||
| @@ -1927,12 +2035,17 @@ int main(int argc, char *argv[]) | |||
| 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/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, "" ); | |||
| @@ -83,19 +83,56 @@ static std::list<Daemon*> daemon_list; /* list | |||
| class NSM_Client : public Fl_Group | |||
| { | |||
| char *_client_id; | |||
| char *_client_label; | |||
| char *_client_name; | |||
| // Fl_Box *client_name; | |||
| Fl_Progress *_progress; | |||
| Fl_Light_Button *_dirty; | |||
| Fl_Light_Button *_gui; | |||
| Fl_Button *_remove_button; | |||
| Fl_Button *_restart_button; | |||
| void | |||
| set_label ( void ) | |||
| { | |||
| char *l; | |||
| if ( _client_label ) | |||
| asprintf( &l, "%s (%s)", _client_name, _client_label ); | |||
| else | |||
| l = strdup( _client_name ); | |||
| if ( label() ) | |||
| free((char*)label()); | |||
| label( l ); | |||
| redraw(); | |||
| } | |||
| public: | |||
| void | |||
| name ( const char *v ) | |||
| { | |||
| label( strdup( v ) ); | |||
| if ( _client_name ) | |||
| free( _client_name ); | |||
| _client_name = strdup( v ); | |||
| set_label(); | |||
| } | |||
| void | |||
| client_label ( const char *s ) | |||
| { | |||
| if ( _client_label ) | |||
| free( _client_label ); | |||
| _client_label = strdup( s ); | |||
| set_label(); | |||
| } | |||
| void | |||
| @@ -121,6 +158,21 @@ public: | |||
| _dirty->redraw(); | |||
| } | |||
| void | |||
| gui_visible ( bool b ) | |||
| { | |||
| _gui->value( b ); | |||
| _gui->redraw(); | |||
| } | |||
| void | |||
| has_optional_gui ( void ) | |||
| { | |||
| _gui->show(); | |||
| _gui->redraw(); | |||
| } | |||
| void | |||
| stopped ( bool b ) | |||
| { | |||
| @@ -193,7 +245,18 @@ public: | |||
| osc->send( (*d)->addr, "/nsm/gui/client/save", _client_id ); | |||
| } | |||
| } | |||
| if ( o == _remove_button ) | |||
| else if ( o == _gui ) | |||
| { | |||
| MESSAGE( "Sending hide/show GUI."); | |||
| foreach_daemon ( d ) | |||
| { | |||
| if ( !_gui->value() ) | |||
| osc->send( (*d)->addr, "/nsm/gui/client/show_optional_gui", _client_id ); | |||
| else | |||
| osc->send( (*d)->addr, "/nsm/gui/client/hide_optional_gui", _client_id ); | |||
| } | |||
| } | |||
| else if ( o == _remove_button ) | |||
| { | |||
| MESSAGE( "Sending remove."); | |||
| foreach_daemon ( d ) | |||
| @@ -221,46 +284,119 @@ public: | |||
| { | |||
| _client_id = NULL; | |||
| _client_name = NULL; | |||
| _client_label = NULL; | |||
| align( FL_ALIGN_LEFT | FL_ALIGN_INSIDE ); | |||
| color( fl_darker( FL_RED ) ); | |||
| box( FL_UP_BOX ); | |||
| int yy = Y + H * 0.25; | |||
| int hh = H * 0.50; | |||
| int xx = X + W - ( 200 + Fl::box_dw( box() ) ); | |||
| int ss = 2; | |||
| { Fl_Progress *o = _progress = new Fl_Progress( ( X + W ) - ( W / 4) - 20, Y + 5, ( W / 4 ), H - 10, NULL ); | |||
| /* dummy group */ | |||
| { Fl_Group *o = new Fl_Group( X, Y, 50, 50 ); | |||
| o->end(); | |||
| resizable( o ); | |||
| } | |||
| { Fl_Progress *o = _progress = new Fl_Progress( xx, Y + H * 0.25, 200, H * 0.50, NULL ); | |||
| o->box( FL_FLAT_BOX ); | |||
| o->color( FL_BLACK ); | |||
| o->label( strdup( "launch" ) ); | |||
| o->minimum( 0.0f ); | |||
| o->maximum( 1.0f ); | |||
| } | |||
| { Fl_Light_Button *o = _dirty = new Fl_Light_Button( _progress->x() - 30, Y + 7, 25, 25 ); | |||
| o->box( FL_UP_BOX ); | |||
| o->type(0); | |||
| o->color(); | |||
| o->selection_color( FL_YELLOW ); | |||
| o->value( 0 ); | |||
| o->callback( cb_button, this ); | |||
| { Fl_Group *o = new Fl_Group( X + W - 400, Y, 400, H ); | |||
| xx -= 50 + ss; | |||
| { Fl_Light_Button *o = _dirty = new Fl_Light_Button( xx, yy, 50, hh, "SAVE" ); | |||
| o->align( FL_ALIGN_LEFT | FL_ALIGN_INSIDE ); | |||
| o->labelsize( 9 ); | |||
| o->box( FL_UP_BOX ); | |||
| o->type(0); | |||
| o->color(); | |||
| o->selection_color( FL_YELLOW ); | |||
| o->value( 0 ); | |||
| o->callback( cb_button, this ); | |||
| } | |||
| xx -= 40 + ss; | |||
| { Fl_Light_Button *o = _gui = new Fl_Light_Button( xx, yy, 40, hh, "GUI" ); | |||
| o->align( FL_ALIGN_LEFT | FL_ALIGN_INSIDE ); | |||
| o->labelsize( 9 ); | |||
| o->box( FL_UP_BOX ); | |||
| o->type(0); | |||
| o->color(); | |||
| o->selection_color( FL_YELLOW ); | |||
| o->value( 0 ); | |||
| o->hide(); | |||
| o->callback( cb_button, this ); | |||
| } | |||
| xx -= 25 + ss; | |||
| { Fl_Button *o = _restart_button = new Fl_Button( xx, yy, 25, hh ); | |||
| o->box( FL_UP_BOX ); | |||
| o->type(0); | |||
| o->color( FL_GREEN ); | |||
| o->value( 0 ); | |||
| o->label( "@>" ); | |||
| o->tooltip( "Resume" ); | |||
| o->hide(); | |||
| o->callback( cb_button, this ); | |||
| } | |||
| xx -= 25 + ss; | |||
| { Fl_Button *o = _remove_button = new Fl_Button( xx, yy, 25, hh ); | |||
| o->box( FL_UP_BOX ); | |||
| o->type(0); | |||
| o->color( FL_RED ); | |||
| o->value( 0 ); | |||
| o->label( "X" ); | |||
| o->tooltip( "Remove" ); | |||
| o->hide(); | |||
| o->callback( cb_button, this ); | |||
| } | |||
| o->end(); | |||
| } | |||
| { Fl_Button *o = _remove_button = new Fl_Button( _progress->x() - 60, Y + 7, 25, 25 ); | |||
| o->box( FL_UP_BOX ); | |||
| o->type(0); | |||
| o->color( FL_RED ); | |||
| o->value( 0 ); | |||
| o->label( "X" ); | |||
| o->tooltip( "Remove" ); | |||
| o->hide(); | |||
| o->callback( cb_button, this ); | |||
| end(); | |||
| } | |||
| ~NSM_Client ( ) | |||
| { | |||
| if ( _client_name ) | |||
| { | |||
| free( _client_name ); | |||
| _client_name = NULL; | |||
| } | |||
| { Fl_Button *o = _restart_button = new Fl_Button( _progress->x() - 90, Y + 7, 25, 25 ); | |||
| o->box( FL_UP_BOX ); | |||
| o->type(0); | |||
| o->color( FL_GREEN ); | |||
| o->value( 0 ); | |||
| o->label( "@>" ); | |||
| o->tooltip( "Resume" ); | |||
| o->hide(); | |||
| o->callback( cb_button, this ); | |||
| if ( _client_label ) | |||
| { | |||
| free( _client_label ); | |||
| _client_label = NULL; | |||
| } | |||
| end(); | |||
| if ( label() ) | |||
| { | |||
| free( (char*)label() ); | |||
| label( NULL ); | |||
| } | |||
| } | |||
| }; | |||
| @@ -735,6 +871,9 @@ public: | |||
| osc->add_method( "/nsm/gui/client/switch", "ss", osc_handler, osc, "path,display_name" ); | |||
| osc->add_method( "/nsm/gui/client/progress", "sf", osc_handler, osc, "path,display_name" ); | |||
| osc->add_method( "/nsm/gui/client/dirty", "si", osc_handler, osc, "path,display_name" ); | |||
| osc->add_method( "/nsm/gui/client/has_optional_gui", "s", osc_handler, osc, "path,display_name" ); | |||
| osc->add_method( "/nsm/gui/client/gui_visible", "si", osc_handler, osc, "path,display_name" ); | |||
| osc->add_method( "/nsm/gui/client/label", "ss", osc_handler, osc, "path,display_name" ); | |||
| osc->start(); | |||
| @@ -852,7 +991,7 @@ private: | |||
| if ( !strncmp( path, "/nsm/gui/client/", strlen( "/nsm/gui/client/" ) ) ) | |||
| { | |||
| if ( !strcmp( path, "/nsm/gui/client/new" ) && | |||
| !strcmp( types, "ss" ) ) | |||
| !strcmp( types, "ss" ) ) | |||
| { | |||
| controller->client_new( &argv[0]->s, &argv[1]->s ); | |||
| } | |||
| @@ -877,6 +1016,21 @@ private: | |||
| { | |||
| c->dirty( argv[1]->i ); | |||
| } | |||
| else if ( !strcmp( path, "/nsm/gui/client/gui_visible" ) && | |||
| !strcmp( types, "si" )) | |||
| { | |||
| c->gui_visible( argv[1]->i ); | |||
| } | |||
| else if ( !strcmp( path, "/nsm/gui/client/label" ) && | |||
| !strcmp( types, "ss" )) | |||
| { | |||
| c->client_label( &argv[1]->s ); | |||
| } | |||
| else if ( !strcmp( path, "/nsm/gui/client/has_optional_gui" ) && | |||
| !strcmp( types, "s" )) | |||
| { | |||
| c->has_optional_gui(); | |||
| } | |||
| else if ( !strcmp( path, "/nsm/gui/client/switch" ) && | |||
| !strcmp( types, "ss" )) | |||
| { | |||