diff --git a/meson.build b/meson.build index c192b68..4e55e59 100644 --- a/meson.build +++ b/meson.build @@ -23,11 +23,10 @@ liblodep = dependency('liblo') #and not 'lo' threaddep = dependency('threads') jackdep = dependency('jack') #and not 'libjack' -executable('nsmd', - sources: ['src/nsmd.cpp', 'src/debug.cpp', 'src/Endpoint.cpp', 'src/file.cpp', 'src/Thread.cpp'], - dependencies: [liblodep, threaddep], - install: true, - ) +cc = meson.get_compiler('c') +fluid = find_program('fluid') +fltkdep = cc.find_library('fltk', required: true) +fltkimagesdep = cc.find_library('fltk_images', required: true) executable('jackpatch', 'src/jackpatch.c', @@ -35,5 +34,40 @@ executable('jackpatch', install: true, ) +executable('non-session-manager', + sources: ['src/session-manager.cpp', 'src/debug.cpp', 'src/Endpoint.cpp', 'src/Thread.cpp', 'src/FL/Fl_Scalepack.C'], + dependencies: [fltkimagesdep, fltkdep, liblodep, threaddep], + install: true, + ) + +executable('nsm-proxy', + sources: ['src/nsm-proxy.cpp', 'src/debug.cpp'], + dependencies: [liblodep, threaddep], + install: true, + ) + +NSM_Proxy_UI_cpp = custom_target( + 'NSM_Proxy_UI.cpp', + output : 'NSM_Proxy_UI.cpp', + input : 'src/NSM_Proxy_UI.fl', + command : [fluid, '-c', '-o', '@OUTPUT@', '@INPUT@'], +) + +NSM_Proxy_UI_h = custom_target( + 'NSM_Proxy_UI.h', + output : 'NSM_Proxy_UI.h', + input : 'src/NSM_Proxy_UI.fl', + command : [fluid, '-c', '-h', '@OUTPUT@', '@INPUT@'], +) +executable('nsm-proxy-gui', + sources: ['src/nsm-proxy-gui.cpp', [NSM_Proxy_UI_cpp, NSM_Proxy_UI_h]], + dependencies: [fltkdep, liblodep, threaddep], + install: true, + ) +executable('nsmd', + sources: ['src/nsmd.cpp', 'src/debug.cpp', 'src/Endpoint.cpp', 'src/file.cpp', 'src/Thread.cpp'], + dependencies: [liblodep, threaddep], + install: true, + ) diff --git a/src/FL/Fl_Packscroller.H b/src/FL/Fl_Packscroller.H new file mode 100644 index 0000000..0661b48 --- /dev/null +++ b/src/FL/Fl_Packscroller.H @@ -0,0 +1,212 @@ + +/*******************************************************************************/ +/* Copyright (C) 2010 Jonathan Moore Liles */ +/* */ +/* This program is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by the */ +/* Free Software Foundation; either version 2 of the License, or (at your */ +/* option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but WITHOUT */ +/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ +/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */ +/* more details. */ +/* */ +/* You should have received a copy of the GNU General Public License along */ +/* with This program; see the file COPYING. If not,write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/*******************************************************************************/ + +/* Scrolling group suitable for containing a single child (a + * pack). When the Fl_Packscroller is resized, the child will be resized + * too. No scrollbars are displayed, but the widget responds to + * FL_MOUSEWHEEL events. */ + +#pragma once + +#include +#include +#include + +/* FIXME: Optimize scroll */ + +class Fl_Packscroller : public Fl_Group +{ + int _increment; + int _yposition; + static const int sbh = 15; /* scroll button height */ + +public: + + Fl_Packscroller ( int X, int Y, int W, int H, const char *L = 0 ) : Fl_Group( X, Y, W, H, L ) + { + _increment = 30; + _yposition = 0; +// color( FL_WHITE ); + } + + int increment ( void ) const { return _increment; } + void increment ( int v ) { _increment = v; } + + void yposition ( int v ) + { + if ( ! children() ) + return; + + int Y = v; + + if ( Y > 0 ) + Y = 0; + + const int H = h(); +// - (sbh * 2); + + Fl_Widget *o = child( 0 ); + + if ( o->h() > H && + Y + o->h() < H ) + Y = H - o->h(); + else if ( o->h() < H ) + Y = 0; + + if ( _yposition != Y ) + { + _yposition = Y; + + damage( FL_DAMAGE_SCROLL ); + } + } + + int yposition ( void ) const + { + if ( children() ) + return child( 0 )->y() - (y() + sbh); + + return 0; + } + + void bbox ( int &X, int &Y, int &W, int &H ) + { + X = x(); + Y = y() + ( sbh * top_sb_visible() ); + W = w(); + H = h() - ( sbh * ( top_sb_visible() + bottom_sb_visible() ) ); + } + + int top_sb_visible ( void ) + { + return children() && child(0)->y() != y() ? 1 : 0; + } + + int bottom_sb_visible ( void ) + { + if ( children() ) + { + Fl_Widget *o = child( 0 ); + + if ( o->h() > h() && o->y() + o->h() != y() + h() ) + return 1; + } + + return 0; + } + + virtual void + draw ( void ) + { + if ( damage() & FL_DAMAGE_ALL ) + { + fl_rectf( x(), y(), w(), h(), color() ); + } + + if ( ! children() ) + return; + + Fl_Widget *o = child( 0 ); + + o->position( x(), y() + _yposition ); + + const int top_sb = top_sb_visible(); + const int bottom_sb = bottom_sb_visible(); + + fl_push_clip( x(), y() + ( sbh * top_sb ), w(), h() - (sbh * (top_sb + bottom_sb) )); + + draw_children(); + + fl_pop_clip(); + + fl_font( FL_HELVETICA, 12 ); + + if ( top_sb ) + { + fl_draw_box( box(), x(), y(), w(), sbh, color() ); + fl_color( fl_contrast( FL_FOREGROUND_COLOR, color() ) ); + fl_draw( "@2<", x(), y(), w(), sbh, FL_ALIGN_CENTER ); + } + + if ( bottom_sb ) + { + fl_draw_box( box(), x(), y() + h() - sbh, w(), sbh, color() ); + fl_color( fl_contrast( FL_FOREGROUND_COLOR, color() ) ); + fl_draw( "@2>", x(), y() + h() - sbh, w(), sbh, FL_ALIGN_CENTER ); + } + } + + virtual int + handle ( int m ) + { + switch ( m ) + { + case FL_PUSH: + if ( top_sb_visible() && + Fl::event_inside( x(), y(), w(), sbh ) ) + { + return 1; + } + else if ( bottom_sb_visible() && + Fl::event_inside( x(), y() + h() - sbh, w(), sbh ) ) + { + return 1; + } + break; + case FL_RELEASE: + { + if ( top_sb_visible() && + Fl::event_inside( x(), y(), w(), sbh ) ) + { + yposition( yposition() + ( h() / 4 ) ); + return 1; + } + else if ( bottom_sb_visible() && + Fl::event_inside( x(), y() + h() - sbh, w(), sbh ) ) + { + yposition( yposition() - (h() / 4 ) ); + return 1; + } + break; + } + case FL_KEYBOARD: + { + if ( Fl::event_key() == FL_Up ) + { + yposition( yposition() + ( h() / 4 ) ); + return 1; + } + else if ( Fl::event_key() == FL_Down ) + { + yposition( yposition() - (h() / 4 ) ); + return 1; + } + break; + } + case FL_MOUSEWHEEL: + { + yposition( yposition() - ( Fl::event_dy() * _increment ) ); + + return 1; + } + } + + return Fl_Group::handle( m ); + } +}; diff --git a/src/FL/Fl_Scalepack.C b/src/FL/Fl_Scalepack.C new file mode 100644 index 0000000..c253838 --- /dev/null +++ b/src/FL/Fl_Scalepack.C @@ -0,0 +1,250 @@ + +/*******************************************************************************/ +/* Copyright (C) 2008 Jonathan Moore Liles */ +/* */ +/* This program is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by the */ +/* Free Software Foundation; either version 2 of the License, or (at your */ +/* option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but WITHOUT */ +/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ +/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */ +/* more details. */ +/* */ +/* You should have received a copy of the GNU General Public License along */ +/* with This program; see the file COPYING. If not,write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/*******************************************************************************/ + + +/* Fl_Scalepack + + This is similar to an Fl_Pack, but instead of the pack resizing + itself to enclose its children, this pack resizes its children to + fit itself. Of course, this only works well with highly flexible + widgets, but the task comes up often enough to warrent this class. + + If and child happens to be the resizable() widget, then it will be + resized so the all the other children can fit around it, with their + current sizes (and the size of the Fl_Scalepack) maintained. + + NOTES: An Fl_Pack as a direct child will not work, because Fl_Pack + changes its size in draw(), which throws off our resize + calculation. The whole idea of widgets being able to resize + themselves within draw() is horribly broken... +*/ + +#include "Fl_Scalepack.H" + +#include +#include +#include + +Fl_Scalepack::Fl_Scalepack ( int X, int Y, int W, int H, const char *L ) : + Fl_Group( X, Y, W, H, L ) +{ + resizable( 0 ); + _spacing = 0; +} + +void +Fl_Scalepack::resize ( int X, int Y, int W, int H ) +{ + /* Fl_Group's resize will change our child widget sizes, which + interferes with our own resizing method. */ + long dx = X - x(); + long dy = Y - y(); + + bool r = W != w() || H != h(); + + Fl_Widget::resize( X, Y, W, H ); + + Fl_Widget*const* a = array(); + + for (int i=children(); i--;) + { + Fl_Widget* o = *a++; + + o->position( o->x() + dx, o->y() + dy ); + } + + if ( r ) + redraw(); +} + +void +Fl_Scalepack::draw ( void ) +{ + + if ( resizable() == this ) + /* this resizable( this ) is the default for Fl_Group and is + * reset by Fl_Group::clear(), but it is not our default... */ + resizable( 0 ); + + int tx = x() + Fl::box_dx( box() ); + int ty = y() + Fl::box_dy( box() ); + int tw = w() - Fl::box_dw( box() ); + int th = h() - Fl::box_dh( box() ); + + if ( damage() & FL_DAMAGE_ALL ) + { + draw_box(); + + draw_label(); + } + + int v = 0; + + int cth = 0; + int ctw = 0; + + Fl_Widget * const * a = array(); + + for ( int i = children(); i--; ) + { + Fl_Widget *o = *a++; + + if ( o->visible() ) + { + ++v; + + if ( o != this->resizable() ) + { + cth += o->h(); + ctw += o->w(); + } + + cth += _spacing; + ctw += _spacing; + } + } + + ctw -= _spacing; + cth -= _spacing; + + if ( 0 == v ) + return; + + if ( this->resizable() ) + { + int pos = 0; + + Fl_Widget * const * a = array(); + + for ( int i = children(); i--; ) + { + Fl_Widget *o = *a++; + + if ( o->visible() ) + { + int X, Y, W, H; + + if ( type() == HORIZONTAL ) + { + X = tx + pos; + Y = ty; + W = o->w(); + H = th; + } + else + { + X = tx; + Y = ty + pos; + W = tw; + H = o->h(); + } + + if ( this->resizable() == o ) + { + if ( type() == HORIZONTAL ) + W = tw - ctw - 3; + else + H = th - cth; + } + + if (X != o->x() || Y != o->y() || W != o->w() || H != o->h() ) + { + o->resize(X,Y,W,H); + o->clear_damage(FL_DAMAGE_ALL); + } + + if ( damage() & FL_DAMAGE_ALL ) + { + draw_child( *o ); + draw_outside_label( *o ); + } + else + update_child( *o ); + +/* if ( this->resizable() == o ) */ +/* fl_rect( o->x(), o->y(), o->w(), o->h(), type() == VERTICAL ? FL_GREEN : FL_BLUE ); */ + + if ( type() == HORIZONTAL ) + pos += o->w() + spacing(); + else + pos += o->h() + spacing(); + + } + } + } + else + { + int sz = 0; + int pos = 0; + + if ( type() == HORIZONTAL ) + sz = (tw - (_spacing * (v - 1))) / v; + else + sz = (th - (_spacing * (v - 1))) / v; + + Fl_Widget * const * a = array(); + + for ( int i = children(); i--; ) + { + Fl_Widget *o = *a++; + + if ( o->visible() ) + { + int X, Y, W, H; + + if ( type() == HORIZONTAL ) + { + X = tx + pos; + Y = ty; + W = sz; + H = th; + } + else + { + X = tx; + Y = ty + pos; + W = tw; + H = sz; + } + + if (X != o->x() || Y != o->y() || W != o->w() || H != o->h() ) + { + o->resize(X,Y,W,H); + o->clear_damage(FL_DAMAGE_ALL); + } + + if ( damage() & FL_DAMAGE_ALL ) + { + draw_child( *o ); + draw_outside_label( *o ); + } + else + update_child( *o ); + +// fl_rect( o->x(), o->y(), o->w(), o->h(), type() == VERTICAL ? FL_RED : FL_YELLOW ); + + if ( type() == HORIZONTAL ) + pos += o->w() + spacing(); + else + pos += o->h() + spacing(); + + } + } + } +} diff --git a/src/FL/Fl_Scalepack.H b/src/FL/Fl_Scalepack.H new file mode 100644 index 0000000..9974bba --- /dev/null +++ b/src/FL/Fl_Scalepack.H @@ -0,0 +1,43 @@ + +/*******************************************************************************/ +/* Copyright (C) 2008 Jonathan Moore Liles */ +/* */ +/* This program is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by the */ +/* Free Software Foundation; either version 2 of the License, or (at your */ +/* option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but WITHOUT */ +/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ +/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */ +/* more details. */ +/* */ +/* You should have received a copy of the GNU General Public License along */ +/* with This program; see the file COPYING. If not,write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/*******************************************************************************/ + +#pragma once + +#include + +class Fl_Scalepack : public Fl_Group +{ + + int _spacing; + +public: + + enum { VERTICAL, HORIZONTAL }; + + Fl_Scalepack ( int X, int Y, int W, int H, const char *L = 0 ); + virtual ~Fl_Scalepack ( ) { } + + int spacing ( void ) const { return _spacing; } + void spacing ( int v ) { _spacing = v; redraw(); } + + virtual void resize ( int, int, int, int ); + + virtual void draw ( void ); + +}; diff --git a/src/NSM_Proxy_UI.fl b/src/NSM_Proxy_UI.fl new file mode 100644 index 0000000..ffa1be3 --- /dev/null +++ b/src/NSM_Proxy_UI.fl @@ -0,0 +1,100 @@ +# data file for the Fltk User Interface Designer (fluid) +version 1.0300 +header_name {.H} +code_name {.C} +class NSM_Proxy_UI {open +} { + Function {make_window()} {open + } { + Fl_Window {} { + label {NSM Proxy} open selected + xywh {644 190 635 665} type Double color 47 labelcolor 55 xclass {NSM-Proxy} visible + } { + Fl_Box {} { + label {Command-line options are incompatible with robust session management for a variety of reasons, so the NSM server does not support them directly. This proxy exists to allow programs which require command-line options to be included in an NSM session. Be warned that referring to files outside of the session directory will impair your ability to reliably archive and transport sessions. Patching the program to use NSM natively will result in a better experience. + +The program will be started with its current directory being a uniquely named directory under the current session directory. It is recommended that you only refer to files in the current directory. +} + xywh {15 11 610 139} box BORDER_BOX color 41 labelfont 8 labelsize 12 labelcolor 55 align 128 + } + Fl_File_Input executable_input { + label {Executable: } + xywh {115 162 495 31} + } + Fl_Input arguments_input { + label {Arguments:} + xywh {110 310 350 28} + } + Fl_Input label_input { + label {Label:} + xywh {110 340 350 28} + } + Fl_Return_Button start_button { + label Start + xywh {535 630 88 25} + } + Fl_Button kill_button { + label Kill + xywh {295 625 80 25} color 72 hide + } + Fl_Choice save_signal_choice { + label {Save Signal:} open + xywh {110 468 170 25} down_box BORDER_BOX + } { + MenuItem {} { + label None + xywh {0 0 40 24} + } + MenuItem {} { + label SIGUSR1 + xywh {10 10 40 24} + } + MenuItem {} { + label SIGUSR2 + xywh {20 20 40 24} + } + MenuItem {} { + label SIGINT + xywh {30 30 40 24} + } + } + Fl_Box {} { + label {The environment variables $NSM_CLIENT_ID and $NSM_SESSION_NAME will contain the unique client ID (suitable for use as e.g. a JACK client name) and the display name for the session, respectively. The variable $CONFIG_FILE will contain the name of the config file selected above.} + xywh {15 235 610 69} box BORDER_BOX color 41 labelfont 8 labelsize 12 labelcolor 55 align 128 + } + Fl_Box {} { + label {Some (very few) programs may respond to a specific Unix signal by somehow saving their state. If 'Save Signal' is set to something other than 'None', then NSM Proxy will deliver the specified signal to the proxied process upon an NSM 'Save' event. Most programs will treat these signals just like SIGTERM and die. You have been warned.} + xywh {15 378 610 79} box BORDER_BOX color 41 labelfont 8 labelsize 12 labelcolor 55 align 128 + } + Fl_Choice stop_signal_choice { + label {Stop Signal:} open + xywh {108 592 170 25} down_box BORDER_BOX + } { + MenuItem {} { + label SIGTERM + xywh {10 10 40 24} + } + MenuItem {} { + label SIGINT + xywh {40 40 40 24} + } + MenuItem {} { + label SIGHUP + xywh {50 50 40 24} + } + } + Fl_Box {} { + label {Most programs will shutdown gracefully when sent a SIGTERM or SIGINT signal. It's impossible to know which signal a specific program will respond to. A unhandled signal will simply kill the process, and may cause problems with the audio subsystem (e.g. JACK). Check the program's documentation or source code to determine which signal to use to stop it gracefully.} + xywh {15 502 610 79} box BORDER_BOX color 41 labelfont 8 labelsize 12 labelcolor 55 align 128 + } + Fl_File_Input config_file_input { + label {Config File:} + xywh {114 195 406 31} + } + Fl_Button config_file_browse_button { + label Browse + xywh {530 195 85 25} + } + } + } +} diff --git a/src/nsm-proxy-gui.cpp b/src/nsm-proxy-gui.cpp new file mode 100644 index 0000000..ef02972 --- /dev/null +++ b/src/nsm-proxy-gui.cpp @@ -0,0 +1,291 @@ + +/*******************************************************************************/ +/* Copyright (C) 2012 Jonathan Moore Liles */ +/* */ +/* This program is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by the */ +/* Free Software Foundation; either version 2 of the License, or (at your */ +/* option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but WITHOUT */ +/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ +/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */ +/* more details. */ +/* */ +/* You should have received a copy of the GNU General Public License along */ +/* with This program; see the file COPYING. If not,write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/*******************************************************************************/ + +#pragma GCC diagnostic ignored "-Wunused-parameter" + + +#define _MODULE_ "nsm-proxy-gui" + +#define APP_NAME "NSM Proxy" +#define APP_TITLE "NSM Proxy" + +#include +#include +#include "NSM_Proxy_UI.H" +#include +#include +#include +#include +#include + +lo_server losrv; +lo_address nsmp_addr; + +static NSM_Proxy_UI *ui; + +static char *client_error; + +int +osc_update ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + printf( "Got update for %s\n", path ); + + Fl::lock(); + + if (!strcmp( path, "/nsm/proxy/label" )) + ui->label_input->value( &argv[0]->s ); + else if (!strcmp( path, "/nsm/proxy/arguments" )) + ui->arguments_input->value( &argv[0]->s ); + else if (!strcmp( path, "/nsm/proxy/executable" )) + ui->executable_input->value( &argv[0]->s ); + else if (!strcmp( path, "/nsm/proxy/config_file" )) + ui->config_file_input->value( &argv[0]->s ); + else if (!strcmp( path, "/nsm/proxy/save_signal" )) + { + if ( argv[0]->i == SIGUSR1 ) + ui->save_signal_choice->value( 1 ); + else if ( argv[0]->i == SIGUSR2 ) + ui->save_signal_choice->value( 2 ); + else if ( argv[0]->i == SIGINT ) + ui->save_signal_choice->value( 3 ); + else + ui->save_signal_choice->value( 0 ); + } + else if (!strcmp( path, "/nsm/proxy/stop_signal" )) + { + if ( argv[0]->i == SIGTERM ) + ui->stop_signal_choice->value( 0 ); + else if ( argv[0]->i == SIGINT ) + ui->stop_signal_choice->value( 1 ); + else if ( argv[0]->i == SIGHUP ) + ui->stop_signal_choice->value( 2 ); + } + if (!strcmp( path, "/nsm/proxy/client_error" )) + { + if ( client_error != NULL ) + free(client_error); + + client_error = NULL; + + if ( strlen(&argv[0]->s) > 0 ) + client_error = strdup(&argv[0]->s); + } + + Fl::unlock(); + + return 0; +} + + +void +init_osc ( const char *osc_port ) +{ + + lo_server_thread loth = lo_server_thread_new( osc_port, NULL ); + losrv = lo_server_thread_get_server( loth ); + +//error_handler ); + + char *url = lo_server_get_url(losrv); + printf("OSC: %s\n",url); + free(url); + + /* GUI */ + + lo_server_thread_add_method( loth, "/nsm/proxy/executable", "s", osc_update, NULL ); + lo_server_thread_add_method( loth, "/nsm/proxy/arguments", "s", osc_update, NULL ); + lo_server_thread_add_method( loth, "/nsm/proxy/config_file", "s", osc_update, NULL ); + lo_server_thread_add_method( loth, "/nsm/proxy/label", "s", osc_update, NULL ); + lo_server_thread_add_method( loth, "/nsm/proxy/save_signal", "i", osc_update, NULL ); + lo_server_thread_add_method( loth, "/nsm/proxy/stop_signal", "i", osc_update, NULL ); + lo_server_thread_add_method( loth, "/nsm/proxy/client_error", "s", osc_update, NULL ); + + lo_server_thread_start( loth ); +} + +/*****************/ +/* GUI Callbacks */ +/*****************/ + +void +handle_kill ( Fl_Widget *o, void *v ) +{ + lo_send_from( nsmp_addr, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/kill", "" ); +} + +void +handle_start ( Fl_Widget *o, void *v ) +{ + lo_send_from( nsmp_addr, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/start", "sss", + ui->executable_input->value(), + ui->arguments_input->value(), + ui->config_file_input->value() ); +} + +void +handle_label ( Fl_Widget *o, void *v ) +{ + lo_send_from( nsmp_addr, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/label", "s", + ui->label_input->value() ); +} + +void +handle_executable ( Fl_Widget *o, void *v ) +{ + ui->label_input->value( ui->executable_input->value() ); +} + + +void +handle_config_file ( Fl_Widget *o, void *v ) +{ +} + +void +handle_config_file_browse ( Fl_Widget *o, void *v ) +{ + const char * file = fl_file_chooser( "Pick file", "*", NULL, 1 ); + + ui->config_file_input->value( file ); +} + +void +handle_save_signal ( Fl_Widget *o, void *v ) +{ + int sig = 0; + + const char* picked = ui->save_signal_choice->mvalue()->label(); + + if ( !strcmp( picked, "SIGUSR1" ) ) + sig = SIGUSR1; + else if ( !strcmp( picked, "SIGUSR2" ) ) + sig = SIGUSR2; + else if ( !strcmp( picked, "SIGINT" ) ) + sig = SIGINT; + + lo_send_from( nsmp_addr, losrv, LO_TT_IMMEDIATE,"/nsm/proxy/save_signal", "i", + sig ); +} + +void +handle_stop_signal ( Fl_Widget *o, void *v ) +{ + int sig = SIGTERM; + + const char* picked = ui->stop_signal_choice->mvalue()->label(); + + if ( !strcmp( picked, "SIGTERM" ) ) + sig = SIGTERM; + else if ( !strcmp( picked, "SIGINT" ) ) + sig = SIGINT; + else if ( !strcmp( picked, "SIGHUP" ) ) + sig = SIGHUP; + + lo_send_from( nsmp_addr, losrv, LO_TT_IMMEDIATE,"/nsm/proxy/stop_signal", "i", + sig ); +} + +void +connect_ui ( void ) +{ + ui->executable_input->callback( handle_executable, NULL ); + ui->config_file_input->callback( handle_config_file, NULL ); + ui->kill_button->callback( handle_kill, NULL ); + ui->start_button->callback( handle_start, NULL ); + ui->save_signal_choice->callback( handle_save_signal, NULL ); + ui->stop_signal_choice->callback( handle_stop_signal, NULL ); + ui->label_input->callback( handle_label, NULL ); + ui->config_file_browse_button->callback( handle_config_file_browse, NULL ); +} + + + +void cb_dismiss_button ( Fl_Widget *w, void *v ) +{ + w->window()->hide(); +} + +void +check_error ( void *v ) +{ + if ( client_error ) + { + { + Fl_Double_Window *o = new Fl_Double_Window(600,300+15,"Abnormal Termination"); + { + Fl_Box *o = new Fl_Box(0+15,0+15,600-30,50); + o->box(FL_BORDER_BOX); + o->color(FL_RED); + o->labelcolor(FL_WHITE); + o->align(FL_ALIGN_CENTER|FL_ALIGN_WRAP); + o->copy_label( client_error ); + } + { + Fl_Text_Display *o = new Fl_Text_Display(0+15,50+15,600-30,300-75-30); + o->buffer(new Fl_Text_Buffer()); + o->buffer()->loadfile( "error.log" ); + } + { + Fl_Button *o = new Fl_Button(600-75-15,300-25,75,25,"Dismiss"); + o->callback(cb_dismiss_button,0); + } + + o->show(); + } + + free(client_error); + client_error = NULL; + } + + Fl::repeat_timeout( 0.5f, check_error, v ); +} + +int +main ( int argc, char **argv ) +{ + if ( argc != 3 ) + { + fprintf( stderr, "Usage: %s --connect-to url\n", argv[0] ); + return 1; + } + + init_osc( NULL ); + + nsmp_addr = lo_address_new_from_url( argv[2] ); + + printf( "Connecting to nsm-proxy at: %s\n", argv[2] ); + + ui = new NSM_Proxy_UI; + + Fl_Double_Window *w = ui->make_window(); + + connect_ui(); + + lo_send_from( nsmp_addr, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/update", "" ); + + w->show(); + + Fl::lock(); + + Fl::add_timeout( 0.5f, check_error, NULL ); + + Fl::run(); + + return 0; +} diff --git a/src/nsm-proxy.cpp b/src/nsm-proxy.cpp new file mode 100644 index 0000000..981c798 --- /dev/null +++ b/src/nsm-proxy.cpp @@ -0,0 +1,760 @@ + +/*******************************************************************************/ +/* Copyright (C) 2012 Jonathan Moore Liles */ +/* */ +/* This program is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by the */ +/* Free Software Foundation; either version 2 of the License, or (at your */ +/* option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but WITHOUT */ +/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ +/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */ +/* more details. */ +/* */ +/* You should have received a copy of the GNU General Public License along */ +/* with This program; see the file COPYING. If not,write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/*******************************************************************************/ + +#pragma GCC diagnostic ignored "-Wunused-parameter" + +#define _MODULE_ "nsm-proxy" +#define APP_NAME "NSM Proxy" +#define APP_TITLE "NSM Proxy" + +#include "debug.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static lo_server losrv; +static lo_address nsm_addr; +static lo_address gui_addr; +static int nsm_is_active; +static char *project_file; +static int die_now = 0; +static int signal_fd; + +static char *nsm_client_id; +static char *nsm_display_name; + +#define CONFIG_FILE_NAME "nsm-proxy.config" + +void show_gui ( void ); + +class NSM_Proxy { + + char *_label; + char *_executable; + char *_config_file; + char *_arguments; + int _save_signal; + int _stop_signal; + int _pid; + char *_client_error; + +public: + + int stop_signal ( void ) {return _stop_signal;} + + NSM_Proxy ( ) + { + _label = _executable = _arguments = _config_file = 0; + _save_signal = 0; + _stop_signal = SIGTERM; + _pid = 0; + _client_error = 0; + } + + ~NSM_Proxy ( ) + { + } + + void handle_client_death ( int status ) + { + printf( "proxied process died unexpectedly... not dying\n" ); + /* proxied process died unexpectedly */ + + if ( _client_error != NULL ) + free(_client_error); + + asprintf(&_client_error, "The proxied process terminated abnormally during invocation. Exit status: %i.", status ); + + show_gui(); + + _pid = 0; + } + + void kill ( void ) + { + if ( _pid ) + { + ::kill( _pid, _stop_signal ); + } + } + + bool start ( const char *executable, const char *arguments, const char *config_file ) + { + if ( _executable ) + free( _executable ); + if ( _arguments ) + free( _arguments ); + if ( _config_file ) + free( _config_file ); + + _executable = strdup( executable ); + + if ( arguments ) + _arguments = strdup( arguments ); + else + _arguments = NULL; + + if ( config_file ) + _config_file = strdup( config_file ); + else + _config_file = NULL; + + return start(); + } + + bool start ( void ) + { + dump( project_file ); + + if ( _pid ) + /* already running */ + return true; + + if ( !_executable ) + { + WARNING( "Executable is null." ); + return false; + } + + int pid; + if ( ! (pid = fork()) ) + { + MESSAGE( "Launching %s\n", _executable ); + +// char *args[] = { strdup( executable ), NULL }; + + char *cmd; + + if ( _arguments ) + asprintf( &cmd, "exec %s %s >error.log 2>&1", _executable, _arguments ); + else + asprintf( &cmd, "exec %s >error.log 2>&1", _executable ); + + char *args[] = { strdup("/bin/sh"), strdup( "-c" ), cmd, NULL }; + + setenv( "NSM_CLIENT_ID", nsm_client_id, 1 ); + setenv( "NSM_SESSION_NAME", nsm_display_name, 1 ); + if ( _config_file ) + setenv( "CONFIG_FILE", _config_file, 1 ); + unsetenv( "NSM_URL" ); + + if ( -1 == execvp( "/bin/sh", args ) ) + { + WARNING( "Error starting process: %s", strerror( errno ) ); + exit(1); + } + } + + _pid = pid; + + return _pid > 0; + } + + void save_signal ( int s ) + { + _save_signal = s; + } + + void stop_signal ( int s ) + { + _stop_signal = s; + } + + void label ( const char *s ) + { + if ( _label ) + free( _label ); + + _label = strdup( s ); + + lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/label", "s", _label ); + } + + void save ( void ) + { + DMESSAGE( "Sending process save signal" ); + if ( _pid ) + ::kill( _pid, _save_signal ); + } + + + bool dump ( const char *path ) + { + char *fname; + asprintf( &fname, "%s/%s", path, CONFIG_FILE_NAME ); + + FILE *fp = fopen( fname, "w" ); + + free( fname ); + + if ( !fp ) + { + WARNING( "Error opening file for saving: %s", strerror( errno ) ); + return false; + } + + if ( _executable && strlen(_executable) ) + fprintf( fp, "executable\n\t%s\n", _executable ); + + if ( _arguments && strlen(_arguments) ) + fprintf( fp, "arguments\n\t%s\n", _arguments ); + + if ( _config_file && strlen(_config_file) ) + fprintf( fp, "config file\n\t%s\n", _config_file ); + + fprintf( fp, "save signal\n\t%i\n", _save_signal ); + + fprintf( fp, "stop signal\n\t%i\n", _stop_signal ); + + if ( _label && strlen(_label) ) + fprintf( fp, "label\n\t%s\n", _label ); + + fclose( fp ); + + return true; + } + + bool restore ( const char *path ) + { + FILE *fp = fopen( path, "r" ); + if ( ! fp ) + { + WARNING( "Error opening file for restore: %s", strerror( errno ) ); + return false; + } + + char *name; + char *value; + + MESSAGE( "Loading file config \"%s\"", path ); + + while ( 2 == fscanf( fp, "%m[^\n]\n\t%m[^\n]\n", &name, &value ) ) + { + + DMESSAGE( "%s=%s", name, value ); + + if ( !strcmp( name, "executable" ) ) + _executable = value; + else if (!strcmp( name, "arguments" ) ) + _arguments = value; + else if (!strcmp( name, "config file" ) ) + _config_file = value; + else if ( !strcmp( name, "save signal" ) ) + { + _save_signal = atoi( value ); + free( value ); + } + else if ( !strcmp( name, "stop signal" ) ) + { + _stop_signal = atoi( value ); + free( value ); + } + else if ( !strcmp( name, "label" ) ) + { + label( value ); + free( value ); + } + else + { + WARNING( "Unknown option \"%s\" in config file", name ); + } + + free( name ); + } + + fclose( fp ); + + start(); + + return true; + } + + void update ( lo_address to ) + { + DMESSAGE( "Sending update" ); + + lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/save_signal", "i", _save_signal ); + lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/label", "s", _label ? _label : "" ); + lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/executable", "s", _executable ? _executable : "" ); + lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/arguments", "s", _arguments ? _arguments : "" ); + lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/config_file", "s", _config_file ? _config_file : "" ); + lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/stop_signal", "i", _stop_signal ); + lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/client_error", "s", _client_error ? _client_error : "" ); + + + } +}; + +NSM_Proxy *nsm_proxy; + +bool +snapshot ( const char *file ) +{ + return nsm_proxy->dump(file); +} +void +announce ( const char *nsm_url, const char *client_name, const char *process_name ) +{ + printf( "Announcing to NSM\n" ); + + lo_address to = lo_address_new_from_url( nsm_url ); + + int pid = (int)getpid(); + + lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/server/announce", "sssiii", + client_name, + ":optional-gui:", + process_name, + 1, /* api_major_version */ + 0, /* api_minor_version */ + pid ); + + lo_address_free( to ); +} + +bool +open ( const char *file ) +{ + char *path; + asprintf( &path, "%s/%s", file, CONFIG_FILE_NAME ); + + bool r = nsm_proxy->restore( path ); + + free( path ); + + return r; +} + +/****************/ +/* OSC HANDLERS */ +/****************/ + +/* NSM */ + +int +osc_announce_error ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + if ( strcmp( types, "sis" ) ) + return -1; + + if ( strcmp( "/nsm/server/announce", &argv[0]->s ) ) + return -1; + + printf( "Failed to register with NSM: %s\n", &argv[2]->s ); + nsm_is_active = 0; + + return 0; +} + + +int +osc_announce_reply ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + if ( strcmp( "/nsm/server/announce", &argv[0]->s ) ) + return -1; + + printf( "Successfully registered. NSM says: %s", &argv[1]->s ); + + nsm_is_active = 1; + nsm_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ) ); + + return 0; +} + +int +osc_save ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + bool r = snapshot( project_file ); + + nsm_proxy->save(); + + if ( r ) + lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" ); + else + lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/error", "sis", path, -1, "Error saving project file" ); + + return 0; +} + +static int gui_pid; + +void +show_gui ( void ) +{ + + int pid; + if ( ! (pid = fork()) ) + { + char executable[] = "nsm-proxy-gui"; + + MESSAGE( "Launching %s\n", executable ); + + char *url = lo_server_get_url( losrv ); + + char *args[] = { executable, strdup( "--connect-to" ), url, NULL }; + + if ( -1 == execvp( executable, args ) ) + { + WARNING( "Error starting process: %s", strerror( errno ) ); + + exit(1); + } + } + + gui_pid = pid; + + lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_shown", "" ); +} + +int +osc_show_gui ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + show_gui(); + + /* FIXME: detect errors */ + + lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" ); + + return 0; +} + +void +hide_gui ( void ) +{ + if ( gui_pid ) + { + kill( gui_pid, SIGTERM ); + } +} + +int +osc_hide_gui ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + hide_gui(); + + lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" ); + + /* FIXME: detect errors */ + + lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" ); + + return 0; +} + +int +osc_open ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + const char *new_path = &argv[0]->s; + const char *display_name = &argv[1]->s; + const char *client_id = &argv[2]->s; + + if ( nsm_client_id ) + free(nsm_client_id); + + nsm_client_id = strdup( client_id ); + + if ( nsm_display_name ) + free( nsm_display_name ); + + nsm_display_name = strdup( display_name ); + + char *new_filename; + + mkdir( new_path, 0777 ); + + chdir( new_path ); + + asprintf( &new_filename, "%s/%s", new_path, CONFIG_FILE_NAME ); + + struct stat st; + + if ( 0 == stat( new_filename, &st ) ) + { + if ( open( new_path ) ) + { + } + else + { + lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/error", "sis", path, -1, "Could not open file" ); + return 0; + } + + lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" ); + } + else + { + show_gui(); + } + + if ( project_file ) + free( project_file ); + + project_file = strdup( new_path ); + +// new_filename; + + lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" ); + + if ( gui_addr ) + nsm_proxy->update( gui_addr ); + + return 0; +} + + + +/* GUI */ + +int +osc_label ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + nsm_proxy->label( &argv[0]->s ); + + return 0; +} + +int +osc_save_signal ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + nsm_proxy->save_signal( argv[0]->i ); + + return 0; +} + +int +osc_stop_signal ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + nsm_proxy->stop_signal( argv[0]->i ); + + return 0; +} + +int +osc_start ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + snapshot( project_file ); + + if ( nsm_proxy->start( &argv[0]->s, &argv[1]->s, &argv[2]->s ) ) + { + hide_gui(); + } + + return 0; +} + +int +osc_kill ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + nsm_proxy->kill(); + + return 0; +} + +int +osc_update ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) +{ + lo_address to = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) )); + + nsm_proxy->update( to ); + + gui_addr = to; + + return 0; +} + + + +void +signal_handler ( int x ) +{ + die_now = 1; +} + +void +set_traps ( void ) +{ + signal( SIGHUP, signal_handler ); + signal( SIGINT, signal_handler ); +// signal( SIGQUIT, signal_handler ); +// signal( SIGSEGV, signal_handler ); +// signal( SIGPIPE, signal_handler ); + signal( SIGTERM, signal_handler ); +} + + +void +init_osc ( const char *osc_port ) +{ + losrv = lo_server_new( osc_port, NULL ); +//error_handler ); + + char *url = lo_server_get_url(losrv); + printf("OSC: %s\n",url); + free(url); + + /* NSM */ + lo_server_add_method( losrv, "/nsm/client/save", "", osc_save, NULL ); + lo_server_add_method( losrv, "/nsm/client/open", "sss", osc_open, NULL ); + lo_server_add_method( losrv, "/nsm/client/show_optional_gui", "", osc_show_gui, NULL ); + lo_server_add_method( losrv, "/nsm/client/hide_optional_gui", "", osc_hide_gui, NULL ); + lo_server_add_method( losrv, "/error", "sis", osc_announce_error, NULL ); + lo_server_add_method( losrv, "/reply", "ssss", osc_announce_reply, NULL ); + + /* GUI */ + lo_server_add_method( losrv, "/nsm/proxy/label", "s", osc_label, NULL ); + lo_server_add_method( losrv, "/nsm/proxy/save_signal", "i", osc_save_signal, NULL ); + lo_server_add_method( losrv, "/nsm/proxy/stop_signal", "i", osc_stop_signal, NULL ); + lo_server_add_method( losrv, "/nsm/proxy/kill", "", osc_kill, NULL ); + lo_server_add_method( losrv, "/nsm/proxy/start", "sss", osc_start, NULL ); + lo_server_add_method( losrv, "/nsm/proxy/update", "", osc_update, NULL ); + +} + +void +die ( void ) +{ + if ( gui_pid ) + { + DMESSAGE( "Killing GUI" ); + + kill( gui_pid, SIGTERM ); + } + + nsm_proxy->kill(); + + exit(0); +} + + +void handle_sigchld ( ) +{ + for ( ;; ) + { + int status; + pid_t pid = waitpid(-1, &status, WNOHANG); + + if (pid <= 0) + break; + + if ( pid == gui_pid ) + { + lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" ); + + gui_pid = 0; + + /* don't care... */ + continue; + } + + if ( WIFSIGNALED(status) ) + { + /* process was killed via signal */ + if (WTERMSIG(status) == SIGTERM || + WTERMSIG(status) == SIGHUP || + WTERMSIG(status) == SIGINT || + WTERMSIG(status) == SIGKILL ) + { + /* process was killed via an appropriate signal */ + MESSAGE( "child was killed (maybe by us)\n" ); + die_now = 1; + continue; + } + } + else if ( WIFEXITED(status) ) + { + /* child called exit() or returned from main() */ + + MESSAGE( "child exit status: %i", WEXITSTATUS(status) ); + + if ( WEXITSTATUS(status) == 0 ) + { + /* apparently normal termination */ + MESSAGE( "child exited without error."); + die_now = 1; + continue; + } + else + { + MESSAGE("child exited abnormally."); + nsm_proxy->handle_client_death(WEXITSTATUS(status)); + } + } + } +} + +int +main ( int argc, char **argv ) +{ + set_traps(); + + sigset_t mask; + + sigemptyset( &mask ); + sigaddset( &mask, SIGCHLD ); + + sigprocmask(SIG_BLOCK, &mask, NULL ); + + signal_fd = signalfd( -1, &mask, SFD_NONBLOCK ); + + nsm_proxy = new NSM_Proxy(); + + init_osc( NULL ); + + const char *nsm_url = getenv( "NSM_URL" ); + + if ( nsm_url ) + { + announce( nsm_url, APP_TITLE, argv[0] ); + } + else + { + fprintf( stderr, "Could not register as NSM client.\n" ); + exit(1); + } + + + struct signalfd_siginfo fdsi; + + /* listen for sigchld signals and process OSC messages forever */ + for ( ;; ) + { + ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo)); + + if (s == sizeof(struct signalfd_siginfo)) + { + if (fdsi.ssi_signo == SIGCHLD) + handle_sigchld(); + } + + lo_server_recv_noblock( losrv, 500 ); + + if ( die_now ) + die(); + } +} diff --git a/src/session-manager.cpp b/src/session-manager.cpp new file mode 100644 index 0000000..bcf01d1 --- /dev/null +++ b/src/session-manager.cpp @@ -0,0 +1,1440 @@ + +/*******************************************************************************/ +/* Copyright (C) 2012 Jonathan Moore Liles */ +/* */ +/* This program is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by the */ +/* Free Software Foundation; either version 2 of the License, or (at your */ +/* option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but WITHOUT */ +/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ +/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */ +/* more details. */ +/* */ +/* You should have received a copy of the GNU General Public License along */ +/* with This program; see the file COPYING. If not,write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/*******************************************************************************/ + + + +#include "Endpoint.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "debug.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "FL/Fl_Packscroller.H" +#include "FL/Fl_Scalepack.H" + +#include +#include +#include +#include + +#define APP_NAME "Non-Session-Manager" +#define APP_TITLE "Non Session Manager" + +// static lo_address nsm_addr = NULL; +static time_t last_ping_response; + +static OSC::Endpoint *osc; + + +struct Daemon +{ + const char *url; + lo_address addr; + bool is_child; + + Daemon ( ) + { + url = NULL; + addr = NULL; + is_child = false; + } +}; + +static std::list daemon_list; /* list of all connected daemons */ + +#define foreach_daemon( _it ) for ( std::list::iterator _it = daemon_list.begin(); _it != daemon_list.end(); ++ _it ) + +static +Fl_Image * +get_program_icon ( const char *name ) +{ + const char *tries[] = + { + "/usr/local/share/icons/hicolor/32x32/apps/%s.png", + "/usr/local/share/pixmaps/%s.png", + "/usr/local/share/pixmaps/%s.xpm", + "/usr/share/icons/hicolor/32x32/apps/%s.png", + "/usr/share/pixmaps/%s.png", + "/usr/share/pixmaps/%s.xpm", + }; + + for ( unsigned int i = 0; i < 6; i++ ) + { + char *icon_p; + + asprintf( &icon_p, tries[i], name ); + + Fl_Image *img = Fl_Shared_Image::get( icon_p ); + + free( icon_p ); + + if ( img ) + return img; + } + + return NULL; +} + +class NSM_Client : public Fl_Group +{ + char *_client_id; + char *_client_label; + char *_client_name; + + Fl_Box *client_name; + Fl_Box *icon_box; + Fl_Progress *_progress; + Fl_Light_Button *_dirty; + Fl_Light_Button *_gui; + Fl_Button *_remove_button; + Fl_Button *_restart_button; + Fl_Button *_kill_button; + + void + set_label ( void ) + { + char *l; + + if ( _client_label ) + asprintf( &l, "%s (%s)", _client_name, _client_label ); + else + l = strdup( _client_name ); + + if ( ! icon_box->image() ) + { + Fl_Image *img = get_program_icon( _client_name ); + + if ( img ) + { + icon_box->image( img ); + } + } + + client_name->copy_label( l ); + + free(l); + + redraw(); + } + +public: + + void + name ( const char *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 + client_id ( const char *v ) + { + if ( _client_id ) + free( _client_id ); + + _client_id = strdup( v ); + } + + void + progress ( float f ) + { + _progress->value( f ); + _progress->redraw(); + } + + void + dirty ( bool b ) + { + _dirty->value( b ); + _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 ) + { + if ( b ) + { + _remove_button->show(); + _restart_button->show(); + _kill_button->hide(); + _gui->deactivate(); + _dirty->deactivate(); + color( fl_color_average( FL_BLACK, FL_RED, 0.50 ) ); + redraw(); + } + else + { + _gui->activate(); + _dirty->activate(); + _kill_button->show(); + _restart_button->hide(); + _remove_button->hide(); + } + + /* _restart_button->redraw(); */ + /* _remove_button->redraw(); */ + } + + void + pending_command ( const char *command ) + { + _progress->copy_label( command ); + + stopped( 0 ); + + if ( ! strcmp( command, "ready" ) ) + { + color( fl_color_average( FL_BLACK, FL_GREEN, 0.50 ) ); + _progress->value( 0.0f ); + } + else if ( ! strcmp( command, "quit" ) || + ! strcmp( command, "kill" ) || + ! strcmp( command, "error" ) ) + { + color( fl_color_average( FL_BLACK, FL_RED, 0.50 ) ); + } + else if ( ! strcmp( command, "stopped" ) ) + { + stopped( 1 ); + } + else + { + color( fl_color_average( FL_BLACK, FL_YELLOW, 0.50 ) ); + } + + redraw(); + } + + + static void + cb_button ( Fl_Widget *o, void * v ) + { + ((NSM_Client*)v)->cb_button( o ); + } + + void + cb_button ( Fl_Widget *o ) + { + if ( o == _dirty ) + { + MESSAGE( "Sending save."); + foreach_daemon ( d ) + { + osc->send( (*d)->addr, "/nsm/gui/client/save", _client_id ); + } + } + 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 ) + { + osc->send( (*d)->addr, "/nsm/gui/client/remove", _client_id ); + } + } + else if ( o == _restart_button ) + { + MESSAGE( "Sending resume" ); + foreach_daemon ( d ) + { + osc->send( (*d)->addr, "/nsm/gui/client/resume", _client_id ); + } + } + else if ( o == _kill_button ) + { + MESSAGE( "Sending stop" ); + foreach_daemon ( d ) + { + osc->send( (*d)->addr, "/nsm/gui/client/stop", _client_id ); + } + } + } + + + const char * + client_id ( void ) + { return _client_id; } + + NSM_Client ( int X, int Y, int W, int H, const char *L ) : + Fl_Group( X, Y, W, H, L ) + { + + _client_id = NULL; + _client_name = NULL; + _client_label = NULL; + + align( FL_ALIGN_LEFT | FL_ALIGN_INSIDE ); + color( fl_darker( FL_RED ) ); + box( FL_UP_FRAME ); + + int yy = Y + H * 0.25; + int hh = H * 0.50; + int xx = X + W - ( 75 + Fl::box_dw( box() ) ); + int ss = 2; + + /* /\* dummy group *\/ */ + /* { Fl_Group *o = new Fl_Group( X, Y, W, H ); */ + /* o->end(); */ + /* resizable( o ); */ + /* } */ + + { Fl_Pack *o = new Fl_Pack( X + 15, Y, 300 - 5, H ); + o->type( FL_HORIZONTAL ); + o->spacing( 10 ); + { icon_box = new Fl_Box( 0, 0, 32, 32 ); + } + + { Fl_Box *o = client_name = new Fl_Box( 0, 0, 300, 48 ); + /* o->color( FL_BLUE ); */ + o->align( FL_ALIGN_INSIDE | FL_ALIGN_LEFT ); + o->labeltype( FL_NORMAL_LABEL ); + } + o->end(); + } + + { Fl_Box *o = new Fl_Box( X + 300, Y, 100, h() ); + Fl_Group::current()->resizable(o); + } + + { Fl_Progress *o = _progress = new Fl_Progress( xx, Y + H * 0.25, 75, H * 0.50, NULL ); + o->box( FL_FLAT_BOX ); + o->color( FL_BLACK ); + o->copy_label( "launch" ); + o->labelsize( 12 ); + o->minimum( 0.0f ); + o->maximum( 1.0f ); + } + + { 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 = _kill_button = new Fl_Button( xx, yy, 25, hh, "@square" ); + o->labelsize( 9 ); + o->box( FL_UP_BOX ); + o->type(0); +// o->color( FL_RED ); + o->value( 0 ); + o->tooltip( "Stop" ); + 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(); + } + end(); + } + + ~NSM_Client ( ) + { + if ( _client_id ) + { + free( _client_id ); + _client_id = NULL; + } + + if ( _client_name ) + { + free( _client_name ); + _client_name = NULL; + } + + if ( _client_label ) + { + free( _client_label ); + _client_label = NULL; + } + + if ( label() ) + { + free( (char*)label() ); + label( NULL ); + } + } +}; + +static +void +fl_awake_alert( void *v ) +{ + if ( v ) + { + fl_alert( "%s", (char*)v ); + free( v ); + } +} + +void +browser_callback ( Fl_Widget *w, void * ) +{ + w->window()->hide(); +} + +class NSM_Controller : public Fl_Group +{ + + Fl_Text_Display *status_display; + +public: + + Fl_Pack *clients_pack; + Fl_Pack *buttons_pack; + Fl_Button *close_button; + Fl_Button *abort_button; + Fl_Button *save_button; + Fl_Button *open_button; + Fl_Button *new_button; + Fl_Button *add_button; + Fl_Button *duplicate_button; + Fl_Button *quit_button; + Fl_Button *refresh_button; + Fl_Box *session_name_box; + + Fl_Tree *session_browser; + + int status_lines; + + static void cb_handle ( Fl_Widget *w, void *v ) + { + ((NSM_Controller*)v)->cb_handle( w ); + + } + + void log_status ( const char *s ) + { + time_t now; + + now = time( NULL ); + + struct tm * tm = localtime( &now ); + + char *ts; + asprintf( &ts, "%02i:%02i:%02i ", tm->tm_hour, tm->tm_min, tm->tm_sec ); + + status_display->buffer()->append( ts ); + free( ts ); + + status_display->buffer()->append( s ); + status_display->scroll( ++status_lines, 0 ); + status_display->buffer()->append( "\n" ); + } + + void + cb_handle ( Fl_Widget *w ) + { + if ( w == abort_button ) + { + if ( 0 == fl_choice( "Are you sure you want to abort this session? Unsaved changes will be lost.", "Abort", "Cancel", NULL ) ) + { + MESSAGE( "Sending abort." ); + + foreach_daemon ( d ) + { + osc->send( (*d)->addr, "/nsm/server/abort" ); + } + } + } + if ( w == close_button ) + { + MESSAGE( "Sending close." ); + foreach_daemon ( d ) + { + osc->send( (*d)->addr, "/nsm/server/close" ); + } + } + else if ( w == save_button ) + { + MESSAGE( "Sending save." ); + foreach_daemon ( d ) + { + osc->send( (*d)->addr, "/nsm/server/save" ); + } + } + else if ( w == open_button ) + { + const char *name = fl_input( "Open Session", NULL ); + + if ( ! name ) + return; + + Fl_Tree_Item *item = session_browser->find_item( name ); + + if ( item ) + session_browser->select_only( item, 1 ); + } + else if ( w == duplicate_button ) + { + const char *name = fl_input( "New Session", NULL ); + + if ( ! name ) + return; + + MESSAGE( "Sending duplicate for: %s", name ); + foreach_daemon ( d ) + { + osc->send( (*d)->addr, "/nsm/server/duplicate", name ); + } + } + else if ( w == quit_button ) + { + window()->do_callback( window(), this ); + } + else if ( w == refresh_button ) + { + session_browser->clear(); + session_browser->redraw(); + MESSAGE( "Refreshing session list." ); + foreach_daemon ( d ) + { + osc->send( (*d)->addr, "/nsm/server/list" ); + } + } + else if ( w == session_browser ) + { + if ( session_browser->callback_reason() != FL_TREE_REASON_SELECTED ) + return; + + Fl_Tree_Item *item = session_browser->callback_item(); + + // session_browser->deselect( item, 0 ); + + if ( item->children() ) + return; + + char name[1024]; + + session_browser->item_pathname( name, sizeof(name), item ); + + foreach_daemon ( d ) + { + osc->send( (*d)->addr, "/nsm/server/open", name ); + } + } + else if ( w == new_button ) + { + const char *name = fl_input( "New Session", NULL ); + + if ( !name ) + return; + + MESSAGE( "Sending new for: %s", name ); + foreach_daemon ( d ) + { + osc->send( (*d)->addr, "/nsm/server/new", name ); + } + } + else if ( w == add_button ) + { + Fl_Select_Browser *browser; + + if ( daemon_list.size() > 1 ) + { + Fl_Window* win = new Fl_Window( window()->x(), window()->y(), 300, 400, "Choose Server" ); + { + { + Fl_Box *o = new Fl_Box( 0,0, 300, 100 ); + + o->label( "Connected to multiple NSM servers, please select which one to add a client to." ); + o->align( FL_ALIGN_CENTER | FL_ALIGN_INSIDE | FL_ALIGN_WRAP ); + } + { + Fl_Select_Browser *o = browser = new Fl_Select_Browser( 0, 100, 300, 300 ); + o->box( FL_ROUNDED_BOX ); + o->color( FL_BLACK ); + o->callback( browser_callback, win ); + foreach_daemon( d ) + { + o->add( (*d)->url ); + } + } + } + + win->end(); + + win->show(); + + while ( win->visible() ) + { + Fl::wait(); + } + + if ( ! browser->value() ) + return; + + const char *n = fl_input( "Enter executable name" ); + + if ( !n ) + return; + + char *name = strdup( n ); + + if ( index( name, ' ' ) ) + { + free( name ); + name = strdup( "nsm-proxy" ); + } + + lo_address nsm_addr = lo_address_new_from_url( browser->text( browser->value() ) ); + + osc->send( nsm_addr, "/nsm/server/add", name ); + + free( name ); + + delete win; + } + else + { + const char *n = fl_input( "Enter executable name" ); + + if ( !n ) + return; + + char *name = strdup( n ); + + if ( index( name, ' ' ) ) + { + free( name ); + name = strdup( "nsm-proxy" ); + } + + MESSAGE( "Sending add for: %s", name ); + /* FIXME: user should get to choose which system to do the add on */ + foreach_daemon ( d ) + { + osc->send( (*d)->addr, "/nsm/server/add", name ); + } + + free( name ); + } + + } + } + + + NSM_Client * + client_by_id ( const char *id ) + { + for ( int i = clients_pack->children(); i--; ) + { + NSM_Client *c = (NSM_Client*)clients_pack->child( i ); + + if ( ! strcmp( c->client_id(), id ) ) + { + return c; + } + } + return NULL; + } + + + const char *session_name ( void ) const + { + return session_name_box->label(); + } + + void + session_name ( const char *name ) + { + session_name_box->copy_label( name ); + + if ( strlen( name ) ) + { + save_button->activate(); + add_button->activate(); + duplicate_button->activate(); + abort_button->activate(); + close_button->activate(); + } + else + { + save_button->deactivate(); + add_button->deactivate(); + duplicate_button->deactivate(); + abort_button->deactivate(); + close_button->deactivate(); + } + + redraw(); + } + + void + client_stopped ( const char *client_id ) + { + NSM_Client *c = client_by_id( client_id ); + + if ( c ) + { + c->stopped( 1 ); + } + } + + void + client_quit ( const char *client_id ) + { + NSM_Client *c = client_by_id( client_id ); + + if ( c ) + { + clients_pack->remove( c ); + delete c; + } + + if ( clients_pack->children() == 0 ) + { + ((Fl_Packscroller*)clients_pack->parent())->yposition( 0 ); + } + + parent()->redraw(); + } + + void + client_new ( const char *client_id, const char *client_name ) + { + + NSM_Client *c; + + c = client_by_id( client_id ); + + if ( c ) + { + c->name( client_name ); + return; + } + + c = new NSM_Client( 0, 0, w(), 40, NULL ); + + c->name( client_name ); + c->client_id( client_id ); + c->stopped( 0 ); + + clients_pack->add( c ); + + redraw(); + } + + void client_pending_command ( NSM_Client *c, const char *command ) + { + if ( c ) + { + if ( ! strcmp( command, "removed" ) ) + { + clients_pack->remove( c ); + delete c; + + parent()->redraw(); + } + else + c->pending_command( command ); + } + } + + + void add_session_to_list ( const char *name ) + { + session_browser->add( name ); + session_browser->redraw(); + } + + + NSM_Controller ( int X, int Y, int W, int H, const char *L ) : + Fl_Group( X, Y, W, H, L ) + { + status_lines = 0; + + align( FL_ALIGN_RIGHT | FL_ALIGN_CENTER | FL_ALIGN_INSIDE ); + + { Fl_Pack *o = buttons_pack = new Fl_Pack( X, Y, W, 30 ); + o->type( Fl_Pack::HORIZONTAL ); + o->box( FL_NO_BOX ); + { Fl_Button *o = quit_button = new Fl_Button( 0, 0, 80, 50, "&Quit" ); + o->shortcut( FL_CTRL | 'q' ); + o->box( FL_UP_BOX ); + o->callback( cb_handle, (void*)this ); + } + { Fl_Button *o = refresh_button = new Fl_Button( 0, 0, 80, 50, "&Refresh" ); + o->shortcut( FL_CTRL | 'r' ); + o->box( FL_UP_BOX ); + o->callback( cb_handle, (void*)this ); + } + { Fl_Button *o = open_button = new Fl_Button( 0, 0, 80, 50, "&Open" ); + o->shortcut( FL_CTRL | 'o' ); + o->box( FL_UP_BOX ); + o->callback( cb_handle, (void*)this ); + } + { Fl_Button *o = close_button = new Fl_Button( 0, 0, 80, 50, "Close" ); + o->shortcut( FL_CTRL | 'q' ); + o->box( FL_UP_BOX ); + o->callback( cb_handle, (void*)this ); + } + { Fl_Button *o = abort_button = new Fl_Button( 0, 0, 80, 50, "Abort" ); + o->box( FL_UP_BOX ); + o->color( fl_color_average( FL_RED, fl_rgb_color(10,10,10), 0.5f ) ); + o->callback( cb_handle, (void*)this ); + } + { Fl_Button *o = save_button = new Fl_Button( 0, 0, 80, 50, "&Save" ); + o->shortcut( FL_CTRL | 's' ); + o->box( FL_UP_BOX ); + o->callback( cb_handle, (void*)this ); + } + { Fl_Button *o = new_button = new Fl_Button( 0, 0, 80, 50, "&New" ); + o->shortcut( FL_CTRL | 'n' ); + o->box( FL_UP_BOX ); + o->callback( cb_handle, (void*)this ); + } + { Fl_Button *o = duplicate_button = new Fl_Button( 0, 0, 100, 50, "Duplicate" ); + o->box( FL_UP_BOX ); + o->callback( cb_handle, (void*)this ); + } + + o->end(); + } + + int SH = 14; + + { Fl_Tile *o = new Fl_Tile( X, Y + 30, W, H - 30 ); + { Fl_Scalepack *o = new Fl_Scalepack( X, Y + 30, 300, H - ( 30 + SH ) ); + o->type( FL_VERTICAL ); + o->spacing( 2 ); + + { new Fl_Box( 0,0,100, 24, "Sessions" ); + } + + { + Fl_Tree *o = session_browser = new Fl_Tree( X, Y + 50, W / 3, H - ( 50 + SH ) ); + o->callback( cb_handle, (void *)this ); + o->color( FL_DARK1 ); + o->item_labelbgcolor( o->color() ); + o->item_labelfgcolor( FL_FOREGROUND_COLOR ); + o->sortorder( FL_TREE_SORT_ASCENDING ); + o->showroot( 0 ); + o->selection_color( fl_darker( FL_GREEN ) ); + o->selectbox( FL_UP_FRAME ); + o->box( FL_FLAT_BOX ); + /* o->label( "Sessions" ); */ + o->end(); + Fl_Group::current()->resizable( o ); + } // Fl_Tree + o->end(); + } + + Fl_Scalepack *scalepack; + { Fl_Scalepack *o = scalepack = new Fl_Scalepack( X + 300, Y + 30, W - 300, H - ( 30 + SH ) ); + o->type( FL_VERTICAL ); + o->spacing( 2 ); + + { session_name_box = new Fl_Box( 0, 0, 100, 25, "" ); + + } + + { Fl_Button *o = add_button = new Fl_Button( 0, 0, 100, 25, "&Add Client to Session" ); + o->shortcut( FL_CTRL | 'a' ); + o->box( FL_UP_BOX ); + o->align( FL_ALIGN_CLIP ); + o->callback( cb_handle, (void*)this ); + } + + { + Fl_Packscroller *o = new Fl_Packscroller( 0, 0, 100, H - ( 30 + SH ) ); + o->align( FL_ALIGN_TOP ); + o->labeltype( FL_SHADOW_LABEL ); + { + Fl_Pack *o = clients_pack = new Fl_Pack( 0, 0, 100, 100 ); + o->align( FL_ALIGN_TOP ); + o->spacing( 4 ); + o->type( Fl_Pack::VERTICAL ); + o->end(); + } + o->end(); + Fl_Group::current()->resizable( o ); + } // Fl_Packscroller + o->end(); + /* Fl_Group::current()->resizable( o ); */ + } // Fl_Scalepack + + { Fl_Box *o = new Fl_Box( X + 300, Y + 30, 100, H - ( 30 + SH )); + Fl_Group::current()->resizable(o); + } + + { Fl_Text_Display *o = status_display = new Fl_Text_Display( X, Y + H - SH, W, SH ); + o->color( FL_DARK1 ); + o->textcolor( FL_FOREGROUND_COLOR ); + o->box( FL_UP_BOX ); + o->textfont( FL_COURIER ); + o->textsize( 10 ); + Fl_Text_Buffer *b = new Fl_Text_Buffer(); + o->buffer(b); + } + + o->end(); + resizable( o ); + + } // Fl_tile + + end(); + + deactivate(); + } + + int min_h ( void ) + { + return 500; + } + + void + ping ( void ) + { + if ( daemon_list.size() ) + { + foreach_daemon( d ) + { + osc->send( (*d)->addr, "/osc/ping" ); + } + } + if ( last_ping_response ) + { + if ( time(NULL) - last_ping_response > 10 ) + { + if ( active() ) + { + deactivate(); + log_status( "Server is not responding..." ); + } + } + else + { + if ( !active() ) + { + log_status( "Server is back." ); + activate(); + } + } + } + } + + + int init_osc ( void ) + { + osc = new OSC::Endpoint(); + + if ( int r = osc->init( LO_UDP ) ) + return r; + + osc->owner = this; + + osc->add_method( "/error", "sis", osc_handler, osc, "msg" ); + osc->add_method( "/reply", "ss", osc_handler, osc, "msg" ); + osc->add_method( "/reply", "s", osc_handler, osc, "" ); + + osc->add_method( "/nsm/server/broadcast", NULL, osc_broadcast_handler, osc, "msg" ); + osc->add_method( "/nsm/gui/server_announce", "s", osc_handler, osc, "msg" ); + osc->add_method( "/nsm/gui/server/message", "s", osc_handler, osc, "msg" ); + osc->add_method( "/nsm/gui/gui_announce", "s", osc_handler, osc, "msg" ); + osc->add_method( "/nsm/gui/session/session", "s", osc_handler, osc, "path,display_name" ); + osc->add_method( "/nsm/gui/session/name", "ss", osc_handler, osc, "path,display_name" ); + osc->add_method( "/nsm/gui/client/new", "ss", osc_handler, osc, "path,display_name" ); + osc->add_method( "/nsm/gui/client/status", "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/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(); + + return 0; + } + + + void announce ( const char *nsm_url ) + { + /* Daemon *d = new Daemon; */ + + /* d->url = nsm_url; */ + lo_address nsm_addr = lo_address_new_from_url( nsm_url ); +// d->is_child = true; + + /* daemon_list.push_back( d ); */ + + osc->send( nsm_addr, "/nsm/gui/gui_announce" ); + } + +private: + + static int osc_broadcast_handler ( const char *path, const char *, lo_arg **, int argc, lo_message msg, void * ) + { + if ( ! argc ) + /* need at least one argument... */ + return 0; + + DMESSAGE( "Relaying broadcast" ); + + foreach_daemon( d ) + { + char *u1 = lo_address_get_url( (*d)->addr ); + char *u2 = lo_address_get_url( lo_message_get_source( msg ) ); + + if ( strcmp( u1, u2 ) ) + { + osc->send( (*d)->addr, path, msg ); + } + + free( u1 ); + free( u2 ); + } + + return 0; + } + + static int osc_handler ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) + { +// OSC_DMSG(); + + NSM_Controller *controller = (NSM_Controller*)((OSC::Endpoint*)user_data)->owner; + + Fl::lock(); + + if ( !strcmp( path, "/nsm/gui/server/message" ) && !strcmp( types, "s" ) ) + { + controller->log_status( &argv[0]->s ); + } + else if ( !strcmp( path, "/nsm/gui/session/session" ) && + ! strcmp( types, "s" ) ) + { + controller->add_session_to_list( &argv[0]->s ); + } + else if ( !strcmp( path, "/nsm/gui/gui_announce" ) ) + { + /* pre-existing server is replying to our announce message */ + controller->activate(); + + lo_address nsm_addr = lo_message_get_source( msg ); + + osc->send( nsm_addr, "/nsm/server/list" ); + } + else if ( !strcmp( path, "/nsm/gui/server_announce" ) ) + { + /* must be a server we launched */ + + controller->activate(); + + Daemon *d = new Daemon; + + d->url = lo_address_get_url( lo_message_get_source( msg ) ); + d->addr = lo_address_new_from_url( d->url ); + d->is_child = true; + + daemon_list.push_back( d ); + + osc->send( d->addr, "/nsm/server/list" ); + } + else if ( !strcmp( path, "/nsm/gui/session/name" ) && + !strcmp( types, "ss" )) + { + controller->session_name( &argv[0]->s ); + + if ( !strcmp( &argv[0]->s, "" ) ) + { + controller->session_browser->deselect_all(); + } + else + { + Fl_Tree_Item *o = controller->session_browser->find_item( &argv[1]->s ); + if ( o ) + { + controller->session_browser->select_only( o, 0 ); + controller->session_browser->show_item( o, 0 ); + } + } + } + else if (!strcmp( path, "/error" ) && + !strcmp( types, "sis" ) ) + { + int err = argv[1]->i; + + if ( err != 0 ) + { + char *s; + asprintf( &s, "Command %s failed with:\n\n%s", &argv[0]->s, &argv[2]->s ); + + Fl::awake(fl_awake_alert, s); + } + } + else if (!strcmp( path, "/reply" ) && argc && 's' == *types ) + { + if ( !strcmp( &argv[0]->s, "/nsm/server/list" ) ) + { + controller->add_session_to_list( &argv[1]->s ); + } + else if ( !strcmp( &argv[0]->s, "/osc/ping" ) ) + { + last_ping_response = time( NULL ); + } + else if ( ! strcmp( types, "ss" ) ) + { + MESSAGE( "%s says %s", &argv[0]->s, &argv[1]->s); + controller->log_status( &argv[1]->s ); + } + } + + if ( !strncmp( path, "/nsm/gui/client/", strlen( "/nsm/gui/client/" ) ) ) + { + if ( !strcmp( path, "/nsm/gui/client/new" ) && + !strcmp( types, "ss" ) ) + { + controller->client_new( &argv[0]->s, &argv[1]->s ); + } + else + { + NSM_Client *c = controller->client_by_id( &argv[0]->s ); + + if ( c ) + { + if ( !strcmp( path, "/nsm/gui/client/status" ) && + !strcmp( types, "ss" )) + { + controller->client_pending_command( c, &argv[1]->s ); + } + else if ( !strcmp( path, "/nsm/gui/client/progress" ) && + !strcmp( types, "sf" )) + { + c->progress( argv[1]->f ); + } + else if ( !strcmp( path, "/nsm/gui/client/dirty" ) && + !strcmp( types, "si" )) + { + 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" )) + { + c->client_id( &argv[1]->s ); + } + } + else + MESSAGE( "Got message %s from unknown client", path ); + } + } + + Fl::unlock(); + Fl::awake(); + + return 0; + } +}; + + +static NSM_Controller *controller; + +void +ping ( void * ) +{ + controller->ping(); + Fl::repeat_timeout( 1.0, ping, NULL ); +} + +void +cb_main ( Fl_Widget *, void * ) +{ + if ( Fl::event_key() != FL_Escape ) + { + int children = 0; + foreach_daemon ( d ) + { + if ( (*d)->is_child ) + ++children; + } + + if ( children ) + { + if ( strlen( controller->session_name() ) ) + { + fl_message( "%s", "You have to close the session before you can quit." ); + return; + } + } + + while ( Fl::first_window() ) Fl::first_window()->hide(); + } +} + +int +main (int argc, char **argv ) +{ + fl_register_images(); + + Fl::lock(); + + Fl_Double_Window *main_window; + + { + Fl_Double_Window *o = main_window = new Fl_Double_Window( 800, 600, APP_TITLE ); + { + main_window->xclass( APP_NAME ); + + Fl_Widget *o = controller = new NSM_Controller( 0, 0, main_window->w(), main_window->h(), NULL ); + controller->session_name( "" ); + + Fl_Group::current()->resizable(o); + } + o->end(); + + o->size_range( main_window->w(), controller->min_h(), 0, 0 ); + + o->callback( (Fl_Callback*)cb_main, main_window ); + + o->show( 0, NULL ); + } + + static struct option long_options[] = + { + { "nsm-url", required_argument, 0, 'n' }, + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } + }; + + int option_index = 0; + int c = 0; + + while ( ( c = getopt_long_only( argc, argv, "", long_options, &option_index ) ) != -1 ) + { + switch ( c ) + { + case 'n': + { + DMESSAGE( "Adding %s to daemon list", optarg ); + Daemon *d = new Daemon; + + d->url = optarg; + d->addr = lo_address_new_from_url( optarg ); + + daemon_list.push_back( d ); + break; + } + case 'h': + printf( "Usage: %s [--nsm-url...] [-- server options ]\n\n", argv[0] ); + exit(0); + break; + } + } + + const char *nsm_url = getenv( "NSM_URL" ); + + if ( nsm_url ) + { + MESSAGE( "Found NSM URL of \"%s\" in environment, attempting to connect.", nsm_url ); + + Daemon *d = new Daemon; + + d->url = nsm_url; + d->addr = lo_address_new_from_url( nsm_url ); + + daemon_list.push_back( d ); + } + + if ( controller->init_osc() ) + FATAL( "Could not create OSC server" ); + + if ( daemon_list.size() ) + { + foreach_daemon ( d ) + { + controller->announce( (*d)->url ); + } + } + else + { + /* start a new daemon... */ + MESSAGE( "Starting daemon..." ); + + char *url = osc->url(); + + if ( ! fork() ) + { + /* pass non-option arguments on to daemon */ + + char *args[4 + argc - optind]; + + int i = 0; + args[i++] = strdup("nsmd"); + args[i++] = strdup("--gui-url"); + args[i++] = url; + + for ( ; optind < argc; i++, optind++ ) + { + DMESSAGE( "Passing argument: %s", argv[optind] ); + args[i] = argv[optind]; + } + + args[i] = 0; + + if ( -1 == execvp( "nsmd", args ) ) + { + FATAL( "Error starting process: %s", strerror( errno ) ); + } + } + + free(url); + } + + Fl::add_timeout( 1.0, ping, NULL ); + Fl::run(); + + foreach_daemon ( d ) + { + if ( (*d)->is_child ) + { + MESSAGE( "Telling server to quit" ); + osc->send( (*d)->addr, "/nsm/server/quit" ); + } + } + + return 0; +} +