Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 14 additions & 24 deletions Frasy/src/layers/main_application_layer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ void MainApplicationLayer::onAttach()
m_resultViewer = std::make_unique<ResultViewer>();
m_resultAnalyzer = std::make_unique<ResultAnalyzer>();
m_testViewer = std::make_unique<TestViewer>();
m_usbTreeViewer = std::make_unique<UsbTreeViewer>();
m_testViewer->SetInterface(this);

bool maximized = Interpreter::Get().getConfig().value("maximized", true);
Expand All @@ -62,6 +63,7 @@ void MainApplicationLayer::onAttach()
m_resultViewer->onAttach();
m_resultAnalyzer->onAttach();
m_testViewer->onAttach();
m_usbTreeViewer->onAttach();
m_resultAnalyzer->setGetTitle([this] { return m_orchestrator.getTitle(); });

m_orchestrator.setCanOpen(&m_canOpen);
Expand All @@ -80,6 +82,7 @@ void MainApplicationLayer::onDetach()
m_resultViewer->onDetach();
m_testViewer->onDetach();
m_resultAnalyzer->onDetach();
m_usbTreeViewer->onDetach();
}


Expand All @@ -100,6 +103,7 @@ void MainApplicationLayer::onUpdate(Brigerad::Timestep ts)
m_resultViewer->onUpdate(ts);
m_testViewer->onUpdate(ts);
m_resultAnalyzer->onUpdate(ts);
m_usbTreeViewer->onUpdate(ts);
}


Expand All @@ -122,6 +126,7 @@ void MainApplicationLayer::onImGuiRender()
if (ImGui::MenuItem("Test Viewer", "F6")) { makeTestViewerVisible(); }
if (ImGui::MenuItem("CANopen Viewer", "F7")) { makeCanOpenViewerVisible(); }
if (ImGui::MenuItem("Lua Profiler", "F8")) { m_renderProfiler = true; }
if (ImGui::MenuItem("USB Tree Viewer")) { m_usbTreeViewer->SetVisibility(true); }
ImGui::Separator();
if (m_noMove && ImGui::MenuItem("Unlock")) { m_noMove = false; }
if (!m_noMove && ImGui::MenuItem("Lock")) { m_noMove = true; }
Expand Down Expand Up @@ -151,6 +156,7 @@ void MainApplicationLayer::onImGuiRender()
m_resultViewer->onImGuiRender();
m_resultAnalyzer->onImGuiRender();
m_testViewer->onImGuiRender();
m_usbTreeViewer->onImGuiRender();

m_orchestrator.renderPopups();
handleResultAnalyserPopup();
Expand Down Expand Up @@ -180,34 +186,22 @@ void MainApplicationLayer::onEvent(Brigerad::Event& e)
}

void MainApplicationLayer::makeLogWindowVisible()
{
m_logWindow->SetVisibility(true);
}
{ m_logWindow->SetVisibility(true); }

void MainApplicationLayer::makeDeviceViewerVisible()
{
m_deviceViewer->setVisibility(true);
}
{ m_deviceViewer->setVisibility(true); }

void MainApplicationLayer::makeCanOpenViewerVisible()
{
m_canOpenViewer->setVisibility(true);
}
{ m_canOpenViewer->setVisibility(true); }

void MainApplicationLayer::makeResultViewerVisible()
{
m_resultViewer->setVisibility(true);
}
{ m_resultViewer->setVisibility(true); }

void MainApplicationLayer::makeResultAnalyzerVisible()
{
m_resultAnalyzer->setVisibility(true);
}
{ m_resultAnalyzer->setVisibility(true); }

void MainApplicationLayer::makeTestViewerVisible()
{
m_testViewer->SetVisibility(true);
}
{ m_testViewer->SetVisibility(true); }

void MainApplicationLayer::renderAbout()
{
Expand Down Expand Up @@ -539,12 +533,8 @@ void MainApplicationLayer::generate()
// m_orchestrator->Generate();
}
void MainApplicationLayer::setTestEnable(const std::string& sequence, const std::string& test, bool enable)
{
m_orchestrator.setTestEnable(sequence, test, enable);
}
{ m_orchestrator.setTestEnable(sequence, test, enable); }
void MainApplicationLayer::setSequenceEnable(const std::string& sequence, bool enable)
{
m_orchestrator.setSequenceEnable(sequence, enable);
}
{ m_orchestrator.setSequenceEnable(sequence, enable); }

} // namespace Frasy
2 changes: 2 additions & 0 deletions Frasy/src/layers/main_application_layer.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# include "result_analyzer.h"
# include "result_viewer.h"
# include "test_viewer.h"
# include "usb_tree_viewer.h"
# include "utils/communication/can_open/can_open.h"
# include "utils/lua/orchestrator/orchestrator.h"

Expand Down Expand Up @@ -93,6 +94,7 @@ class MainApplicationLayer : public Brigerad::Layer, public TestViewer::Interfac
std::unique_ptr<ResultViewer> m_resultViewer = nullptr;
std::unique_ptr<ResultAnalyzer> m_resultAnalyzer = nullptr;
std::unique_ptr<TestViewer> m_testViewer = nullptr;
std::unique_ptr<UsbTreeViewer> m_usbTreeViewer = nullptr;

Brigerad::Ref<Brigerad::Texture2D> m_run;
Brigerad::Ref<Brigerad::Texture2D> m_runWarn;
Expand Down
176 changes: 176 additions & 0 deletions Frasy/src/layers/usb_tree_viewer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/**
* @file usb_tree_viewer.cpp
* @author Sam Martel
* @date 2026-03-03
* @brief
*
* @copyright
* 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 <https://www.gnu.org/licenses/>.
*/
#include "usb_tree_viewer.h"

#include "utils/misc/visit.h"
#include "utils/usb_enumerator/usb_enumerator.h"

#include <imgui.h>

namespace Frasy {

namespace {
void RenderNodes(const std::vector<Usb::Node>& tree);

struct RenderRootHubInfo {
void operator()(const Usb::RootHubInfo& info) const
{
bool hovered = false;
if (ImGui::TreeNodeEx(&info, ImGuiTreeNodeFlags_DefaultOpen, "Root Hub: %s", info.hubName.c_str())) {
hovered = ImGui::IsItemHovered();
ImGui::BulletText("%d Nodes", info.nodes.size());
RenderNodes(info.nodes);

ImGui::TreePop();
}
else {
hovered = ImGui::IsItemHovered();
}

if (hovered) {
ImGui::BeginTooltip();
ImGui::Text("Hub Name: %s", info.leafName.c_str());
ImGui::EndTooltip();
}
}
};

struct RenderHostControllerInfo {
void operator()(const Usb::HostControllerInfo& info) const
{
bool hovered = false;
if (ImGui::TreeNodeEx(&info,
ImGuiTreeNodeFlags_DefaultOpen,
"Host Controller: %s - %s",
info.deviceDesc.c_str(),
info.driverKey.c_str())) {
hovered = ImGui::IsItemHovered();
RenderRootHubInfo {}(info.rootHub);
ImGui::TreePop();
}
else {
hovered = ImGui::IsItemHovered();
}
if (hovered) {
ImGui::BeginTooltip();
ImGui::Text("VendorID: %d, DeviceID: %d, SubSysId: %d, Revision: %d",
info.vendorId,
info.deviceId,
info.subSysId,
info.revision);
if (!info.busDeviceFunctionValid) { ImGui::Text("Invalid bus/device/function"); }
else {
ImGui::Text(
"Bus number: %d, device: %d, function: %d", info.busNumber, info.busDevice, info.busFunction);
}
ImGui::EndTooltip();
}
}
};

void RenderStringDescriptors(const Usb::UsbNodeConnectionInformationEx& connInfo,
const std::vector<Usb::StringDescriptorNode>& descriptors)
{
ImGui::BulletText("# of supported configurations: %d", connInfo.DeviceDescriptor.bNumConfigurations);
ImGui::BulletText("# of endpoints: %d", connInfo.NumberOfOpenPipes);

auto renderDesc = [&](std::string_view name, uint8_t idx) {
std::string_view str = "Absent";
if (idx != 0 && idx < descriptors.size()) { str = descriptors[idx].stringDescriptor.toUtf8(); }
ImGui::BulletText("%s: %s", name.data(), str.data());
};
renderDesc("Manufacturer", connInfo.DeviceDescriptor.iManufacturer);
renderDesc("Product", connInfo.DeviceDescriptor.iProduct);
renderDesc("Serial Number", connInfo.DeviceDescriptor.iSerialNumber);
renderDesc("Device Class", connInfo.DeviceDescriptor.bDeviceClass);
// The first descriptor is the language ID descriptors.
for (auto& descriptor : descriptors | std::views::drop(1)) {
ImGui::BulletText("Descriptor Index: %d, language ID: %d, descType: %d, data: %s",
descriptor.descriptorIndex,
descriptor.languageId,
descriptor.stringDescriptor.descriptorType,
descriptor.stringDescriptor.toUtf8().c_str());
}
}

struct RenderExternalHubInfo {
void operator()(const Usb::ExternalHubInfo& info) const
{
bool hovered = false;
if (ImGui::TreeNodeEx(&info, ImGuiTreeNodeFlags_DefaultOpen, "%s", info.leafName.c_str())) {
hovered = ImGui::IsItemHovered();
ImGui::BulletText("%d Nodes", info.nodes.size());
RenderNodes(info.nodes);

ImGui::TreePop();
}
else {
hovered = ImGui::IsItemHovered();
}

if (hovered) {
ImGui::BeginTooltip();
ImGui::Text("Hub Name: %s", info.hubName.c_str());
RenderStringDescriptors(info.connectionInfo, info.stringDescriptors);
ImGui::EndTooltip();
}
}
};

struct RenderDeviceInfo {
void operator()(const Usb::DeviceInfo& info) const
{
ImGui::BulletText("%s", info.leafName.c_str());
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
RenderStringDescriptors(info.connectionInfo, info.stringDescriptors);
ImGui::EndTooltip();
}
}
};

void RenderNodes(const std::vector<Usb::Node>& tree)
{
for (const auto& node : tree) {
visit(node, RenderHostControllerInfo {}, RenderRootHubInfo {}, RenderExternalHubInfo {}, RenderDeviceInfo {});
}
}
} // namespace

UsbTreeViewer::UsbTreeViewer() : Layer("UsbTreeViewer")
{
}

void UsbTreeViewer::onImGuiRender()
{
if (!m_isVisible) { return; }

if (ImGui::Begin("USB Tree", &m_isVisible)) {
bool isRefreshing = m_isRefreshing; // To avoid TOCTOU :D
ImGui::Text("%d nodes found %s", m_nodes.size(), isRefreshing ? "(Refreshing...)" : "");
if (ImGui::Button("Refresh")) {
m_refreshFuture = std::async(std::launch::async, [this] {
m_isRefreshing = true;
m_nodes = Usb::EnumerateUsbTree();
m_isRefreshing = false;
});
}
if (!isRefreshing) { RenderNodes(m_nodes); }
}
ImGui::End();
}
} // namespace Frasy
50 changes: 50 additions & 0 deletions Frasy/src/layers/usb_tree_viewer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @file usb_tree_viewer.h
* @author Sam Martel
* @date 2026-03-03
* @brief
*
* @copyright
* 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 <https://www.gnu.org/licenses/>.
*/


#ifndef FRASY_SRC_LAYERS_USB_TREE_VIEWER_H
#define FRASY_SRC_LAYERS_USB_TREE_VIEWER_H
#include "Brigerad/Core/Layer.h"

#include "utils/usb_enumerator/external_hub_info.h"
#include "utils/usb_enumerator/host_controller_info.h"
#include "utils/usb_enumerator/root_hub_info.h"
#include "utils/usb_enumerator/usb_device_info.h"
#include "utils/usb_enumerator/usb_node.h"

#include <atomic>
#include <future>

namespace Frasy {
class UsbTreeViewer : public Brigerad::Layer {
public:
UsbTreeViewer();
~UsbTreeViewer() override = default;

void onImGuiRender() override;

void SetVisibility(bool visibility) { m_isVisible = visibility; }

private:
bool m_isVisible = false;
std::atomic<bool> m_isRefreshing = false;
std::future<void> m_refreshFuture;
std::vector<Usb::Node> m_nodes;
};
} // namespace Frasy

#endif // FRASY_SRC_LAYERS_USB_TREE_VIEWER_H
Loading