/* This file is part of Ingen. Copyright 2007-2015 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. Ingen 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 Affero General Public License for details. You should have received a copy of the GNU Affero General Public License along with Ingen. If not, see . */ #include "ConnectWindow.hpp" #include "App.hpp" #include "Window.hpp" #include "WindowFactory.hpp" #include "ingen/Atom.hpp" #include "ingen/Configuration.hpp" #include "ingen/EngineBase.hpp" #include "ingen/Forge.hpp" #include "ingen/Interface.hpp" #include "ingen/Log.hpp" #include "ingen/QueuedInterface.hpp" #include "ingen/Status.hpp" #include "ingen/URIs.hpp" #include "ingen/World.hpp" #include "ingen/client/ClientStore.hpp" #include "ingen/client/GraphModel.hpp" // IWYU pragma: keep #include "ingen/client/SigClientInterface.hpp" #include "ingen/client/SocketClient.hpp" #include "ingen/paths.hpp" #include "raul/Path.hpp" #include "raul/Process.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ingen::gui { ConnectWindow::ConnectWindow(BaseObjectType* cobject, Glib::RefPtr xml) : Dialog(cobject) , _xml(std::move(xml)) {} void ConnectWindow::message(const Message& msg) { if (const Response* const r = std::get_if(&msg)) { ingen_response(r->id, r->status, r->subject); } else if (const Error* const e = std::get_if(&msg)) { error(e->message); } } void ConnectWindow::error(const std::string& msg) { if (!is_visible()) { present(); set_connecting_widget_states(); } if (_progress_label) { _progress_label->set_text(msg); } if (_app) { _app->world().log().error(msg + "\n"); } } void ConnectWindow::start(App& app, ingen::World& world) { _app = &app; if (world.engine()) { _mode = Mode::INTERNAL; } set_connected_to(world.interface()); connect(!!world.interface()); } void ConnectWindow::ingen_response(int32_t id, Status status, const std::string& subject) { if (id == _ping_id) { if (status != Status::SUCCESS) { error("Failed to get root patch"); } else { _attached = true; } } } void ConnectWindow::set_connected_to(const std::shared_ptr& engine) { _app->world().set_interface(engine); if (!_widgets_loaded) { return; } if (engine) { _icon->set(Gtk::Stock::CONNECT, Gtk::ICON_SIZE_LARGE_TOOLBAR); _progress_bar->set_fraction(1.0); _progress_label->set_text("Connected to engine"); _url_entry->set_sensitive(false); _url_entry->set_text(engine->uri().string()); _connect_button->set_sensitive(false); _disconnect_button->set_label("gtk-disconnect"); _disconnect_button->set_sensitive(true); _port_spinbutton->set_sensitive(false); _launch_radio->set_sensitive(false); _internal_radio->set_sensitive(false); _activate_button->set_sensitive(true); _deactivate_button->set_sensitive(true); } else { _icon->set(Gtk::Stock::DISCONNECT, Gtk::ICON_SIZE_LARGE_TOOLBAR); _progress_bar->set_fraction(0.0); _connect_button->set_sensitive(true); _disconnect_button->set_sensitive(false); _internal_radio->set_sensitive(true); _server_radio->set_sensitive(true); _launch_radio->set_sensitive(true); _activate_button->set_sensitive(false); _deactivate_button->set_sensitive(false); if (_mode == Mode::CONNECT_REMOTE) { _url_entry->set_sensitive(true); } else if (_mode == Mode::LAUNCH_REMOTE) { _port_spinbutton->set_sensitive(true); } _progress_label->set_text(std::string("Disconnected")); } } void ConnectWindow::set_connecting_widget_states() { if (!_widgets_loaded) { return; } _connect_button->set_sensitive(false); _disconnect_button->set_label("gtk-cancel"); _disconnect_button->set_sensitive(true); _server_radio->set_sensitive(false); _launch_radio->set_sensitive(false); _internal_radio->set_sensitive(false); _url_entry->set_sensitive(false); _port_spinbutton->set_sensitive(false); } bool ConnectWindow::connect_remote(const URI& uri) { ingen::World& world = _app->world(); auto sci = std::make_shared(); auto qi = std::make_shared(sci); const std::shared_ptr iface{world.new_interface(uri, qi)}; if (iface) { world.set_interface(iface); _app->attach(qi); _app->register_callbacks(); return true; } return false; } /** Set up initial connect stage and launch connect callback. */ void ConnectWindow::connect(bool existing) { if (_app->client()) { error("Already connected"); return; } _attached = false; set_connecting_widget_states(); _connect_stage = 0; ingen::World& world = _app->world(); if (_mode == Mode::CONNECT_REMOTE) { std::string uri_str = world.conf().option("connect").ptr(); if (existing) { uri_str = world.interface()->uri(); _connect_stage = 1; auto client = std::dynamic_pointer_cast( world.interface()); if (client) { _app->attach(client->respondee()); _app->register_callbacks(); } else { error("Connected with invalid client interface type"); return; } } else if (_widgets_loaded) { uri_str = _url_entry->get_text(); } if (!URI::is_valid(uri_str)) { error(fmt("Invalid socket URI %1%", uri_str)); return; } _connect_uri = URI(uri_str); } else if (_mode == Mode::LAUNCH_REMOTE) { const std::string port = std::to_string(_port_spinbutton->get_value_as_int()); const char* cmd[] = { "ingen", "-e", "-E", port.c_str(), nullptr }; if (!raul::Process::launch(cmd)) { error("Failed to launch engine process"); return; } _connect_uri = URI(std::string("tcp://localhost:") + port); } else if (_mode == Mode::INTERNAL) { if (!world.engine()) { if (!world.load_module("server")) { error("Failed to load server module"); return; } if (!world.load_module("jack")) { error("Failed to load jack module"); return; } if (!world.engine()->activate()) { error("Failed to activate engine"); return; } } } set_connecting_widget_states(); if (_widgets_loaded) { _progress_label->set_text("Connecting..."); } Glib::signal_timeout().connect( sigc::mem_fun(this, &ConnectWindow::gtk_callback), 33); } void ConnectWindow::disconnect() { _connect_stage = -1; _attached = false; _app->detach(); set_connected_to(nullptr); if (!_widgets_loaded) { return; } _activate_button->set_sensitive(false); _deactivate_button->set_sensitive(false); _progress_bar->set_fraction(0.0); _connect_button->set_sensitive(true); _disconnect_button->set_sensitive(false); } void ConnectWindow::activate() { if (!_app->interface()) { return; } _app->interface()->set_property(URI("ingen:/driver"), _app->uris().ingen_enabled, _app->forge().make(true)); } void ConnectWindow::deactivate() { if (!_app->interface()) { return; } _app->interface()->set_property(URI("ingen:/driver"), _app->uris().ingen_enabled, _app->forge().make(false)); } void ConnectWindow::on_show() { if (!_widgets_loaded) { load_widgets(); } if (_attached) { set_connected_to(_app->interface()); } Gtk::Dialog::on_show(); } void ConnectWindow::load_widgets() { _xml->get_widget("connect_icon", _icon); _xml->get_widget("connect_progress_bar", _progress_bar); _xml->get_widget("connect_progress_label", _progress_label); _xml->get_widget("connect_server_radiobutton", _server_radio); _xml->get_widget("connect_url_entry", _url_entry); _xml->get_widget("connect_launch_radiobutton", _launch_radio); _xml->get_widget("connect_port_spinbutton", _port_spinbutton); _xml->get_widget("connect_internal_radiobutton", _internal_radio); _xml->get_widget("connect_activate_button", _activate_button); _xml->get_widget("connect_deactivate_button", _deactivate_button); _xml->get_widget("connect_disconnect_button", _disconnect_button); _xml->get_widget("connect_connect_button", _connect_button); _xml->get_widget("connect_quit_button", _quit_button); _server_radio->signal_toggled().connect( sigc::mem_fun(this, &ConnectWindow::server_toggled)); _launch_radio->signal_toggled().connect( sigc::mem_fun(this, &ConnectWindow::launch_toggled)); _internal_radio->signal_clicked().connect( sigc::mem_fun(this, &ConnectWindow::internal_toggled)); _activate_button->signal_clicked().connect( sigc::mem_fun(this, &ConnectWindow::activate)); _deactivate_button->signal_clicked().connect( sigc::mem_fun(this, &ConnectWindow::deactivate)); _disconnect_button->signal_clicked().connect( sigc::mem_fun(this, &ConnectWindow::disconnect)); _connect_button->signal_clicked().connect( sigc::bind(sigc::mem_fun(this, &ConnectWindow::connect), false)); _quit_button->signal_clicked().connect( sigc::mem_fun(this, &ConnectWindow::quit_clicked)); _url_entry->set_text(_app->world().conf().option("connect").ptr()); if (URI::is_valid(_url_entry->get_text())) { _connect_uri = URI(_url_entry->get_text()); } _port_spinbutton->set_range(1, std::numeric_limits::max()); _port_spinbutton->set_increments(1, 100); _port_spinbutton->set_value( _app->world().conf().option("engine-port").get()); _progress_bar->set_pulse_step(0.01); _widgets_loaded = true; server_toggled(); } void ConnectWindow::on_hide() { Gtk::Dialog::on_hide(); if (_app->window_factory()->num_open_graph_windows() == 0) { quit(); } } void ConnectWindow::quit_clicked() { if (_app->quit(this)) { _quit_flag = true; } } void ConnectWindow::server_toggled() { _url_entry->set_sensitive(true); _port_spinbutton->set_sensitive(false); _mode = Mode::CONNECT_REMOTE; } void ConnectWindow::launch_toggled() { _url_entry->set_sensitive(false); _port_spinbutton->set_sensitive(true); _mode = Mode::LAUNCH_REMOTE; } void ConnectWindow::internal_toggled() { _url_entry->set_sensitive(false); _port_spinbutton->set_sensitive(false); _mode = Mode::INTERNAL; } void ConnectWindow::next_stage() { static const char* labels[] = { "Connecting...", "Pinging engine...", "Attaching to engine...", "Requesting root graph...", "Waiting for root graph...", "Connected" }; ++_connect_stage; if (_widgets_loaded) { _progress_label->set_text(labels[_connect_stage]); } } bool ConnectWindow::gtk_callback() { /* If I call this a "state machine" it's not ugly code any more */ if (_quit_flag) { return false; // deregister this callback } // Timing stuff for repeated attach attempts timeval now = {}; gettimeofday(&now, nullptr); static const timeval start = now; static timeval last = now; static unsigned attempts = 0; // Show if attempted connection goes on for a noticeable amount of time if (!is_visible()) { const float ms_since_start = (now.tv_sec - start.tv_sec) * 1000.0f + (now.tv_usec - start.tv_usec) * 0.001f; if (ms_since_start > 500) { present(); set_connecting_widget_states(); } } if (_connect_stage == 0) { const float ms_since_last = (now.tv_sec - last.tv_sec) * 1000.0f + (now.tv_usec - last.tv_usec) * 0.001f; if (ms_since_last >= 250) { last = now; if (_mode == Mode::INTERNAL) { auto client = std::make_shared(); _app->world().interface()->set_respondee(client); _app->attach(client); _app->register_callbacks(); next_stage(); } else if (connect_remote(_connect_uri)) { next_stage(); } } } else if (_connect_stage == 1) { _attached = false; _app->sig_client()->signal_message().connect( sigc::mem_fun(this, &ConnectWindow::message)); _ping_id = g_random_int_range(1, std::numeric_limits::max()); _app->interface()->set_response_id(_ping_id); _app->interface()->get(URI("ingen:/engine")); last = now; attempts = 0; next_stage(); } else if (_connect_stage == 2) { if (_attached) { next_stage(); } else { const float ms_since_last = (now.tv_sec - last.tv_sec) * 1000.0f + (now.tv_usec - last.tv_usec) * 0.001f; if (attempts > 10) { error("Failed to ping engine"); _connect_stage = -1; } else if (ms_since_last > 1000) { _app->interface()->set_response_id(_ping_id); _app->interface()->get(URI("ingen:/engine")); last = now; ++attempts; } } } else if (_connect_stage == 3) { _app->interface()->get(URI(main_uri().string() + "/")); next_stage(); } else if (_connect_stage == 4) { if (!_app->store()->empty()) { auto root = std::dynamic_pointer_cast( _app->store()->object(raul::Path("/"))); if (root) { set_connected_to(_app->interface()); _app->window_factory()->present_graph(root); next_stage(); } } } else if (_connect_stage == 5) { hide(); _connect_stage = 0; // set ourselves up for next time (if there is one) _finished_connecting = true; _app->interface()->set_response_id(1); return false; // deregister this callback } if (_widgets_loaded) { _progress_bar->pulse(); } if (_connect_stage == -1) { // we were cancelled if (_widgets_loaded) { _icon->set(Gtk::Stock::DISCONNECT, Gtk::ICON_SIZE_LARGE_TOOLBAR); _progress_bar->set_fraction(0.0); _connect_button->set_sensitive(true); _disconnect_button->set_sensitive(false); _disconnect_button->set_label("gtk-disconnect"); _progress_label->set_text(std::string("Disconnected")); } return false; } return true; } void ConnectWindow::quit() { _quit_flag = true; Gtk::Main::quit(); } } // namespace ingen::gui