diff --git a/NERODevelopment/CMakeLists.txt b/NERODevelopment/CMakeLists.txt index f8581ac..0983a7a 100644 --- a/NERODevelopment/CMakeLists.txt +++ b/NERODevelopment/CMakeLists.txt @@ -1,12 +1,41 @@ cmake_minimum_required(VERSION 3.21.1) -project(NEROApp LANGUAGES CXX) +project(NEROApp LANGUAGES CXX C) # Find Qt6 first before version checks find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick Network Mqtt Protobuf) qt_standard_project_setup(REQUIRES 6.8) +# ============================================================ +# DOOM Integration — auto-download doomgeneric engine +# ============================================================ +include(FetchContent) +FetchContent_Declare( + doomgeneric + GIT_REPOSITORY https://github.com/ozkl/doomgeneric.git + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(doomgeneric) + +set(DOOMGENERIC_DIR ${doomgeneric_SOURCE_DIR}/doomgeneric) + +file(GLOB DOOM_SOURCES ${DOOMGENERIC_DIR}/*.c) + +# Remove platform backends we don't use (we provide our own in doomcontroller.cpp) +# Remove ALL platform backends (doomgeneric_*.c) — we provide our own in doomcontroller.cpp +# The core engine file is doomgeneric.c (no underscore) so it's kept +list(FILTER DOOM_SOURCES EXCLUDE REGEX "doomgeneric_[a-z].*\\.c$") + +# Remove SDL and Allegro sound/music backends — we don't have those libraries +list(FILTER DOOM_SOURCES EXCLUDE REGEX "i_sdl.*\\.c$") +list(FILTER DOOM_SOURCES EXCLUDE REGEX "i_allegro.*\\.c$") + +# Suppress warnings in third-party DOOM code +set_source_files_properties(${DOOM_SOURCES} PROPERTIES COMPILE_FLAGS "-w") + +# ============================================================ + # Consolidate all sources in one place set(PROJECT_SOURCES # Headers - Constants @@ -24,6 +53,7 @@ set(PROJECT_SOURCES src/controllers/speedcontroller.h src/controllers/snakecontroller.h src/controllers/tankcontroller.h + src/controllers/doomcontroller.h # Headers - Models src/models/model.h @@ -51,6 +81,7 @@ set(PROJECT_SOURCES src/controllers/speedcontroller.cpp src/controllers/snakecontroller.cpp src/controllers/tankcontroller.cpp + src/controllers/doomcontroller.cpp # Implementation - Models src/models/model.cpp @@ -73,12 +104,12 @@ set(PROJECT_SOURCES set(CMAKE_AUTORCC ON) set(CMAKE_AUTOMOC ON) -# Create executable with all sources consolidated -qt_add_executable(NEROApp ${PROJECT_SOURCES} - +# Create executable with all sources + DOOM engine +qt_add_executable(NEROApp ${PROJECT_SOURCES} ${DOOM_SOURCES}) -) +# Add doomgeneric headers to include path +target_include_directories(NEROApp PRIVATE ${DOOMGENERIC_DIR}) # Add protobuf qt_add_protobuf(NEROApp @@ -86,7 +117,6 @@ qt_add_protobuf(NEROApp src/proto/serverdata/serverdata.proto ) - # Link libraries target_link_libraries(NEROApp PRIVATE Qt6::Core @@ -100,6 +130,13 @@ target_link_libraries(NEROApp PRIVATE set(CMAKE_OBJECT_PATH_MAX 500) +# Copy WAD file to build directory if it exists +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/resources/doom/DOOM1.WAD) + file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/resources/doom/DOOM1.WAD + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + message(STATUS "DOOM: WAD file copied to build directory") +endif() + # include the qml config include(${CMAKE_CURRENT_SOURCE_DIR}/qmlmodules) include(${CMAKE_CURRENT_SOURCE_DIR}/qmlcomponents) diff --git a/NERODevelopment/content/CMakeLists.txt b/NERODevelopment/content/CMakeLists.txt index 2ec85fb..2fbbffb 100644 --- a/NERODevelopment/content/CMakeLists.txt +++ b/NERODevelopment/content/CMakeLists.txt @@ -35,6 +35,7 @@ qt6_add_qml_module(content TorqueAdj.qml SpeedMode.qml Snake.qml + DoomView.qml TimerDisplay.qml MaxSpeedComparator.qml MaxDrawGraph.qml diff --git a/NERODevelopment/content/DoomView.qml b/NERODevelopment/content/DoomView.qml new file mode 100644 index 0000000..313a078 --- /dev/null +++ b/NERODevelopment/content/DoomView.qml @@ -0,0 +1,174 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +Item { + id: doomView + anchors.fill: parent + focus: doomView.isFocused + visible: true + + property bool isFocused: false + + readonly property int doomKeyUp: 0xAD + readonly property int doomKeyDown: 0xAF + readonly property int doomKeyLeft: 0xAC + readonly property int doomKeyRight: 0xAE + readonly property int doomKeyEnter: 0x0D + readonly property int doomKeyUse: 0xA2 + readonly property int doomKeyFire: 0xA3 + readonly property int doomKeyEscape: 0x1B + + // Hold to exit + property bool escHeld: false + + Timer { + id: escExitTimer + interval: 1000 + repeat: false + onTriggered: { + doomView.escHeld = true + if (doomController.running) { + doomController.stopGame() + } + navigationController.goHome() + } + } + + onIsFocusedChanged: { + if (isFocused) { + if (!doomController.running) { + doomController.startGame() + } + } else { + escExitTimer.stop() + escHeld = false + if (doomController.running) { + doomController.stopGame() + } + } + } + + Rectangle { + anchors.fill: parent + color: "black" + } + + Image { + id: doomFrame + anchors.centerIn: parent + + width: { + var scaleX = parent.width / 320 + var scaleY = parent.height / 200 + var scale = Math.min(scaleX, scaleY) + return 320 * scale + } + height: { + var scaleX = parent.width / 320 + var scaleY = parent.height / 200 + var scale = Math.min(scaleX, scaleY) + return 200 * scale + } + + source: doomController.running + ? "image://doom/frame?" + doomController.frameCounter + : "" + + smooth: false + fillMode: Image.Stretch + cache: false + visible: doomController.running + } + + Keys.onPressed: function(event) { + if (event.isAutoRepeat) return + + switch (event.key) { + case Qt.Key_Up: + doomController.sendKey(doomKeyUp, true) + event.accepted = true + break + case Qt.Key_Down: + doomController.sendKey(doomKeyDown, true) + event.accepted = true + break + case Qt.Key_Left: + doomController.sendKey(doomKeyLeft, true) + event.accepted = true + break + case Qt.Key_Right: + doomController.sendKey(doomKeyRight, true) + event.accepted = true + break + case Qt.Key_Space: + doomController.sendKey(doomKeyUse, true) + event.accepted = true + break + case Qt.Key_Return: + if (!doomController.running) { + doomController.startGame() + } else { + doomController.sendKey(doomKeyEnter, true) + doomController.sendKey(doomKeyFire, true) + } + event.accepted = true + break + case Qt.Key_Escape: + if (doomController.running) { + escExitTimer.start() + } else { + navigationController.goHome() + } + event.accepted = true + break + } + } + + Keys.onReleased: function(event) { + if (event.isAutoRepeat) return + + switch (event.key) { + case Qt.Key_Up: + doomController.sendKey(doomKeyUp, false) + event.accepted = true + break + case Qt.Key_Down: + doomController.sendKey(doomKeyDown, false) + event.accepted = true + break + case Qt.Key_Left: + doomController.sendKey(doomKeyLeft, false) + event.accepted = true + break + case Qt.Key_Right: + doomController.sendKey(doomKeyRight, false) + event.accepted = true + break + case Qt.Key_Space: + doomController.sendKey(doomKeyUse, false) + event.accepted = true + break + case Qt.Key_Return: + doomController.sendKey(doomKeyEnter, false) + doomController.sendKey(doomKeyFire, false) + event.accepted = true + break + case Qt.Key_Escape: + escExitTimer.stop() + if (!escHeld && doomController.running) { + doomController.sendKey(doomKeyEscape, true) + doomController.sendKey(doomKeyEscape, false) + } + escHeld = false + event.accepted = true + break + } + } + + Component.onDestruction: { + escExitTimer.stop() + if (doomController.running) { + doomController.stopGame() + } + } +} diff --git a/NERODevelopment/resources/doom/DOOM1.WAD b/NERODevelopment/resources/doom/DOOM1.WAD new file mode 100644 index 0000000..1a58f66 Binary files /dev/null and b/NERODevelopment/resources/doom/DOOM1.WAD differ diff --git a/NERODevelopment/src/controllers/doomcontroller.cpp b/NERODevelopment/src/controllers/doomcontroller.cpp new file mode 100644 index 0000000..1d44be2 --- /dev/null +++ b/NERODevelopment/src/controllers/doomcontroller.cpp @@ -0,0 +1,382 @@ +/** + * doomcontroller.cpp + * + * DOOM integration for the NERO dashboard. + * Bridges the doomgeneric C engine with Qt's C++ framework. + * + * The doomgeneric platform callbacks (DG_DrawFrame, DG_GetKey, etc.) + * are implemented directly in this file via extern "C" — no separate + * .c file needed. + */ + +#include "doomcontroller.h" + +#include +#include +#include +#include + +/* ============================================================ + * doomgeneric C headers + * ============================================================ */ +extern "C" { +#include "doomgeneric.h" +#include "doomkeys.h" +extern int joybspeed; +} + +/* ============================================================ + * Platform bridge — static state for C callbacks + * ============================================================ */ +static DoomWorker *g_doomWorker = nullptr; + +static void platform_frame_callback(uint32_t *framebuffer, int width, int height); +static int platform_getkey_callback(unsigned char *pressed, unsigned char *doomKey); + +/* ============================================================ + * doomgeneric platform callbacks (extern "C") + * + * These are the functions doomgeneric calls at runtime. + * This replaces the need for a separate doom_platform.c file. + * ============================================================ */ +extern "C" { + +void DG_Init(void) +{ + printf("[DOOM] Platform initialized (NERO Qt backend)\n"); +} + +void DG_DrawFrame(void) +{ + platform_frame_callback(DG_ScreenBuffer, DOOMGENERIC_RESX, DOOMGENERIC_RESY); +} + +void DG_SleepMs(uint32_t ms) +{ + QThread::msleep(ms); +} + +uint32_t DG_GetTicksMs(void) +{ + static QElapsedTimer timer; + static bool started = false; + if (!started) { + timer.start(); + started = true; + } + return static_cast(timer.elapsed()); +} + +int DG_GetKey(int *pressed, unsigned char *doom_key) +{ + unsigned char p = 0; + unsigned char k = 0; + int result = platform_getkey_callback(&p, &k); + if (result) { + *pressed = static_cast(p); + *doom_key = k; + return 1; + } + return 0; +} + +void DG_SetWindowTitle(const char *title) +{ + printf("[DOOM] Title: %s\n", title); +} + +} + +/* ============================================================ + * Platform callback implementations + * ============================================================ */ + +static void platform_frame_callback(uint32_t *framebuffer, int width, int height) +{ + if (!g_doomWorker) return; + + QImage frame( + reinterpret_cast(framebuffer), + width, + height, + width * static_cast(sizeof(uint32_t)), + QImage::Format_RGB32 + ); + + emit g_doomWorker->frameReady(frame.copy()); +} + +static int platform_getkey_callback(unsigned char *pressed, unsigned char *doomKey) +{ + if (!g_doomWorker) return 0; + return g_doomWorker->dequeueKey(pressed, doomKey); +} + +/* ============================================================ + * DoomImageProvider + * ============================================================ */ + +DoomImageProvider::DoomImageProvider() + : QQuickImageProvider(QQuickImageProvider::Image) + , m_currentFrame(320, 200, QImage::Format_RGB32) +{ + m_currentFrame.fill(Qt::black); +} + +QImage DoomImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) +{ + Q_UNUSED(id) + QMutexLocker lock(&m_mutex); + + if (size) + *size = m_currentFrame.size(); + + if (requestedSize.isValid() && requestedSize != m_currentFrame.size()) + return m_currentFrame.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + return m_currentFrame; +} + +void DoomImageProvider::updateFrame(const QImage &frame) +{ + QMutexLocker lock(&m_mutex); + m_currentFrame = frame; +} + +/* ============================================================ + * DoomWorker — Runs the game loop on a dedicated thread + * ============================================================ */ + +DoomWorker::DoomWorker(const QString &wadPath, QObject *parent) + : QObject(parent) + , m_wadPath(wadPath) + , m_running(false) +{ +} + +DoomWorker::~DoomWorker() +{ + stop(); +} + +void DoomWorker::enqueueKey(const DoomKeyEvent &event) +{ + QMutexLocker lock(&m_keyMutex); + if (m_keyQueue.size() < 64) { + m_keyQueue.enqueue(event); + } +} + +int DoomWorker::dequeueKey(unsigned char *pressed, unsigned char *doomKey) +{ + QMutexLocker lock(&m_keyMutex); + if (m_keyQueue.isEmpty()) + return 0; + + DoomKeyEvent event = m_keyQueue.dequeue(); + *pressed = event.pressed; + *doomKey = event.doomKey; + return 1; +} + +void DoomWorker::start() +{ + if (m_running) return; + + g_doomWorker = this; + + QByteArray wadPathUtf8 = m_wadPath.toUtf8(); + + char *argv[] = { + const_cast("nero_doom"), + const_cast("-iwad"), + wadPathUtf8.data(), + nullptr + }; + int argc = 3; + + qInfo() << "[DOOM] Starting engine with WAD:" << m_wadPath; + + doomgeneric_Create(argc, argv); + + // Enable autorun (tricks engine into always run) + joybspeed = 29; + + m_running = true; + emit started(); + + qInfo() << "[DOOM] Engine running, entering game loop"; + + while (m_running) { + doomgeneric_Tick(); + } + + qInfo() << "[DOOM] Game loop exited"; + g_doomWorker = nullptr; + emit stopped(); +} + +void DoomWorker::stop() +{ + m_running = false; +} + +/* ============================================================ + * DoomController — Main QML-facing controller + * ============================================================ */ + +DoomController::DoomController(Model *model, QObject *parent) + : QObject(parent) + , m_model(model) + , m_worker(nullptr) + , m_gameThread(nullptr) + , m_imageProvider(nullptr) + , m_running(false) + , m_frameCounter(0) + , m_statusText("Press ENTER to start DOOM") +{ + QStringList wadSearchPaths = { + QCoreApplication::applicationDirPath() + "/DOOM1.WAD", + QCoreApplication::applicationDirPath() + "/doom1.wad", + QDir::currentPath() + "/DOOM1.WAD", + QDir::currentPath() + "/doom1.wad", + "/opt/nero/DOOM1.WAD", + "/usr/share/doom/DOOM1.WAD", + }; + + for (const QString &path : wadSearchPaths) { + if (QFile::exists(path)) { + m_wadPath = path; + qInfo() << "[DOOM] Found WAD file:" << path; + break; + } + } + + if (m_wadPath.isEmpty()) { + qWarning() << "[DOOM] WAD file not found! Searched:" << wadSearchPaths; + m_statusText = "DOOM1.WAD not found!"; + } +} + +DoomController::~DoomController() +{ + stopGame(); +} + +DoomImageProvider *DoomController::createImageProvider() +{ + m_imageProvider = new DoomImageProvider(); + return m_imageProvider; +} + +void DoomController::startGame() +{ + if (m_running) return; + + if (m_wadPath.isEmpty()) { + m_statusText = "Cannot start: DOOM1.WAD not found"; + emit statusTextChanged(); + return; + } + + qInfo() << "[DOOM] Starting game..."; + m_statusText = "Loading DOOM..."; + emit statusTextChanged(); + + m_gameThread = new QThread(this); + m_worker = new DoomWorker(m_wadPath); + m_worker->moveToThread(m_gameThread); + + connect(m_gameThread, &QThread::started, m_worker, &DoomWorker::start); + connect(m_worker, &DoomWorker::frameReady, this, &DoomController::onFrameReady, Qt::QueuedConnection); + connect(m_worker, &DoomWorker::started, this, &DoomController::onWorkerStarted, Qt::QueuedConnection); + connect(m_worker, &DoomWorker::stopped, this, &DoomController::onWorkerStopped, Qt::QueuedConnection); + + connect(m_worker, &DoomWorker::stopped, m_gameThread, &QThread::quit); + connect(m_gameThread, &QThread::finished, m_worker, &QObject::deleteLater); + + m_gameThread->start(); +} + +void DoomController::stopGame() +{ + if (!m_running || !m_worker) return; + + qInfo() << "[DOOM] Stopping game..."; + m_worker->stop(); + + if (m_gameThread) { + m_gameThread->quit(); + m_gameThread->wait(3000); + if (m_gameThread->isRunning()) { + qWarning() << "[DOOM] Force terminating game thread"; + m_gameThread->terminate(); + m_gameThread->wait(1000); + } + } + + m_running = false; + m_statusText = "Press ENTER to start DOOM"; + emit runningChanged(); + emit statusTextChanged(); +} + +void DoomController::sendKey(int doomKeyCode, bool pressed) +{ + if (!m_worker || !m_running) return; + + DoomKeyEvent event; + event.pressed = pressed ? 1 : 0; + event.doomKey = static_cast(doomKeyCode); + m_worker->enqueueKey(event); +} + +void DoomController::onNeroButton(const QString &buttonName, bool pressed) +{ + unsigned char doomKey = mapNeroButtonToDoomKey(buttonName); + if (doomKey == 0) return; + + if (!m_running && pressed && buttonName == "Enter") { + startGame(); + return; + } + + sendKey(doomKey, pressed); +} + +void DoomController::onFrameReady(const QImage &frame) +{ + if (m_imageProvider) { + m_imageProvider->updateFrame(frame); + } + m_frameCounter++; + emit frameCounterChanged(); +} + +void DoomController::onWorkerStarted() +{ + m_running = true; + m_statusText = "DOOM is running"; + emit runningChanged(); + emit statusTextChanged(); +} + +void DoomController::onWorkerStopped() +{ + m_running = false; + m_statusText = "Press ENTER to start DOOM"; + emit runningChanged(); + emit statusTextChanged(); +} + +unsigned char DoomController::mapNeroButtonToDoomKey(const QString &buttonName) +{ + if (buttonName == "Forward") return KEY_UPARROW; + if (buttonName == "Backward") return KEY_DOWNARROW; + if (buttonName == "Left") return KEY_LEFTARROW; + if (buttonName == "Right") return KEY_RIGHTARROW; + if (buttonName == "Enter") return KEY_USE; + if (buttonName == "Up") return KEY_FIRE; + if (buttonName == "Down") return KEY_TAB; + return 0; +} diff --git a/NERODevelopment/src/controllers/doomcontroller.h b/NERODevelopment/src/controllers/doomcontroller.h new file mode 100644 index 0000000..9651c19 --- /dev/null +++ b/NERODevelopment/src/controllers/doomcontroller.h @@ -0,0 +1,115 @@ +#ifndef DOOMCONTROLLER_H +#define DOOMCONTROLLER_H + +#include +#include +#include +#include +#include +#include +#include + +class Model; + +struct DoomKeyEvent { + unsigned char pressed; + unsigned char doomKey; +}; + +/** + * @brief Image provider that serves the current DOOM frame to QML + */ +class DoomImageProvider : public QQuickImageProvider +{ +public: + DoomImageProvider(); + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override; + void updateFrame(const QImage &frame); + +private: + QImage m_currentFrame; + QMutex m_mutex; +}; + +/** + * @brief Worker that runs the DOOM game loop on a separate thread + */ +class DoomWorker : public QObject +{ + Q_OBJECT + +public: + explicit DoomWorker(const QString &wadPath, QObject *parent = nullptr); + ~DoomWorker(); + + void enqueueKey(const DoomKeyEvent &event); + int dequeueKey(unsigned char *pressed, unsigned char *doomKey); + +public slots: + void start(); + void stop(); + +signals: + void frameReady(const QImage &frame); + void started(); + void stopped(); + +private: + QString m_wadPath; + volatile bool m_running; + QQueue m_keyQueue; + QMutex m_keyMutex; +}; + +/** + * @brief Main controller for DOOM in the NERO dashboard. + * Same pattern as FlappyBirdController and SnakeController. + */ +class DoomController : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool running READ running NOTIFY runningChanged) + Q_PROPERTY(int frameCounter READ frameCounter NOTIFY frameCounterChanged) + Q_PROPERTY(QString statusText READ statusText NOTIFY statusTextChanged) + +public: + explicit DoomController(Model *model, QObject *parent = nullptr); + ~DoomController(); + + bool running() const { return m_running; } + int frameCounter() const { return m_frameCounter; } + QString statusText() const { return m_statusText; } + + DoomImageProvider *createImageProvider(); + + Q_INVOKABLE void startGame(); + Q_INVOKABLE void stopGame(); + Q_INVOKABLE void sendKey(int doomKeyCode, bool pressed); + Q_INVOKABLE void onNeroButton(const QString &buttonName, bool pressed); + +signals: + void runningChanged(); + void frameCounterChanged(); + void statusTextChanged(); + +private slots: + void onFrameReady(const QImage &frame); + void onWorkerStarted(); + void onWorkerStopped(); + +private: + unsigned char mapNeroButtonToDoomKey(const QString &buttonName); + + Model *m_model; + DoomWorker *m_worker; + QThread *m_gameThread; + DoomImageProvider *m_imageProvider; + + bool m_running; + int m_frameCounter; + QString m_statusText; + QString m_wadPath; +}; + +#endif // DOOMCONTROLLER_H diff --git a/NERODevelopment/src/controllers/navigationcontroller.cpp b/NERODevelopment/src/controllers/navigationcontroller.cpp index 2a08523..2977e63 100644 --- a/NERODevelopment/src/controllers/navigationcontroller.cpp +++ b/NERODevelopment/src/controllers/navigationcontroller.cpp @@ -13,6 +13,7 @@ const std::vector &getPages() { {"GAMES", Type::Category, "game.png", nullptr, nullptr}, {"FLAPPY BIRD", Type::SubPage, nullptr, "FlappyBird.qml", nullptr}, {"SNAKE", Type::SubPage, nullptr, "Snake.qml", nullptr}, + {"DOOM", Type::SubPage, nullptr, "DoomView.qml", nullptr}, {"THEMES", Type::Category, "themes.png", nullptr, nullptr}, {"LIGHT", Type::SubAction, nullptr, nullptr, [](NavigationController *c) { diff --git a/NERODevelopment/src/main.cpp b/NERODevelopment/src/main.cpp index 4f6903b..8e24c57 100644 --- a/NERODevelopment/src/main.cpp +++ b/NERODevelopment/src/main.cpp @@ -10,6 +10,7 @@ #include "controllers/offviewcontroller.h" #include "controllers/snakecontroller.h" #include "controllers/speedcontroller.h" +#include "controllers/doomcontroller.h" #include "import_qml_components_plugins.h" #include "import_qml_plugins.h" #include "models/raspberry_model.h" @@ -18,42 +19,46 @@ #include int main(int argc, char *argv[]) { - set_qt_environment(); - - QGuiApplication app(argc, argv); - - QQmlApplicationEngine engine; - - Model *model = new RaspberryModel(); - model->connectToMQTT(); - - HomeController homeController(model); - HeaderController headerController(model); - OffViewController offViewController(model); - NavigationController navigationController(model); - FlappyBirdController flappyBirdController(model); - SnakeController snakeController(model); - EfficiencyController efficencyController(model); - SpeedController speedController(model); - - engine.rootContext()->setContextProperty("homeController", &homeController); - engine.rootContext()->setContextProperty("headerController", - &headerController); - engine.rootContext()->setContextProperty("offViewController", - &offViewController); - engine.rootContext()->setContextProperty("navigationController", - &navigationController); - engine.rootContext()->setContextProperty("flappyBirdController", - &flappyBirdController); - engine.rootContext()->setContextProperty("snakeController", &snakeController); - engine.rootContext()->setContextProperty("efficiencyController", - &efficencyController); - engine.rootContext()->setContextProperty("speedController", &speedController); - - QObject::connect( - &engine, &QQmlApplicationEngine::objectCreationFailed, &app, - []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); - engine.loadFromModule("content", "App"); - - return app.exec(); + set_qt_environment(); + + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + + Model *model = new RaspberryModel(); + model->connectToMQTT(); + + HomeController homeController(model); + HeaderController headerController(model); + OffViewController offViewController(model); + NavigationController navigationController(model); + FlappyBirdController flappyBirdController(model); + SnakeController snakeController(model); + DoomController doomController(model); + EfficiencyController efficencyController(model); + SpeedController speedController(model); + + engine.addImageProvider("doom", doomController.createImageProvider()); + + engine.rootContext()->setContextProperty("homeController", &homeController); + engine.rootContext()->setContextProperty("headerController", + &headerController); + engine.rootContext()->setContextProperty("offViewController", + &offViewController); + engine.rootContext()->setContextProperty("navigationController", + &navigationController); + engine.rootContext()->setContextProperty("flappyBirdController", + &flappyBirdController); + engine.rootContext()->setContextProperty("snakeController", &snakeController); + engine.rootContext()->setContextProperty("doomController", &doomController); + engine.rootContext()->setContextProperty("efficiencyController", + &efficencyController); + engine.rootContext()->setContextProperty("speedController", &speedController); + + QObject::connect( + &engine, &QQmlApplicationEngine::objectCreationFailed, &app, + []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); + engine.loadFromModule("content", "App"); + + return app.exec(); }