// License: Apache 2.0. See LICENSE file in root directory. // Copyright(c) 2017 Intel Corporation. All Rights Reserved. #include #include "viewer.h" #include "os.h" #include "ux-window.h" #include "fw-update-helper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INTERNAL_FW #include "common/fw/D4XX_FW_Image.h" #else #define FW_D4XX_FW_IMAGE_VERSION "" #endif // INTERNAL_FW using namespace rs2; using namespace rs400; void update_viewer_configuration(viewer_model& viewer_model) { // Hide options from the Viewer application viewer_model._hidden_options.emplace(RS2_OPTION_ENABLE_IR_REFLECTIVITY); } bool add_remote_device(context& ctx, std::string address) { // Return true if a network device exists at the given address; throw an error otherwise // For now, this is unsupported! return false; } void add_playback_device( context & ctx, std::shared_ptr< device_models_list > device_models, std::string & error_message, viewer_model & viewer_model, const std::string & file ) { bool was_loaded = false; bool failed = false; try { auto dev = ctx.load_device(file); was_loaded = true; device_models->emplace_back( // Will cause the new device to appear in the left panel new device_model( dev, error_message, viewer_model ) ); if (auto p = dev.as()) { auto filename = p.file_name(); p.set_status_changed_callback( [&viewer_model, weak_device_models = std::weak_ptr< device_models_list >( device_models ), filename]( rs2_playback_status status ) { auto device_models = weak_device_models.lock(); if( ! device_models ) return; if( status == RS2_PLAYBACK_STATUS_STOPPED ) { auto it = std::find_if( device_models->begin(), device_models->end(), [&]( const std::unique_ptr< device_model > & dm ) { if( auto p = dm->dev.as< playback >() ) return p.file_name() == filename; return false; } ); if( it != device_models->end() ) { auto subs = ( *it )->subdevices; if( ( *it )->_playback_repeat ) { // Calling from different since playback callback is from reading thread std::thread{ [subs, &viewer_model, it]() { if( ! ( *it )->dev_syncer ) ( *it )->dev_syncer = viewer_model.syncer->create_syncer(); for( auto && sub : subs ) { if( sub->streaming ) { auto profiles = sub->get_selected_profiles(); sub->play( profiles, viewer_model, ( *it )->dev_syncer ); } } } } .detach(); } else { for( auto && sub : subs ) { if( sub->streaming ) { sub->stop( viewer_model.not_model ); } } } } } } ); } } catch (const error& e) { error_message = rsutils::string::from() << "Failed to load file " << file << ". Reason: " << error_to_string(e); failed = true; } catch (const std::exception& e) { error_message = rsutils::string::from() << "Failed to load file " << file << ". Reason: " << e.what(); failed = true; } if (failed && was_loaded) { try { ctx.unload_device(file); } catch (...) {} } } // This function is called every frame // If between the frames there was an asyncronous connect/disconnect event // the function will pick up on this and add the device to the viewer bool refresh_devices(std::mutex& m, context& ctx, device_changes& devices_connection_changes, std::vector& current_connected_devices, std::vector>& device_names, device_models_list& device_models, viewer_model& viewer_model, std::string& error_message) { event_information info({}, {}); if (!devices_connection_changes.try_get_next_changes(info)) return false; try { //Remove disconnected auto dev_itr = begin(current_connected_devices); while (dev_itr != end(current_connected_devices)) { auto dev = *dev_itr; if (info.was_removed(dev)) { //Notify change viewer_model.not_model->add_notification({ get_device_name(dev).first + " Disconnected\n", RS2_LOG_SEVERITY_INFO, RS2_NOTIFICATION_CATEGORY_UNKNOWN_ERROR }); //Remove from devices auto dev_model_itr = std::find_if(begin(device_models), end(device_models), [&](const std::unique_ptr& other) { return get_device_name(other->dev) == get_device_name(dev); }); if (dev_model_itr != end(device_models)) { for (auto&& s : (*dev_model_itr)->subdevices) s->streaming = false; dev_model_itr->reset(); device_models.erase(dev_model_itr); if (device_models.size() == 0) { viewer_model.ppf.depth_stream_active = false; // Stopping post processing filter rendering thread in case of disconnection viewer_model.ppf.stop(); } } auto dev_name_itr = std::find(begin(device_names), end(device_names), get_device_name(dev)); if (dev_name_itr != end(device_names)) device_names.erase(dev_name_itr); dev_itr = current_connected_devices.erase(dev_itr); continue; } ++dev_itr; } //Add connected static bool initial_refresh = true; try { for (auto dev : info.get_new_devices()) { auto dev_descriptor = get_device_name(dev); device_names.push_back(dev_descriptor); bool added = false; if (device_models.size() == 0 && dev.supports(RS2_CAMERA_INFO_NAME) && std::string(dev.get_info(RS2_CAMERA_INFO_NAME)) != "Platform Camera" && std::string(dev.get_info(RS2_CAMERA_INFO_NAME)).find("IP Device") == std::string::npos) { device_models.emplace_back(new device_model(dev, error_message, viewer_model)); viewer_model.not_model->add_log( rsutils::string::from() << ( *device_models.rbegin() )->dev.get_info( RS2_CAMERA_INFO_NAME ) << " was selected as a default device" ); added = true; } if (!initial_refresh) { if (added || dev.is()) viewer_model.not_model->add_notification({ dev_descriptor.first + " Connected\n", RS2_LOG_SEVERITY_INFO, RS2_NOTIFICATION_CATEGORY_UNKNOWN_ERROR }); else if (added || dev.supports(RS2_CAMERA_INFO_IP_ADDRESS)) viewer_model.not_model->add_notification({ dev_descriptor.first + " Connected\n", RS2_LOG_SEVERITY_INFO, RS2_NOTIFICATION_CATEGORY_UNKNOWN_ERROR }); else viewer_model.not_model->add_notification({ dev_descriptor.first + " Connected\n", RS2_LOG_SEVERITY_INFO, RS2_NOTIFICATION_CATEGORY_UNKNOWN_ERROR }, [&device_models, &viewer_model, &error_message, dev] { auto device = dev; device_models.emplace_back( new device_model(device, error_message, viewer_model)); }); } current_connected_devices.push_back(dev); for (auto&& s : dev.query_sensors()) { s.set_notifications_callback([&, dev_descriptor](const notification& n) { if (n.get_category() == RS2_NOTIFICATION_CATEGORY_HARDWARE_EVENT) { auto data = n.get_serialized_data(); if (!data.empty()) { auto dev_model_itr = std::find_if(begin(device_models), end(device_models), [&](const std::unique_ptr& other) { return get_device_name(other->dev) == dev_descriptor; }); if (dev_model_itr == end(device_models)) return; (*dev_model_itr)->handle_hardware_events(data); } } viewer_model.not_model->add_notification({ n.get_description(), n.get_severity(), n.get_category() }); }); } } } catch (std::exception& e) { std::stringstream s; s << "Couldn't refresh devices - " << e.what(); log(RS2_LOG_SEVERITY_WARN, s.str().c_str()); } initial_refresh = false; } catch (const error& e) { error_message = error_to_string(e); } catch (const std::exception& e) { error_message = e.what(); } catch (...) { error_message = "Unknown error"; } return true; } int main(int argc, const char** argv) try { TCLAP::CmdLine cmd( "realsense-viewer", ' ', RS2_API_FULL_VERSION_STR ); #ifdef BUILD_EASYLOGGINGPP TCLAP::SwitchArg debug_arg( "", "debug", "Turn on LibRS debug logs" ); cmd.add( debug_arg ); #endif TCLAP::SwitchArg only_sw_arg( "", "sw-only", "Show only software devices (playback, DDS, etc. -- but not USB/HID/etc.)" ); cmd.add( only_sw_arg ); // There isn't always a console... so if we need to show an error/usage, we need to enable it: class cmdline_output : public TCLAP::StdOutput { typedef TCLAP::StdOutput super; public: void usage( TCLAP::CmdLineInterface & c ) override { rsutils::os::ensure_console(); super::usage( c ); } void version( TCLAP::CmdLineInterface & c ) override { rsutils::os::ensure_console(); super::version( c ); } void failure( TCLAP::CmdLineInterface & c, TCLAP::ArgException & e ) override { rsutils::os::ensure_console(); super::failure( c, e ); } } our_cmdline_output; cmd.setOutput( &our_cmdline_output ); cmd.parse( argc, argv ); #ifdef BUILD_EASYLOGGINGPP if( debug_arg.getValue() ) rsutils::os::ensure_console(); #if defined( WIN32 ) // In Windows, we have no console unless we start the viewer from one; without one, calling log_to_console will // ensure a console, so we want to avoid it by default! if( GetStdHandle( STD_OUTPUT_HANDLE ) ) #endif rs2::log_to_console( debug_arg.getValue() ? RS2_LOG_SEVERITY_DEBUG : RS2_LOG_SEVERITY_INFO ); #endif std::shared_ptr device_models = std::make_shared(); rsutils::json settings = rsutils::json::object(); if( only_sw_arg.getValue() ) { #if defined( BUILD_WITH_DDS ) settings["dds"]["enabled"]; // null: remove global dds:false or dds/enabled:false, if any #endif settings["device-mask"] = RS2_PRODUCT_LINE_SW_ONLY | RS2_PRODUCT_LINE_ANY; } context ctx( settings.dump() ); ux_window window("Intel RealSense Viewer", ctx); // Create RealSense Context device_changes devices_connection_changes(ctx); std::vector> device_names; std::string error_message; std::string label; device_model* device_to_remove = nullptr; bool is_ip_device_connected = false; std::string ip_address; #ifdef BUILD_EASYLOGGINGPP bool const disable_log_to_console = debug_arg.getValue(); #else bool const disable_log_to_console = false; #endif viewer_model viewer_model( ctx, disable_log_to_console ); update_viewer_configuration(viewer_model); std::vector connected_devs; std::mutex m; #ifdef BUILD_EASYLOGGINGPP std::weak_ptr notifications = viewer_model.not_model; rs2::log_to_callback( RS2_LOG_SEVERITY_INFO, [notifications]( rs2_log_severity severity, rs2::log_message const& msg ) { if (auto not_model = notifications.lock()) { not_model->output.add_log(severity, msg.filename(), (int)(msg.line_number()), msg.raw()); } }); #endif window.on_file_drop = [&](std::string filename) { add_playback_device(ctx, device_models, error_message, viewer_model, filename); if (!error_message.empty()) { viewer_model.not_model->add_notification({ error_message, RS2_LOG_SEVERITY_ERROR, RS2_NOTIFICATION_CATEGORY_UNKNOWN_ERROR }); } }; for (int i = 1; i < argc; i++) { try { const char* arg = argv[i]; std::ifstream file(arg); if (!file.good()) continue; add_playback_device(ctx, device_models, error_message, viewer_model, arg); } catch (const rs2::error& e) { error_message = error_to_string(e); } catch (const std::exception& e) { error_message = e.what(); } } window.on_load = [&]() { refresh_devices(m, ctx, devices_connection_changes, connected_devs, device_names, *device_models, viewer_model, error_message); return true; }; if (argc == 2) { try { is_ip_device_connected = add_remote_device(ctx, argv[1]); } catch (std::runtime_error e) { error_message = e.what(); } } // Closing the window while (window) { auto device_changed = refresh_devices(m, ctx, devices_connection_changes, connected_devs, device_names, *device_models, viewer_model, error_message); auto output_height = viewer_model.get_output_height(); rect viewer_rect = { viewer_model.panel_width, viewer_model.panel_y, window.width() - viewer_model.panel_width, window.height() - viewer_model.panel_y - output_height }; // Flags for pop-up window - no window resize, move or collaps auto flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoBringToFrontOnFocus; ImGui::SetNextWindowPos({ 0, 0 }); ImGui::SetNextWindowSize({ viewer_model.panel_width, viewer_model.panel_y }); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::Begin("Add Device Panel", nullptr, flags); ImGui::PushFont(window.get_large_font()); ImGui::PushStyleColor(ImGuiCol_PopupBg, from_rgba(230, 230, 230, 255)); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, from_rgba(0, 0xae, 0xff, 255)); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, from_rgba(255, 255, 255, 255)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(5, 5)); ImGui::SetNextWindowPos({ 0, viewer_model.panel_y }); std::string add_source_button_text = rsutils::string::from() << " " << textual_icons::plus_circle << " Add Source\t\t\t\t\t\t\t\t\t\t\t"; if (ImGui::Button(add_source_button_text.c_str(), { viewer_model.panel_width - 1, viewer_model.panel_y })) ImGui::OpenPopup("select"); auto new_devices_count = device_names.size() + 1; for (auto&& dev_model : *device_models) { auto connected_devs_itr = std::find_if(begin(connected_devs), end(connected_devs), [&](const device& d) { return get_device_name(d) == get_device_name(dev_model->dev); }); if (connected_devs_itr != end(connected_devs) || dev_model->dev.is()) new_devices_count--; } ImGui::PushFont(window.get_font()); int multiline_devices_names = 0; for (size_t i = 0; i < device_names.size(); i++) { if (device_names[i].first.find("\n") != std::string::npos) { bool show_device_in_list = true; for (auto&& dev_model : *device_models) { if (get_device_name(dev_model->dev) == device_names[i]) { show_device_in_list = false; break; } } if (show_device_in_list) { multiline_devices_names++; } } } float line_h = ImGui::GetTextLineHeightWithSpacing() + 2; float separator_h = new_devices_count > 1 ? ImGui::GetStyle().ItemSpacing.y : 0; float popup_select_h = line_h * ( new_devices_count + multiline_devices_names ) + separator_h + ( is_ip_device_connected ? 0 : line_h ); ImVec2 popup_select_size = { viewer_model.panel_width, popup_select_h }; ImGui::SetNextWindowSize( popup_select_size ); if (ImGui::BeginPopup("select")) { ImGui::PushStyleColor(ImGuiCol_Text, dark_grey); ImGui::Columns(1, "DevicesList", false); for (int i = 0; i < device_names.size(); i++) { bool skip = false; for (auto&& dev_model : *device_models) if (get_device_name(dev_model->dev) == device_names[i]) skip = true; if (skip) continue; auto dev = connected_devs[i]; std::string dev_type; if( dev.supports( RS2_CAMERA_INFO_USB_TYPE_DESCRIPTOR ) ) { dev_type = "USB"; dev_type += dev.get_info( RS2_CAMERA_INFO_USB_TYPE_DESCRIPTOR ); } else if( dev.supports( RS2_CAMERA_INFO_PRODUCT_ID ) ) { dev_type = dev.get_info( RS2_CAMERA_INFO_PRODUCT_ID ); if( dev_type == "ABCD" ) // Specific for D457 dev_type = "GMSL"; } std::string line = rsutils::string::from() << dev.get_info( RS2_CAMERA_INFO_NAME ) << " (" << dev_type << ") S/N " << device_names[i].second.c_str(); ImGui::PushID(static_cast(i)); if (ImGui::Selectable(line.c_str(), false, ImGuiSelectableFlags_SpanAllColumns)/* || switch_to_newly_loaded_device*/) { try { device_models->emplace_back(new device_model(dev, error_message, viewer_model)); } catch (const error& e) { error_message = error_to_string(e); } catch (const std::exception& e) { error_message = e.what(); } } ImGui::PopID(); } if (new_devices_count > 1) ImGui::Separator(); if (ImGui::Selectable("Load Recorded Sequence", false, ImGuiSelectableFlags_SpanAllColumns)) { if (auto ret = file_dialog_open(open_file, "ROS-bag\0*.bag\0", NULL, NULL)) { add_playback_device(ctx, device_models, error_message, viewer_model, ret); } } ImGui::NextColumn(); bool close_ip_popup = false; if (!is_ip_device_connected) { if (ImGui::Selectable("Add Network Device", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_DontClosePopups)) { error_message = "RealSense Network Devices are unsupported at this time"; } float width = 300; float height = 125; float posx = window.width() * 0.5f - width * 0.5f; float posy = window.height() * 0.5f - height * 0.5f; ImGui::SetNextWindowPos({ posx, posy }); ImGui::SetNextWindowSize({ width, height }); ImGui::PushStyleColor(ImGuiCol_PopupBg, sensor_bg); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white); ImGui::PushStyleColor(ImGuiCol_Text, light_grey); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); if (ImGui::BeginPopupModal("Network Device", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) { ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 3); ImGui::SetCursorPosX(10); ImGui::Text("Connect to a Linux system running rs-server"); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5); static char ip_input[255]; std::copy(ip_address.begin(), ip_address.end(), ip_input); ip_input[ip_address.size()] = '\0'; ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5); ImGui::SetCursorPosX(10); ImGui::Text("Device IP: "); ImGui::SameLine(); //ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 1); ImGui::PushItemWidth(width - ImGui::GetCursorPosX() - 10); if (ImGui::GetWindowIsFocused() && !ImGui::IsAnyItemActive()) { ImGui::SetKeyboardFocusHere(); } ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_blue); ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 3); if (ImGui::InputText("##ip", ip_input, 255)) { ip_address = ip_input; } ImGui::PopStyleColor(); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 6); ImGui::PopItemWidth(); ImGui::SetCursorPosX(width / 2 - 105); if (ImGui::ButtonEx("OK", { 100.f, 25.f }) || ImGui::IsKeyDown(GLFW_KEY_ENTER) || ImGui::IsKeyDown(GLFW_KEY_KP_ENTER)) { try { is_ip_device_connected = add_remote_device(ctx, ip_address); refresh_devices(m, ctx, devices_connection_changes, connected_devs, device_names, *device_models, viewer_model, error_message); auto dev = connected_devs[connected_devs.size() - 1]; device_models->emplace_back(new device_model(dev, error_message, viewer_model)); config_file::instance().set(configurations::viewer::last_ip, ip_address); } catch (std::runtime_error e) { error_message = e.what(); } ip_address = ""; close_ip_popup = true; ImGui::CloseCurrentPopup(); } ImGui::SameLine(); ImGui::SetCursorPosX(width / 2 + 5); if (ImGui::Button("Cancel", { 100.f, 25.f }) || ImGui::IsKeyDown(GLFW_KEY_ESCAPE)) { ip_address = ""; close_ip_popup = true; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } ImGui::PopStyleColor(3); ImGui::PopStyleVar(1); } if (close_ip_popup) { ImGui::CloseCurrentPopup(); close_ip_popup = false; } ImGui::PopStyleColor(); ImGui::EndPopup(); } ImGui::PopFont(); ImGui::PopStyleVar(); ImGui::PopStyleColor(); ImGui::PopStyleColor(); ImGui::PopStyleColor(); ImGui::PopFont(); ImGui::End(); ImGui::PopStyleVar(); viewer_model.show_top_bar(window, viewer_rect, *device_models); auto output_rect = rect{ viewer_model.panel_width, window.height() - viewer_model.get_output_height(), window.width() - viewer_model.panel_width, float(viewer_model.get_output_height()) }; viewer_model.not_model->output.draw(window, output_rect, *device_models); // Set window position and size ImGui::SetNextWindowPos({ 0, viewer_model.panel_y }); ImGui::SetNextWindowSize({ viewer_model.panel_width, window.height() - viewer_model.panel_y }); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::PushStyleColor(ImGuiCol_WindowBg, sensor_bg); // ********************* // Creating window menus // ********************* ImGui::Begin("Control Panel", nullptr, flags | ImGuiWindowFlags_AlwaysVerticalScrollbar); if (device_models->size() > 0) { std::vector> draw_later; auto windows_width = ImGui::GetContentRegionMax().x; for (auto&& dev_model : *device_models) { dev_model->draw_controls(viewer_model.panel_width, viewer_model.panel_y, window, error_message, device_to_remove, viewer_model, windows_width, draw_later); } if (viewer_model.ppf.is_rendering()) { if (!std::any_of(device_models->begin(), device_models->end(), [](const std::unique_ptr& dm) { return dm->is_streaming(); })) { // Stopping post processing filter rendering thread viewer_model.ppf.stop(); } } ImGui::SetContentRegionWidth(windows_width); auto pos = ImGui::GetCursorScreenPos(); auto h = ImGui::GetWindowHeight(); if (h > pos.y - viewer_model.panel_y) { ImGui::GetWindowDrawList()->AddLine({ pos.x,pos.y }, { pos.x + viewer_model.panel_width,pos.y }, ImColor(from_rgba(0, 0, 0, 0xff))); ImRect bb(pos, ImVec2(pos.x + ImGui::GetContentRegionAvail().x, pos.y + ImGui::GetContentRegionAvail().y)); ImGui::GetWindowDrawList()->AddRectFilled(bb.GetTL(), bb.GetBR(), ImColor(dark_window_background)); } for (auto&& lambda : draw_later) { try { lambda(); } catch (const error& e) { error_message = error_to_string(e); } catch (const std::exception& e) { error_message = e.what(); } } if (device_to_remove) { if (auto p = device_to_remove->dev.as()) { ctx.unload_device(p.file_name()); } viewer_model.syncer->remove_syncer(device_to_remove->dev_syncer); auto it = std::find_if(begin(*device_models), end(*device_models), [&](const std::unique_ptr& other) { return get_device_name(other->dev) == get_device_name(device_to_remove->dev); }); if (it != device_models->end()) { it->reset(); device_models->erase(it); } device_to_remove = nullptr; } } else { const ImVec2 pos = ImGui::GetCursorScreenPos(); ImRect bb(pos, ImVec2(pos.x + ImGui::GetContentRegionAvail().x, pos.y + ImGui::GetContentRegionAvail().y)); ImGui::GetWindowDrawList()->AddRectFilled(bb.GetTL(), bb.GetBR(), ImColor(dark_window_background)); viewer_model.show_no_device_overlay(window.get_large_font(), 50, static_cast(viewer_model.panel_y + 50)); } ImGui::End(); ImGui::PopStyleVar(); ImGui::PopStyleColor(); // Fetch and process frames from queue viewer_model.handle_ready_frames(viewer_rect, window, static_cast(device_models->size()), error_message); } // Stopping post processing filter rendering thread viewer_model.ppf.stop(); // Stop all subdevices for (auto&& device_model : *device_models) for (auto&& sub : device_model->subdevices) { if (sub->streaming) sub->stop(viewer_model.not_model); } return EXIT_SUCCESS; } catch (const error & e) { std::cerr << "RealSense error calling " << e.get_failed_function() << "(" << e.get_failed_args() << "):\n " << e.what() << std::endl; return EXIT_FAILURE; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; }