/*
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(bool(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