From 5419f57b3a63f581dde6b0485624f0a2f698d878 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:18:10 -0700 Subject: [PATCH] cabana: de-QT streams (#38718) --- openpilot/tools/cabana/cabana.cc | 3 ++- openpilot/tools/cabana/mainwin.cc | 3 +++ .../tools/cabana/streams/abstractstream.h | 1 + .../tools/cabana/streams/devicestream.cc | 26 +++++++------------ openpilot/tools/cabana/streams/devicestream.h | 7 ++--- .../tools/cabana/streams/replaystream.cc | 18 ++++++------- openpilot/tools/cabana/streamselector.cc | 5 +++- openpilot/tools/cabana/utils/util.cc | 22 ++++++++++++---- openpilot/tools/cabana/utils/util.h | 1 + 9 files changed, 50 insertions(+), 36 deletions(-) diff --git a/openpilot/tools/cabana/cabana.cc b/openpilot/tools/cabana/cabana.cc index e3a185034..6cc6046be 100644 --- a/openpilot/tools/cabana/cabana.cc +++ b/openpilot/tools/cabana/cabana.cc @@ -148,7 +148,7 @@ int main(int argc, char *argv[]) { if (args.msgq) { stream = new DeviceStream(); } else if (!args.zmq.empty()) { - stream = new DeviceStream(QString::fromStdString(args.zmq)); + stream = new DeviceStream(args.zmq); } else if (args.panda || !args.panda_serial.empty()) { try { stream = new PandaStream({.serial = args.panda_serial}); @@ -175,6 +175,7 @@ int main(int argc, char *argv[]) { } if (!route.isEmpty()) { auto replay_stream = std::make_unique(); + Connection err = replay_stream->error.connect([](const std::string &msg) { fprintf(stderr, "%s\n", msg.c_str()); }); if (!replay_stream->loadRoute(route.toStdString(), args.data_dir, replay_flags, args.auto_source)) { return 0; } diff --git a/openpilot/tools/cabana/mainwin.cc b/openpilot/tools/cabana/mainwin.cc index 3ae5896a2..939aade26 100644 --- a/openpilot/tools/cabana/mainwin.cc +++ b/openpilot/tools/cabana/mainwin.cc @@ -358,6 +358,9 @@ void MainWindow::startStream(AbstractStream *stream, QString dbc_file) { delete video_splitter; can = stream; // take ownership + stream_connections_.push_back(can->error.connect([this](const std::string &msg) { + QMessageBox::warning(this, tr("Error"), QString::fromStdString(msg)); + })); can->start(); loadFile(dbc_file); diff --git a/openpilot/tools/cabana/streams/abstractstream.h b/openpilot/tools/cabana/streams/abstractstream.h index 4f7c0dc3e..b992b086f 100644 --- a/openpilot/tools/cabana/streams/abstractstream.h +++ b/openpilot/tools/cabana/streams/abstractstream.h @@ -64,6 +64,7 @@ public: Observable> &> timeRangeChanged; Observable eventsMerged; Observable *, bool> msgsReceived; + Observable error; SourceSet sources; diff --git a/openpilot/tools/cabana/streams/devicestream.cc b/openpilot/tools/cabana/streams/devicestream.cc index 6d580cc91..3a338c7f5 100644 --- a/openpilot/tools/cabana/streams/devicestream.cc +++ b/openpilot/tools/cabana/streams/devicestream.cc @@ -9,17 +9,16 @@ #include #include #include +#include #include #include #include "openpilot/cereal/services.h" - -#include -#include +#include "tools/cabana/utils/util.h" // DeviceStream -DeviceStream::DeviceStream(QString address) : zmq_address(address) { +DeviceStream::DeviceStream(std::string address) : zmq_address(std::move(address)) { } DeviceStream::~DeviceStream() { @@ -46,19 +45,16 @@ void DeviceStream::stopBridge() { } void DeviceStream::start() { - if (!zmq_address.isEmpty()) { + if (!zmq_address.empty()) { stopBridge(); - const std::string path = (std::filesystem::path(QCoreApplication::applicationDirPath().toStdString()) / - "../../cereal/messaging/bridge").lexically_normal().string(); - const std::string addr = zmq_address.toStdString(); + const std::string path = (executableDir() / "../../cereal/messaging/bridge").lexically_normal().string(); const char *can_filter = "/\"can/\""; // Self-pipe: write end is CLOEXEC so it closes on successful exec. If exec // fails, the child writes errno and the parent aborts stream start. int err_pipe[2] = {-1, -1}; if (::pipe(err_pipe) != 0) { - QMessageBox::warning(nullptr, "Error", - QString("Failed to start bridge: %1").arg(QString::fromLocal8Bit(strerror(errno)))); + error(std::string("Failed to start bridge: ") + strerror(errno)); return; } @@ -66,7 +62,7 @@ void DeviceStream::start() { if (pid == 0) { ::close(err_pipe[0]); ::fcntl(err_pipe[1], F_SETFD, FD_CLOEXEC); - execl(path.c_str(), path.c_str(), addr.c_str(), can_filter, static_cast(nullptr)); + execl(path.c_str(), path.c_str(), zmq_address.c_str(), can_filter, static_cast(nullptr)); const int err = errno; (void)!::write(err_pipe[1], &err, sizeof(err)); _exit(127); @@ -75,8 +71,7 @@ void DeviceStream::start() { ::close(err_pipe[1]); if (pid < 0) { ::close(err_pipe[0]); - QMessageBox::warning(nullptr, "Error", - QString("Failed to start bridge: %1").arg(QString::fromLocal8Bit(strerror(errno)))); + error(std::string("Failed to start bridge: ") + strerror(errno)); return; } @@ -87,8 +82,7 @@ void DeviceStream::start() { // Child failed to exec; reap and surface the error. int status = 0; ::waitpid(pid, &status, 0); - QMessageBox::warning(nullptr, "Error", - QString("Failed to start bridge: %1").arg(QString::fromLocal8Bit(strerror(exec_errno)))); + error(std::string("Failed to start bridge: ") + strerror(exec_errno)); return; } @@ -99,7 +93,7 @@ void DeviceStream::start() { } void DeviceStream::streamThread() { - zmq_address.isEmpty() ? unsetenv("ZMQ") : setenv("ZMQ", "1", 1); + zmq_address.empty() ? unsetenv("ZMQ") : setenv("ZMQ", "1", 1); std::unique_ptr context(Context::create()); std::unique_ptr sock(SubSocket::create(context.get(), "can", "127.0.0.1", false, true, services.at("can").queue_size)); diff --git a/openpilot/tools/cabana/streams/devicestream.h b/openpilot/tools/cabana/streams/devicestream.h index 71205e5a8..3770d952a 100644 --- a/openpilot/tools/cabana/streams/devicestream.h +++ b/openpilot/tools/cabana/streams/devicestream.h @@ -2,14 +2,15 @@ #include "tools/cabana/streams/livestream.h" +#include #include class DeviceStream : public LiveStream { public: - DeviceStream(QString address = {}); + DeviceStream(std::string address = {}); ~DeviceStream(); inline std::string routeName() const override { - return "Live Streaming From " + (zmq_address.isEmpty() ? std::string("127.0.0.1") : zmq_address.toStdString()); + return "Live Streaming From " + (zmq_address.empty() ? std::string("127.0.0.1") : zmq_address); } protected: @@ -17,5 +18,5 @@ protected: void streamThread() override; void stopBridge(); pid_t bridge_pid = -1; - const QString zmq_address; + const std::string zmq_address; }; diff --git a/openpilot/tools/cabana/streams/replaystream.cc b/openpilot/tools/cabana/streams/replaystream.cc index e9df40059..81ed34e8d 100644 --- a/openpilot/tools/cabana/streams/replaystream.cc +++ b/openpilot/tools/cabana/streams/replaystream.cc @@ -1,6 +1,6 @@ #include "tools/cabana/streams/replaystream.h" -#include +#include #include "common/timing.h" #include "common/util.h" @@ -59,27 +59,25 @@ bool ReplayStream::loadRoute(const std::string &route, const std::string &data_d bool success = replay->load(); if (!success) { + std::string message; if (replay->lastRouteError() == RouteLoadError::Unauthorized) { auto auth_content = util::read_file(util::getenv("HOME") + "/.comma/auth.json"); - QString message; if (auth_content.empty()) { message = "Authentication Required. Please run the following command to authenticate:\n\n" "python3 openpilot/tools/lib/auth.py\n\n" "This will grant access to routes from your comma account."; } else { - message = QString("Access Denied. You do not have permission to access route:\n\n%1\n\n" - "This is likely a private route.").arg(QString::fromStdString(route)); + message = "Access Denied. You do not have permission to access route:\n\n" + route + "\n\n" + "This is likely a private route."; } - QMessageBox::warning(nullptr, "Access Denied", message); } else if (replay->lastRouteError() == RouteLoadError::NetworkError) { - QMessageBox::warning(nullptr, "Network Error", - QString("Unable to load the route:\n\n %1.\n\nPlease check your network connection and try again.").arg(QString::fromStdString(route))); + message = "Unable to load the route:\n\n " + route + ".\n\nPlease check your network connection and try again."; } else if (replay->lastRouteError() == RouteLoadError::FileNotFound) { - QMessageBox::warning(nullptr, "Route Not Found", - QString("The specified route could not be found:\n\n %1.\n\nPlease check the route name and try again.").arg(QString::fromStdString(route))); + message = "The specified route could not be found:\n\n " + route + ".\n\nPlease check the route name and try again."; } else { - QMessageBox::warning(nullptr, "Route Load Failed", QString("Failed to load route: '%1'").arg(QString::fromStdString(route))); + message = "Failed to load route: '" + route + "'"; } + error(message); } return success; } diff --git a/openpilot/tools/cabana/streamselector.cc b/openpilot/tools/cabana/streamselector.cc index dae022be7..8c95bf584 100644 --- a/openpilot/tools/cabana/streamselector.cc +++ b/openpilot/tools/cabana/streamselector.cc @@ -63,6 +63,9 @@ AbstractStream *OpenReplayWidget::open() { QMessageBox::warning(nullptr, tr("Warning"), tr("Invalid route format: '%1'").arg(route)); } else { auto replay_stream = std::make_unique(); + Connection err = replay_stream->error.connect([](const std::string &msg) { + QMessageBox::warning(nullptr, tr("Error"), QString::fromStdString(msg)); + }); uint32_t flags = REPLAY_FLAG_NONE; if (cameras[1]->isChecked()) flags |= REPLAY_FLAG_CABIN_CAMERA; if (cameras[2]->isChecked()) flags |= REPLAY_FLAG_WIDE_ROAD; @@ -211,7 +214,7 @@ OpenDeviceWidget::OpenDeviceWidget(QWidget *parent) : AbstractOpenStreamWidget(p } AbstractStream *OpenDeviceWidget::open() { - QString ip = ip_address->text().isEmpty() ? "127.0.0.1" : ip_address->text(); + std::string ip = ip_address->text().isEmpty() ? "127.0.0.1" : ip_address->text().toStdString(); bool msgq = group->checkedId() == 0; return new DeviceStream(msgq ? "" : ip); } diff --git a/openpilot/tools/cabana/utils/util.cc b/openpilot/tools/cabana/utils/util.cc index 9a44ef505..d5cd77a8c 100644 --- a/openpilot/tools/cabana/utils/util.cc +++ b/openpilot/tools/cabana/utils/util.cc @@ -16,6 +16,9 @@ #include #include #include +#ifdef __APPLE__ +#include +#endif #include #include @@ -454,27 +457,36 @@ void sigTermHandler(int s) { qApp->quit(); } +std::filesystem::path executableDir() { +#ifdef __APPLE__ + char buf[PATH_MAX]; + uint32_t size = sizeof(buf); + if (_NSGetExecutablePath(buf, &size) != 0) return {}; + std::error_code ec; + auto path = std::filesystem::canonical(buf, ec); + return (ec ? std::filesystem::path(buf) : path).parent_path(); +#else + return std::filesystem::path(util::readlink("/proc/self/exe")).parent_path(); +#endif +} + void initApp(int argc, char *argv[], bool disable_hidpi) { // setup signal handlers to exit gracefully std::signal(SIGINT, sigTermHandler); std::signal(SIGTERM, sigTermHandler); - std::filesystem::path app_dir; #ifdef __APPLE__ // Get the devicePixelRatio, and scale accordingly to maintain 1:1 rendering QApplication tmp(argc, argv); - app_dir = QCoreApplication::applicationDirPath().toStdString(); if (disable_hidpi) { qputenv("QT_SCALE_FACTOR", QString::number(1.0 / tmp.devicePixelRatio()).toLocal8Bit()); } -#else - app_dir = std::filesystem::path(util::readlink("/proc/self/exe")).parent_path(); #endif qputenv("QT_DBL_CLICK_DIST", "150"); // ensure the current dir matches the exectuable's directory std::error_code ec; - std::filesystem::current_path(app_dir, ec); + std::filesystem::current_path(executableDir(), ec); } // embedded at build time from the bootstrap_icons package (see SConscript) diff --git a/openpilot/tools/cabana/utils/util.h b/openpilot/tools/cabana/utils/util.h index 77a634fc9..e0d13208a 100644 --- a/openpilot/tools/cabana/utils/util.h +++ b/openpilot/tools/cabana/utils/util.h @@ -219,5 +219,6 @@ private: }; int num_decimals(double num); +std::filesystem::path executableDir(); void initApp(int argc, char *argv[], bool disable_hidpi = true); QPixmap bootstrapPixmap(const QString &id);