@@ -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" )) | |||
{ | |||