From a6121541235b78ed8fd0994c0ea9291917e9d523 Mon Sep 17 00:00:00 2001 From: Bruno Herbelin Date: Sat, 18 Dec 2021 16:02:37 +0100 Subject: [PATCH] Initial implementation of Control manager Control manager will handle control actions, recorded or from OSC. Here skeleton for receiving OSC messages is in place. Cleanup of includes for NetworkToolkit. Touched a bit the BaseToolkit. --- BaseToolkit.cpp | 18 +++++-- BaseToolkit.h | 7 ++- CMakeLists.txt | 1 + Connection.cpp | 10 ++-- Connection.h | 27 +++++----- ControlManager.cpp | 110 +++++++++++++++++++++++++++++++++++++++ ControlManager.h | 43 +++++++++++++++ ImGuiVisitor.cpp | 6 +-- NetworkSource.cpp | 5 +- NetworkSource.h | 34 +++++------- NetworkToolkit.h | 9 ++-- Settings.h | 12 +++++ Streamer.cpp | 4 +- Streamer.h | 27 ++++------ SystemToolkit.cpp | 20 +++---- UserInterfaceManager.cpp | 14 ++--- defines.h | 2 + main.cpp | 12 +++++ 18 files changed, 269 insertions(+), 92 deletions(-) create mode 100644 ControlManager.cpp create mode 100644 ControlManager.h diff --git a/BaseToolkit.cpp b/BaseToolkit.cpp index 5e023e9..d3f5da0 100644 --- a/BaseToolkit.cpp +++ b/BaseToolkit.cpp @@ -141,16 +141,26 @@ std::string BaseToolkit::bits_to_string(long b) } -std::string BaseToolkit::trunc_string(const std::string& path, int N) +std::string BaseToolkit::truncated(const std::string& str, int N) { - std::string trunc = path; - int l = path.size(); + std::string trunc = str; + int l = str.size(); if ( l > N ) { - trunc = std::string("...") + path.substr( l - N + 3 ); + trunc = std::string("...") + str.substr( l - N + 3 ); } return trunc; } +std::list BaseToolkit::splitted(const std::string& str, char delim) { + std::list strings; + size_t start; + size_t end = 0; + while ((start = str.find_first_not_of(delim, end)) != std::string::npos) { + end = str.find(delim, start); + strings.push_back(str.substr(start, end - start)); + } + return strings; +} std::string BaseToolkit::common_prefix( const std::list & allStrings ) { diff --git a/BaseToolkit.h b/BaseToolkit.h index 9f97435..8dee544 100644 --- a/BaseToolkit.h +++ b/BaseToolkit.h @@ -22,8 +22,11 @@ std::string byte_to_string(long b); // get a string to display bit size with unit Kbit, MBit, Gbit, Tbit std::string bits_to_string(long b); -// Truncate a string to display the right most N characters (e.g. ./home/me/toto.mpg -> ...ome/me/toto.mpg) -std::string trunc_string(const std::string& path, int N); +// cut a string to display the right most N characters (e.g. /home/me/toto.mpg -> ...ome/me/toto.mpg) +std::string truncated(const std::string& str, int N); + +// split a string into list of strings separated by delimitor (e.g. /home/me/toto.mpg -> {home, me, toto.mpg} ) +std::list splitted(const std::string& str, char delim); // find common parts in a list of strings std::string common_prefix(const std::list &allStrings); diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ed142d..d50ce55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -337,6 +337,7 @@ set(VMIX_SRCS ActionManager.cpp Overlay.cpp Metronome.cpp + ControlManager.cpp ) diff --git a/Connection.cpp b/Connection.cpp index 5269177..d9ca129 100644 --- a/Connection.cpp +++ b/Connection.cpp @@ -19,28 +19,26 @@ #include #include -#include #include #include "osc/OscOutboundPacketStream.h" #include "defines.h" -#include "Connection.h" #include "Settings.h" #include "Streamer.h" #include "Log.h" +#include "Connection.h" + #ifndef NDEBUG #define CONNECTION_DEBUG #endif -Connection::Connection() +Connection::Connection() : receiver_(nullptr) { - receiver_ = nullptr; } - Connection::~Connection() { if (receiver_!=nullptr) { @@ -226,7 +224,7 @@ void Connection::ask() } -void ConnectionRequestListener::ProcessMessage( const osc::ReceivedMessage& m, +void Connection::RequestListener::ProcessMessage( const osc::ReceivedMessage& m, const IpEndpointName& remoteEndpoint ) { char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH]; diff --git a/Connection.h b/Connection.h index 660f9d4..ad62137 100644 --- a/Connection.h +++ b/Connection.h @@ -1,22 +1,17 @@ #ifndef CONNECTION_H #define CONNECTION_H +#include #include -#include "osc/OscReceivedElements.h" -#include "osc/OscPacketListener.h" -#include "ip/UdpSocket.h" - #include "NetworkToolkit.h" +#define MAX_HANDSHAKE 20 +#define HANDSHAKE_PORT 71310 +#define STREAM_REQUEST_PORT 71510 +#define OSC_DIALOG_PORT 71010 #define ALIVE 3 -class ConnectionRequestListener : public osc::OscPacketListener { - -protected: - virtual void ProcessMessage( const osc::ReceivedMessage& m, - const IpEndpointName& remoteEndpoint ); -}; struct ConnectionInfo { @@ -58,8 +53,6 @@ struct ConnectionInfo { class Connection { - friend class ConnectionRequestListener; - // Private Constructor Connection(); Connection(Connection const& copy) = delete; @@ -82,11 +75,19 @@ public: int index(const std::string &name) const; ConnectionInfo info(int index = 0); // index 0 for self +protected: + class RequestListener : public osc::OscPacketListener { + + protected: + virtual void ProcessMessage( const osc::ReceivedMessage& m, + const IpEndpointName& remoteEndpoint ); + }; + private: static void ask(); static void listen(); - ConnectionRequestListener listener_; + RequestListener listener_; UdpListeningReceiveSocket *receiver_; std::vector< ConnectionInfo > connections_; diff --git a/ControlManager.cpp b/ControlManager.cpp new file mode 100644 index 0000000..67fa2f3 --- /dev/null +++ b/ControlManager.cpp @@ -0,0 +1,110 @@ +/* + * This file is part of vimix - video live mixer + * + * **Copyright** (C) 2020-2021 Bruno Herbelin + * + * 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 3 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. If not, see . +**/ + +#include +#include + +#include "osc/OscOutboundPacketStream.h" + +#include "Log.h" +#include "Settings.h" +#include "BaseToolkit.h" + +#include "ControlManager.h" + +#ifndef NDEBUG +#define CONTROL_DEBUG +#endif + + +void Control::RequestListener::ProcessMessage( const osc::ReceivedMessage& m, + const IpEndpointName& remoteEndpoint ) +{ + char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH]; + remoteEndpoint.AddressAndPortAsString(sender); + + try{ + +#ifdef CONTROL_DEBUG + Log::Info("%s sent an OSC message %s.", sender, m.AddressPattern()); +#endif + + std::list address = BaseToolkit::splitted(m.AddressPattern(), '/'); + + if (address.size() == 3 && address.front().compare(APP_NAME) == 0 ){ + address.pop_front(); + Log::Info("Wellformed vimix message %s.", address.front().c_str()); + } + + } + catch( osc::Exception& e ){ + // any parsing errors such as unexpected argument types, or + // missing arguments get thrown as exceptions. + Log::Info("error while parsing message '%s' from %s : %s", m.AddressPattern(), sender, e.what()); + } +} + + +void Control::listen() +{ +#ifdef CONTROL_DEBUG + Log::Info("Accepting OSC messages on port %d", Settings::application.control.osc_port); +#endif + if (Control::manager().receiver_) + Control::manager().receiver_->Run(); +} + + +Control::Control() : receiver_(nullptr) +{ +} + +Control::~Control() +{ + if (receiver_!=nullptr) { + receiver_->Break(); + delete receiver_; + } +} + +bool Control::init() +{ + try { + // try to create listenning socket + // through exception runtime if fails + receiver_ = new UdpListeningReceiveSocket( IpEndpointName( IpEndpointName::ANY_ADDRESS, + Settings::application.control.osc_port ), &listener_ ); + } + catch (const std::runtime_error&) { + // arg, the receiver could not be initialized + // because the port was not available + receiver_ = nullptr; + } + + // listen for answers + std::thread(listen).detach(); + + return receiver_ != nullptr; +} + +void Control::terminate() +{ + if (receiver_!=nullptr) + receiver_->AsynchronousBreak(); +} diff --git a/ControlManager.h b/ControlManager.h new file mode 100644 index 0000000..1d78df0 --- /dev/null +++ b/ControlManager.h @@ -0,0 +1,43 @@ +#ifndef CONTROL_H +#define CONTROL_H + +#include "NetworkToolkit.h" + +class Control +{ + // Private Constructor + Control(); + Control(Control const& copy) = delete; + Control& operator=(Control const& copy) = delete; + +public: + + static Control& manager () + { + // The only instance + static Control _instance; + return _instance; + } + ~Control(); + + bool init(); + void terminate(); + + // void setOscPort(int P); + +protected: + + class RequestListener : public osc::OscPacketListener { + protected: + virtual void ProcessMessage( const osc::ReceivedMessage& m, + const IpEndpointName& remoteEndpoint ); + }; + +private: + + static void listen(); + RequestListener listener_; + UdpListeningReceiveSocket *receiver_; +}; + +#endif // CONTROL_H diff --git a/ImGuiVisitor.cpp b/ImGuiVisitor.cpp index 39b3d7e..a83108e 100644 --- a/ImGuiVisitor.cpp +++ b/ImGuiVisitor.cpp @@ -582,7 +582,7 @@ void ImGuiVisitor::visit (MediaSource& s) // folder std::string path = SystemToolkit::path_filename(s.path()); - std::string label = BaseToolkit::trunc_string(path, 25); + std::string label = BaseToolkit::truncated(path, 25); label = BaseToolkit::transliterate(label); ImGuiToolkit::ButtonOpenUrl( label.c_str(), path.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) ); @@ -637,7 +637,7 @@ void ImGuiVisitor::visit (SessionFileSource& s) ImGui::Text("File"); std::string path = SystemToolkit::path_filename(s.path()); - std::string label = BaseToolkit::trunc_string(path, 25); + std::string label = BaseToolkit::truncated(path, 25); label = BaseToolkit::transliterate(label); ImGuiToolkit::ButtonOpenUrl( label.c_str(), path.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) ); ImGui::SameLine(); @@ -864,7 +864,7 @@ void ImGuiVisitor::visit (MultiFileSource& s) // offer to open file browser at location std::string path = SystemToolkit::path_filename(s.sequence().location); - std::string label = BaseToolkit::trunc_string(path, 25); + std::string label = BaseToolkit::truncated(path, 25); label = BaseToolkit::transliterate(label); ImGuiToolkit::ButtonOpenUrl( label.c_str(), path.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) ); ImGui::SameLine(); diff --git a/NetworkSource.cpp b/NetworkSource.cpp index 42cb4de..15afec1 100644 --- a/NetworkSource.cpp +++ b/NetworkSource.cpp @@ -27,13 +27,14 @@ #include #include +#include "osc/OscOutboundPacketStream.h" + #include "SystemToolkit.h" #include "defines.h" #include "Stream.h" #include "Decorations.h" #include "Visitor.h" #include "Log.h" -#include "Connection.h" #include "NetworkSource.h" @@ -43,7 +44,7 @@ // this is called when receiving an answer for streaming request -void StreamerResponseListener::ProcessMessage( const osc::ReceivedMessage& m, +void NetworkStream::ResponseListener::ProcessMessage( const osc::ReceivedMessage& m, const IpEndpointName& remoteEndpoint ) { char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH]; diff --git a/NetworkSource.h b/NetworkSource.h index be00872..02a4ca3 100644 --- a/NetworkSource.h +++ b/NetworkSource.h @@ -1,33 +1,13 @@ #ifndef NETWORKSOURCE_H #define NETWORKSOURCE_H -#include "osc/OscReceivedElements.h" -#include "osc/OscPacketListener.h" -#include "osc/OscOutboundPacketStream.h" -#include "ip/UdpSocket.h" - #include "NetworkToolkit.h" #include "Connection.h" #include "StreamSource.h" -class NetworkStream; - -class StreamerResponseListener : public osc::OscPacketListener -{ -protected: - class NetworkStream *parent_; - virtual void ProcessMessage( const osc::ReceivedMessage& m, - const IpEndpointName& remoteEndpoint ); -public: - inline void setParent(NetworkStream *s) { parent_ = s; } - StreamerResponseListener() : parent_(nullptr) {} -}; - class NetworkStream : public Stream { - friend class StreamerResponseListener; - public: NetworkStream(); @@ -43,10 +23,22 @@ public: std::string clientAddress() const; std::string serverAddress() const; +protected: + class ResponseListener : public osc::OscPacketListener + { + protected: + class NetworkStream *parent_; + virtual void ProcessMessage( const osc::ReceivedMessage& m, + const IpEndpointName& remoteEndpoint ); + public: + inline void setParent(NetworkStream *s) { parent_ = s; } + ResponseListener() : parent_(nullptr) {} + }; + private: // connection information ConnectionInfo streamer_; - StreamerResponseListener listener_; + ResponseListener listener_; UdpListeningReceiveSocket *receiver_; std::atomic received_config_; std::atomic connected_; diff --git a/NetworkToolkit.h b/NetworkToolkit.h index 88ac471..f2e56bd 100644 --- a/NetworkToolkit.h +++ b/NetworkToolkit.h @@ -4,6 +4,10 @@ #include #include +#include "osc/OscReceivedElements.h" +#include "osc/OscPacketListener.h" +#include "ip/UdpSocket.h" + #define OSC_PREFIX "/vimix" #define OSC_PING "/ping" #define OSC_PONG "/pong" @@ -12,11 +16,6 @@ #define OSC_STREAM_REJECT "/reject" #define OSC_STREAM_DISCONNECT "/disconnect" -#define STREAMING_FPS 30 -#define MAX_HANDSHAKE 20 -#define HANDSHAKE_PORT 71310 -#define STREAM_REQUEST_PORT 71510 -#define OSC_DIALOG_PORT 71010 #define IP_MTU_SIZE 1536 namespace NetworkToolkit diff --git a/Settings.h b/Settings.h index d63b8d3..7b699b7 100644 --- a/Settings.h +++ b/Settings.h @@ -189,6 +189,15 @@ struct TimerConfig } }; +struct ControllerConfig +{ + int osc_port; + + ControllerConfig() { + osc_port = OSC_DEFAULT_PORT; + } +}; + struct Application { // instance check @@ -237,6 +246,9 @@ struct Application // settings transition TransitionConfig transition; + // settings controller + ControllerConfig control; + // multiple windows handling std::vector windows; diff --git a/Streamer.cpp b/Streamer.cpp index 08cb3a4..4c1c71e 100644 --- a/Streamer.cpp +++ b/Streamer.cpp @@ -32,6 +32,8 @@ // gstreamer #include #include +#include +#include //osc #include "osc/OscOutboundPacketStream.h" @@ -52,7 +54,7 @@ #define STREAMER_DEBUG #endif -void StreamingRequestListener::ProcessMessage( const osc::ReceivedMessage& m, +void Streaming::RequestListener::ProcessMessage( const osc::ReceivedMessage& m, const IpEndpointName& remoteEndpoint ) { char sender[IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH]; diff --git a/Streamer.h b/Streamer.h index f57dae7..276b921 100644 --- a/Streamer.h +++ b/Streamer.h @@ -3,31 +3,15 @@ #include -#include -#include - -#include "osc/OscReceivedElements.h" -#include "osc/OscPacketListener.h" -#include "ip/UdpSocket.h" - #include "NetworkToolkit.h" #include "FrameGrabber.h" +#define STREAMING_FPS 30 -class Session; class VideoStreamer; -class StreamingRequestListener : public osc::OscPacketListener { - -protected: - virtual void ProcessMessage( const osc::ReceivedMessage& m, - const IpEndpointName& remoteEndpoint ); -}; - class Streaming { - friend class StreamingRequestListener; - // Private Constructor Streaming(); Streaming(Streaming const& copy) = delete; @@ -53,13 +37,20 @@ public: std::vector listStreams(); protected: + + class RequestListener : public osc::OscPacketListener { + + protected: + virtual void ProcessMessage( const osc::ReceivedMessage& m, + const IpEndpointName& remoteEndpoint ); + }; void addStream(const std::string &sender, int reply_to, const std::string &clientname); void refuseStream(const std::string &sender, int reply_to); private: bool enabled_; - StreamingRequestListener listener_; + RequestListener listener_; UdpListeningReceiveSocket *receiver_; std::vector streamers_; diff --git a/SystemToolkit.cpp b/SystemToolkit.cpp index d7346d1..0911b12 100644 --- a/SystemToolkit.cpp +++ b/SystemToolkit.cpp @@ -286,7 +286,7 @@ bool SystemToolkit::file_exists(const string& path) // tests if dir is a directory and return its path, empty string otherwise -std::string SystemToolkit::path_directory(const std::string& path) +string SystemToolkit::path_directory(const string& path) { string directorypath = ""; @@ -359,13 +359,13 @@ void SystemToolkit::execute(const string& command) -vector split(const string& str, char delim) { +vector split_path(const string& path) { vector strings; size_t start; size_t end = 0; - while ((start = str.find_first_not_of(delim, end)) != string::npos) { - end = str.find(delim, start); - strings.push_back(str.substr(start, end - start)); + while ((start = path.find_first_not_of(PATH_SEP, end)) != string::npos) { + end = path.find(PATH_SEP, start); + strings.push_back(path.substr(start, end - start)); } return strings; } @@ -376,8 +376,8 @@ vector split(const string& str, char delim) { string SystemToolkit::path_relative_to_path( const string& absolutePath, const string& relativeTo ) { string relativePath = ""; - vector absoluteDirectories = split(absolutePath, PATH_SEP); - vector relativeToDirectories = split(relativeTo, PATH_SEP); + vector absoluteDirectories = split_path(absolutePath); + vector relativeToDirectories = split_path(relativeTo); // Get the shortest of the two paths size_t length = MINI( absoluteDirectories.size(), relativeToDirectories.size() ); @@ -415,11 +415,11 @@ string SystemToolkit::path_relative_to_path( const string& absolutePath, const s return relativePath; } -std::string SystemToolkit::path_absolute_from_path(const std::string& relativePath, const std::string& relativeTo) +string SystemToolkit::path_absolute_from_path(const string& relativePath, const string& relativeTo) { string absolutePath = string(1, PATH_SEP); - vector relativeDirectories = split(relativePath, PATH_SEP); - vector relativeToDirectories = split(relativeTo, PATH_SEP); + vector relativeDirectories = split_path(relativePath); + vector relativeToDirectories = split_path(relativeTo); // how many ".." size_t count_relative = 0; diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 92d2236..1f887fe 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -4349,7 +4349,7 @@ void Navigator::RenderNewPannel() // combo to offer lists ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::BeginCombo("##SelectionNewMedia", BaseToolkit::trunc_string(Settings::application.recentImportFolders.path, 25).c_str() )) + if (ImGui::BeginCombo("##SelectionNewMedia", BaseToolkit::truncated(Settings::application.recentImportFolders.path, 25).c_str() )) { // Mode MEDIA_RECENT : recent files if (ImGui::Selectable( ICON_FA_LIST_OL IMGUI_LABEL_RECENT_FILES) ) { @@ -4362,7 +4362,7 @@ void Navigator::RenderNewPannel() // Mode MEDIA_FOLDER : known folders for(auto foldername = Settings::application.recentImportFolders.filenames.begin(); foldername != Settings::application.recentImportFolders.filenames.end(); foldername++) { - std::string f = std::string(ICON_FA_FOLDER) + " " + BaseToolkit::trunc_string( *foldername, 40); + std::string f = std::string(ICON_FA_FOLDER) + " " + BaseToolkit::truncated( *foldername, 40); if (ImGui::Selectable( f.c_str() )) { setNewMedia(MEDIA_FOLDER, *foldername); } @@ -4458,7 +4458,7 @@ void Navigator::RenderNewPannel() for(auto it = sourceMediaFiles.begin(); it != sourceMediaFiles.end(); ++it) { // build displayed file name std::string filename = BaseToolkit::transliterate(*it); - std::string label = BaseToolkit::trunc_string(SystemToolkit::filename(filename), 25); + std::string label = BaseToolkit::truncated(SystemToolkit::filename(filename), 25); // add selectable item to ListBox; open if clickec if (ImGui::Selectable( label.c_str(), sourceMediaFileCurrent.compare(*it) == 0 )) { // set new source preview @@ -4721,7 +4721,7 @@ void Navigator::RenderMainPannelVimix() // Show combo box of quick selection modes ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::BeginCombo("##SelectionSession", BaseToolkit::trunc_string(Settings::application.recentFolders.path, 25).c_str() )) { + if (ImGui::BeginCombo("##SelectionSession", BaseToolkit::truncated(Settings::application.recentFolders.path, 25).c_str() )) { // Mode 0 : recent files if (ImGui::Selectable( ICON_FA_LIST_OL IMGUI_LABEL_RECENT_FILES) ) { @@ -4732,7 +4732,7 @@ void Navigator::RenderMainPannelVimix() // Mode 1 : known folders for(auto foldername = Settings::application.recentFolders.filenames.begin(); foldername != Settings::application.recentFolders.filenames.end(); foldername++) { - std::string f = std::string(ICON_FA_FOLDER) + " " + BaseToolkit::trunc_string( *foldername, 40); + std::string f = std::string(ICON_FA_FOLDER) + " " + BaseToolkit::truncated( *foldername, 40); if (ImGui::Selectable( f.c_str() )) { // remember which path was selected Settings::application.recentFolders.path.assign(*foldername); @@ -5017,7 +5017,7 @@ void Navigator::RenderMainPannelVimix() { // Folder std::string path = SystemToolkit::path_filename(sessionfilename); - std::string label = BaseToolkit::trunc_string(path, 23); + std::string label = BaseToolkit::truncated(path, 23); label = BaseToolkit::transliterate(label); ImGuiToolkit::ButtonOpenUrl( label.c_str(), path.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) ); ImGui::SameLine(); @@ -5607,7 +5607,7 @@ void SourcePreview::setSource(Source *s, const string &label) delete source_; source_ = s; - label_ = BaseToolkit::trunc_string(label, 35); + label_ = BaseToolkit::truncated(label, 35); reset_ = true; } diff --git a/defines.h b/defines.h index d540e66..7a80e1a 100644 --- a/defines.h +++ b/defines.h @@ -105,4 +105,6 @@ #define USE_GST_APPSINK_CALLBACKS +#define OSC_DEFAULT_PORT 7000 + #endif // VMIX_DEFINES_H diff --git a/main.cpp b/main.cpp index 730e550..d58da43 100644 --- a/main.cpp +++ b/main.cpp @@ -27,6 +27,7 @@ #include "Mixer.h" #include "RenderingManager.h" #include "UserInterfaceManager.h" +#include "ControlManager.h" #include "Connection.h" #include "Metronome.h" @@ -91,6 +92,12 @@ int main(int argc, char *argv[]) if ( !Connection::manager().init() ) return 1; + /// + /// CONTROLLER INIT + /// + if ( !Control::manager().init() ) + return 1; + /// /// METRONOME INIT /// @@ -151,6 +158,11 @@ int main(int argc, char *argv[]) /// Metronome::manager().terminate(); + /// + /// CONTROLLER TERMINATE + /// + Control::manager().terminate(); + /// /// CONNECTION TERMINATE ///