zynaddsubfx

ZynAddSubFX open source synthesizer
Log | Files | Refs | Submodules | LICENSE

guimain.cpp (20077B)


      1 /*
      2   ZynAddSubFX - a software synthesizer
      3 
      4   guimain.cpp  -  Main file of synthesizer GUI
      5   Copyright (C) 2015 Mark McCurry
      6 
      7   This program is free software; you can redistribute it and/or
      8   modify it under the terms of the GNU General Public License
      9   as published by the Free Software Foundation; either version 2
     10   of the License, or (at your option) any later version.
     11 */
     12 
     13 #include <rtosc/thread-link.h>
     14 #include <lo/lo.h>
     15 #include <string>
     16 #include <thread>
     17 
     18 //GUI System
     19 #include "Connection.h"
     20 #include "NSM.H"
     21 
     22 #include <sys/stat.h>
     23 GUI::ui_handle_t gui = 0;
     24 const char *embedId = NULL;
     25 #if USE_NSM
     26 NSM_Client *nsm = NULL;
     27 #endif
     28 lo_server server;
     29 std::string sendtourl;
     30 
     31 /*
     32  * Program exit
     33  */
     34 void exitprogram()
     35 {
     36     GUI::destroyUi(gui);
     37 }
     38 
     39 int Pexitprogram=0;
     40 
     41 
     42 #include "Connection.h"
     43 #include "Fl_Osc_Interface.h"
     44 #include "../globals.h"
     45 #include <map>
     46 #include <cassert>
     47 
     48 #include <rtosc/rtosc.h>
     49 #include <rtosc/ports.h>
     50 
     51 #include <FL/Fl.H>
     52 #include "Fl_Osc_Tree.H"
     53 #include "common.H"
     54 #include "MasterUI.h"
     55 
     56 #ifdef NTK_GUI
     57 #include <FL/Fl_Shared_Image.H>
     58 #include <FL/Fl_Tiled_Image.H>
     59 #include <FL/Fl_Dial.H>
     60 #include <FL/x.H>
     61 #include <err.h>
     62 #endif // NTK_GUI
     63 
     64 #ifndef NO_UI
     65 #include "Fl_Osc_Widget.H"
     66 #endif
     67 
     68 using namespace GUI;
     69 class MasterUI *ui=0;
     70 
     71 // exceptionally extension of the namespace outside the core
     72 namespace zyn
     73 {
     74     bool isPlugin = false;
     75     bool fileexists(const char *filename)
     76     {
     77         struct stat tmp;
     78         int result = stat(filename, &tmp);
     79         if(result >= 0)
     80             return true;
     81 
     82         return false;
     83     }
     84 }
     85 
     86 #ifdef NTK_GUI
     87 static Fl_Tiled_Image *module_backdrop;
     88 #endif
     89 
     90 int kb_shortcut_handler(int)
     91 {
     92     const bool undo_ = Fl::event_ctrl() && Fl::event_key() == 'z';
     93     const bool redo = Fl::event_ctrl() && Fl::event_key() == 'r';
     94     const bool show = Fl::event_ctrl() && Fl::event_shift() &&
     95         Fl::event_key() == 's';
     96     const bool panel = Fl::event_ctrl() && Fl::event_shift() &&
     97         Fl::event_key() == 'p';
     98     if(undo_)
     99         ui->osc->write("/undo", "");
    100     else if(redo)
    101         ui->osc->write("/redo", "");
    102     else if (show) {
    103         ui->simplemasterwindow->hide();
    104         ui->masterwindow->show();
    105     }
    106     else if (panel)
    107         ui->panelwindow->show();
    108     return undo_ || redo || show;
    109 }
    110 
    111 void
    112 set_module_parameters ( Fl_Widget *o )
    113 {
    114 #ifdef NTK_GUI
    115     o->box( FL_DOWN_FRAME );
    116     o->align( o->align() | FL_ALIGN_IMAGE_BACKDROP );
    117     o->color( FL_BLACK );
    118     o->image( module_backdrop );
    119     o->labeltype( FL_SHADOW_LABEL );
    120     if(o->parent()) {
    121         o->parent()->labeltype(FL_NO_LABEL);
    122         o->parent()->box(FL_NO_BOX);
    123     }
    124 #else
    125     o->box( FL_PLASTIC_UP_BOX );
    126     o->color( FL_CYAN );
    127     o->labeltype( FL_EMBOSSED_LABEL );
    128 #endif
    129 }
    130 
    131 ui_handle_t GUI::createUi(Fl_Osc_Interface *osc, void *exit)
    132 {
    133 #ifdef NTK_GUI
    134     fl_register_images();
    135 
    136     Fl_Dial::default_style(Fl_Dial::PIXMAP_DIAL);
    137 
    138 #ifdef CARLA_VERSION_STRING
    139     if(Fl_Shared_Image *img = Fl_Shared_Image::get(gUiPixmapPath + "/knob.png"))
    140         Fl_Dial::default_image(img);
    141 #else
    142     if(Fl_Shared_Image *img = Fl_Shared_Image::get(PIXMAP_PATH "/knob.png"))
    143         Fl_Dial::default_image(img);
    144 #endif
    145     else if(Fl_Shared_Image *img = Fl_Shared_Image::get(SOURCE_DIR "/pixmaps/knob.png"))
    146         Fl_Dial::default_image(img);
    147     else
    148         errx(1, "ERROR: Cannot find pixmaps/knob.png");
    149 
    150 
    151 #ifdef CARLA_VERSION_STRING
    152     if(Fl_Shared_Image *img = Fl_Shared_Image::get(gUiPixmapPath + "/window_backdrop.png"))
    153         Fl::scheme_bg(new Fl_Tiled_Image(img));
    154 #else
    155     if(Fl_Shared_Image *img = Fl_Shared_Image::get(PIXMAP_PATH "/window_backdrop.png"))
    156         Fl::scheme_bg(new Fl_Tiled_Image(img));
    157 #endif
    158     else if(Fl_Shared_Image *img = Fl_Shared_Image::get(SOURCE_DIR "/pixmaps/window_backdrop.png"))
    159         Fl::scheme_bg(new Fl_Tiled_Image(img));
    160     else
    161         errx(1, "ERROR: Cannot find pixmaps/window_backdrop.png");
    162 
    163 #ifdef CARLA_VERSION_STRING
    164     if(Fl_Shared_Image *img = Fl_Shared_Image::get(gUiPixmapPath + "/module_backdrop.png"))
    165         module_backdrop = new Fl_Tiled_Image(img);
    166 #else
    167     if(Fl_Shared_Image *img = Fl_Shared_Image::get(PIXMAP_PATH "/module_backdrop.png"))
    168         module_backdrop = new Fl_Tiled_Image(img);
    169 #endif
    170     else if(Fl_Shared_Image *img = Fl_Shared_Image::get(SOURCE_DIR "/pixmaps/module_backdrop.png"))
    171         module_backdrop = new Fl_Tiled_Image(img);
    172     else
    173         errx(1, "ERROR: Cannot find pixmaps/module_backdrop");
    174 
    175     Fl::background(50,  50,  50);
    176     Fl::background2(70, 70,  70);
    177     Fl::foreground(255, 255, 255);
    178 #endif
    179 
    180     //Fl_Window *midi_win = new Fl_Double_Window(400, 400, "Midi connections");
    181     //Fl_Osc_Tree *tree   = new Fl_Osc_Tree(0,0,400,400);
    182     //midi_win->resizable(tree);
    183     //tree->root_ports    = &Master::ports;
    184     //tree->osc           = osc;
    185     //midi_win->show();
    186 
    187     Fl::add_handler(kb_shortcut_handler);
    188 
    189     ui = new MasterUI((int*)exit, osc);
    190 
    191     if (embedId != NULL)
    192     {
    193         if (long long winId = atoll(embedId))
    194         {
    195             // embedId passed means running as plugin
    196             isPlugin = true;
    197             MasterUI::menu_mastermenu[11].hide(); // file -> nio settings
    198             MasterUI::menu_mastermenu[26].deactivate(); // misc -> switch interface mode
    199 #ifdef NTK_GUI
    200             if (winId != 1)
    201             {
    202                 MasterUI::menu_mastermenu[13].hide(); // file -> exit
    203                 fl_embed(ui->masterwindow, winId);
    204             }
    205 #else
    206             (void) winId; // Silences compiler warning.
    207 #endif
    208             ui->masterwindow->show();
    209         }
    210     }
    211 
    212     return (void*) ui;
    213 }
    214 void GUI::destroyUi(ui_handle_t ui)
    215 {
    216     delete static_cast<MasterUI*>(ui);
    217 }
    218 
    219 #define BEGIN(x) {x,":non-realtime\0",NULL,[](const char *m, rtosc::RtData d){ \
    220     MasterUI *ui   = static_cast<MasterUI*>(d.obj); \
    221     rtosc_arg_t a0 = {}, a1 = {}; \
    222     if(rtosc_narguments(m) > 0) \
    223         a0 = rtosc_argument(m,0); \
    224     if(rtosc_narguments(m) > 1) \
    225         a1 = rtosc_argument(m,1); \
    226     (void)ui;(void)a1;(void)a0;
    227 
    228 #define END }},
    229 
    230 struct uiPorts {
    231     static rtosc::Ports ports;
    232 };
    233 
    234 //DSL based ports
    235 rtosc::Ports uiPorts::ports = {
    236     BEGIN("show:i") {
    237         ui->showUI(a0.i);
    238     } END
    239     BEGIN("alert:s") {
    240         fl_alert("%s",a0.s);
    241     } END
    242     BEGIN("session-type:s") {
    243         if(strcmp(a0.s,"LASH"))
    244             return;
    245         ui->sm_indicator1->value(1);
    246         ui->sm_indicator2->value(1);
    247         ui->sm_indicator1->tooltip("LASH");
    248         ui->sm_indicator2->tooltip("LASH");
    249     } END
    250     BEGIN("save-master:s") {
    251         ui->do_save_master(a0.s);
    252     } END
    253     BEGIN("load-master:s") {
    254         ui->do_load_master(a0.s);
    255     } END
    256     BEGIN("vu-meter:bb") {
    257 #ifdef DEBUG
    258         printf("Vu meter handler...\n");
    259 #endif
    260         if(a0.b.len == sizeof(vuData) &&
    261                 a1.b.len == sizeof(float)*NUM_MIDI_PARTS) {
    262 #ifdef DEBUG
    263             printf("Normal behavior...\n");
    264 #endif
    265             //Refresh the primary VU meters
    266             ui->simplemastervu->update((vuData*)a0.b.data);
    267             ui->mastervu->update((vuData*)a0.b.data);
    268 
    269             float *partvu = (float*)a1.b.data;
    270             for(int i=0; i<NUM_MIDI_PARTS; ++i)
    271                 ui->panellistitem[i]->partvu->update(partvu[i]);
    272         }
    273     } END
    274     BEGIN("close-ui") {
    275         ui->close();
    276     } END
    277 };
    278 
    279 
    280 void GUI::raiseUi(ui_handle_t gui, const char *message)
    281 {
    282     if(!gui)
    283         return;
    284     MasterUI *mui = (MasterUI*)gui;
    285     mui->osc->tryLink(message);
    286 #ifdef DEBUG
    287     printf("got message for UI '%s:%s'\n", message, rtosc_argument_string(message));
    288 #endif
    289     char buffer[1024];
    290     memset(buffer, 0, sizeof(buffer));
    291     rtosc::RtData d;
    292     d.loc = buffer;
    293     d.loc_size = 1024;
    294     d.obj = gui;
    295     uiPorts::ports.dispatch(message+1, d);
    296 }
    297 
    298 void GUI::raiseUi(ui_handle_t gui, const char *dest, const char *args, ...)
    299 {
    300     char buffer[1024];
    301     va_list va;
    302     va_start(va,args);
    303     if(rtosc_vmessage(buffer,1024,dest,args,va))
    304         raiseUi(gui, buffer);
    305     va_end(va);
    306 }
    307 
    308 void GUI::tickUi(ui_handle_t)
    309 {
    310     Fl::wait(0.02f);
    311 }
    312 
    313 /******************************************************************************
    314  *    OSC Interface For User Interface                                        *
    315  *                                                                            *
    316  *    This is a largely out of date section of code                           *
    317  *    Most type specific write methods are no longer used                     *
    318  *    See UI/Fl_Osc_* to see what is actually used in this interface          *
    319  ******************************************************************************/
    320 class UI_Interface:public Fl_Osc_Interface
    321 {
    322     public:
    323         UI_Interface()
    324         {}
    325 
    326         void transmitMsg(const char *path, const char *args, ...)
    327         {
    328             char buffer[1024];
    329             va_list va;
    330             va_start(va,args);
    331             if(rtosc_vmessage(buffer,1024,path,args,va))
    332                 transmitMsg(buffer);
    333             else
    334                 fprintf(stderr, "Error in transmitMsg(...)\n");
    335             va_end(va);
    336         }
    337 
    338         void transmitMsg(const char *rtmsg)
    339         {
    340             //Send to known url
    341             if(!sendtourl.empty()) {
    342                 lo_message msg  = lo_message_deserialise((void*)rtmsg,
    343                         rtosc_message_length(rtmsg, rtosc_message_length(rtmsg,-1)), NULL);
    344                 lo_address addr = lo_address_new_from_url(sendtourl.c_str());
    345                 lo_send_message(addr, rtmsg, msg);
    346             }
    347         }
    348 
    349         void requestValue(string s) override
    350         {
    351             //printf("Request Value '%s'\n", s.c_str());
    352             assert(s!="/Psysefxvol-1/part0");
    353             //Fl_Osc_Interface::requestValue(s);
    354             /*
    355             if(impl->activeUrl() != "GUI") {
    356                 impl->transmitMsg("/echo", "ss", "OSC_URL", "GUI");
    357                 impl->activeUrl("GUI");
    358             }*/
    359 
    360             transmitMsg(s.c_str(),"");
    361         }
    362 
    363         void write(string s, const char *args, ...) override
    364         {
    365             char buffer[4096];
    366             va_list va;
    367             va_start(va, args);
    368             rtosc_vmessage(buffer, sizeof(buffer), s.c_str(), args, va);
    369             //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 4 + 30, 0 + 40);
    370             ////fprintf(stderr, ".");
    371             //fprintf(stderr, "write(%s:%s)\n", s.c_str(), args);
    372             //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
    373             transmitMsg(buffer);
    374             va_end(va);
    375         }
    376 
    377         void writeRaw(const char *msg) override
    378         {
    379             //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 4 + 30, 0 + 40);
    380             ////fprintf(stderr, ".");
    381             //fprintf(stderr, "rawWrite(%s:%s)\n", msg, rtosc_argument_string(msg));
    382             //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
    383             transmitMsg(msg);
    384         }
    385 
    386         void writeValue(string s, string ss) override
    387         {
    388             //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 4 + 30, 0 + 40);
    389             //fprintf(stderr, "writevalue<string>(%s,%s)\n", s.c_str(),ss.c_str());
    390             //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
    391             transmitMsg(s.c_str(), "s", ss.c_str());
    392         }
    393 
    394         void writeValue(string s, char c) override
    395         {
    396             //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 4 + 30, 0 + 40);
    397             //fprintf(stderr, "writevalue<char>(%s,%d)\n", s.c_str(),c);
    398             //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
    399             transmitMsg(s.c_str(), "c", c);
    400         }
    401 
    402         void writeValue(string s, float f) override
    403         {
    404             //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 4 + 30, 0 + 40);
    405             //fprintf(stderr, "writevalue<float>(%s,%f)\n", s.c_str(),f);
    406             //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
    407             transmitMsg(s.c_str(), "f", f);
    408         }
    409 
    410         void createLink(string s, class Fl_Osc_Widget*w) override
    411         {
    412             assert(s.length() != 0);
    413             Fl_Osc_Interface::createLink(s,w);
    414             assert(!strstr(s.c_str(), "/part0/kit-1"));
    415             map.insert(std::pair<string,Fl_Osc_Widget*>(s,w));
    416         }
    417 
    418         void renameLink(string old, string newer, Fl_Osc_Widget *w) override
    419         {
    420             fprintf(stdout, "renameLink('%s','%s',%p)\n",
    421                     old.c_str(), newer.c_str(), w);
    422             removeLink(old, w);
    423             createLink(newer, w);
    424         }
    425 
    426         void removeLink(string s, class Fl_Osc_Widget*w) override
    427         {
    428             for(auto i = map.begin(); i != map.end(); ++i) {
    429                 if(i->first == s && i->second == w) {
    430                     map.erase(i);
    431                     break;
    432                 }
    433             }
    434             //printf("[%d] removing '%s' (%p)...\n", map.size(), s.c_str(), w);
    435         }
    436 
    437         virtual void removeLink(class Fl_Osc_Widget *w) override
    438         {
    439             bool processing = true;
    440             while(processing)
    441             {
    442                 //Verify Iterator invalidation sillyness
    443                 processing = false;//Exit if no new elements are found
    444                 for(auto i = map.begin(); i != map.end(); ++i) {
    445                     if(i->second == w) {
    446                         //printf("[%d] removing '%s' (%p)...\n", map.size()-1,
    447                         //        i->first.c_str(), w);
    448                         map.erase(i);
    449                         processing = true;
    450                         break;
    451                     }
    452                 }
    453             }
    454         }
    455 
    456         //A very simplistic implementation of a UI agnostic refresh method
    457         virtual void damage(const char *path) override
    458         {
    459             //printf("\n\nDamage(\"%s\")\n", path);
    460             for(auto pair:map) {
    461                 if(strstr(pair.first.c_str(), path)) {
    462                     auto *tmp = dynamic_cast<Fl_Widget*>(pair.second);
    463                     //if(tmp)
    464                     //    printf("%x, %d %d [%s]\n", (int)pair.second, tmp->visible_r(), tmp->visible(), pair.first.c_str());
    465                     //else
    466                     //    printf("%x, (NULL)[%s]\n", (int)pair.second,pair.first.c_str());
    467                     if(!tmp || tmp->visible_r())
    468                         pair.second->update();
    469                 }
    470             }
    471         }
    472 
    473         void tryLink(const char *msg) override
    474         {
    475 
    476             //DEBUG
    477             //if(strcmp(msg, "/vu-meter"))//Ignore repeated message
    478             //    printf("trying the link for a '%s'<%s>\n", msg, rtosc_argument_string(msg));
    479             const char *handle = strrchr(msg,'/');
    480             if(handle)
    481                 ++handle;
    482 
    483             int found_count = 0;
    484 
    485             auto range = map.equal_range(msg);
    486             for(auto itr = range.first; itr != range.second; ++itr) {
    487                 auto widget = itr->second;
    488                 found_count++;
    489                 const char *arg_str = rtosc_argument_string(msg);
    490 
    491                 //Always provide the raw message
    492                 widget->OSC_raw(msg);
    493 
    494                 if(!strcmp(arg_str, "b")) {
    495                     widget->OSC_value(rtosc_argument(msg,0).b.len,
    496                             rtosc_argument(msg,0).b.data,
    497                             handle);
    498                 } else if(!strcmp(arg_str, "c")) {
    499                     widget->OSC_value((char)rtosc_argument(msg,0).i,
    500                             handle);
    501                 } else if(!strcmp(arg_str, "s")) {
    502                     widget->OSC_value((const char*)rtosc_argument(msg,0).s,
    503                             handle);
    504                 } else if(!strcmp(arg_str, "i")) {
    505                     widget->OSC_value((int)rtosc_argument(msg,0).i,
    506                             handle);
    507                 } else if(!strcmp(arg_str, "f")) {
    508                     widget->OSC_value((float)rtosc_argument(msg,0).f,
    509                             handle);
    510                 } else if(!strcmp(arg_str, "T") || !strcmp(arg_str, "F")) {
    511                     widget->OSC_value((bool)rtosc_argument(msg,0).T, handle);
    512                 }
    513             }
    514 
    515             if(found_count == 0
    516                     && strcmp(msg, "/vu-meter")
    517                     && strcmp(msg, "undo_change")
    518                     && !strstr(msg, "parameter")
    519                     && !strstr(msg, "Prespoint")) {
    520                 //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 1, 7 + 30, 0 + 40);
    521                 //fprintf(stderr, "Unknown widget '%s'\n", msg);
    522                 //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
    523             }
    524         };
    525 
    526         void dumpLookupTable(void)
    527         {
    528             if(!map.empty()) {
    529                 printf("Leaked controls:\n");
    530                 for(auto i = map.begin(); i != map.end(); ++i) {
    531                     printf("Known control  '%s' (%p)...\n", i->first.c_str(), i->second);
    532                 }
    533             }
    534         }
    535 
    536 
    537     private:
    538         std::multimap<string,Fl_Osc_Widget*> map;
    539 };
    540 
    541 Fl_Osc_Interface *GUI::genOscInterface(MiddleWare *)
    542 {
    543     return new UI_Interface();
    544 }
    545 
    546 rtosc::ThreadLink lo_buffer(4096*2, 1000);
    547 
    548 static void liblo_error_cb(int i, const char *m, const char *loc)
    549 {
    550     fprintf(stderr, "liblo :-( %d-%s@%s\n",i,m,loc);
    551 }
    552 
    553 static int handler_function(const char *path, const char *types, lo_arg **argv,
    554         int argc, lo_message msg, void *user_data)
    555 {
    556     (void) types;
    557     (void) argv;
    558     (void) argc;
    559     (void) user_data;
    560     char buffer[8192];
    561     size_t size;
    562 
    563     size = lo_message_length(msg, path);
    564     if (size > sizeof(buffer)) {
    565         /*
    566          * Sometimes search results may return too much data to handle
    567          * by the lo_buffer. Just print a warning and ignore such
    568          * messages for now.
    569          */
    570         fprintf(stderr, "guimain.cpp:%u Received too many bytes "
    571             "%zu > %zu (ignored)\n", __LINE__, size, sizeof(buffer));
    572         return 0;
    573     }
    574     memset(buffer, 0, sizeof(buffer));
    575     lo_message_serialise(msg, path, buffer, &size);
    576     assert(size <= sizeof(buffer));
    577     lo_buffer.raw_write(buffer);
    578     return 0;
    579 }
    580 
    581 void watch_lo(void)
    582 {
    583     while(server && Pexitprogram == 0)
    584         lo_server_recv_noblock(server, 100);
    585 }
    586 
    587 const char *help_message =
    588 "zynaddsubfx-ext-gui [options] uri - Connect to remote ZynAddSubFX\n"
    589 "    --help               print this help message\n"
    590 "    --no-uri             run without a remote ZynAddSubFX\n"
    591 "    --embed <window ID>  internal flag to let UI run in plugin mode\n"
    592 "                         NTK only: Embed master window into passed window\n"
    593 "\n"
    594 "    example: zynaddsubfx-ext-gui osc.udp://localhost:1234/\n"
    595 "      This will connect to a running zynaddsubfx instance on the same\n"
    596 "      machine on port 1234.\n";
    597 
    598 #ifndef CARLA_VERSION_STRING
    599 int main(int argc, char *argv[])
    600 {
    601     const char *uri    = NULL;
    602     const char *title  = NULL;
    603     bool        help   = false;
    604     bool        no_uri = false;
    605     for(int i=1; i<argc; ++i) {
    606         if(!strcmp("--help", argv[i]))
    607             help = true;
    608         else if(!strcmp("--no-uri", argv[i]))
    609             no_uri = true;
    610         else if(!strcmp("--embed", argv[i]))
    611             embedId = argv[++i];
    612         else if(!strcmp("--title", argv[i]))
    613             title = argv[++i];
    614         else
    615             uri = argv[i];
    616     }
    617     if(uri == NULL && no_uri == false)
    618         help = true;
    619 
    620     if(help) {
    621         puts(help_message);
    622         return 1;
    623     }
    624 
    625     //Startup Liblo Link
    626     if(uri) {
    627         server = lo_server_new_with_proto(NULL, LO_UDP, liblo_error_cb);
    628         lo_server_add_method(server, NULL, NULL, handler_function, 0);
    629         sendtourl = uri;
    630     }
    631     fprintf(stderr, "ext client running on %d\n", lo_server_get_port(server));
    632     std::thread lo_watch(watch_lo);
    633 
    634     gui = GUI::createUi(new UI_Interface(), &Pexitprogram);
    635 
    636     if (title != NULL)
    637         GUI::raiseUi(gui, "/ui/title", "s", title);
    638 
    639     GUI::raiseUi(gui, "/show",  "i", 1);
    640     while(Pexitprogram == 0) {
    641         GUI::tickUi(gui);
    642         while(lo_buffer.hasNext())
    643             raiseUi(gui, lo_buffer.read());
    644     }
    645 
    646     exitprogram();
    647     lo_watch.join();
    648     return 0;
    649 }
    650 #endif