#include "selfdrive/ui/qt/ready.h" #include #include #include #include #include #include #include #include #include #include "common/params.h" #include "common/timing.h" #include "system/hardware/hw.h" #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/util.h" ReadyWindow::ReadyWindow(QWidget *parent) : QWidget(parent) { QGridLayout *layout = new QGridLayout(this); layout->setSpacing(0); layout->setMargin(0); setAttribute(Qt::WA_OpaquePaintEvent); setStyleSheet("ReadyWindow { background-color: black; }"); timer = new QTimer(this); timer->callOnTimeout(this, &ReadyWindow::refresh); uptime.start(); } void ReadyWindow::showEvent(QShowEvent *event) { refresh(); timer->start(2 * 1000); } void ReadyWindow::hideEvent(QHideEvent *event) { timer->stop(); } void ReadyWindow::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.fillRect(rect(), Qt::black); if (is_hot) { if (img_hot.isNull()) { img_hot.load("/data/openpilot/selfdrive/clearpilot/theme/clearpilot/images/hot.png"); } int x = (width() - img_hot.width()) / 2; int y = (height() - img_hot.height()) / 2; painter.drawPixmap(x, y, img_hot); } else { // Boot logo — same rotation as spinner (bg.jpg is pre-rotated 90° CW for framebuffer) if (img_bg.isNull()) { QPixmap raw("/usr/comma/bg.jpg"); if (!raw.isNull()) { QTransform rot; rot.rotate(-90); img_bg = raw.transformed(rot); } } if (!img_bg.isNull()) { QPixmap scaled = img_bg.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); int x = (width() - scaled.width()) / 2; int y = (height() - scaled.height()) / 2; painter.drawPixmap(x, y, scaled); } if (error_msg.isEmpty()) { // "READY!" 8-bit text sprite, 2x size, 15% below center static QPixmap ready_text("/data/openpilot/selfdrive/clearpilot/theme/clearpilot/images/ready_text.png"); if (!ready_text.isNull()) { QPixmap scaled = ready_text.scaled(ready_text.width() * 3 / 2, ready_text.height() * 3 / 2, Qt::KeepAspectRatio, Qt::FastTransformation); int tx = (width() - scaled.width()) / 2; int ty = height() / 2 + height() * 15 / 100; painter.drawPixmap(tx, ty, scaled); } } else { // Error state: red text at 25% below center QFont font("Inter", 50, QFont::Bold); painter.setFont(font); painter.setPen(QColor(0xFF, 0x44, 0x44)); int ty = height() / 2 + height() * 25 / 100; QRect text_rect(0, ty, width(), 100); painter.drawText(text_rect, Qt::AlignHCenter | Qt::AlignTop, error_msg); } } } void ReadyWindow::refresh() { bool changed = false; // Temperature check std::string bytes = params.get("Offroad_TemperatureTooHigh"); bool was_hot = is_hot; if (!bytes.empty()) { auto doc = QJsonDocument::fromJson(bytes.data()); is_hot = true; cur_temp = doc["extra"].toString(); } else { is_hot = false; } if (is_hot != was_hot) changed = true; // Error state checks (only when not hot — hot has its own display) if (!is_hot) { QString prev_error = error_msg; // Panda check — same logic as sidebar, with 10s grace period on startup if (uptime.elapsed() > 10000 && uiState()->scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) { error_msg = "PANDA NOT CONNECTED"; } // Car unrecognized check else if (!params.get("Offroad_CarUnrecognized").empty()) { error_msg = "CAR NOT RECOGNIZED"; } else { error_msg = ""; } if (error_msg != prev_error) changed = true; } if (changed) update(); }