diff --git a/BaseToolkit.h b/BaseToolkit.h index da50b53..0acb79d 100644 --- a/BaseToolkit.h +++ b/BaseToolkit.h @@ -35,7 +35,7 @@ std::list splitted(const std::string& str, char delim); std::string joinned(std::list strlist, char separator = ' '); // returns true if the string -bool is_a_number(const std::string& str, int *val = nullptr); +bool is_a_number(const std::string& str, int *val); // find common parts in a list of strings std::string common_prefix(const std::list &allStrings); diff --git a/Settings.cpp b/Settings.cpp index 96a7c17..a319502 100644 --- a/Settings.cpp +++ b/Settings.cpp @@ -52,6 +52,22 @@ XMLElement *save_history(Settings::History &h, const char *nodename, XMLDocument } +XMLElement *save_knownhost(Settings::KnownHosts &h, const char *nodename, XMLDocument &xmlDoc) +{ + XMLElement *pElement = xmlDoc.NewElement( nodename ); + pElement->SetAttribute("protocol", h.protocol.c_str()); + for(auto it = h.hosts.cbegin(); it != h.hosts.cend(); ++it) { + XMLElement *hostNode = xmlDoc.NewElement("host"); + XMLText *text = xmlDoc.NewText( it->first.c_str() ); + hostNode->InsertEndChild( text ); + hostNode->SetAttribute("port", it->second.c_str()); + pElement->InsertFirstChild(hostNode); + } + return pElement; +} + + + void Settings::Save(uint64_t runtime) { // impose C locale for all app @@ -111,8 +127,6 @@ void Settings::Save(uint64_t runtime) applicationNode->SetAttribute("pannel_history_mode", application.pannel_current_session_mode); applicationNode->SetAttribute("stream_protocol", application.stream_protocol); applicationNode->SetAttribute("broadcast_port", application.broadcast_port); - applicationNode->SetAttribute("custom_connect_ip", application.custom_connect_ip.c_str()); - applicationNode->SetAttribute("custom_connect_port", application.custom_connect_port.c_str()); pRoot->InsertEndChild(applicationNode); // Widgets @@ -245,6 +259,17 @@ void Settings::Save(uint64_t runtime) pRoot->InsertEndChild(recent); } + // hosts known hosts + { + XMLElement *knownhosts = xmlDoc.NewElement( "Hosts" ); + + // recent SRT hosts + knownhosts->InsertEndChild( save_knownhost(application.recentSRT, "SRT", xmlDoc)); + + + pRoot->InsertEndChild(knownhosts); + } + // Timer Metronome XMLElement *timerConfNode = xmlDoc.NewElement( "Timer" ); timerConfNode->SetAttribute("mode", application.timer.mode); @@ -304,6 +329,33 @@ void load_history(Settings::History &h, const char *nodename, XMLElement *root) } } + +void load_knownhost(Settings::KnownHosts &h, const char *nodename, XMLElement *root) +{ + XMLElement * pElement = root->FirstChildElement(nodename); + if (pElement) + { + // list of hosts + h.hosts.clear(); + XMLElement* host_ = pElement->FirstChildElement("host"); + for( ; host_ ; host_ = host_->NextSiblingElement()) + { + const char *ip_ = host_->GetText(); + if (ip_) { + const char *port_ = host_->Attribute("port"); + if (port_) + h.push( std::string(ip_), std::string(port_) ); + else + h.push( std::string(ip_) ); + } + } + // protocol attribute + const char *protocol_ = pElement->Attribute("protocol"); + if (protocol_) + h.protocol = std::string(protocol_); + } +} + void Settings::Load() { // impose C locale for all app @@ -346,16 +398,6 @@ void Settings::Load() applicationNode->QueryIntAttribute("pannel_history_mode", &application.pannel_current_session_mode); applicationNode->QueryIntAttribute("stream_protocol", &application.stream_protocol); applicationNode->QueryIntAttribute("broadcast_port", &application.broadcast_port); - const char *ip = applicationNode->Attribute("custom_connect_ip"); - if (ip) - application.custom_connect_ip = std::string(ip); - else - application.custom_connect_ip = "127.0.0.1"; - const char *p = applicationNode->Attribute("custom_connect_port"); - if (p) - application.custom_connect_port = std::string(p); - else - application.custom_connect_port = "8888"; } // Widgets @@ -533,6 +575,16 @@ void Settings::Load() } } + // bloc of known hosts + { + XMLElement * pElement = pRoot->FirstChildElement("Hosts"); + if (pElement) + { + // recent SRT hosts + load_knownhost(application.recentSRT, "SRT", pElement); + } + } + // Timer Metronome XMLElement * timerconfnode = pRoot->FirstChildElement("Timer"); if (timerconfnode != nullptr) { @@ -595,6 +647,29 @@ void Settings::History::validate() } } +void Settings::KnownHosts::push(const string &ip, const string &port) +{ + if (!ip.empty()) { + + std::pair h = { ip, port }; + hosts.remove(h); + hosts.push_front(h); + if (hosts.size() > MAX_RECENT_HISTORY) + hosts.pop_back(); + } +} + +void Settings::KnownHosts::remove(const string &ip) +{ + for (auto hit = hosts.begin(); hit != hosts.end();) { + if ( ip.compare( hit->first ) > 0 ) + ++hit; + else + hit = hosts.erase(hit); + } +} + + void Settings::Lock() { diff --git a/Settings.h b/Settings.h index cf1a6cf..0ce3e5f 100644 --- a/Settings.h +++ b/Settings.h @@ -126,6 +126,18 @@ struct History void validate(); }; +struct KnownHosts +{ + std::string protocol; + std::pair default_host; + std::list< std::pair > hosts; + + KnownHosts() {} + + void push(const std::string &ip, const std::string &port = ""); + void remove(const std::string &ip); +}; + struct TransitionConfig { bool cross_fade; @@ -250,8 +262,7 @@ struct Application bool accept_connections; int stream_protocol; int broadcast_port; - std::string custom_connect_ip; - std::string custom_connect_port; + KnownHosts recentSRT; // Settings of widgets WidgetsConfig widget; @@ -307,8 +318,8 @@ struct Application accept_connections = false; stream_protocol = 0; broadcast_port = 7070; - custom_connect_ip = "127.0.0.1"; - custom_connect_port = "8888"; + recentSRT.protocol = "srt://"; + recentSRT.default_host = { "127.0.0.1", "7070"}; pannel_current_session_mode = 0; current_view = 1; current_workspace= 1; diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 94c3e7f..18aa307 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -6731,7 +6731,7 @@ void Navigator::RenderNewPannel() else if (Settings::application.source.new_type == SOURCE_CONNECTED){ ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::BeginCombo("##External", "Select device")) + if (ImGui::BeginCombo("##External", "Select stream")) { for (int d = 0; d < Device::manager().numDevices(); ++d){ std::string namedev = Device::manager().name(d); @@ -6758,36 +6758,90 @@ void Navigator::RenderNewPannel() // Indication ImGui::SameLine(); - ImGuiToolkit::HelpToolTip("Create a source getting images from connected devices or machines;\n" + ImVec2 pos = ImGui::GetCursorPos(); + ImGuiToolkit::HelpToolTip("Create a source capturing video streams from connected devices or machines;\n" ICON_FA_CARET_RIGHT " webcams or frame grabbers\n" ICON_FA_CARET_RIGHT " screen capture\n" - ICON_FA_CARET_RIGHT " stream shared by vimix on local network\n" - ICON_FA_CARET_RIGHT " SRT stream (e.g. broadcasted by vimix)"); + ICON_FA_CARET_RIGHT " shared by vimix on local network\n" + ICON_FA_CARET_RIGHT " broadcasted with SRT over network."); if (custom_connected) { + bool valid_ = false; + static std::string url_; + static std::string ip_ = Settings::application.recentSRT.hosts.empty() ? Settings::application.recentSRT.default_host.first : Settings::application.recentSRT.hosts.front().first; + static std::string port_ = Settings::application.recentSRT.hosts.empty() ? Settings::application.recentSRT.default_host.second : Settings::application.recentSRT.hosts.front().second; + static std::regex ipv4("(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"); + static std::regex numport("([0-9]){4,6}"); + ImGui::Text("\nCall an SRT broadcaster:"); + ImGui::SetCursorPos(pos + ImVec2(0.f, 1.8f * ImGui::GetFrameHeight()) ); + ImGuiToolkit::Indication("Set the IP and Port for connecting with Secure Reliable Transport (SRT) protocol to a video broadcaster that is waiting for connections (listener mode).", ICON_FA_PODCAST); + // Entry field for IP ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGuiToolkit::InputText("IP", &Settings::application.custom_connect_ip, ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsDecimal); + ImGuiToolkit::InputText("IP", &ip_, ImGuiInputTextFlags_CharsDecimal); + valid_ = std::regex_match(ip_, ipv4); + // Entry field for port ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGuiToolkit::InputText("Port", &Settings::application.custom_connect_port, ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsDecimal); + ImGuiToolkit::InputText("Port", &port_, ImGuiInputTextFlags_CharsDecimal); + valid_ &= std::regex_match(port_, numport); - static char bufurl[32]; - ImFormatString(bufurl, IM_ARRAYSIZE(bufurl), "srt://%s:%s", - Settings::application.custom_connect_ip.c_str(), - Settings::application.custom_connect_port.c_str() ); + // URL generated from protorol, IP and port + url_ = Settings::application.recentSRT.protocol + ip_ + ":" + port_; + + // push style for disabled text entry + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.8f)); - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGui::InputText("##url", bufurl, IM_ARRAYSIZE(bufurl), ImGuiInputTextFlags_ReadOnly); + + // display default IP & port + if (Settings::application.recentSRT.hosts.empty()) { + ImGuiToolkit::InputText("##url", &url_, ImGuiInputTextFlags_ReadOnly); + } + // display most recent host & offer list of known hosts + else { + if (ImGui::BeginCombo("##SRThosts", url_.c_str())) { + for (auto it = Settings::application.recentSRT.hosts.begin(); it != Settings::application.recentSRT.hosts.end(); ++it) { + + if (ImGui::Selectable( std::string(Settings::application.recentSRT.protocol + it->first + ":" + it->second).c_str() ) ) { + ip_ = it->first; + port_ = it->second; + } + } + ImGui::EndCombo(); + } + // icons to clear lists + ImVec2 pos_top = ImGui::GetCursorPos(); + ImGui::SameLine(); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.7); + if (ImGuiToolkit::IconButton( ICON_FA_BACKSPACE, "Clear list")) { + Settings::application.recentSRT.hosts.clear(); + ip_ = Settings::application.recentSRT.default_host.first; + port_ = Settings::application.recentSRT.default_host.second; + } + ImGui::PopStyleVar(); + ImGui::SetCursorPos(pos_top); + + } + + // pop disabled style ImGui::PopStyleColor(1); - ImGui::SameLine(0); ImGuiToolkit::Indication("URL for connecting with Secure Reliable Transport (SRT) protocol to a broadcaster that is waiting for connections (listener mode).", ICON_SOURCE_SRT); + // push a RED color style if host is not valid + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(0.0f, valid_ ? 0.0f : 0.6f, 0.4f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(0.0f, valid_ ? 0.0f : 0.7f, 0.3f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(0.0f, valid_ ? 0.0f : 0.8f, 0.2f)); - if ( ImGui::Button("Call", ImVec2(IMGUI_RIGHT_ALIGN, 0)) ) { - new_source_preview_.setSource( Mixer::manager().createSourceSrt(Settings::application.custom_connect_ip, Settings::application.custom_connect_port), bufurl); + // create a new SRT source if host is valid + if ( ImGui::Button("Call", ImVec2(IMGUI_RIGHT_ALIGN, 0)) && valid_ ) { + // set preview source + new_source_preview_.setSource( Mixer::manager().createSourceSrt(ip_, port_), url_); + // remember known host + Settings::application.recentSRT.push(ip_, port_); } + + ImGui::PopStyleColor(3); } }