NSM: Add support for clients with optional GUIs.

This commit is contained in:
Jonathan Moore Liles 2012-04-08 20:48:06 -07:00
parent fdcf74ddbe
commit 885ef30a88
3 changed files with 347 additions and 48 deletions

View File

@ -2,7 +2,7 @@
! title Non Session Management API ! title Non Session Management API
! author Jonathan Moore Liles #(email,male@tuxfamily.org) ! author Jonathan Moore Liles #(email,male@tuxfamily.org)
! date August 1, 2010 ! date August 1, 2010
! revision Version 1.0 ! revision Version 1.1
! extra #(image,logo,icon.png) ! extra #(image,logo,icon.png)
-- Table Of Contents -- Table Of Contents
@ -156,6 +156,7 @@
[[ dirty, client knows when it has unsaved changes [[ dirty, client knows when it has unsaved changes
[[ progress, client can send progress updates during time-consuming operations [[ progress, client can send progress updates during time-consuming operations
[[ message, client can send textual status updates [[ message, client can send textual status updates
[[ optional-gui, client has an optional GUI
:::: Response :::: Response
@ -179,6 +180,7 @@
[[ Name, Description [[ Name, Description
[[ server_control, client-to-server control [[ server_control, client-to-server control
[[ broadcast, server responds to /nsm/server/broadcast message [[ 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 A client should not consider itself to be under session management
until it receives this response. For example, the Non applications until it receives this response. For example, the Non applications
@ -366,6 +368,22 @@
This message does not require a response. 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 ::: Client to Server Informational Messages
These are optional messages which a client can send to the NSM 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 appropriate value to its `capabilities` string when composing the
`announce` message. `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 :::: Progress
> /nsm/client/progress f:progress > /nsm/client/progress f:progress

View File

@ -36,9 +36,7 @@
#include <sys/signalfd.h> #include <sys/signalfd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <uuid/uuid.h>
#include <unistd.h> #include <unistd.h>
#include <uuid/uuid.h>
#include <time.h> #include <time.h>
#include <libgen.h> #include <libgen.h>
#include <dirent.h> #include <dirent.h>
@ -97,6 +95,10 @@ private:
int _pending_command; /* */ int _pending_command; /* */
struct timeval _command_sent_time; struct timeval _command_sent_time;
bool _gui_visible;
char *_label;
public: public:
lo_address addr; /* */ lo_address addr; /* */
@ -105,7 +107,6 @@ public:
int pid; /* PID of client process */ int pid; /* PID of client process */
float progress; /* */ float progress; /* */
bool active; /* client has registered via announce */ 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 */ // 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 *client_id; /* short part of client ID */
char *capabilities; /* client capabilities... will be null for dumb clients */ char *capabilities; /* client capabilities... will be null for dumb clients */
@ -113,14 +114,32 @@ public:
bool pre_existing; bool pre_existing;
const char *status; 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 bool
has_error ( void ) has_error ( void ) const
{ {
return _reply_errcode != 0; return _reply_errcode != 0;
} }
int int
error_code ( void ) error_code ( void ) const
{ {
return _reply_errcode; return _reply_errcode;
} }
@ -173,12 +192,20 @@ public:
return _pending_command; 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 ( ) Client ( )
{ {
_gui_visible = true;
addr = 0; addr = 0;
_reply_errcode = 0; _reply_errcode = 0;
_reply_message = 0; _reply_message = 0;
dead_because_we_said = false;
pid = 0; pid = 0;
progress = -0; progress = -0;
_pending_command = 0; _pending_command = 0;
@ -265,10 +292,12 @@ handle_client_process_death ( int pid )
{ {
MESSAGE( "Client %s died.", c->name ); MESSAGE( "Client %s died.", c->name );
bool dead_because_we_said = false;
if ( c->pending_command() == COMMAND_KILL || if ( c->pending_command() == COMMAND_KILL ||
c->pending_command() == COMMAND_QUIT ) c->pending_command() == COMMAND_QUIT )
{ {
c->dead_because_we_said = true; dead_because_we_said = true;
} }
c->pending_command( COMMAND_NONE ); c->pending_command( COMMAND_NONE );
@ -276,7 +305,7 @@ handle_client_process_death ( int pid )
c->active = false; c->active = false;
c->pid = 0; c->pid = 0;
if ( c->dead_because_we_said ) if ( dead_because_we_said )
{ {
if ( gui_is_active ) if ( gui_is_active )
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "removed" ); 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/new", c->client_id, c->name );
osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status = "open" ); 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; 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 bool
dumb_clients_are_alive ( ) dumb_clients_are_alive ( )
@ -1109,7 +1134,7 @@ load_session_file ( const char * path )
i != client.end(); i != client.end();
++i ) ++i )
{ {
if ( ! client_is_capable_of( *i, ":switch:" ) if ( ! (*i)->is_capable_of( ":switch:" )
|| ||
! client_by_name( (*i)->name, &new_clients ) ) ! client_by_name( (*i)->name, &new_clients ) )
{ {
@ -1636,6 +1661,39 @@ OSC_HANDLER( is_clean )
return 0; 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 ) OSC_HANDLER( message )
{ {
@ -1650,6 +1708,24 @@ OSC_HANDLER( message )
return 0; 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 */ /* Response Handlers */
/**********************/ /**********************/
@ -1779,6 +1855,38 @@ OSC_HANDLER( client_save )
return 0; 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 void
announce_gui( const char *url, bool is_reply ) 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_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/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/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/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/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/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/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, "" ); osc_server->add_method( "/osc/ping", "", OSC_NAME( ping ), NULL, "" );

View File

@ -83,19 +83,56 @@ static std::list<Daemon*> daemon_list; /* list
class NSM_Client : public Fl_Group class NSM_Client : public Fl_Group
{ {
char *_client_id; char *_client_id;
char *_client_label;
char *_client_name;
// Fl_Box *client_name; // Fl_Box *client_name;
Fl_Progress *_progress; Fl_Progress *_progress;
Fl_Light_Button *_dirty; Fl_Light_Button *_dirty;
Fl_Light_Button *_gui;
Fl_Button *_remove_button; Fl_Button *_remove_button;
Fl_Button *_restart_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: public:
void void
name ( const char *v ) 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 void
@ -121,6 +158,21 @@ public:
_dirty->redraw(); _dirty->redraw();
} }
void
gui_visible ( bool b )
{
_gui->value( b );
_gui->redraw();
}
void
has_optional_gui ( void )
{
_gui->show();
_gui->redraw();
}
void void
stopped ( bool b ) stopped ( bool b )
{ {
@ -193,7 +245,18 @@ public:
osc->send( (*d)->addr, "/nsm/gui/client/save", _client_id ); 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."); MESSAGE( "Sending remove.");
foreach_daemon ( d ) foreach_daemon ( d )
@ -221,46 +284,119 @@ public:
{ {
_client_id = NULL; _client_id = NULL;
_client_name = NULL;
_client_label = NULL;
align( FL_ALIGN_LEFT | FL_ALIGN_INSIDE ); align( FL_ALIGN_LEFT | FL_ALIGN_INSIDE );
color( fl_darker( FL_RED ) ); color( fl_darker( FL_RED ) );
box( FL_UP_BOX ); box( FL_UP_BOX );
{ Fl_Progress *o = _progress = new Fl_Progress( ( X + W ) - ( W / 4) - 20, Y + 5, ( W / 4 ), H - 10, NULL ); int yy = Y + H * 0.25;
int hh = H * 0.50;
int xx = X + W - ( 200 + Fl::box_dw( box() ) );
int ss = 2;
/* 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->label( strdup( "launch" ) );
o->minimum( 0.0f ); o->minimum( 0.0f );
o->maximum( 1.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 ); { Fl_Group *o = new Fl_Group( X + W - 400, Y, 400, H );
o->type(0);
o->color(); xx -= 50 + ss;
o->selection_color( FL_YELLOW );
o->value( 0 ); { Fl_Light_Button *o = _dirty = new Fl_Light_Button( xx, yy, 50, hh, "SAVE" );
o->callback( cb_button, this );
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 ); end();
o->box( FL_UP_BOX ); }
o->type(0);
o->color( FL_RED ); ~NSM_Client ( )
o->value( 0 ); {
o->label( "X" ); if ( _client_name )
o->tooltip( "Remove" ); {
o->hide(); free( _client_name );
o->callback( cb_button, this ); _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 );
} }
end(); if ( _client_label )
{
free( _client_label );
_client_label = NULL;
}
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/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/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/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(); osc->start();
@ -852,7 +991,7 @@ private:
if ( !strncmp( path, "/nsm/gui/client/", strlen( "/nsm/gui/client/" ) ) ) if ( !strncmp( path, "/nsm/gui/client/", strlen( "/nsm/gui/client/" ) ) )
{ {
if ( !strcmp( path, "/nsm/gui/client/new" ) && if ( !strcmp( path, "/nsm/gui/client/new" ) &&
!strcmp( types, "ss" ) ) !strcmp( types, "ss" ) )
{ {
controller->client_new( &argv[0]->s, &argv[1]->s ); controller->client_new( &argv[0]->s, &argv[1]->s );
} }
@ -877,6 +1016,21 @@ private:
{ {
c->dirty( argv[1]->i ); 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" ) && else if ( !strcmp( path, "/nsm/gui/client/switch" ) &&
!strcmp( types, "ss" )) !strcmp( types, "ss" ))
{ {