/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * This file is part of the 86Box distribution. * * Program settings UI module. * * * * Authors: Joakim L. Gilje * Cacodemon345 * Teemu Korhonen * * Copyright 2021 Joakim L. Gilje * Copyright 2021-2021 Teemu Korhonen * Copyright 2021-2022 Cacodemon345 */ #include "qt_rendererstack.hpp" #include "ui_qt_rendererstack.h" #include "qt_hardwarerenderer.hpp" #include "qt_openglrenderer.hpp" #include "qt_softwarerenderer.hpp" #include "qt_vulkanwindowrenderer.hpp" #ifdef Q_OS_WIN # include "qt_d3d9renderer.hpp" #endif #include "qt_mainwindow.hpp" #include "qt_util.hpp" #include "ui_qt_mainwindow.h" #include "evdev_mouse.hpp" #include #include #include #include #ifdef __APPLE__ # include #endif extern "C" { #include <86box/86box.h> #include <86box/config.h> #include <86box/mouse.h> #include <86box/plat.h> #include <86box/video.h> double mouse_sensitivity = 1.0; double mouse_x_error = 0.0, mouse_y_error = 0.0; } struct mouseinputdata { atomic_int deltax, deltay, deltaz; atomic_int mousebuttons; atomic_bool mouse_tablet_in_proximity; std::atomic x_abs, y_abs; }; static mouseinputdata mousedata; extern "C" void macos_poll_mouse(); extern MainWindow *main_window; RendererStack::RendererStack(QWidget *parent, int monitor_index) : QStackedWidget(parent) , ui(new Ui::RendererStack) { ui->setupUi(this); m_monitor_index = monitor_index; #if defined __unix__ && !defined __HAIKU__ char *mouse_type = getenv("EMU86BOX_MOUSE"), auto_mouse_type[16]; if (!mouse_type || (mouse_type[0] == '\0') || !stricmp(mouse_type, "auto")) { if (QApplication::platformName().contains("wayland")) strcpy(auto_mouse_type, "wayland"); else if (QApplication::platformName() == "eglfs") strcpy(auto_mouse_type, "evdev"); else if (QApplication::platformName() == "xcb") strcpy(auto_mouse_type, "xinput2"); else auto_mouse_type[0] = '\0'; mouse_type = auto_mouse_type; } # ifdef WAYLAND if (!stricmp(mouse_type, "wayland")) { wl_init(); this->mouse_poll_func = wl_mouse_poll; this->mouse_capture_func = wl_mouse_capture; this->mouse_uncapture_func = wl_mouse_uncapture; } # endif # ifdef EVDEV_INPUT if (!stricmp(mouse_type, "evdev")) { evdev_init(); this->mouse_poll_func = evdev_mouse_poll; } # endif if (!stricmp(mouse_type, "xinput2")) { extern void xinput2_init(); extern void xinput2_poll(); extern void xinput2_exit(); xinput2_init(); this->mouse_poll_func = xinput2_poll; this->mouse_exit_func = xinput2_exit; } #endif #ifdef __APPLE__ this->mouse_poll_func = macos_poll_mouse; #endif } RendererStack::~RendererStack() { QApplication::restoreOverrideCursor(); delete ui; } void qt_mouse_capture(int on) { if (!on) { mouse_capture = 0; QApplication::setOverrideCursor(Qt::ArrowCursor); #ifdef __APPLE__ CGAssociateMouseAndMouseCursorPosition(true); #endif return; } mouse_capture = 1; QApplication::setOverrideCursor(Qt::BlankCursor); #ifdef __APPLE__ CGAssociateMouseAndMouseCursorPosition(false); #endif return; } void RendererStack::mousePoll() { #ifdef Q_OS_WINDOWS if (mouse_mode == 0) { mouse_x_abs = mousedata.x_abs; mouse_y_abs = mousedata.y_abs; return; } #endif #ifndef __APPLE__ mouse_x = mousedata.deltax; mouse_y = mousedata.deltay; mouse_z = mousedata.deltaz; mousedata.deltax = mousedata.deltay = mousedata.deltaz = 0; mouse_buttons = mousedata.mousebuttons; if (this->mouse_poll_func) #endif this->mouse_poll_func(); mouse_x_abs = mousedata.x_abs; mouse_y_abs = mousedata.y_abs; mouse_tablet_in_proximity = mousedata.mouse_tablet_in_proximity; double scaled_x = mouse_x * mouse_sensitivity + mouse_x_error; double scaled_y = mouse_y * mouse_sensitivity + mouse_y_error; mouse_x = static_cast(scaled_x); mouse_y = static_cast(scaled_y); mouse_x_error = scaled_x - mouse_x; mouse_y_error = scaled_y - mouse_y; } int ignoreNextMouseEvent = 1; void RendererStack::mouseReleaseEvent(QMouseEvent *event) { if (this->geometry().contains(event->pos()) && event->button() == Qt::LeftButton && !mouse_capture && (isMouseDown & 1) && (mouse_get_buttons() != 0) && mouse_mode == 0) { plat_mouse_capture(1); this->setCursor(Qt::BlankCursor); if (!ignoreNextMouseEvent) ignoreNextMouseEvent++; // Avoid jumping cursor when moved. isMouseDown &= ~1; return; } if (mouse_capture && (event->button() == Qt::MiddleButton) && (mouse_get_buttons() < 3)) { plat_mouse_capture(0); this->setCursor(Qt::ArrowCursor); isMouseDown &= ~1; return; } if (mouse_capture || mouse_mode >= 1) { mousedata.mousebuttons &= ~event->button(); } isMouseDown &= ~1; } void RendererStack::mousePressEvent(QMouseEvent *event) { isMouseDown |= 1; if (mouse_capture || mouse_mode >= 1) { mousedata.mousebuttons |= event->button(); } event->accept(); } void RendererStack::wheelEvent(QWheelEvent *event) { if (mouse_capture) { mousedata.deltaz += event->pixelDelta().y(); } } void RendererStack::mouseMoveEvent(QMouseEvent *event) { if (QApplication::platformName().contains("wayland")) { event->accept(); return; } if (!mouse_capture) { event->ignore(); return; } #if defined __APPLE__ || defined _WIN32 event->accept(); return; #else static QPoint oldPos = QCursor::pos(); if (ignoreNextMouseEvent) { oldPos = event->pos(); ignoreNextMouseEvent--; event->accept(); return; } mousedata.deltax += event->pos().x() - oldPos.x(); mousedata.deltay += event->pos().y() - oldPos.y(); if (QApplication::platformName() == "eglfs") { leaveEvent((QEvent *) event); ignoreNextMouseEvent--; } QCursor::setPos(mapToGlobal(QPoint(width() / 2, height() / 2))); ignoreNextMouseEvent = 2; oldPos = event->pos(); #endif } void #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) RendererStack::enterEvent(QEnterEvent *event) #else RendererStack::enterEvent(QEvent *event) #endif { mousedata.mouse_tablet_in_proximity = 1; if (mouse_mode == 1) QApplication::setOverrideCursor(Qt::BlankCursor); } void RendererStack::leaveEvent(QEvent *event) { mousedata.mouse_tablet_in_proximity = 0; if (mouse_mode == 1) QApplication::setOverrideCursor(Qt::ArrowCursor); if (QApplication::platformName().contains("wayland")) { event->accept(); return; } if (!mouse_capture) return; QCursor::setPos(mapToGlobal(QPoint(width() / 2, height() / 2))); ignoreNextMouseEvent = 2; event->accept(); } void RendererStack::switchRenderer(Renderer renderer) { startblit(); if (current) { if ((current_vid_api == Renderer::Direct3D9 && renderer != Renderer::Direct3D9) || (current_vid_api != Renderer::Direct3D9 && renderer == Renderer::Direct3D9)) { rendererWindow->finalize(); if (rendererWindow->hasBlitFunc()) { while (directBlitting) { } connect(this, &RendererStack::blit, this, &RendererStack::blitDummy, Qt::DirectConnection); disconnect(this, &RendererStack::blit, this, &RendererStack::blitRenderer); } else { connect(this, &RendererStack::blit, this, &RendererStack::blitDummy, Qt::DirectConnection); disconnect(this, &RendererStack::blit, this, &RendererStack::blitCommon); } removeWidget(current.get()); disconnect(this, &RendererStack::blitToRenderer, nullptr, nullptr); /* Create new renderer only after previous is destroyed! */ connect(current.get(), &QObject::destroyed, [this, renderer](QObject *) { createRenderer(renderer); disconnect(this, &RendererStack::blit, this, &RendererStack::blitDummy); blitDummied = false; QTimer::singleShot(1000, this, [this]() { blitDummied = false; }); }); rendererWindow->hasBlitFunc() ? current.reset() : current.release()->deleteLater(); } else { rendererWindow->finalize(); removeWidget(current.get()); disconnect(this, &RendererStack::blitToRenderer, nullptr, nullptr); /* Create new renderer only after previous is destroyed! */ connect(current.get(), &QObject::destroyed, [this, renderer](QObject *) { createRenderer(renderer); }); current.release()->deleteLater(); } } else { createRenderer(renderer); } } void RendererStack::createRenderer(Renderer renderer) { current_vid_api = renderer; switch (renderer) { default: case Renderer::Software: { auto sw = new SoftwareRenderer(this); rendererWindow = sw; connect(this, &RendererStack::blitToRenderer, sw, &SoftwareRenderer::onBlit, Qt::QueuedConnection); #ifdef __HAIKU__ current.reset(sw); #else current.reset(this->createWindowContainer(sw, this)); #endif } break; case Renderer::OpenGL: { this->createWinId(); auto hw = new HardwareRenderer(this); rendererWindow = hw; connect(this, &RendererStack::blitToRenderer, hw, &HardwareRenderer::onBlit, Qt::QueuedConnection); current.reset(this->createWindowContainer(hw, this)); break; } case Renderer::OpenGLES: { this->createWinId(); auto hw = new HardwareRenderer(this, HardwareRenderer::RenderType::OpenGLES); rendererWindow = hw; connect(this, &RendererStack::blitToRenderer, hw, &HardwareRenderer::onBlit, Qt::QueuedConnection); current.reset(this->createWindowContainer(hw, this)); break; } case Renderer::OpenGL3: { this->createWinId(); auto hw = new OpenGLRenderer(this); rendererWindow = hw; connect(this, &RendererStack::blitToRenderer, hw, &OpenGLRenderer::onBlit, Qt::QueuedConnection); connect(hw, &OpenGLRenderer::initialized, [=]() { /* Buffers are available only after initialization. */ imagebufs = rendererWindow->getBuffers(); endblit(); emit rendererChanged(); }); connect(hw, &OpenGLRenderer::errorInitializing, [=]() { /* Renderer not could initialize, fallback to software. */ imagebufs = {}; QTimer::singleShot(0, this, [this]() { switchRenderer(Renderer::Software); }); }); current.reset(this->createWindowContainer(hw, this)); break; } #ifdef Q_OS_WIN case Renderer::Direct3D9: { this->createWinId(); auto hw = new D3D9Renderer(this, m_monitor_index); rendererWindow = hw; connect(hw, &D3D9Renderer::error, this, [this](QString str) { auto msgBox = new QMessageBox(QMessageBox::Critical, "86Box", QString("Failed to initialize D3D9 renderer. Falling back to software rendering.\n\n") + str, QMessageBox::Ok); msgBox->setAttribute(Qt::WA_DeleteOnClose); msgBox->show(); imagebufs = {}; QTimer::singleShot(0, this, [this]() { switchRenderer(Renderer::Software); }); }); connect(hw, &D3D9Renderer::initialized, this, [this]() { endblit(); emit rendererChanged(); }); current.reset(hw); break; } #endif #if QT_CONFIG(vulkan) case Renderer::Vulkan: { this->createWinId(); VulkanWindowRenderer *hw = nullptr; try { hw = new VulkanWindowRenderer(this); } catch (std::runtime_error &e) { auto msgBox = new QMessageBox(QMessageBox::Critical, "86Box", e.what() + QString("\nFalling back to software rendering."), QMessageBox::Ok); msgBox->setAttribute(Qt::WA_DeleteOnClose); msgBox->show(); imagebufs = {}; QTimer::singleShot(0, this, [this]() { switchRenderer(Renderer::Software); }); current.reset(nullptr); break; }; rendererWindow = hw; connect(this, &RendererStack::blitToRenderer, hw, &VulkanWindowRenderer::onBlit, Qt::QueuedConnection); connect(hw, &VulkanWindowRenderer::rendererInitialized, [=]() { /* Buffers are available only after initialization. */ imagebufs = rendererWindow->getBuffers(); endblit(); emit rendererChanged(); }); connect(hw, &VulkanWindowRenderer::errorInitializing, [=]() { /* Renderer could not initialize, fallback to software. */ auto msgBox = new QMessageBox(QMessageBox::Critical, "86Box", QString("Failed to initialize Vulkan renderer.\nFalling back to software rendering."), QMessageBox::Ok); msgBox->setAttribute(Qt::WA_DeleteOnClose); msgBox->show(); imagebufs = {}; QTimer::singleShot(0, this, [this]() { switchRenderer(Renderer::Software); }); }); current.reset(this->createWindowContainer(hw, this)); break; } #endif } if (current.get() == nullptr) return; current->setFocusPolicy(Qt::NoFocus); current->setFocusProxy(this); current->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); current->setStyleSheet("background-color: black"); addWidget(current.get()); this->setStyleSheet("background-color: black"); currentBuf = 0; if (rendererWindow->hasBlitFunc()) { connect(this, &RendererStack::blit, this, &RendererStack::blitRenderer, Qt::DirectConnection); } else { connect(this, &RendererStack::blit, this, &RendererStack::blitCommon, Qt::DirectConnection); } if (renderer != Renderer::OpenGL3 && renderer != Renderer::Vulkan && renderer != Renderer::Direct3D9) { imagebufs = rendererWindow->getBuffers(); endblit(); emit rendererChanged(); } } void RendererStack::blitDummy(int x, int y, int w, int h) { video_blit_complete_monitor(m_monitor_index); blitDummied = true; } void RendererStack::blitRenderer(int x, int y, int w, int h) { if (blitDummied) { blitDummied = false; video_blit_complete_monitor(m_monitor_index); return; } directBlitting = true; rendererWindow->blit(x, y, w, h); directBlitting = false; } // called from blitter thread void RendererStack::blitCommon(int x, int y, int w, int h) { if (blitDummied || (x < 0) || (y < 0) || (w <= 0) || (h <= 0) || (w > 2048) || (h > 2048) || (monitors[m_monitor_index].target_buffer == NULL) || imagebufs.empty() || std::get(imagebufs[currentBuf])->test_and_set()) { video_blit_complete_monitor(m_monitor_index); return; } sx = x; sy = y; sw = this->w = w; sh = this->h = h; uint8_t *imagebits = std::get(imagebufs[currentBuf]); for (int y1 = y; y1 < (y + h); y1++) { auto scanline = imagebits + (y1 * rendererWindow->getBytesPerRow()) + (x * 4); video_copy(scanline, &(monitors[m_monitor_index].target_buffer->line[y1][x]), w * 4); } if (monitors[m_monitor_index].mon_screenshots) { video_screenshot_monitor((uint32_t *) imagebits, x, y, 2048, m_monitor_index); } video_blit_complete_monitor(m_monitor_index); emit blitToRenderer(currentBuf, sx, sy, sw, sh); currentBuf = (currentBuf + 1) % imagebufs.size(); } void RendererStack::closeEvent(QCloseEvent *event) { if (cpu_thread_run == 1 || is_quit == 0) { event->accept(); main_window->ui->actionShow_non_primary_monitors->setChecked(false); return; } event->ignore(); main_window->close(); } void RendererStack::changeEvent(QEvent *event) { if (m_monitor_index != 0 && isVisible()) { monitor_settings[m_monitor_index].mon_window_maximized = isMaximized(); config_save(); } } bool RendererStack::event(QEvent* event) { if (event->type() == QEvent::MouseMove) { QMouseEvent* mouse_event = (QMouseEvent*)event; if (mouse_mode >= 1) { mousedata.x_abs = (mouse_event->localPos().x()) / (long double)width(); mousedata.y_abs = (mouse_event->localPos().y()) / (long double)height(); } } return QStackedWidget::event(event); }