2483 lines
67 KiB
C++
2483 lines
67 KiB
C++
/*
|
|
* 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.
|
|
*
|
|
* Main window module.
|
|
*
|
|
*
|
|
*
|
|
* Authors: Joakim L. Gilje <jgilje@jgilje.net>
|
|
* Cacodemon345
|
|
* Teemu Korhonen
|
|
* dob205
|
|
*
|
|
* Copyright 2021 Joakim L. Gilje
|
|
* Copyright 2021-2022 Cacodemon345
|
|
* Copyright 2021-2022 Teemu Korhonen
|
|
* Copyright 2022 dob205
|
|
*/
|
|
#include <QDebug>
|
|
|
|
#include "qt_mainwindow.hpp"
|
|
#include "ui_qt_mainwindow.h"
|
|
|
|
#include "qt_specifydimensions.h"
|
|
#include "qt_soundgain.hpp"
|
|
#include "qt_progsettings.hpp"
|
|
#include "qt_mcadevicelist.hpp"
|
|
|
|
#include "qt_rendererstack.hpp"
|
|
#include "qt_renderercommon.hpp"
|
|
|
|
extern "C" {
|
|
#include <86box/86box.h>
|
|
#include <86box/config.h>
|
|
#include <86box/keyboard.h>
|
|
#include <86box/plat.h>
|
|
#include <86box/ui.h>
|
|
#ifdef DISCORD
|
|
# include <86box/discord.h>
|
|
#endif
|
|
#include <86box/device.h>
|
|
#include <86box/video.h>
|
|
#include <86box/mouse.h>
|
|
#include <86box/machine.h>
|
|
#include <86box/vid_ega.h>
|
|
#include <86box/version.h>
|
|
//#include <86box/acpi.h> /* Requires timer.h include, which conflicts with Qt headers */
|
|
extern atomic_int acpi_pwrbut_pressed;
|
|
extern int acpi_enabled;
|
|
|
|
#ifdef USE_VNC
|
|
# include <86box/vnc.h>
|
|
#endif
|
|
|
|
extern int qt_nvr_save(void);
|
|
|
|
#ifdef MTR_ENABLED
|
|
# include <minitrace/minitrace.h>
|
|
#endif
|
|
};
|
|
|
|
#include <QGuiApplication>
|
|
#include <QWindow>
|
|
#include <QTimer>
|
|
#include <QThread>
|
|
#include <QKeyEvent>
|
|
#include <QMessageBox>
|
|
#include <QFocusEvent>
|
|
#include <QApplication>
|
|
#include <QPushButton>
|
|
#include <QDesktopServices>
|
|
#include <QUrl>
|
|
#include <QMenuBar>
|
|
#include <QCheckBox>
|
|
#include <QActionGroup>
|
|
#include <QOpenGLContext>
|
|
#include <QScreen>
|
|
#include <QString>
|
|
#include <QDir>
|
|
#include <QSysInfo>
|
|
#if QT_CONFIG(vulkan)
|
|
# include <QVulkanInstance>
|
|
# include <QVulkanFunctions>
|
|
#endif
|
|
|
|
#include <array>
|
|
#include <unordered_map>
|
|
|
|
#include "qt_settings.hpp"
|
|
#include "qt_machinestatus.hpp"
|
|
#include "qt_mediamenu.hpp"
|
|
#include "qt_util.hpp"
|
|
|
|
#if defined __unix__ && !defined __HAIKU__
|
|
# ifndef Q_OS_MACOS
|
|
# include "evdev_keyboard.hpp"
|
|
# endif
|
|
# ifdef XKBCOMMON
|
|
# include "xkbcommon_keyboard.hpp"
|
|
# ifdef XKBCOMMON_X11
|
|
# include "xkbcommon_x11_keyboard.hpp"
|
|
# endif
|
|
# ifdef WAYLAND
|
|
# include "xkbcommon_wl_keyboard.hpp"
|
|
# endif
|
|
# endif
|
|
# include <X11/Xlib.h>
|
|
# include <X11/keysym.h>
|
|
# undef KeyPress
|
|
# undef KeyRelease
|
|
#endif
|
|
|
|
#ifdef Q_OS_MACOS
|
|
// The namespace is required to avoid clashing typedefs; we only use this
|
|
// header for its #defines anyway.
|
|
namespace IOKit {
|
|
# include <IOKit/hidsystem/IOLLEvent.h>
|
|
}
|
|
#endif
|
|
|
|
#ifdef __HAIKU__
|
|
# include <os/AppKit.h>
|
|
# include <os/InterfaceKit.h>
|
|
|
|
extern MainWindow *main_window;
|
|
|
|
filter_result
|
|
keyb_filter(BMessage *message, BHandler **target, BMessageFilter *filter)
|
|
{
|
|
if (message->what == B_KEY_DOWN || message->what == B_KEY_UP
|
|
|| message->what == B_UNMAPPED_KEY_DOWN || message->what == B_UNMAPPED_KEY_UP) {
|
|
int key_state = 0, key_scancode = 0;
|
|
key_state = message->what == B_KEY_DOWN || message->what == B_UNMAPPED_KEY_DOWN;
|
|
message->FindInt32("key", &key_scancode);
|
|
QGuiApplication::postEvent(main_window, new QKeyEvent(key_state ? QEvent::KeyPress : QEvent::KeyRelease, 0, QGuiApplication::keyboardModifiers(), key_scancode, 0, 0));
|
|
if (key_scancode == 0x68 && key_state) {
|
|
QGuiApplication::postEvent(main_window, new QKeyEvent(QEvent::KeyRelease, 0, QGuiApplication::keyboardModifiers(), key_scancode, 0, 0));
|
|
}
|
|
}
|
|
return B_DISPATCH_MESSAGE;
|
|
}
|
|
|
|
static BMessageFilter *filter;
|
|
#endif
|
|
|
|
std::atomic<bool> blitDummied { false };
|
|
|
|
extern void qt_mouse_capture(int);
|
|
extern "C" void qt_blit(int x, int y, int w, int h, int monitor_index);
|
|
|
|
extern MainWindow *main_window;
|
|
|
|
MainWindow::MainWindow(QWidget *parent)
|
|
: QMainWindow(parent)
|
|
, ui(new Ui::MainWindow)
|
|
{
|
|
mm = std::make_shared<MediaMenu>(this);
|
|
MediaMenu::ptr = mm;
|
|
status = std::make_unique<MachineStatus>(this);
|
|
|
|
#ifdef __HAIKU__
|
|
filter = new BMessageFilter(B_PROGRAMMED_DELIVERY, B_ANY_SOURCE, keyb_filter);
|
|
((BWindow *) this->winId())->AddFilter(filter);
|
|
#endif
|
|
setUnifiedTitleAndToolBarOnMac(true);
|
|
extern MainWindow *main_window;
|
|
main_window = this;
|
|
ui->setupUi(this);
|
|
ui->stackedWidget->setMouseTracking(true);
|
|
statusBar()->setVisible(!hide_status_bar);
|
|
statusBar()->setStyleSheet("QStatusBar::item {border: None; } QStatusBar QLabel { margin-right: 2px; margin-bottom: 1px; }");
|
|
this->centralWidget()->setStyleSheet("background-color: black;");
|
|
ui->toolBar->setVisible(!hide_tool_bar);
|
|
#ifdef _WIN32
|
|
ui->toolBar->setBackgroundRole(QPalette::Light);
|
|
#endif
|
|
renderers[0].reset(nullptr);
|
|
auto toolbar_spacer = new QWidget();
|
|
toolbar_spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
ui->toolBar->addWidget(toolbar_spacer);
|
|
|
|
auto toolbar_label = new QLabel();
|
|
ui->toolBar->addWidget(toolbar_label);
|
|
|
|
#ifdef RELEASE_BUILD
|
|
this->setWindowIcon(QIcon(":/settings/win/icons/86Box-green.ico"));
|
|
#elif defined ALPHA_BUILD
|
|
this->setWindowIcon(QIcon(":/settings/win/icons/86Box-red.ico"));
|
|
#elif defined BETA_BUILD
|
|
this->setWindowIcon(QIcon(":/settings/win/icons/86Box-yellow.ico"));
|
|
#else
|
|
this->setWindowIcon(QIcon(":/settings/win/icons/86Box-gray.ico"));
|
|
#endif
|
|
this->setWindowFlag(Qt::MSWindowsFixedSizeDialogHint, vid_resize != 1);
|
|
this->setWindowFlag(Qt::WindowMaximizeButtonHint, vid_resize == 1);
|
|
|
|
QString vmname(vm_name);
|
|
if (vmname.at(vmname.size() - 1) == '"' || vmname.at(vmname.size() - 1) == '\'')
|
|
vmname.truncate(vmname.size() - 1);
|
|
this->setWindowTitle(QString("%1 - %2 %3").arg(vmname, EMU_NAME, EMU_VERSION_FULL));
|
|
|
|
connect(this, &MainWindow::hardResetCompleted, this, [this]() {
|
|
ui->actionMCA_devices->setVisible(machine_has_bus(machine, MACHINE_BUS_MCA));
|
|
QApplication::setOverrideCursor(Qt::ArrowCursor);
|
|
ui->menuTablet_tool->menuAction()->setVisible(mouse_mode >= 1);
|
|
});
|
|
|
|
connect(this, &MainWindow::showMessageForNonQtThread, this, &MainWindow::showMessage_, Qt::BlockingQueuedConnection);
|
|
|
|
connect(this, &MainWindow::setTitle, this, [this, toolbar_label](const QString &title) {
|
|
if (dopause && !hide_tool_bar) {
|
|
toolbar_label->setText(toolbar_label->text() + tr(" - PAUSED"));
|
|
return;
|
|
}
|
|
if (!hide_tool_bar)
|
|
#ifdef _WIN32
|
|
toolbar_label->setText(title);
|
|
#else
|
|
{
|
|
/* get the percentage and mouse message, TODO: refactor ui_window_title() */
|
|
auto parts = title.split(" - ");
|
|
if (parts.size() >= 2) {
|
|
if (parts.size() < 5)
|
|
toolbar_label->setText(parts[1]);
|
|
else
|
|
toolbar_label->setText(QString("%1 - %2").arg(parts[1], parts.last()));
|
|
}
|
|
}
|
|
#endif
|
|
ui->actionPause->setChecked(false);
|
|
ui->actionPause->setCheckable(false);
|
|
});
|
|
connect(this, &MainWindow::getTitleForNonQtThread, this, &MainWindow::getTitle_, Qt::BlockingQueuedConnection);
|
|
|
|
connect(this, &MainWindow::updateMenuResizeOptions, [this]() {
|
|
ui->actionResizable_window->setEnabled(vid_resize != 2);
|
|
ui->actionResizable_window->setChecked(vid_resize == 1);
|
|
ui->menuWindow_scale_factor->setEnabled(vid_resize == 0);
|
|
});
|
|
|
|
connect(this, &MainWindow::updateWindowRememberOption, [this]() {
|
|
ui->actionRemember_size_and_position->setChecked(window_remember);
|
|
});
|
|
|
|
emit updateMenuResizeOptions();
|
|
|
|
connect(this, &MainWindow::pollMouse, ui->stackedWidget, &RendererStack::mousePoll, Qt::DirectConnection);
|
|
|
|
connect(this, &MainWindow::setMouseCapture, this, [this](bool state) {
|
|
mouse_capture = state ? 1 : 0;
|
|
qt_mouse_capture(mouse_capture);
|
|
if (mouse_capture) {
|
|
this->grabKeyboard();
|
|
if (ui->stackedWidget->mouse_capture_func)
|
|
ui->stackedWidget->mouse_capture_func(this->windowHandle());
|
|
} else {
|
|
this->releaseKeyboard();
|
|
if (ui->stackedWidget->mouse_uncapture_func)
|
|
ui->stackedWidget->mouse_uncapture_func();
|
|
}
|
|
});
|
|
|
|
connect(qApp, &QGuiApplication::applicationStateChanged, [this](Qt::ApplicationState state) {
|
|
if (mouse_capture && state != Qt::ApplicationState::ApplicationActive)
|
|
emit setMouseCapture(false);
|
|
});
|
|
|
|
connect(this, &MainWindow::resizeContents, this, [this](int w, int h) {
|
|
if (shownonce) {
|
|
if (resizableonce == false)
|
|
ui->stackedWidget->setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
|
resizableonce = true;
|
|
}
|
|
if (!QApplication::platformName().contains("eglfs") && vid_resize != 1) {
|
|
w = (w / (!dpi_scale ? util::screenOfWidget(this)->devicePixelRatio() : 1.));
|
|
|
|
int modifiedHeight = (h / (!dpi_scale ? util::screenOfWidget(this)->devicePixelRatio() : 1.))
|
|
+ menuBar()->height()
|
|
+ (statusBar()->height() * !hide_status_bar)
|
|
+ (ui->toolBar->height() * !hide_tool_bar);
|
|
|
|
ui->stackedWidget->resize(w, (h / (!dpi_scale ? util::screenOfWidget(this)->devicePixelRatio() : 1.)));
|
|
setFixedSize(w, modifiedHeight);
|
|
}
|
|
});
|
|
|
|
connect(this, &MainWindow::resizeContentsMonitor, this, [this](int w, int h, int monitor_index) {
|
|
if (!QApplication::platformName().contains("eglfs") && vid_resize != 1) {
|
|
qDebug() << "Resize";
|
|
w = (w / (!dpi_scale ? util::screenOfWidget(renderers[monitor_index].get())->devicePixelRatio() : 1.));
|
|
|
|
int modifiedHeight = (h / (!dpi_scale ? util::screenOfWidget(renderers[monitor_index].get())->devicePixelRatio() : 1.));
|
|
|
|
renderers[monitor_index]->setFixedSize(w, modifiedHeight);
|
|
}
|
|
});
|
|
|
|
connect(ui->menubar, &QMenuBar::triggered, this, [this] {
|
|
config_save();
|
|
if (QApplication::activeWindow() == this) {
|
|
ui->stackedWidget->setFocusRenderer();
|
|
}
|
|
});
|
|
|
|
connect(this, &MainWindow::updateStatusBarPanes, this, [this] {
|
|
refreshMediaMenu();
|
|
});
|
|
connect(this, &MainWindow::updateStatusBarPanes, this, &MainWindow::refreshMediaMenu);
|
|
connect(this, &MainWindow::updateStatusBarTip, status.get(), &MachineStatus::updateTip);
|
|
connect(this, &MainWindow::statusBarMessage, status.get(), &MachineStatus::message, Qt::QueuedConnection);
|
|
|
|
ui->actionKeyboard_requires_capture->setChecked(kbd_req_capture);
|
|
ui->actionRight_CTRL_is_left_ALT->setChecked(rctrl_is_lalt);
|
|
ui->actionResizable_window->setChecked(vid_resize == 1);
|
|
ui->actionRemember_size_and_position->setChecked(window_remember);
|
|
ui->menuWindow_scale_factor->setEnabled(vid_resize == 0);
|
|
ui->actionHiDPI_scaling->setChecked(dpi_scale);
|
|
ui->actionHide_status_bar->setChecked(hide_status_bar);
|
|
ui->actionHide_tool_bar->setChecked(hide_tool_bar);
|
|
ui->actionShow_non_primary_monitors->setChecked(show_second_monitors);
|
|
ui->actionUpdate_status_bar_icons->setChecked(update_icons);
|
|
ui->actionEnable_Discord_integration->setChecked(enable_discord);
|
|
ui->actionApply_fullscreen_stretch_mode_when_maximized->setChecked(video_fullscreen_scale_maximized);
|
|
|
|
#ifndef DISCORD
|
|
ui->actionEnable_Discord_integration->setVisible(false);
|
|
#endif
|
|
|
|
#if defined Q_OS_WINDOWS || defined Q_OS_MACOS
|
|
/* Make the option visible only if ANGLE is loaded. */
|
|
ui->actionHardware_Renderer_OpenGL_ES->setVisible(QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES);
|
|
if (QOpenGLContext::openGLModuleType() != QOpenGLContext::LibGLES && vid_api == 2)
|
|
vid_api = 1;
|
|
#endif
|
|
ui->actionHardware_Renderer_OpenGL->setVisible(QOpenGLContext::openGLModuleType() != QOpenGLContext::LibGLES);
|
|
if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES && vid_api == 1)
|
|
vid_api = 0;
|
|
|
|
if ((QApplication::platformName().contains("eglfs") || QApplication::platformName() == "haiku")) {
|
|
if (vid_api >= 1)
|
|
fprintf(stderr, "OpenGL renderers are unsupported on %s.\n", QApplication::platformName().toUtf8().data());
|
|
vid_api = 0;
|
|
ui->actionHardware_Renderer_OpenGL->setVisible(false);
|
|
ui->actionHardware_Renderer_OpenGL_ES->setVisible(false);
|
|
ui->actionVulkan->setVisible(false);
|
|
ui->actionOpenGL_3_0_Core->setVisible(false);
|
|
}
|
|
#if !defined Q_OS_WINDOWS
|
|
ui->actionDirect3D_9->setVisible(false);
|
|
if (vid_api == 5)
|
|
vid_api = 0;
|
|
#endif
|
|
|
|
#ifndef USE_VNC
|
|
if (vid_api == 6)
|
|
vid_api = 0;
|
|
ui->actionVNC->setVisible(false);
|
|
#endif
|
|
|
|
#if QT_CONFIG(vulkan)
|
|
bool vulkanAvailable = false;
|
|
{
|
|
QVulkanInstance instance;
|
|
instance.setApiVersion(QVersionNumber(1, 0));
|
|
if (instance.create()) {
|
|
uint32_t physicalDevices = 0;
|
|
instance.functions()->vkEnumeratePhysicalDevices(instance.vkInstance(), &physicalDevices, nullptr);
|
|
if (physicalDevices != 0) {
|
|
vulkanAvailable = true;
|
|
}
|
|
}
|
|
}
|
|
if (!vulkanAvailable)
|
|
#endif
|
|
{
|
|
if (vid_api == 4)
|
|
vid_api = 0;
|
|
ui->actionVulkan->setVisible(false);
|
|
}
|
|
|
|
QActionGroup *actGroup = nullptr;
|
|
|
|
actGroup = new QActionGroup(this);
|
|
actGroup->addAction(ui->actionSoftware_Renderer);
|
|
actGroup->addAction(ui->actionHardware_Renderer_OpenGL);
|
|
actGroup->addAction(ui->actionHardware_Renderer_OpenGL_ES);
|
|
actGroup->addAction(ui->actionOpenGL_3_0_Core);
|
|
actGroup->addAction(ui->actionVulkan);
|
|
actGroup->addAction(ui->actionDirect3D_9);
|
|
actGroup->addAction(ui->actionVNC);
|
|
actGroup->setExclusive(true);
|
|
|
|
connect(actGroup, &QActionGroup::triggered, [this](QAction *action) {
|
|
vid_api = action->property("vid_api").toInt();
|
|
#ifdef USE_VNC
|
|
if (vnc_enabled && vid_api != 6) {
|
|
startblit();
|
|
vnc_enabled = 0;
|
|
vnc_close();
|
|
video_setblit(qt_blit);
|
|
endblit();
|
|
}
|
|
#endif
|
|
RendererStack::Renderer newVidApi = RendererStack::Renderer::Software;
|
|
switch (vid_api) {
|
|
case 0:
|
|
newVidApi = RendererStack::Renderer::Software;
|
|
break;
|
|
case 1:
|
|
newVidApi = (RendererStack::Renderer::OpenGL);
|
|
break;
|
|
case 2:
|
|
newVidApi = (RendererStack::Renderer::OpenGLES);
|
|
break;
|
|
case 3:
|
|
newVidApi = (RendererStack::Renderer::OpenGL3);
|
|
break;
|
|
case 4:
|
|
newVidApi = (RendererStack::Renderer::Vulkan);
|
|
break;
|
|
case 5:
|
|
newVidApi = (RendererStack::Renderer::Direct3D9);
|
|
break;
|
|
#ifdef USE_VNC
|
|
case 6:
|
|
{
|
|
newVidApi = RendererStack::Renderer::Software;
|
|
startblit();
|
|
vnc_enabled = vnc_init(nullptr);
|
|
endblit();
|
|
}
|
|
#endif
|
|
}
|
|
ui->stackedWidget->switchRenderer(newVidApi);
|
|
if (!show_second_monitors)
|
|
return;
|
|
for (int i = 1; i < MONITORS_NUM; i++) {
|
|
if (renderers[i])
|
|
renderers[i]->switchRenderer(newVidApi);
|
|
}
|
|
});
|
|
|
|
connect(ui->stackedWidget, &RendererStack::rendererChanged, [this]() {
|
|
ui->actionRenderer_options->setVisible(ui->stackedWidget->hasOptions());
|
|
});
|
|
|
|
/* Trigger initial renderer switch */
|
|
for (auto action : actGroup->actions())
|
|
if (action->property("vid_api").toInt() == vid_api) {
|
|
action->setChecked(true);
|
|
emit actGroup->triggered(action);
|
|
break;
|
|
}
|
|
|
|
switch (scale) {
|
|
case 0:
|
|
ui->action0_5x->setChecked(true);
|
|
break;
|
|
case 1:
|
|
ui->action1x->setChecked(true);
|
|
break;
|
|
case 2:
|
|
ui->action1_5x->setChecked(true);
|
|
break;
|
|
case 3:
|
|
ui->action2x->setChecked(true);
|
|
break;
|
|
case 4:
|
|
ui->action3x->setChecked(true);
|
|
break;
|
|
case 5:
|
|
ui->action4x->setChecked(true);
|
|
break;
|
|
case 6:
|
|
ui->action5x->setChecked(true);
|
|
break;
|
|
case 7:
|
|
ui->action6x->setChecked(true);
|
|
break;
|
|
case 8:
|
|
ui->action7x->setChecked(true);
|
|
break;
|
|
case 9:
|
|
ui->action8x->setChecked(true);
|
|
break;
|
|
}
|
|
actGroup = new QActionGroup(this);
|
|
actGroup->addAction(ui->action0_5x);
|
|
actGroup->addAction(ui->action1x);
|
|
actGroup->addAction(ui->action1_5x);
|
|
actGroup->addAction(ui->action2x);
|
|
actGroup->addAction(ui->action3x);
|
|
actGroup->addAction(ui->action4x);
|
|
actGroup->addAction(ui->action5x);
|
|
actGroup->addAction(ui->action6x);
|
|
actGroup->addAction(ui->action7x);
|
|
actGroup->addAction(ui->action8x);
|
|
switch (video_filter_method) {
|
|
case 0:
|
|
ui->actionNearest->setChecked(true);
|
|
break;
|
|
case 1:
|
|
ui->actionLinear->setChecked(true);
|
|
break;
|
|
}
|
|
actGroup = new QActionGroup(this);
|
|
actGroup->addAction(ui->actionNearest);
|
|
actGroup->addAction(ui->actionLinear);
|
|
switch (video_fullscreen_scale) {
|
|
case FULLSCR_SCALE_FULL:
|
|
ui->actionFullScreen_stretch->setChecked(true);
|
|
break;
|
|
case FULLSCR_SCALE_43:
|
|
ui->actionFullScreen_43->setChecked(true);
|
|
break;
|
|
case FULLSCR_SCALE_KEEPRATIO:
|
|
ui->actionFullScreen_keepRatio->setChecked(true);
|
|
break;
|
|
case FULLSCR_SCALE_INT:
|
|
ui->actionFullScreen_int->setChecked(true);
|
|
break;
|
|
}
|
|
actGroup = new QActionGroup(this);
|
|
actGroup->addAction(ui->actionFullScreen_stretch);
|
|
actGroup->addAction(ui->actionFullScreen_43);
|
|
actGroup->addAction(ui->actionFullScreen_keepRatio);
|
|
actGroup->addAction(ui->actionFullScreen_int);
|
|
switch (video_grayscale) {
|
|
case 0:
|
|
ui->actionRGB_Color->setChecked(true);
|
|
break;
|
|
case 1:
|
|
ui->actionRGB_Grayscale->setChecked(true);
|
|
break;
|
|
case 2:
|
|
ui->actionAmber_monitor->setChecked(true);
|
|
break;
|
|
case 3:
|
|
ui->actionGreen_monitor->setChecked(true);
|
|
break;
|
|
case 4:
|
|
ui->actionWhite_monitor->setChecked(true);
|
|
break;
|
|
}
|
|
actGroup = new QActionGroup(this);
|
|
actGroup->addAction(ui->actionRGB_Grayscale);
|
|
actGroup->addAction(ui->actionAmber_monitor);
|
|
actGroup->addAction(ui->actionGreen_monitor);
|
|
actGroup->addAction(ui->actionWhite_monitor);
|
|
actGroup->addAction(ui->actionRGB_Color);
|
|
switch (video_graytype) {
|
|
case 0:
|
|
ui->actionBT601_NTSC_PAL->setChecked(true);
|
|
break;
|
|
case 1:
|
|
ui->actionBT709_HDTV->setChecked(true);
|
|
break;
|
|
case 2:
|
|
ui->actionAverage->setChecked(true);
|
|
break;
|
|
}
|
|
actGroup = new QActionGroup(this);
|
|
actGroup->addAction(ui->actionBT601_NTSC_PAL);
|
|
actGroup->addAction(ui->actionBT709_HDTV);
|
|
actGroup->addAction(ui->actionAverage);
|
|
if (force_43 > 0) {
|
|
ui->actionForce_4_3_display_ratio->setChecked(true);
|
|
}
|
|
if (enable_overscan > 0) {
|
|
ui->actionCGA_PCjr_Tandy_EGA_S_VGA_overscan->setChecked(true);
|
|
}
|
|
if (vid_cga_contrast > 0) {
|
|
ui->actionChange_contrast_for_monochrome_display->setChecked(true);
|
|
}
|
|
|
|
#ifdef Q_OS_MACOS
|
|
ui->actionFullscreen->setShortcutVisibleInContextMenu(true);
|
|
ui->actionCtrl_Alt_Del->setShortcutVisibleInContextMenu(true);
|
|
ui->actionTake_screenshot->setShortcutVisibleInContextMenu(true);
|
|
#endif
|
|
if (!vnc_enabled)
|
|
video_setblit(qt_blit);
|
|
|
|
#ifdef MTR_ENABLED
|
|
{
|
|
ui->actionBegin_trace->setVisible(true);
|
|
ui->actionEnd_trace->setVisible(true);
|
|
ui->actionBegin_trace->setShortcut(QKeySequence(Qt::Key_Control + Qt::Key_T));
|
|
ui->actionEnd_trace->setShortcut(QKeySequence(Qt::Key_Control + Qt::Key_T));
|
|
ui->actionEnd_trace->setDisabled(true);
|
|
static auto init_trace = [&] {
|
|
mtr_init("trace.json");
|
|
mtr_start();
|
|
};
|
|
static auto shutdown_trace = [&] {
|
|
mtr_stop();
|
|
mtr_shutdown();
|
|
};
|
|
# ifdef Q_OS_MACOS
|
|
ui->actionBegin_trace->setShortcutVisibleInContextMenu(true);
|
|
ui->actionEnd_trace->setShortcutVisibleInContextMenu(true);
|
|
# endif
|
|
static bool trace = false;
|
|
connect(ui->actionBegin_trace, &QAction::triggered, this, [this] {
|
|
if (trace)
|
|
return;
|
|
ui->actionBegin_trace->setDisabled(true);
|
|
ui->actionEnd_trace->setDisabled(false);
|
|
init_trace();
|
|
trace = true;
|
|
});
|
|
connect(ui->actionEnd_trace, &QAction::triggered, this, [this] {
|
|
if (!trace)
|
|
return;
|
|
ui->actionBegin_trace->setDisabled(false);
|
|
ui->actionEnd_trace->setDisabled(true);
|
|
shutdown_trace();
|
|
trace = false;
|
|
});
|
|
}
|
|
#endif
|
|
|
|
setContextMenuPolicy(Qt::PreventContextMenu);
|
|
|
|
connect(this, &MainWindow::initRendererMonitor, this, &MainWindow::initRendererMonitorSlot);
|
|
connect(this, &MainWindow::initRendererMonitorForNonQtThread, this, &MainWindow::initRendererMonitorSlot, Qt::BlockingQueuedConnection);
|
|
connect(this, &MainWindow::destroyRendererMonitor, this, &MainWindow::destroyRendererMonitorSlot);
|
|
connect(this, &MainWindow::destroyRendererMonitorForNonQtThread, this, &MainWindow::destroyRendererMonitorSlot, Qt::BlockingQueuedConnection);
|
|
|
|
#ifdef Q_OS_MACOS
|
|
QTimer::singleShot(0, this, [this]() {
|
|
for (auto curObj : this->menuBar()->children()) {
|
|
if (qobject_cast<QMenu *>(curObj)) {
|
|
auto menu = qobject_cast<QMenu *>(curObj);
|
|
menu->setSeparatorsCollapsible(false);
|
|
for (auto curObj2 : menu->children()) {
|
|
if (qobject_cast<QMenu *>(curObj2)) {
|
|
auto menu2 = qobject_cast<QMenu *>(curObj2);
|
|
menu2->setSeparatorsCollapsible(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
#endif
|
|
|
|
actGroup = new QActionGroup(this);
|
|
actGroup->addAction(ui->actionCursor_Puck);
|
|
actGroup->addAction(ui->actionPen);
|
|
|
|
if (tablet_tool_type == 1) {
|
|
ui->actionPen->setChecked(true);
|
|
} else {
|
|
ui->actionCursor_Puck->setChecked(true);
|
|
}
|
|
|
|
#ifdef XKBCOMMON
|
|
# ifdef XKBCOMMON_X11
|
|
if (QApplication::platformName().contains("xcb"))
|
|
xkbcommon_x11_init();
|
|
else
|
|
# endif
|
|
# ifdef WAYLAND
|
|
if (QApplication::platformName().contains("wayland"))
|
|
xkbcommon_wl_init();
|
|
else
|
|
# endif
|
|
{}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
MainWindow::closeEvent(QCloseEvent *event)
|
|
{
|
|
if (mouse_capture) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
if (confirm_exit && confirm_exit_cmdl && cpu_thread_run) {
|
|
QMessageBox questionbox(QMessageBox::Icon::Question, "86Box", tr("Are you sure you want to exit 86Box?"), QMessageBox::Yes | QMessageBox::No, this);
|
|
QCheckBox *chkbox = new QCheckBox(tr("Don't show this message again"));
|
|
questionbox.setCheckBox(chkbox);
|
|
chkbox->setChecked(!confirm_exit);
|
|
|
|
QObject::connect(chkbox, &QCheckBox::stateChanged, [](int state) {
|
|
confirm_exit = (state == Qt::CheckState::Unchecked);
|
|
});
|
|
questionbox.exec();
|
|
if (questionbox.result() == QMessageBox::No) {
|
|
confirm_exit = true;
|
|
event->ignore();
|
|
return;
|
|
}
|
|
}
|
|
if (window_remember) {
|
|
window_w = ui->stackedWidget->width();
|
|
window_h = ui->stackedWidget->height();
|
|
if (!QApplication::platformName().contains("wayland")) {
|
|
window_x = this->geometry().x();
|
|
window_y = this->geometry().y();
|
|
}
|
|
for (int i = 1; i < MONITORS_NUM; i++) {
|
|
if (renderers[i]) {
|
|
monitor_settings[i].mon_window_w = renderers[i]->geometry().width();
|
|
monitor_settings[i].mon_window_h = renderers[i]->geometry().height();
|
|
if (QApplication::platformName().contains("wayland"))
|
|
continue;
|
|
monitor_settings[i].mon_window_x = renderers[i]->geometry().x();
|
|
monitor_settings[i].mon_window_y = renderers[i]->geometry().y();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ui->stackedWidget->mouse_exit_func)
|
|
ui->stackedWidget->mouse_exit_func();
|
|
|
|
ui->stackedWidget->switchRenderer(RendererStack::Renderer::Software);
|
|
|
|
qt_nvr_save();
|
|
config_save();
|
|
QApplication::processEvents();
|
|
cpu_thread_run = 0;
|
|
event->accept();
|
|
}
|
|
|
|
void
|
|
MainWindow::initRendererMonitorSlot(int monitor_index)
|
|
{
|
|
auto &secondaryRenderer = this->renderers[monitor_index];
|
|
secondaryRenderer.reset(new RendererStack(nullptr, monitor_index));
|
|
if (secondaryRenderer) {
|
|
connect(secondaryRenderer.get(), &RendererStack::rendererChanged, this, [this, monitor_index] {
|
|
this->renderers[monitor_index]->show();
|
|
});
|
|
secondaryRenderer->setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint);
|
|
secondaryRenderer->setWindowTitle(QObject::tr("86Box Monitor #") + QString::number(monitor_index + 1));
|
|
|
|
if (vid_resize == 2) {
|
|
secondaryRenderer->setFixedSize(fixed_size_x, fixed_size_y);
|
|
}
|
|
secondaryRenderer->setWindowIcon(this->windowIcon());
|
|
if (show_second_monitors) {
|
|
secondaryRenderer->show();
|
|
if (window_remember) {
|
|
secondaryRenderer->setGeometry(monitor_settings[monitor_index].mon_window_x < 120 ? 120 : monitor_settings[monitor_index].mon_window_x,
|
|
monitor_settings[monitor_index].mon_window_y < 120 ? 120 : monitor_settings[monitor_index].mon_window_y,
|
|
monitor_settings[monitor_index].mon_window_w > 2048 ? 2048 : monitor_settings[monitor_index].mon_window_w,
|
|
monitor_settings[monitor_index].mon_window_h > 2048 ? 2048 : monitor_settings[monitor_index].mon_window_h);
|
|
}
|
|
if (monitor_settings[monitor_index].mon_window_maximized) {
|
|
secondaryRenderer->showMaximized();
|
|
}
|
|
secondaryRenderer->switchRenderer((RendererStack::Renderer) vid_api);
|
|
secondaryRenderer->setMouseTracking(true);
|
|
}
|
|
connect(this, &MainWindow::pollMouse, secondaryRenderer.get(), &RendererStack::mousePoll, Qt::DirectConnection);
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::destroyRendererMonitorSlot(int monitor_index)
|
|
{
|
|
if (this->renderers[monitor_index]) {
|
|
if (window_remember) {
|
|
monitor_settings[monitor_index].mon_window_w = renderers[monitor_index]->geometry().width();
|
|
monitor_settings[monitor_index].mon_window_h = renderers[monitor_index]->geometry().height();
|
|
monitor_settings[monitor_index].mon_window_x = renderers[monitor_index]->geometry().x();
|
|
monitor_settings[monitor_index].mon_window_y = renderers[monitor_index]->geometry().y();
|
|
}
|
|
config_save();
|
|
this->renderers[monitor_index].release()->deleteLater();
|
|
ui->stackedWidget->switchRenderer((RendererStack::Renderer) vid_api);
|
|
}
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
delete ui;
|
|
}
|
|
|
|
void
|
|
MainWindow::showEvent(QShowEvent *event)
|
|
{
|
|
if (shownonce)
|
|
return;
|
|
shownonce = true;
|
|
if (window_remember) {
|
|
if (window_w == 0)
|
|
window_w = 320;
|
|
if (window_h == 0)
|
|
window_h = 200;
|
|
}
|
|
|
|
if (window_remember && !QApplication::platformName().contains("wayland")) {
|
|
setGeometry(window_x, window_y, window_w, window_h + menuBar()->height() + (hide_status_bar ? 0 : statusBar()->height()) + (hide_tool_bar ? 0 : ui->toolBar->height()));
|
|
}
|
|
if (vid_resize == 2) {
|
|
setFixedSize(fixed_size_x, fixed_size_y + menuBar()->height() + (hide_status_bar ? 0 : statusBar()->height()) + (hide_tool_bar ? 0 : ui->toolBar->height()));
|
|
|
|
monitors[0].mon_scrnsz_x = fixed_size_x;
|
|
monitors[0].mon_scrnsz_y = fixed_size_y;
|
|
}
|
|
if (window_remember && vid_resize == 1) {
|
|
ui->stackedWidget->setFixedSize(window_w, window_h);
|
|
QApplication::processEvents();
|
|
this->adjustSize();
|
|
}
|
|
if (start_in_fullscreen) {
|
|
start_in_fullscreen = 0;
|
|
QTimer::singleShot(0, ui->actionFullscreen, &QAction::trigger);
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionKeyboard_requires_capture_triggered()
|
|
{
|
|
kbd_req_capture ^= 1;
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionRight_CTRL_is_left_ALT_triggered()
|
|
{
|
|
rctrl_is_lalt ^= 1;
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionHard_Reset_triggered()
|
|
{
|
|
if (confirm_reset) {
|
|
QMessageBox questionbox(QMessageBox::Icon::Question, "86Box", tr("Are you sure you want to hard reset the emulated machine?"), QMessageBox::NoButton, this);
|
|
questionbox.addButton(tr("Reset"), QMessageBox::AcceptRole);
|
|
questionbox.addButton(tr("Don't reset"), QMessageBox::RejectRole);
|
|
QCheckBox *chkbox = new QCheckBox(tr("Don't show this message again"));
|
|
questionbox.setCheckBox(chkbox);
|
|
chkbox->setChecked(!confirm_reset);
|
|
|
|
QObject::connect(chkbox, &QCheckBox::stateChanged, [](int state) {
|
|
confirm_reset = (state == Qt::CheckState::Unchecked);
|
|
});
|
|
questionbox.exec();
|
|
if (questionbox.result() == QDialog::Accepted) {
|
|
confirm_reset = true;
|
|
return;
|
|
}
|
|
}
|
|
config_changed = 2;
|
|
pc_reset_hard();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionCtrl_Alt_Del_triggered()
|
|
{
|
|
pc_send_cad();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionCtrl_Alt_Esc_triggered()
|
|
{
|
|
pc_send_cae();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionPause_triggered()
|
|
{
|
|
plat_pause(dopause ^ 1);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionExit_triggered()
|
|
{
|
|
close();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionSettings_triggered()
|
|
{
|
|
int currentPause = dopause;
|
|
plat_pause(1);
|
|
Settings settings(this);
|
|
settings.setModal(true);
|
|
settings.setWindowModality(Qt::WindowModal);
|
|
settings.exec();
|
|
|
|
switch (settings.result()) {
|
|
case QDialog::Accepted:
|
|
/*
|
|
pc_reset_hard_close();
|
|
settings.save();
|
|
config_changed = 2;
|
|
pc_reset_hard_init();
|
|
*/
|
|
settings.save();
|
|
config_changed = 2;
|
|
pc_reset_hard();
|
|
|
|
break;
|
|
case QDialog::Rejected:
|
|
break;
|
|
}
|
|
plat_pause(currentPause);
|
|
}
|
|
|
|
#if defined(__unix__) && !defined(__HAIKU__)
|
|
std::array<uint32_t, 256> x11_to_xt_base {
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0x01,
|
|
0x02,
|
|
0x03,
|
|
0x04,
|
|
0x05,
|
|
0x06,
|
|
0x07,
|
|
0x08,
|
|
0x09,
|
|
0x0A,
|
|
0x0B,
|
|
0x0C,
|
|
0x0D,
|
|
0x0E,
|
|
0x0F,
|
|
0x10,
|
|
0x11,
|
|
0x12,
|
|
0x13,
|
|
0x14,
|
|
0x15,
|
|
0x16,
|
|
0x17,
|
|
0x18,
|
|
0x19,
|
|
0x1A,
|
|
0x1B,
|
|
0x1C,
|
|
0x1D,
|
|
0x1E,
|
|
0x1F,
|
|
0x20,
|
|
0x21,
|
|
0x22,
|
|
0x23,
|
|
0x24,
|
|
0x25,
|
|
0x26,
|
|
0x27,
|
|
0x28,
|
|
0x29,
|
|
0x2A,
|
|
0x2B,
|
|
0x2C,
|
|
0x2D,
|
|
0x2E,
|
|
0x2F,
|
|
0x30,
|
|
0x31,
|
|
0x32,
|
|
0x33,
|
|
0x34,
|
|
0x35,
|
|
0x36,
|
|
0x37,
|
|
0x38,
|
|
0x39,
|
|
0x3A,
|
|
0x3B,
|
|
0x3C,
|
|
0x3D,
|
|
0x3E,
|
|
0x3F,
|
|
0x40,
|
|
0x41,
|
|
0x42,
|
|
0x43,
|
|
0x44,
|
|
0x45,
|
|
0x46,
|
|
0x47,
|
|
0x48,
|
|
0x49,
|
|
0x4A,
|
|
0x4B,
|
|
0x4C,
|
|
0x4D,
|
|
0x4E,
|
|
0x4F,
|
|
0x50,
|
|
0x51,
|
|
0x52,
|
|
0x53,
|
|
0x54,
|
|
0x55,
|
|
0x56,
|
|
0x57,
|
|
0x58,
|
|
0x147,
|
|
0x148,
|
|
0x149,
|
|
0,
|
|
0x14B,
|
|
0,
|
|
0x14D,
|
|
0x14F,
|
|
0x150,
|
|
0x151,
|
|
0x152,
|
|
0x153,
|
|
0x11C,
|
|
0x11D,
|
|
0, // Pause/Break key.
|
|
0x137,
|
|
0x135,
|
|
0x138,
|
|
0, // Ditto as above comment.
|
|
0x15B,
|
|
0x15C,
|
|
0x15D,
|
|
};
|
|
|
|
std::array<uint32_t, 256> x11_to_xt_2 {
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0x01,
|
|
0x02,
|
|
0x03,
|
|
0x04,
|
|
0x05,
|
|
0x06,
|
|
0x07,
|
|
0x08,
|
|
0x09,
|
|
0x0A,
|
|
0x0B,
|
|
0x0C,
|
|
0x0D,
|
|
0x0E,
|
|
0x0F,
|
|
0x10,
|
|
0x11,
|
|
0x12,
|
|
0x13,
|
|
0x14,
|
|
0x15,
|
|
0x16,
|
|
0x17,
|
|
0x18,
|
|
0x19,
|
|
0x1A,
|
|
0x1B,
|
|
0x1C,
|
|
0x1D,
|
|
0x1E,
|
|
0x1F,
|
|
0x20,
|
|
0x21,
|
|
0x22,
|
|
0x23,
|
|
0x24,
|
|
0x25,
|
|
0x26,
|
|
0x27,
|
|
0x28,
|
|
0x29,
|
|
0x2A,
|
|
0x2B,
|
|
0x2C,
|
|
0x2D,
|
|
0x2E,
|
|
0x2F,
|
|
0x30,
|
|
0x31,
|
|
0x32,
|
|
0x33,
|
|
0x34,
|
|
0x35,
|
|
0x36,
|
|
0x37,
|
|
0x38,
|
|
0x39,
|
|
0x3A,
|
|
0x3B,
|
|
0x3C,
|
|
0x3D,
|
|
0x3E,
|
|
0x3F,
|
|
0x40,
|
|
0x41,
|
|
0x42,
|
|
0x43,
|
|
0x44,
|
|
0x45,
|
|
0x46,
|
|
0x47,
|
|
0x48,
|
|
0x49,
|
|
0x4A,
|
|
0x4B,
|
|
0x4C,
|
|
0x4D,
|
|
0x4E,
|
|
0x4F,
|
|
0x50,
|
|
0x51,
|
|
0x52,
|
|
0x53,
|
|
0x138,
|
|
0x55,
|
|
0x56,
|
|
0x57,
|
|
0x58,
|
|
0x56,
|
|
0x70,
|
|
0x7B,
|
|
0x7D,
|
|
0x2B,
|
|
0x7E,
|
|
0,
|
|
0x11C,
|
|
0x11D,
|
|
0x135,
|
|
0x137,
|
|
0x138,
|
|
0,
|
|
0x147,
|
|
0x148,
|
|
0x149,
|
|
0x14B,
|
|
0x14D,
|
|
0x14F,
|
|
0x150,
|
|
0x151,
|
|
0x152,
|
|
0x153,
|
|
0,
|
|
0, /* Mute */
|
|
0, /* Volume Down */
|
|
0, /* Volume Up */
|
|
0, /* Power Off */
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0x70,
|
|
0x7B,
|
|
0x73,
|
|
0x15B,
|
|
0x15C,
|
|
0x15D
|
|
};
|
|
|
|
std::array<uint32_t, 256> x11_to_xt_vnc {
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0x1D,
|
|
0x11D,
|
|
0x2A,
|
|
0x36,
|
|
0,
|
|
0,
|
|
0x38,
|
|
0x138,
|
|
0x39,
|
|
0x0B,
|
|
0x02,
|
|
0x03,
|
|
0x04,
|
|
0x05,
|
|
0x06,
|
|
0x07,
|
|
0x08,
|
|
0x09,
|
|
0x0A,
|
|
0x0C,
|
|
0x0D,
|
|
0x1A,
|
|
0x1B,
|
|
0x27,
|
|
0x28,
|
|
0x29,
|
|
0x33,
|
|
0x34,
|
|
0x35,
|
|
0x2B,
|
|
0x1E,
|
|
0x30,
|
|
0x2E,
|
|
0x20,
|
|
0x12,
|
|
0x21,
|
|
0x22,
|
|
0x23,
|
|
0x17,
|
|
0x24,
|
|
0x25,
|
|
0x26,
|
|
0x32,
|
|
0x31,
|
|
0x18,
|
|
0x19,
|
|
0x10,
|
|
0x13,
|
|
0x1F,
|
|
0x14,
|
|
0x16,
|
|
0x2F,
|
|
0x11,
|
|
0x2D,
|
|
0x15,
|
|
0x2C,
|
|
0x0E,
|
|
0x1C,
|
|
0x0F,
|
|
0x01,
|
|
0x153,
|
|
0x147,
|
|
0x14F,
|
|
0x149,
|
|
0x151,
|
|
0x148,
|
|
0x150,
|
|
0x14B,
|
|
0x14D,
|
|
};
|
|
#endif
|
|
|
|
#ifdef Q_OS_MACOS
|
|
std::array<uint32_t, 256> darwin_to_xt {
|
|
0x1E,
|
|
0x1F,
|
|
0x20,
|
|
0x21,
|
|
0x23,
|
|
0x22,
|
|
0x2C,
|
|
0x2D,
|
|
0x2E,
|
|
0x2F,
|
|
0x2B,
|
|
0x30,
|
|
0x10,
|
|
0x11,
|
|
0x12,
|
|
0x13,
|
|
0x15,
|
|
0x14,
|
|
0x02,
|
|
0x03,
|
|
0x04,
|
|
0x05,
|
|
0x07,
|
|
0x06,
|
|
0x0D,
|
|
0x0A,
|
|
0x08,
|
|
0x0C,
|
|
0x09,
|
|
0x0B,
|
|
0x1B,
|
|
0x18,
|
|
0x16,
|
|
0x1A,
|
|
0x17,
|
|
0x19,
|
|
0x1C,
|
|
0x26,
|
|
0x24,
|
|
0x28,
|
|
0x25,
|
|
0x27,
|
|
0x2B,
|
|
0x33,
|
|
0x35,
|
|
0x31,
|
|
0x32,
|
|
0x34,
|
|
0x0F,
|
|
0x39,
|
|
0x29,
|
|
0x0E,
|
|
0x11C,
|
|
0x01,
|
|
0x15C,
|
|
0x15B,
|
|
0x2A,
|
|
0x3A,
|
|
0x38,
|
|
0x1D,
|
|
0x36,
|
|
0x138,
|
|
0x11D,
|
|
0x15C,
|
|
0,
|
|
0x53,
|
|
0,
|
|
0x37,
|
|
0,
|
|
0x4E,
|
|
0,
|
|
0x45,
|
|
0x130,
|
|
0x12E,
|
|
0x120,
|
|
0x135,
|
|
0x11C,
|
|
0,
|
|
0x4A,
|
|
0,
|
|
0,
|
|
0,
|
|
0x52,
|
|
0x4F,
|
|
0x50,
|
|
0x51,
|
|
0x4B,
|
|
0x4C,
|
|
0x4D,
|
|
0x47,
|
|
0,
|
|
0x48,
|
|
0x49,
|
|
0,
|
|
0,
|
|
0,
|
|
0x3F,
|
|
0x40,
|
|
0x41,
|
|
0x3D,
|
|
0x42,
|
|
0x43,
|
|
0,
|
|
0x57,
|
|
0,
|
|
0x137,
|
|
0,
|
|
0x46,
|
|
0,
|
|
0x44,
|
|
0x15D,
|
|
0x58,
|
|
0,
|
|
0, // Pause/Break key.
|
|
0x152,
|
|
0x147,
|
|
0x149,
|
|
0x153,
|
|
0x3E,
|
|
0x14F,
|
|
0x3C,
|
|
0x151,
|
|
0x3B,
|
|
0x14B,
|
|
0x14D,
|
|
0x150,
|
|
0x148,
|
|
0,
|
|
};
|
|
#endif
|
|
|
|
#ifdef __HAIKU__
|
|
static std::unordered_map<uint8_t, uint16_t> be_to_xt = {
|
|
{0x01, 0x01 },
|
|
{ B_F1_KEY, 0x3B },
|
|
{ B_F2_KEY, 0x3C },
|
|
{ B_F3_KEY, 0x3D },
|
|
{ B_F4_KEY, 0x3E },
|
|
{ B_F5_KEY, 0x3F },
|
|
{ B_F6_KEY, 0x40 },
|
|
{ B_F7_KEY, 0x41 },
|
|
{ B_F8_KEY, 0x42 },
|
|
{ B_F9_KEY, 0x43 },
|
|
{ B_F10_KEY, 0x44 },
|
|
{ B_F11_KEY, 0x57 },
|
|
{ B_F12_KEY, 0x58 },
|
|
{ 0x11, 0x29 },
|
|
{ 0x12, 0x02 },
|
|
{ 0x13, 0x03 },
|
|
{ 0x14, 0x04 },
|
|
{ 0x15, 0x05 },
|
|
{ 0x16, 0x06 },
|
|
{ 0x17, 0x07 },
|
|
{ 0x18, 0x08 },
|
|
{ 0x19, 0x09 },
|
|
{ 0x1A, 0x0A },
|
|
{ 0x1B, 0x0B },
|
|
{ 0x1C, 0x0C },
|
|
{ 0x1D, 0x0D },
|
|
{ 0x1E, 0x0E },
|
|
{ 0x1F, 0x152},
|
|
{ 0x20, 0x147},
|
|
{ 0x21, 0x149},
|
|
{ 0x22, 0x45 },
|
|
{ 0x23, 0x135},
|
|
{ 0x24, 0x37 },
|
|
{ 0x25, 0x4A },
|
|
{ 0x26, 0x0F },
|
|
{ 0x27, 0x10 },
|
|
{ 0x28, 0x11 },
|
|
{ 0x29, 0x12 },
|
|
{ 0x2A, 0x13 },
|
|
{ 0x2B, 0x14 },
|
|
{ 0x2C, 0x15 },
|
|
{ 0x2D, 0x16 },
|
|
{ 0x2E, 0x17 },
|
|
{ 0x2F, 0x18 },
|
|
{ 0x30, 0x19 },
|
|
{ 0x31, 0x1A },
|
|
{ 0x32, 0x1B },
|
|
{ 0x33, 0x2B },
|
|
{ 0x34, 0x153},
|
|
{ 0x35, 0x14F},
|
|
{ 0x36, 0x151},
|
|
{ 0x37, 0x47 },
|
|
{ 0x38, 0x48 },
|
|
{ 0x39, 0x49 },
|
|
{ 0x3A, 0x4E },
|
|
{ 0x3B, 0x3A },
|
|
{ 0x3C, 0x1E },
|
|
{ 0x3D, 0x1F },
|
|
{ 0x3E, 0x20 },
|
|
{ 0x3F, 0x21 },
|
|
{ 0x40, 0x22 },
|
|
{ 0x41, 0x23 },
|
|
{ 0x42, 0x24 },
|
|
{ 0x43, 0x25 },
|
|
{ 0x44, 0x26 },
|
|
{ 0x45, 0x27 },
|
|
{ 0x46, 0x28 },
|
|
{ 0x47, 0x1C },
|
|
{ 0x48, 0x4B },
|
|
{ 0x49, 0x4C },
|
|
{ 0x4A, 0x4D },
|
|
{ 0x4B, 0x2A },
|
|
{ 0x4C, 0x2C },
|
|
{ 0x4D, 0x2D },
|
|
{ 0x4E, 0x2E },
|
|
{ 0x4F, 0x2F },
|
|
{ 0x50, 0x30 },
|
|
{ 0x51, 0x31 },
|
|
{ 0x52, 0x32 },
|
|
{ 0x53, 0x33 },
|
|
{ 0x54, 0x34 },
|
|
{ 0x55, 0x35 },
|
|
{ 0x56, 0x36 },
|
|
{ 0x57, 0x148},
|
|
{ 0x58, 0x51 },
|
|
{ 0x59, 0x50 },
|
|
{ 0x5A, 0x4F },
|
|
{ 0x5B, 0x11C},
|
|
{ 0x5C, 0x1D },
|
|
{ 0x5D, 0x38 },
|
|
{ 0x5E, 0x39 },
|
|
{ 0x5F, 0x138},
|
|
{ 0x60, 0x11D},
|
|
{ 0x61, 0x14B},
|
|
{ 0x62, 0x150},
|
|
{ 0x63, 0x14D},
|
|
{ 0x64, 0x52 },
|
|
{ 0x65, 0x53 },
|
|
|
|
{ 0x0e, 0x137},
|
|
{ 0x0f, 0x46 },
|
|
{ 0x66, 0x15B},
|
|
{ 0x67, 0x15C},
|
|
{ 0x68, 0x15D},
|
|
{ 0x69, 0x56 }
|
|
};
|
|
#endif
|
|
|
|
#if defined(__unix__) && !defined(__HAIKU__)
|
|
static std::array<uint32_t, 256> &selected_keycode = x11_to_xt_base;
|
|
#endif
|
|
|
|
uint16_t
|
|
x11_keycode_to_keysym(uint32_t keycode)
|
|
{
|
|
uint16_t finalkeycode = 0;
|
|
#if defined(Q_OS_WINDOWS)
|
|
finalkeycode = (keycode & 0xFFFF);
|
|
#elif defined(Q_OS_MACOS)
|
|
finalkeycode = darwin_to_xt[keycode];
|
|
#elif defined(__HAIKU__)
|
|
finalkeycode = be_to_xt[keycode];
|
|
#else
|
|
# ifdef XKBCOMMON
|
|
if (xkbcommon_keymap) {
|
|
finalkeycode = xkbcommon_translate(keycode);
|
|
} else
|
|
# endif
|
|
# ifdef EVDEV_KEYBOARD_HPP
|
|
if (QApplication::platformName().contains("eglfs")) {
|
|
finalkeycode = evdev_translate(keycode);
|
|
} else
|
|
# endif
|
|
{
|
|
static Display *x11display = nullptr;
|
|
if (QApplication::platformName().contains("wayland")) {
|
|
selected_keycode = x11_to_xt_2;
|
|
} else if (!x11display) {
|
|
x11display = XOpenDisplay(nullptr);
|
|
if (XKeysymToKeycode(x11display, XK_Home) == 110) {
|
|
selected_keycode = x11_to_xt_2;
|
|
} else if (XKeysymToKeycode(x11display, XK_Home) == 69) {
|
|
selected_keycode = x11_to_xt_vnc;
|
|
}
|
|
}
|
|
finalkeycode = selected_keycode[keycode];
|
|
}
|
|
#endif
|
|
/* Special case for Ctrl+Pause. */
|
|
if ((finalkeycode == 0x145) && (keyboard_recv(0x1d) || keyboard_recv(0x11d)))
|
|
finalkeycode = 0x146;
|
|
|
|
if (rctrl_is_lalt && finalkeycode == 0x11D)
|
|
finalkeycode = 0x38;
|
|
return finalkeycode;
|
|
}
|
|
|
|
#ifdef Q_OS_MACOS
|
|
// These modifiers are listed as "device-dependent" in IOLLEvent.h, but
|
|
// that's followed up with "(really?)". It's the only way to distinguish
|
|
// left and right modifiers with Qt 6 on macOS, so let's just roll with it.
|
|
static std::unordered_map<uint32_t, uint16_t> mac_modifiers_to_xt = {
|
|
{NX_DEVICELCTLKEYMASK, 0x1D },
|
|
{ NX_DEVICELSHIFTKEYMASK, 0x2A },
|
|
{ NX_DEVICERSHIFTKEYMASK, 0x36 },
|
|
{ NX_DEVICELCMDKEYMASK, 0x15B},
|
|
{ NX_DEVICERCMDKEYMASK, 0x15C},
|
|
{ NX_DEVICELALTKEYMASK, 0x38 },
|
|
{ NX_DEVICERALTKEYMASK, 0x138},
|
|
{ NX_DEVICE_ALPHASHIFT_STATELESS_MASK, 0x3A },
|
|
{ NX_DEVICERCTLKEYMASK, 0x11D},
|
|
};
|
|
|
|
void
|
|
MainWindow::processMacKeyboardInput(bool down, const QKeyEvent *event)
|
|
{
|
|
// Per QTBUG-69608 (https://bugreports.qt.io/browse/QTBUG-69608),
|
|
// QKeyEvents QKeyEvents for presses/releases of modifiers on macOS give
|
|
// nativeVirtualKey() == 0 (at least in Qt 6). Handle this by manually
|
|
// processing the nativeModifiers(). We need to check whether the key() is
|
|
// a known modifier because because kVK_ANSI_A is also 0, so the
|
|
// nativeVirtualKey() == 0 condition is ambiguous...
|
|
if (event->nativeVirtualKey() == 0
|
|
&& (event->key() == Qt::Key_Shift
|
|
|| event->key() == Qt::Key_Control
|
|
|| event->key() == Qt::Key_Meta
|
|
|| event->key() == Qt::Key_Alt
|
|
|| event->key() == Qt::Key_AltGr
|
|
|| event->key() == Qt::Key_CapsLock)) {
|
|
// We only process one modifier at a time since events from Qt seem to
|
|
// always be non-coalesced (NX_NONCOALESCEDMASK is always set).
|
|
uint32_t changed_modifiers = last_modifiers ^ event->nativeModifiers();
|
|
for (auto const &pair : mac_modifiers_to_xt) {
|
|
if (changed_modifiers & pair.first) {
|
|
last_modifiers ^= pair.first;
|
|
keyboard_input(down, pair.second);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Caps Lock seems to be delivered as a single key press event when
|
|
// enabled and a single key release event when disabled, so we can't
|
|
// detect Caps Lock being held down; just send an infinitesimally-long
|
|
// press and release as a compromise.
|
|
//
|
|
// The event also doesn't get delivered if you turn Caps Lock off after
|
|
// turning it on when the window isn't focused. Doing better than this
|
|
// probably requires bypassing Qt input processing.
|
|
//
|
|
// It's possible that other lock keys get delivered in this way, but
|
|
// standard Apple keyboards don't have them, so this is untested.
|
|
if (event->key() == Qt::Key_CapsLock) {
|
|
keyboard_input(1, 0x3A);
|
|
keyboard_input(0, 0x3A);
|
|
}
|
|
} else {
|
|
keyboard_input(down, x11_keycode_to_keysym(event->nativeVirtualKey()));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void
|
|
MainWindow::on_actionFullscreen_triggered()
|
|
{
|
|
if (video_fullscreen > 0) {
|
|
showNormal();
|
|
if (vid_api == 5)
|
|
QTimer::singleShot(0, this, [this]() { ui->stackedWidget->switchRenderer(RendererStack::Renderer::Direct3D9); });
|
|
ui->menubar->show();
|
|
if (!hide_status_bar)
|
|
ui->statusbar->show();
|
|
if (!hide_tool_bar)
|
|
ui->toolBar->show();
|
|
video_fullscreen = 0;
|
|
if (vid_resize != 1) {
|
|
emit resizeContents(vid_resize == 2 ? fixed_size_x : monitors[0].mon_scrnsz_x, vid_resize == 2 ? fixed_size_y : monitors[0].mon_scrnsz_y);
|
|
}
|
|
} else {
|
|
if (video_fullscreen_first) {
|
|
bool wasCaptured = mouse_capture == 1;
|
|
|
|
QMessageBox questionbox(QMessageBox::Icon::Information, tr("Entering fullscreen mode"), tr("Press Ctrl+Alt+PgDn to return to windowed mode."), QMessageBox::Ok, this);
|
|
QCheckBox *chkbox = new QCheckBox(tr("Don't show this message again"));
|
|
questionbox.setCheckBox(chkbox);
|
|
chkbox->setChecked(!video_fullscreen_first);
|
|
|
|
QObject::connect(chkbox, &QCheckBox::stateChanged, [](int state) {
|
|
video_fullscreen_first = (state == Qt::CheckState::Unchecked);
|
|
});
|
|
questionbox.exec();
|
|
config_save();
|
|
|
|
/* (re-capture mouse after dialog). */
|
|
if (wasCaptured)
|
|
emit setMouseCapture(true);
|
|
}
|
|
video_fullscreen = 1;
|
|
setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
|
ui->menubar->hide();
|
|
ui->statusbar->hide();
|
|
ui->toolBar->hide();
|
|
ui->stackedWidget->setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
|
showFullScreen();
|
|
if (vid_api == 5)
|
|
QTimer::singleShot(0, this, [this]() { ui->stackedWidget->switchRenderer(RendererStack::Renderer::Direct3D9); });
|
|
}
|
|
ui->stackedWidget->onResize(width(), height());
|
|
}
|
|
|
|
void
|
|
MainWindow::getTitle_(wchar_t *title)
|
|
{
|
|
this->windowTitle().toWCharArray(title);
|
|
}
|
|
|
|
void
|
|
MainWindow::getTitle(wchar_t *title)
|
|
{
|
|
if (QThread::currentThread() == this->thread()) {
|
|
getTitle_(title);
|
|
} else {
|
|
emit getTitleForNonQtThread(title);
|
|
}
|
|
}
|
|
|
|
bool
|
|
MainWindow::eventFilter(QObject *receiver, QEvent *event)
|
|
{
|
|
if (!dopause && (mouse_capture || !kbd_req_capture)) {
|
|
if (event->type() == QEvent::Shortcut) {
|
|
auto shortcutEvent = (QShortcutEvent *) event;
|
|
if (shortcutEvent->key() == ui->actionExit->shortcut()) {
|
|
event->accept();
|
|
return true;
|
|
}
|
|
}
|
|
if (event->type() == QEvent::KeyPress) {
|
|
event->accept();
|
|
this->keyPressEvent((QKeyEvent *) event);
|
|
return true;
|
|
}
|
|
if (event->type() == QEvent::KeyRelease) {
|
|
event->accept();
|
|
this->keyReleaseEvent((QKeyEvent *) event);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (receiver == this) {
|
|
static auto curdopause = dopause;
|
|
if (event->type() == QEvent::WindowBlocked) {
|
|
curdopause = dopause;
|
|
plat_pause(1);
|
|
emit setMouseCapture(false);
|
|
} else if (event->type() == QEvent::WindowUnblocked) {
|
|
plat_pause(curdopause);
|
|
}
|
|
}
|
|
|
|
return QMainWindow::eventFilter(receiver, event);
|
|
}
|
|
|
|
void
|
|
MainWindow::refreshMediaMenu()
|
|
{
|
|
mm->refresh(ui->menuMedia);
|
|
status->refresh(ui->statusbar);
|
|
ui->actionMCA_devices->setVisible(machine_has_bus(machine, MACHINE_BUS_MCA));
|
|
ui->actionACPI_Shutdown->setEnabled(!!acpi_enabled);
|
|
}
|
|
|
|
void
|
|
MainWindow::showMessage(int flags, const QString &header, const QString &message)
|
|
{
|
|
if (QThread::currentThread() == this->thread()) {
|
|
showMessage_(flags, header, message);
|
|
} else {
|
|
emit showMessageForNonQtThread(flags, header, message);
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::showMessage_(int flags, const QString &header, const QString &message)
|
|
{
|
|
QMessageBox box(QMessageBox::Warning, header, message, QMessageBox::NoButton, this);
|
|
if (flags & (MBX_FATAL)) {
|
|
box.setIcon(QMessageBox::Critical);
|
|
} else if (!(flags & (MBX_ERROR | MBX_WARNING))) {
|
|
box.setIcon(QMessageBox::Warning);
|
|
}
|
|
box.setTextFormat(Qt::TextFormat::RichText);
|
|
box.exec();
|
|
if (cpu_thread_run == 0)
|
|
QApplication::exit(-1);
|
|
}
|
|
|
|
void
|
|
MainWindow::keyPressEvent(QKeyEvent *event)
|
|
{
|
|
if (send_keyboard_input && !(kbd_req_capture && !mouse_capture)) {
|
|
// Windows keys in Qt have one-to-one mapping.
|
|
if (event->key() == Qt::Key_Pause && !keyboard_recv(0x38) && !keyboard_recv(0x138)) {
|
|
if ((keyboard_recv(0x1D) || keyboard_recv(0x11D))) {
|
|
keyboard_input(1, 0x46);
|
|
} else {
|
|
keyboard_input(0, 0xE1);
|
|
keyboard_input(0, 0x1D);
|
|
keyboard_input(0, 0x45);
|
|
keyboard_input(0, 0xE1);
|
|
keyboard_input(1, 0x1D);
|
|
keyboard_input(1, 0x45);
|
|
}
|
|
} else
|
|
#ifdef Q_OS_MACOS
|
|
processMacKeyboardInput(true, event);
|
|
#else
|
|
keyboard_input(1, x11_keycode_to_keysym(event->nativeScanCode()));
|
|
#endif
|
|
}
|
|
|
|
if ((video_fullscreen > 0) && keyboard_isfsexit()) {
|
|
ui->actionFullscreen->trigger();
|
|
}
|
|
|
|
if (keyboard_ismsexit()) {
|
|
plat_mouse_capture(0);
|
|
}
|
|
|
|
if ((video_fullscreen > 0) && (keyboard_recv(0x1D) || keyboard_recv(0x11D))) {
|
|
if (keyboard_recv(0x57))
|
|
ui->actionTake_screenshot->trigger();
|
|
else if (keyboard_recv(0x58))
|
|
pc_send_cad();
|
|
}
|
|
|
|
event->accept();
|
|
}
|
|
|
|
void
|
|
MainWindow::blitToWidget(int x, int y, int w, int h, int monitor_index)
|
|
{
|
|
if (monitor_index >= 1) {
|
|
if (!blitDummied && renderers[monitor_index] && renderers[monitor_index]->isVisible())
|
|
renderers[monitor_index]->blit(x, y, w, h);
|
|
else
|
|
video_blit_complete_monitor(monitor_index);
|
|
} else
|
|
ui->stackedWidget->blit(x, y, w, h);
|
|
}
|
|
|
|
void
|
|
MainWindow::keyReleaseEvent(QKeyEvent *event)
|
|
{
|
|
if (event->key() == Qt::Key_Pause) {
|
|
if (keyboard_recv(0x38) && keyboard_recv(0x138)) {
|
|
plat_pause(dopause ^ 1);
|
|
}
|
|
}
|
|
if (!send_keyboard_input)
|
|
return;
|
|
|
|
#ifdef Q_OS_MACOS
|
|
processMacKeyboardInput(false, event);
|
|
#else
|
|
keyboard_input(0, x11_keycode_to_keysym(event->nativeScanCode()));
|
|
#endif
|
|
}
|
|
|
|
QSize
|
|
MainWindow::getRenderWidgetSize()
|
|
{
|
|
return ui->stackedWidget->size();
|
|
}
|
|
|
|
void
|
|
MainWindow::focusInEvent(QFocusEvent *event)
|
|
{
|
|
this->grabKeyboard();
|
|
}
|
|
|
|
void
|
|
MainWindow::focusOutEvent(QFocusEvent *event)
|
|
{
|
|
this->releaseKeyboard();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionResizable_window_triggered(bool checked)
|
|
{
|
|
if (checked) {
|
|
vid_resize = 1;
|
|
setWindowFlag(Qt::WindowMaximizeButtonHint);
|
|
setWindowFlag(Qt::MSWindowsFixedSizeDialogHint, false);
|
|
setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
|
for (int i = 1; i < MONITORS_NUM; i++) {
|
|
if (monitors[i].target_buffer) {
|
|
renderers[i]->setWindowFlag(Qt::WindowMaximizeButtonHint);
|
|
renderers[i]->setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
|
}
|
|
}
|
|
} else {
|
|
vid_resize = 0;
|
|
setWindowFlag(Qt::WindowMaximizeButtonHint, false);
|
|
setWindowFlag(Qt::MSWindowsFixedSizeDialogHint);
|
|
for (int i = 1; i < MONITORS_NUM; i++) {
|
|
if (monitors[i].target_buffer) {
|
|
renderers[i]->setWindowFlag(Qt::WindowMaximizeButtonHint, false);
|
|
emit resizeContentsMonitor(monitors[i].mon_scrnsz_x, monitors[i].mon_scrnsz_y, i);
|
|
}
|
|
}
|
|
}
|
|
show();
|
|
ui->menuWindow_scale_factor->setEnabled(!checked);
|
|
emit resizeContents(monitors[0].mon_scrnsz_x, monitors[0].mon_scrnsz_y);
|
|
ui->stackedWidget->switchRenderer((RendererStack::Renderer) vid_api);
|
|
for (int i = 1; i < MONITORS_NUM; i++) {
|
|
if (monitors[i].target_buffer && show_second_monitors) {
|
|
renderers[i]->show();
|
|
renderers[i]->switchRenderer((RendererStack::Renderer) vid_api);
|
|
QApplication::processEvents();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
video_toggle_option(QAction *action, int *val)
|
|
{
|
|
startblit();
|
|
*val ^= 1;
|
|
video_copy = (video_grayscale || invert_display) ? video_transform_copy : memcpy;
|
|
action->setChecked(*val > 0 ? true : false);
|
|
endblit();
|
|
config_save();
|
|
reset_screen_size();
|
|
device_force_redraw();
|
|
for (int i = 0; i < MONITORS_NUM; i++) {
|
|
if (monitors[i].target_buffer)
|
|
video_force_resize_set_monitor(1, i);
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionInverted_VGA_monitor_triggered()
|
|
{
|
|
video_toggle_option(ui->actionInverted_VGA_monitor, &invert_display);
|
|
}
|
|
|
|
static void
|
|
update_scaled_checkboxes(Ui::MainWindow *ui, QAction *selected)
|
|
{
|
|
ui->action0_5x->setChecked(ui->action0_5x == selected);
|
|
ui->action1x->setChecked(ui->action1x == selected);
|
|
ui->action1_5x->setChecked(ui->action1_5x == selected);
|
|
ui->action2x->setChecked(ui->action2x == selected);
|
|
ui->action3x->setChecked(ui->action3x == selected);
|
|
ui->action4x->setChecked(ui->action4x == selected);
|
|
ui->action5x->setChecked(ui->action5x == selected);
|
|
ui->action6x->setChecked(ui->action6x == selected);
|
|
ui->action7x->setChecked(ui->action7x == selected);
|
|
ui->action8x->setChecked(ui->action8x == selected);
|
|
|
|
reset_screen_size();
|
|
device_force_redraw();
|
|
for (int i = 0; i < MONITORS_NUM; i++) {
|
|
if (monitors[i].target_buffer)
|
|
video_force_resize_set_monitor(1, i);
|
|
}
|
|
config_save();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_action0_5x_triggered()
|
|
{
|
|
scale = 0;
|
|
update_scaled_checkboxes(ui, ui->action0_5x);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_action1x_triggered()
|
|
{
|
|
scale = 1;
|
|
update_scaled_checkboxes(ui, ui->action1x);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_action1_5x_triggered()
|
|
{
|
|
scale = 2;
|
|
update_scaled_checkboxes(ui, ui->action1_5x);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_action2x_triggered()
|
|
{
|
|
scale = 3;
|
|
update_scaled_checkboxes(ui, ui->action2x);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_action3x_triggered()
|
|
{
|
|
scale = 4;
|
|
update_scaled_checkboxes(ui, ui->action3x);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_action4x_triggered()
|
|
{
|
|
scale = 5;
|
|
update_scaled_checkboxes(ui, ui->action4x);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_action5x_triggered()
|
|
{
|
|
scale = 6;
|
|
update_scaled_checkboxes(ui, ui->action5x);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_action6x_triggered()
|
|
{
|
|
scale = 7;
|
|
update_scaled_checkboxes(ui, ui->action6x);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_action7x_triggered()
|
|
{
|
|
scale = 8;
|
|
update_scaled_checkboxes(ui, ui->action7x);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_action8x_triggered()
|
|
{
|
|
scale = 9;
|
|
update_scaled_checkboxes(ui, ui->action8x);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionNearest_triggered()
|
|
{
|
|
video_filter_method = 0;
|
|
ui->actionLinear->setChecked(false);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionLinear_triggered()
|
|
{
|
|
video_filter_method = 1;
|
|
ui->actionNearest->setChecked(false);
|
|
}
|
|
|
|
static void
|
|
update_fullscreen_scale_checkboxes(Ui::MainWindow *ui, QAction *selected)
|
|
{
|
|
ui->actionFullScreen_stretch->setChecked(ui->actionFullScreen_stretch == selected);
|
|
ui->actionFullScreen_43->setChecked(ui->actionFullScreen_43 == selected);
|
|
ui->actionFullScreen_keepRatio->setChecked(ui->actionFullScreen_keepRatio == selected);
|
|
ui->actionFullScreen_int->setChecked(ui->actionFullScreen_int == selected);
|
|
|
|
{
|
|
auto widget = ui->stackedWidget->currentWidget();
|
|
ui->stackedWidget->onResize(widget->width(), widget->height());
|
|
}
|
|
|
|
for (int i = 1; i < MONITORS_NUM; i++) {
|
|
if (main_window->renderers[i])
|
|
main_window->renderers[i]->onResize(main_window->renderers[i]->width(), main_window->renderers[i]->height());
|
|
}
|
|
|
|
device_force_redraw();
|
|
config_save();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionFullScreen_stretch_triggered()
|
|
{
|
|
video_fullscreen_scale = FULLSCR_SCALE_FULL;
|
|
update_fullscreen_scale_checkboxes(ui, ui->actionFullScreen_stretch);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionFullScreen_43_triggered()
|
|
{
|
|
video_fullscreen_scale = FULLSCR_SCALE_43;
|
|
update_fullscreen_scale_checkboxes(ui, ui->actionFullScreen_43);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionFullScreen_keepRatio_triggered()
|
|
{
|
|
video_fullscreen_scale = FULLSCR_SCALE_KEEPRATIO;
|
|
update_fullscreen_scale_checkboxes(ui, ui->actionFullScreen_keepRatio);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionFullScreen_int_triggered()
|
|
{
|
|
video_fullscreen_scale = FULLSCR_SCALE_INT;
|
|
update_fullscreen_scale_checkboxes(ui, ui->actionFullScreen_int);
|
|
}
|
|
|
|
static void
|
|
update_greyscale_checkboxes(Ui::MainWindow *ui, QAction *selected, int value)
|
|
{
|
|
ui->actionRGB_Color->setChecked(ui->actionRGB_Color == selected);
|
|
ui->actionRGB_Grayscale->setChecked(ui->actionRGB_Grayscale == selected);
|
|
ui->actionAmber_monitor->setChecked(ui->actionAmber_monitor == selected);
|
|
ui->actionGreen_monitor->setChecked(ui->actionGreen_monitor == selected);
|
|
ui->actionWhite_monitor->setChecked(ui->actionWhite_monitor == selected);
|
|
|
|
startblit();
|
|
video_grayscale = value;
|
|
video_copy = (video_grayscale || invert_display) ? video_transform_copy : memcpy;
|
|
endblit();
|
|
device_force_redraw();
|
|
config_save();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionRGB_Color_triggered()
|
|
{
|
|
update_greyscale_checkboxes(ui, ui->actionRGB_Color, 0);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionRGB_Grayscale_triggered()
|
|
{
|
|
update_greyscale_checkboxes(ui, ui->actionRGB_Grayscale, 1);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionAmber_monitor_triggered()
|
|
{
|
|
update_greyscale_checkboxes(ui, ui->actionAmber_monitor, 2);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionGreen_monitor_triggered()
|
|
{
|
|
update_greyscale_checkboxes(ui, ui->actionGreen_monitor, 3);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionWhite_monitor_triggered()
|
|
{
|
|
update_greyscale_checkboxes(ui, ui->actionWhite_monitor, 4);
|
|
}
|
|
|
|
static void
|
|
update_greyscale_type_checkboxes(Ui::MainWindow *ui, QAction *selected, int value)
|
|
{
|
|
ui->actionBT601_NTSC_PAL->setChecked(ui->actionBT601_NTSC_PAL == selected);
|
|
ui->actionBT709_HDTV->setChecked(ui->actionBT709_HDTV == selected);
|
|
ui->actionAverage->setChecked(ui->actionAverage == selected);
|
|
|
|
video_graytype = value;
|
|
device_force_redraw();
|
|
config_save();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionBT601_NTSC_PAL_triggered()
|
|
{
|
|
update_greyscale_type_checkboxes(ui, ui->actionBT601_NTSC_PAL, 0);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionBT709_HDTV_triggered()
|
|
{
|
|
update_greyscale_type_checkboxes(ui, ui->actionBT709_HDTV, 1);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionAverage_triggered()
|
|
{
|
|
update_greyscale_type_checkboxes(ui, ui->actionAverage, 2);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionAbout_Qt_triggered()
|
|
{
|
|
QApplication::aboutQt();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionAbout_86Box_triggered()
|
|
{
|
|
QMessageBox msgBox;
|
|
msgBox.setTextFormat(Qt::RichText);
|
|
QString versioninfo;
|
|
#ifdef EMU_GIT_HASH
|
|
versioninfo = QString(" [%1]").arg(EMU_GIT_HASH);
|
|
#endif
|
|
#ifdef USE_DYNAREC
|
|
# ifdef USE_NEW_DYNAREC
|
|
# define DYNAREC_STR "new dynarec"
|
|
# else
|
|
# define DYNAREC_STR "old dynarec"
|
|
# endif
|
|
#else
|
|
# define DYNAREC_STR "no dynarec"
|
|
#endif
|
|
versioninfo.append(QString(" [%1, %2]").arg(QSysInfo::buildCpuArchitecture(), tr(DYNAREC_STR)));
|
|
msgBox.setText(QString("<b>%3%1%2</b>").arg(EMU_VERSION_FULL, versioninfo, tr("86Box v")));
|
|
msgBox.setInformativeText(tr("An emulator of old computers\n\nAuthors: Sarah Walker, Miran Grca, Fred N. van Kempen (waltje), SA1988, Tiseno100, reenigne, leilei, JohnElliott, greatpsycho, and others.\n\nReleased under the GNU General Public License version 2 or later. See LICENSE for more information."));
|
|
msgBox.setWindowTitle("About 86Box");
|
|
msgBox.addButton("OK", QMessageBox::ButtonRole::AcceptRole);
|
|
auto webSiteButton = msgBox.addButton(EMU_SITE, QMessageBox::ButtonRole::HelpRole);
|
|
webSiteButton->connect(webSiteButton, &QPushButton::released, []() {
|
|
QDesktopServices::openUrl(QUrl("https://" EMU_SITE));
|
|
});
|
|
#ifdef RELEASE_BUILD
|
|
msgBox.setIconPixmap(QIcon(":/settings/win/icons/86Box-green.ico").pixmap(32, 32));
|
|
#elif defined ALPHA_BUILD
|
|
msgBox.setIconPixmap(QIcon(":/settings/win/icons/86Box-red.ico").pixmap(32, 32));
|
|
#elif defined BETA_BUILD
|
|
msgBox.setIconPixmap(QIcon(":/settings/win/icons/86Box-yellow.ico").pixmap(32, 32));
|
|
#else
|
|
msgBox.setIconPixmap(QIcon(":/settings/win/icons/86Box-gray.ico").pixmap(32, 32));
|
|
#endif
|
|
msgBox.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint);
|
|
msgBox.exec();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionDocumentation_triggered()
|
|
{
|
|
QDesktopServices::openUrl(QUrl(EMU_DOCS_URL));
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionCGA_PCjr_Tandy_EGA_S_VGA_overscan_triggered()
|
|
{
|
|
update_overscan = 1;
|
|
video_toggle_option(ui->actionCGA_PCjr_Tandy_EGA_S_VGA_overscan, &enable_overscan);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionChange_contrast_for_monochrome_display_triggered()
|
|
{
|
|
vid_cga_contrast ^= 1;
|
|
cgapal_rebuild();
|
|
config_save();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionForce_4_3_display_ratio_triggered()
|
|
{
|
|
video_toggle_option(ui->actionForce_4_3_display_ratio, &force_43);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionRemember_size_and_position_triggered()
|
|
{
|
|
window_remember ^= 1;
|
|
window_w = ui->stackedWidget->width();
|
|
window_h = ui->stackedWidget->height();
|
|
if (!QApplication::platformName().contains("wayland")) {
|
|
window_x = geometry().x();
|
|
window_y = geometry().y();
|
|
}
|
|
for (int i = 1; i < MONITORS_NUM; i++) {
|
|
if (window_remember && renderers[i]) {
|
|
monitor_settings[i].mon_window_w = renderers[i]->geometry().width();
|
|
monitor_settings[i].mon_window_h = renderers[i]->geometry().height();
|
|
monitor_settings[i].mon_window_x = renderers[i]->geometry().x();
|
|
monitor_settings[i].mon_window_y = renderers[i]->geometry().y();
|
|
}
|
|
}
|
|
ui->actionRemember_size_and_position->setChecked(window_remember);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionSpecify_dimensions_triggered()
|
|
{
|
|
SpecifyDimensions dialog(this);
|
|
dialog.setWindowModality(Qt::WindowModal);
|
|
dialog.exec();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionHiDPI_scaling_triggered()
|
|
{
|
|
dpi_scale ^= 1;
|
|
ui->actionHiDPI_scaling->setChecked(dpi_scale);
|
|
emit resizeContents(monitors[0].mon_scrnsz_x, monitors[0].mon_scrnsz_y);
|
|
for (int i = 1; i < MONITORS_NUM; i++) {
|
|
if (renderers[i])
|
|
emit resizeContentsMonitor(monitors[i].mon_scrnsz_x, monitors[i].mon_scrnsz_y, i);
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionHide_status_bar_triggered()
|
|
{
|
|
auto w = ui->stackedWidget->width();
|
|
auto h = ui->stackedWidget->height();
|
|
hide_status_bar ^= 1;
|
|
ui->actionHide_status_bar->setChecked(hide_status_bar);
|
|
statusBar()->setVisible(!hide_status_bar);
|
|
if (vid_resize >= 2) {
|
|
setFixedSize(fixed_size_x, fixed_size_y + menuBar()->height() + (hide_status_bar ? 0 : statusBar()->height()) + (hide_tool_bar ? 0 : ui->toolBar->height()));
|
|
} else {
|
|
int vid_resize_orig = vid_resize;
|
|
vid_resize = 0;
|
|
emit resizeContents(w, h);
|
|
vid_resize = vid_resize_orig;
|
|
if (vid_resize == 1)
|
|
setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionHide_tool_bar_triggered()
|
|
{
|
|
auto w = ui->stackedWidget->width();
|
|
auto h = ui->stackedWidget->height();
|
|
hide_tool_bar ^= 1;
|
|
ui->actionHide_tool_bar->setChecked(hide_tool_bar);
|
|
ui->toolBar->setVisible(!hide_tool_bar);
|
|
if (vid_resize >= 2) {
|
|
setFixedSize(fixed_size_x, fixed_size_y + menuBar()->height() + (hide_status_bar ? 0 : statusBar()->height()) + (hide_tool_bar ? 0 : ui->toolBar->height()));
|
|
} else {
|
|
int vid_resize_orig = vid_resize;
|
|
vid_resize = 0;
|
|
emit resizeContents(w, h);
|
|
vid_resize = vid_resize_orig;
|
|
if (vid_resize == 1)
|
|
setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionUpdate_status_bar_icons_triggered()
|
|
{
|
|
update_icons ^= 1;
|
|
ui->actionUpdate_status_bar_icons->setChecked(update_icons);
|
|
|
|
/* Prevent icons staying when disabled during activity. */
|
|
status->clearActivity();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionTake_screenshot_triggered()
|
|
{
|
|
startblit();
|
|
for (int i = 0; i < MONITORS_NUM; i++)
|
|
monitors[i].mon_screenshots++;
|
|
endblit();
|
|
device_force_redraw();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionSound_gain_triggered()
|
|
{
|
|
SoundGain gain(this);
|
|
gain.exec();
|
|
}
|
|
|
|
void
|
|
MainWindow::setSendKeyboardInput(bool enabled)
|
|
{
|
|
send_keyboard_input = enabled;
|
|
}
|
|
|
|
void
|
|
MainWindow::updateUiPauseState()
|
|
{
|
|
auto pause_icon = dopause ? QIcon(":/menuicons/win/icons/run.ico") : QIcon(":/menuicons/win/icons/pause.ico");
|
|
auto tooltip_text = dopause ? QString(tr("Resume execution")) : QString(tr("Pause execution"));
|
|
ui->actionPause->setIcon(pause_icon);
|
|
ui->actionPause->setToolTip(tooltip_text);
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionPreferences_triggered()
|
|
{
|
|
ProgSettings progsettings(this);
|
|
progsettings.exec();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionEnable_Discord_integration_triggered(bool checked)
|
|
{
|
|
enable_discord = checked;
|
|
#ifdef DISCORD
|
|
if (enable_discord) {
|
|
discord_init();
|
|
discord_update_activity(dopause);
|
|
} else
|
|
discord_close();
|
|
#endif
|
|
}
|
|
|
|
void
|
|
MainWindow::showSettings()
|
|
{
|
|
if (findChild<Settings *>() == nullptr)
|
|
ui->actionSettings->trigger();
|
|
}
|
|
|
|
void
|
|
MainWindow::hardReset()
|
|
{
|
|
ui->actionHard_Reset->trigger();
|
|
}
|
|
|
|
void
|
|
MainWindow::togglePause()
|
|
{
|
|
ui->actionPause->trigger();
|
|
}
|
|
|
|
void
|
|
MainWindow::changeEvent(QEvent *event)
|
|
{
|
|
#ifdef Q_OS_WINDOWS
|
|
if (event->type() == QEvent::LanguageChange) {
|
|
auto font_name = tr("FONT_NAME");
|
|
auto font_size = tr("FONT_SIZE");
|
|
QApplication::setFont(QFont(font_name, font_size.toInt()));
|
|
}
|
|
#endif
|
|
QWidget::changeEvent(event);
|
|
if (isVisible()) {
|
|
monitor_settings[0].mon_window_maximized = isMaximized();
|
|
config_save();
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionRenderer_options_triggered()
|
|
{
|
|
auto dlg = ui->stackedWidget->getOptions(this);
|
|
|
|
if (dlg) {
|
|
if (dlg->exec() == QDialog::Accepted) {
|
|
for (int i = 1; i < MONITORS_NUM; i++) {
|
|
if (renderers[i] && renderers[i]->hasOptions())
|
|
renderers[i]->reloadOptions();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionMCA_devices_triggered()
|
|
{
|
|
auto dlg = new MCADeviceList(this);
|
|
|
|
if (dlg)
|
|
dlg->exec();
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionShow_non_primary_monitors_triggered()
|
|
{
|
|
show_second_monitors = (int) ui->actionShow_non_primary_monitors->isChecked();
|
|
|
|
blitDummied = true;
|
|
|
|
if (show_second_monitors) {
|
|
for (int monitor_index = 1; monitor_index < MONITORS_NUM; monitor_index++) {
|
|
auto &secondaryRenderer = renderers[monitor_index];
|
|
if (!renderers[monitor_index])
|
|
continue;
|
|
secondaryRenderer->show();
|
|
if (window_remember) {
|
|
secondaryRenderer->setGeometry(monitor_settings[monitor_index].mon_window_x < 120 ? 120 : monitor_settings[monitor_index].mon_window_x,
|
|
monitor_settings[monitor_index].mon_window_y < 120 ? 120 : monitor_settings[monitor_index].mon_window_y,
|
|
monitor_settings[monitor_index].mon_window_w > 2048 ? 2048 : monitor_settings[monitor_index].mon_window_w,
|
|
monitor_settings[monitor_index].mon_window_h > 2048 ? 2048 : monitor_settings[monitor_index].mon_window_h);
|
|
}
|
|
secondaryRenderer->switchRenderer((RendererStack::Renderer) vid_api);
|
|
ui->stackedWidget->switchRenderer((RendererStack::Renderer) vid_api);
|
|
}
|
|
} else {
|
|
for (int monitor_index = 1; monitor_index < MONITORS_NUM; monitor_index++) {
|
|
auto &secondaryRenderer = renderers[monitor_index];
|
|
if (!renderers[monitor_index])
|
|
continue;
|
|
secondaryRenderer->hide();
|
|
if (window_remember && renderers[monitor_index]) {
|
|
monitor_settings[monitor_index].mon_window_w = renderers[monitor_index]->geometry().width();
|
|
monitor_settings[monitor_index].mon_window_h = renderers[monitor_index]->geometry().height();
|
|
monitor_settings[monitor_index].mon_window_x = renderers[monitor_index]->geometry().x();
|
|
monitor_settings[monitor_index].mon_window_y = renderers[monitor_index]->geometry().y();
|
|
}
|
|
}
|
|
}
|
|
|
|
blitDummied = false;
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionOpen_screenshots_folder_triggered()
|
|
{
|
|
QDir(QString(usr_path) + QString("/screenshots/")).mkpath(".");
|
|
QDesktopServices::openUrl(QUrl(QString("file:///") + usr_path + QString("/screenshots/")));
|
|
}
|
|
|
|
void
|
|
MainWindow::on_actionApply_fullscreen_stretch_mode_when_maximized_triggered(bool checked)
|
|
{
|
|
video_fullscreen_scale_maximized = checked;
|
|
|
|
auto widget = ui->stackedWidget->currentWidget();
|
|
ui->stackedWidget->onResize(widget->width(), widget->height());
|
|
|
|
for (int i = 1; i < MONITORS_NUM; i++) {
|
|
if (renderers[i])
|
|
renderers[i]->onResize(renderers[i]->width(), renderers[i]->height());
|
|
}
|
|
|
|
device_force_redraw();
|
|
config_save();
|
|
}
|
|
|
|
void MainWindow::on_actionCursor_Puck_triggered()
|
|
{
|
|
tablet_tool_type = 0;
|
|
config_save();
|
|
}
|
|
|
|
void MainWindow::on_actionPen_triggered()
|
|
{
|
|
tablet_tool_type = 1;
|
|
config_save();
|
|
}
|
|
|
|
void MainWindow::on_actionACPI_Shutdown_triggered()
|
|
{
|
|
acpi_pwrbut_pressed = 1;
|
|
}
|