From 81a49c8c5c84015324844890c7a61b5892cdd7ee Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Thu, 25 Nov 2021 10:20:21 +0100 Subject: [PATCH 01/30] set vendor field width to 9 (matshita overflows at 8) --- src/include/86box/mo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/86box/mo.h b/src/include/86box/mo.h index e99d5e381..caf5f2417 100644 --- a/src/include/86box/mo.h +++ b/src/include/86box/mo.h @@ -51,7 +51,7 @@ static const mo_type_t mo_types[KNOWN_MO_TYPES] = { typedef struct { - const char vendor[8]; + const char vendor[9]; const char model[16]; const char revision[5]; int8_t supported_media[KNOWN_MO_TYPES]; From c587a02b1adaae6e305b0f756da04d11cb0a7af5 Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Thu, 25 Nov 2021 10:20:56 +0100 Subject: [PATCH 02/30] Initial Qt Commit --- CMakeLists.txt | 1 + src/CMakeLists.txt | 5 +- src/qt/CMakeLists.txt | 86 +++ src/qt/TODO | 1 + src/qt/cpp11_thread.cpp | 130 ++++ src/qt/qt.c | 70 +++ src/qt/qt_cdrom.c | 264 ++++++++ src/qt/qt_deviceconfig.cpp | 157 +++++ src/qt/qt_deviceconfig.hpp | 28 + src/qt/qt_deviceconfig.ui | 74 +++ src/qt/qt_filefield.cpp | 34 ++ src/qt/qt_filefield.hpp | 35 ++ src/qt/qt_filefield.ui | 47 ++ src/qt/qt_harddiskdialog.cpp | 706 +++++++++++++++++++++ src/qt/qt_harddiskdialog.hpp | 57 ++ src/qt/qt_harddiskdialog.ui | 230 +++++++ src/qt/qt_harddrive_common.cpp | 101 +++ src/qt/qt_harddrive_common.hpp | 13 + src/qt/qt_main.cpp | 104 ++++ src/qt/qt_mainwindow.cpp | 299 +++++++++ src/qt/qt_mainwindow.hpp | 83 +++ src/qt/qt_mainwindow.ui | 143 +++++ src/qt/qt_midi.cpp | 32 + src/qt/qt_models_common.cpp | 15 + src/qt/qt_models_common.hpp | 8 + src/qt/qt_platform.cpp | 382 ++++++++++++ src/qt/qt_sdl.c | 743 +++++++++++++++++++++++ src/qt/qt_sdl.h | 73 +++ src/qt/qt_settings.cpp | 131 ++++ src/qt/qt_settings.hpp | 46 ++ src/qt/qt_settings.ui | 87 +++ src/qt/qt_settingsdisplay.cpp | 105 ++++ src/qt/qt_settingsdisplay.hpp | 34 ++ src/qt/qt_settingsdisplay.ui | 77 +++ src/qt/qt_settingsfloppycdrom.cpp | 217 +++++++ src/qt/qt_settingsfloppycdrom.hpp | 35 ++ src/qt/qt_settingsfloppycdrom.ui | 148 +++++ src/qt/qt_settingsharddisks.cpp | 225 +++++++ src/qt/qt_settingsharddisks.hpp | 35 ++ src/qt/qt_settingsharddisks.ui | 95 +++ src/qt/qt_settingsinput.cpp | 114 ++++ src/qt/qt_settingsinput.hpp | 33 + src/qt/qt_settingsinput.ui | 134 ++++ src/qt/qt_settingsmachine.cpp | 348 +++++++++++ src/qt/qt_settingsmachine.hpp | 41 ++ src/qt/qt_settingsmachine.ui | 229 +++++++ src/qt/qt_settingsnetwork.cpp | 113 ++++ src/qt/qt_settingsnetwork.hpp | 33 + src/qt/qt_settingsnetwork.ui | 101 +++ src/qt/qt_settingsotherperipherals.cpp | 133 ++++ src/qt/qt_settingsotherperipherals.hpp | 53 ++ src/qt/qt_settingsotherperipherals.ui | 162 +++++ src/qt/qt_settingsotherremovable.cpp | 267 ++++++++ src/qt/qt_settingsotherremovable.hpp | 52 ++ src/qt/qt_settingsotherremovable.ui | 151 +++++ src/qt/qt_settingsports.cpp | 81 +++ src/qt/qt_settingsports.hpp | 28 + src/qt/qt_settingsports.ui | 133 ++++ src/qt/qt_settingssound.cpp | 223 +++++++ src/qt/qt_settingssound.hpp | 44 ++ src/qt/qt_settingssound.ui | 160 +++++ src/qt/qt_settingsstoragecontrollers.cpp | 233 +++++++ src/qt/qt_settingsstoragecontrollers.hpp | 76 +++ src/qt/qt_settingsstoragecontrollers.ui | 202 ++++++ src/qt/qt_ui.cpp | 111 ++++ src/qt_resources.qrc | 24 + 66 files changed, 8434 insertions(+), 1 deletion(-) create mode 100644 src/qt/CMakeLists.txt create mode 100644 src/qt/TODO create mode 100644 src/qt/cpp11_thread.cpp create mode 100644 src/qt/qt.c create mode 100644 src/qt/qt_cdrom.c create mode 100644 src/qt/qt_deviceconfig.cpp create mode 100644 src/qt/qt_deviceconfig.hpp create mode 100644 src/qt/qt_deviceconfig.ui create mode 100644 src/qt/qt_filefield.cpp create mode 100644 src/qt/qt_filefield.hpp create mode 100644 src/qt/qt_filefield.ui create mode 100644 src/qt/qt_harddiskdialog.cpp create mode 100644 src/qt/qt_harddiskdialog.hpp create mode 100644 src/qt/qt_harddiskdialog.ui create mode 100644 src/qt/qt_harddrive_common.cpp create mode 100644 src/qt/qt_harddrive_common.hpp create mode 100644 src/qt/qt_main.cpp create mode 100644 src/qt/qt_mainwindow.cpp create mode 100644 src/qt/qt_mainwindow.hpp create mode 100644 src/qt/qt_mainwindow.ui create mode 100644 src/qt/qt_midi.cpp create mode 100644 src/qt/qt_models_common.cpp create mode 100644 src/qt/qt_models_common.hpp create mode 100644 src/qt/qt_platform.cpp create mode 100644 src/qt/qt_sdl.c create mode 100644 src/qt/qt_sdl.h create mode 100644 src/qt/qt_settings.cpp create mode 100644 src/qt/qt_settings.hpp create mode 100644 src/qt/qt_settings.ui create mode 100644 src/qt/qt_settingsdisplay.cpp create mode 100644 src/qt/qt_settingsdisplay.hpp create mode 100644 src/qt/qt_settingsdisplay.ui create mode 100644 src/qt/qt_settingsfloppycdrom.cpp create mode 100644 src/qt/qt_settingsfloppycdrom.hpp create mode 100644 src/qt/qt_settingsfloppycdrom.ui create mode 100644 src/qt/qt_settingsharddisks.cpp create mode 100644 src/qt/qt_settingsharddisks.hpp create mode 100644 src/qt/qt_settingsharddisks.ui create mode 100644 src/qt/qt_settingsinput.cpp create mode 100644 src/qt/qt_settingsinput.hpp create mode 100644 src/qt/qt_settingsinput.ui create mode 100644 src/qt/qt_settingsmachine.cpp create mode 100644 src/qt/qt_settingsmachine.hpp create mode 100644 src/qt/qt_settingsmachine.ui create mode 100644 src/qt/qt_settingsnetwork.cpp create mode 100644 src/qt/qt_settingsnetwork.hpp create mode 100644 src/qt/qt_settingsnetwork.ui create mode 100644 src/qt/qt_settingsotherperipherals.cpp create mode 100644 src/qt/qt_settingsotherperipherals.hpp create mode 100644 src/qt/qt_settingsotherperipherals.ui create mode 100644 src/qt/qt_settingsotherremovable.cpp create mode 100644 src/qt/qt_settingsotherremovable.hpp create mode 100644 src/qt/qt_settingsotherremovable.ui create mode 100644 src/qt/qt_settingsports.cpp create mode 100644 src/qt/qt_settingsports.hpp create mode 100644 src/qt/qt_settingsports.ui create mode 100644 src/qt/qt_settingssound.cpp create mode 100644 src/qt/qt_settingssound.hpp create mode 100644 src/qt/qt_settingssound.ui create mode 100644 src/qt/qt_settingsstoragecontrollers.cpp create mode 100644 src/qt/qt_settingsstoragecontrollers.hpp create mode 100644 src/qt/qt_settingsstoragecontrollers.ui create mode 100644 src/qt/qt_ui.cpp create mode 100644 src/qt_resources.qrc diff --git a/CMakeLists.txt b/CMakeLists.txt index 89e661fcb..64d0087e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ option(MUNT "MUNT" ON) option(VRAMDUMP "Video RAM dumping" OFF) option(DINPUT "DirectInput" OFF) option(DISCORD "Discord integration" ON) +option(QT "QT GUI" ON) option(NEW_DYNAREC "Use the PCem v15 (\"new\") dynamic recompiler" OFF) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7e9e24e89..f5b3c99f8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -221,7 +221,10 @@ add_subdirectory(sio) add_subdirectory(scsi) add_subdirectory(sound) add_subdirectory(video) -if(APPLE) + +if (QT) + add_subdirectory(qt) +elseif(APPLE) add_subdirectory(mac) add_subdirectory(unix) elseif(WIN32) diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt new file mode 100644 index 000000000..ab923e042 --- /dev/null +++ b/src/qt/CMakeLists.txt @@ -0,0 +1,86 @@ +find_package(Qt5 COMPONENTS Core Widgets REQUIRED) + +# Find includes in corresponding build directories +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) + +add_library(plat STATIC qt.c qt_main.cpp qt_platform.cpp qt_midi.cpp cpp11_thread.cpp) +add_library(ui STATIC + qt_ui.cpp + qt_cdrom.c + qt_sdl.c + + qt_mainwindow.cpp + qt_mainwindow.hpp + qt_mainwindow.ui + + qt_settings.cpp + qt_settings.hpp + qt_settings.ui + + qt_settingsmachine.cpp + qt_settingsmachine.hpp + qt_settingsmachine.ui + qt_settingsdisplay.cpp + qt_settingsdisplay.hpp + qt_settingsdisplay.ui + qt_settingsinput.cpp + qt_settingsinput.hpp + qt_settingsinput.ui + qt_settingssound.cpp + qt_settingssound.hpp + qt_settingssound.ui + qt_settingsnetwork.cpp + qt_settingsnetwork.hpp + qt_settingsnetwork.ui + qt_settingsports.cpp + qt_settingsports.hpp + qt_settingsports.ui + qt_settingsstoragecontrollers.cpp + qt_settingsstoragecontrollers.hpp + qt_settingsstoragecontrollers.ui + qt_settingsharddisks.cpp + qt_settingsharddisks.hpp + qt_settingsharddisks.ui + qt_settingsfloppycdrom.cpp + qt_settingsfloppycdrom.hpp + qt_settingsfloppycdrom.ui + qt_settingsotherremovable.cpp + qt_settingsotherremovable.hpp + qt_settingsotherremovable.ui + qt_settingsotherperipherals.cpp + qt_settingsotherperipherals.hpp + qt_settingsotherperipherals.ui + + qt_deviceconfig.cpp + qt_deviceconfig.hpp + qt_deviceconfig.ui + + qt_filefield.cpp + qt_filefield.hpp + qt_filefield.ui + qt_harddiskdialog.cpp + qt_harddiskdialog.hpp + qt_harddiskdialog.ui + + qt_harddrive_common.cpp + qt_harddrive_common.hpp + qt_models_common.cpp + qt_models_common.hpp + + ../qt_resources.qrc +) + +target_link_libraries( + plat + PRIVATE + Qt5::Widgets +) + +target_link_libraries( + ui + PRIVATE + Qt5::Widgets +) diff --git a/src/qt/TODO b/src/qt/TODO new file mode 100644 index 000000000..a8b0b4970 --- /dev/null +++ b/src/qt/TODO @@ -0,0 +1 @@ +* Joystick support diff --git a/src/qt/cpp11_thread.cpp b/src/qt/cpp11_thread.cpp new file mode 100644 index 000000000..270832f10 --- /dev/null +++ b/src/qt/cpp11_thread.cpp @@ -0,0 +1,130 @@ +#include +#include +#include +#include + +#include <86box/plat.h> + +struct event_cpp11_t +{ + std::condition_variable cond; + std::mutex mutex; + std::atomic_bool state = false; +}; + +extern "C" { + +thread_t * +thread_create(void (*thread_rout)(void *param), void *param) +{ + auto thread = new std::thread([thread_rout, param] { + thread_rout(param); + }); + return thread; +} + +mutex_t * +thread_create_mutex_with_spin_count(unsigned int spin_count) +{ + /* Setting spin count of a mutex is not possible with pthreads. */ + return thread_create_mutex(); +} + +int +thread_wait(thread_t *arg, int timeout) +{ + (void) timeout; + auto thread = reinterpret_cast(arg); + thread->join(); + return 0; +} + +mutex_t * +thread_create_mutex(void) +{ + auto mutex = new std::mutex; + return mutex; +} + +int +thread_wait_mutex(mutex_t *_mutex) +{ + if (_mutex == nullptr) + return(0); + auto mutex = reinterpret_cast(_mutex); + mutex->lock(); + return 1; +} + + +int +thread_release_mutex(mutex_t *_mutex) +{ + if (_mutex == nullptr) + return(0); + auto mutex = reinterpret_cast(_mutex); + mutex->unlock(); + return 1; +} + + +void +thread_close_mutex(mutex_t *_mutex) +{ + auto mutex = reinterpret_cast(_mutex); + delete mutex; +} + +event_t * +thread_create_event() +{ + auto ev = new event_cpp11_t; + return ev; +} + +int +thread_wait_event(event_t *handle, int timeout) +{ + auto event = reinterpret_cast(handle); + auto lock = std::unique_lock(event->mutex); + + if (timeout < 0) { + event->cond.wait(lock, [=] { return event->state.load(); }); + } else { + auto to = std::chrono::system_clock::now() + std::chrono::milliseconds(timeout); + std::cv_status status; + + do { + status = event->cond.wait_until(lock, to); + } while ((status != std::cv_status::timeout) && !event->state); + + if (status == std::cv_status::timeout) { + return 1; + } + } + return 0; +} + +void +thread_set_event(event_t *handle) +{ + auto event = reinterpret_cast(handle); + event->state = true; + event->cond.notify_all(); +} + +void +thread_reset_event(event_t *handle) +{ + auto event = reinterpret_cast(handle); + event->state = false; +} + +void +thread_destroy_event(event_t *handle) +{ + auto event = reinterpret_cast(handle); + delete event; +} + +} diff --git a/src/qt/qt.c b/src/qt/qt.c new file mode 100644 index 000000000..2ebab6f69 --- /dev/null +++ b/src/qt/qt.c @@ -0,0 +1,70 @@ +/* + * C functionality for Qt platform, where the C equivalent is not easily + * implemented in Qt + */ + +#include +#include + +#include <86box/86box.h> +#include <86box/device.h> +#include <86box/plat.h> +#include <86box/timer.h> +#include <86box/nvr.h> + +#include "qt_sdl.h" + +int qt_nvr_save(void) { + return nvr_save(); +} + +char icon_set[256] = ""; /* name of the iconset to be used */ + +wchar_t* plat_get_string(int i) +{ + switch (i) + { + case IDS_2077: + return L"Click to capture mouse."; + case IDS_2078: + return L"Press CTRL-END to release mouse"; + case IDS_2079: + return L"Press CTRL-END or middle button to release mouse"; + case IDS_2080: + return L"Failed to initialize FluidSynth"; + case IDS_4099: + return L"MFM/RLL or ESDI CD-ROM drives never existed"; + case IDS_2093: + return L"Failed to set up PCap"; + case IDS_2094: + return L"No PCap devices found"; + case IDS_2110: + return L"Unable to initialize FreeType"; + case IDS_2111: + return L"Unable to initialize SDL, libsdl2 is required"; + case IDS_2131: + return L"libfreetype is required for ESC/P printer emulation."; + case IDS_2132: + return L"libgs is required for automatic conversion of PostScript files to PDF.\n\nAny documents sent to the generic PostScript printer will be saved as PostScript (.ps) files."; + case IDS_2129: + return L"Make sure libpcap is installed and that you are on a libpcap-compatible network connection."; + case IDS_2114: + return L"Unable to initialize Ghostscript"; + case IDS_2063: + return L"Machine \"%hs\" is not available due to missing ROMs in the roms/machines directory. Switching to an available machine."; + case IDS_2064: + return L"Video card \"%hs\" is not available due to missing ROMs in the roms/video directory. Switching to an available video card."; + case IDS_2128: + return L"Hardware not available"; + } + return L""; +} + +int +plat_vidapi(char* api) { + return 0; +} + +char* plat_vidapi_name(int api) { + return "default"; +} diff --git a/src/qt/qt_cdrom.c b/src/qt/qt_cdrom.c new file mode 100644 index 000000000..9eb3d962a --- /dev/null +++ b/src/qt/qt_cdrom.c @@ -0,0 +1,264 @@ +/* + * 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. + * + * Handle the platform-side of CDROM/ZIP/MO drives. + * + * + * + * Authors: Sarah Walker, + * Miran Grca, + * Fred N. van Kempen, + * + * Copyright 2016-2018 Miran Grca. + * Copyright 2017,2018 Fred N. van Kempen. + */ + +#include +#include +#include +#include +#include +#include <86box/86box.h> +#include <86box/config.h> +#include <86box/timer.h> +#include <86box/device.h> +#include <86box/cassette.h> +#include <86box/cartridge.h> +#include <86box/fdd.h> +#include <86box/hdd.h> +#include <86box/scsi_device.h> +#include <86box/cdrom.h> +#include <86box/mo.h> +#include <86box/zip.h> +#include <86box/scsi_disk.h> +#include <86box/plat.h> +#include <86box/ui.h> + + + +void +cassette_mount(char *fn, uint8_t wp) +{ + pc_cas_set_fname(cassette, NULL); + memset(cassette_fname, 0, sizeof(cassette_fname)); + cassette_ui_writeprot = wp; + pc_cas_set_fname(cassette, fn); + if (fn != NULL) + memcpy(cassette_fname, fn, MIN(511, strlen(fn))); + ui_sb_update_icon_state(SB_CASSETTE, (fn == NULL) ? 1 : 0); + //media_menu_update_cassette(); + ui_sb_update_tip(SB_CASSETTE); + config_save(); +} + + +void +cassette_eject(void) +{ + pc_cas_set_fname(cassette, NULL); + memset(cassette_fname, 0x00, sizeof(cassette_fname)); + ui_sb_update_icon_state(SB_CASSETTE, 1); + //media_menu_update_cassette(); + ui_sb_update_tip(SB_CASSETTE); + config_save(); +} + + +void +cartridge_mount(uint8_t id, char *fn, uint8_t wp) +{ + cart_close(id); + cart_load(id, fn); + ui_sb_update_icon_state(SB_CARTRIDGE | id, strlen(cart_fns[id]) ? 0 : 1); + //media_menu_update_cartridge(id); + ui_sb_update_tip(SB_CARTRIDGE | id); + config_save(); +} + + +void +cartridge_eject(uint8_t id) +{ + cart_close(id); + ui_sb_update_icon_state(SB_CARTRIDGE | id, 1); + //media_menu_update_cartridge(id); + ui_sb_update_tip(SB_CARTRIDGE | id); + config_save(); +} + + +void +floppy_mount(uint8_t id, char *fn, uint8_t wp) +{ + fdd_close(id); + ui_writeprot[id] = wp; + fdd_load(id, fn); + ui_sb_update_icon_state(SB_FLOPPY | id, strlen(floppyfns[id]) ? 0 : 1); + //media_menu_update_floppy(id); + ui_sb_update_tip(SB_FLOPPY | id); + config_save(); +} + + +void +floppy_eject(uint8_t id) +{ + fdd_close(id); + ui_sb_update_icon_state(SB_FLOPPY | id, 1); + //media_menu_update_floppy(id); + ui_sb_update_tip(SB_FLOPPY | id); + config_save(); +} + + +void +plat_cdrom_ui_update(uint8_t id, uint8_t reload) +{ + cdrom_t *drv = &cdrom[id]; + + if (drv->host_drive == 0) { + ui_sb_update_icon_state(SB_CDROM|id, 1); + } else { + ui_sb_update_icon_state(SB_CDROM|id, 0); + } + + //media_menu_update_cdrom(id); + ui_sb_update_tip(SB_CDROM|id); +} + +void +cdrom_mount(uint8_t id, char *fn) +{ + cdrom[id].prev_host_drive = cdrom[id].host_drive; + strcpy(cdrom[id].prev_image_path, cdrom[id].image_path); + if (cdrom[id].ops && cdrom[id].ops->exit) + cdrom[id].ops->exit(&(cdrom[id])); + cdrom[id].ops = NULL; + memset(cdrom[id].image_path, 0, sizeof(cdrom[id].image_path)); + cdrom_image_open(&(cdrom[id]), fn); + /* Signal media change to the emulated machine. */ + if (cdrom[id].insert) + cdrom[id].insert(cdrom[id].priv); + cdrom[id].host_drive = (strlen(cdrom[id].image_path) == 0) ? 0 : 200; + if (cdrom[id].host_drive == 200) { + ui_sb_update_icon_state(SB_CDROM | id, 0); + } else { + ui_sb_update_icon_state(SB_CDROM | id, 1); + } + //media_menu_update_cdrom(id); + ui_sb_update_tip(SB_CDROM | id); + config_save(); +} + +void +mo_eject(uint8_t id) +{ + mo_t *dev = (mo_t *) mo_drives[id].priv; + + mo_disk_close(dev); + if (mo_drives[id].bus_type) { + /* Signal disk change to the emulated machine. */ + mo_insert(dev); + } + + ui_sb_update_icon_state(SB_MO | id, 1); + //media_menu_update_mo(id); + ui_sb_update_tip(SB_MO | id); + config_save(); +} + + +void +mo_mount(uint8_t id, char *fn, uint8_t wp) +{ + mo_t *dev = (mo_t *) mo_drives[id].priv; + + mo_disk_close(dev); + mo_drives[id].read_only = wp; + mo_load(dev, fn); + mo_insert(dev); + + ui_sb_update_icon_state(SB_MO | id, strlen(mo_drives[id].image_path) ? 0 : 1); + //media_menu_update_mo(id); + ui_sb_update_tip(SB_MO | id); + + config_save(); +} + + +void +mo_reload(uint8_t id) +{ + mo_t *dev = (mo_t *) mo_drives[id].priv; + + mo_disk_reload(dev); + if (strlen(mo_drives[id].image_path) == 0) { + ui_sb_update_icon_state(SB_MO|id, 1); + } else { + ui_sb_update_icon_state(SB_MO|id, 0); + } + + //media_menu_update_mo(id); + ui_sb_update_tip(SB_MO|id); + + config_save(); +} + +void +zip_eject(uint8_t id) +{ + zip_t *dev = (zip_t *) zip_drives[id].priv; + + zip_disk_close(dev); + if (zip_drives[id].bus_type) { + /* Signal disk change to the emulated machine. */ + zip_insert(dev); + } + + ui_sb_update_icon_state(SB_ZIP | id, 1); + //media_menu_update_zip(id); + ui_sb_update_tip(SB_ZIP | id); + config_save(); +} + + +void +zip_mount(uint8_t id, char *fn, uint8_t wp) +{ + zip_t *dev = (zip_t *) zip_drives[id].priv; + + zip_disk_close(dev); + zip_drives[id].read_only = wp; + zip_load(dev, fn); + zip_insert(dev); + + ui_sb_update_icon_state(SB_ZIP | id, strlen(zip_drives[id].image_path) ? 0 : 1); + //media_menu_update_zip(id); + ui_sb_update_tip(SB_ZIP | id); + + config_save(); +} + + +void +zip_reload(uint8_t id) +{ + zip_t *dev = (zip_t *) zip_drives[id].priv; + + zip_disk_reload(dev); + if (strlen(zip_drives[id].image_path) == 0) { + ui_sb_update_icon_state(SB_ZIP|id, 1); + } else { + ui_sb_update_icon_state(SB_ZIP|id, 0); + } + + //media_menu_update_zip(id); + ui_sb_update_tip(SB_ZIP|id); + + config_save(); +} diff --git a/src/qt/qt_deviceconfig.cpp b/src/qt/qt_deviceconfig.cpp new file mode 100644 index 000000000..a0d62c1c9 --- /dev/null +++ b/src/qt/qt_deviceconfig.cpp @@ -0,0 +1,157 @@ +#include "qt_deviceconfig.hpp" +#include "ui_qt_deviceconfig.h" + +#include +#include +#include +#include +#include + +extern "C" { +#include <86box/86box.h> +#include <86box/config.h> +#include <86box/device.h> +} + +#include "qt_filefield.hpp" + +DeviceConfig::DeviceConfig(QWidget *parent) : + QDialog(parent), + ui(new Ui::DeviceConfig) +{ + ui->setupUi(this); +} + +DeviceConfig::~DeviceConfig() +{ + delete ui; +} + +void DeviceConfig::ConfigureDevice(const _device_* device) { + DeviceConfig dc; + dc.setWindowTitle(QString("%1 Device Configuration").arg(device->name)); + + device_context_t device_context; + device_set_context(&device_context, device, 0); + + const auto* config = device->config; + while (config->type != -1) { + switch (config->type) { + case CONFIG_BINARY: + { + auto value = config_get_int(device_context.name, const_cast(config->name), config->default_int); + auto* cbox = new QCheckBox(); + cbox->setObjectName(config->name); + cbox->setChecked(value > 0); + dc.ui->formLayout->addRow(config->description, cbox); + break; + } + case CONFIG_SELECTION: + case CONFIG_MIDI: + case CONFIG_MIDI_IN: + case CONFIG_HEX16: + case CONFIG_HEX20: + { + auto* cbox = new QComboBox(); + cbox->setObjectName(config->name); + auto* model = cbox->model(); + int currentIndex = -1; + int selected = config_get_int(device_context.name, const_cast(config->name), config->default_int); + + for (auto* sel = config->selection; (sel->description != nullptr) && (strlen(sel->description) > 0); ++sel) { + int rows = model->rowCount(); + model->insertRow(rows); + auto idx = model->index(rows, 0); + + model->setData(idx, sel->description, Qt::DisplayRole); + model->setData(idx, sel->value, Qt::UserRole); + + if (selected == sel->value) { + currentIndex = idx.row(); + } + } + + dc.ui->formLayout->addRow(config->description, cbox); + cbox->setCurrentIndex(currentIndex); + break; + } + case CONFIG_SPINNER: + { + int value = config_get_int(device_context.name, const_cast(config->name), config->default_int); + auto* spinBox = new QSpinBox(); + spinBox->setObjectName(config->name); + spinBox->setMaximum(config->spinner.max); + spinBox->setMinimum(config->spinner.min); + if (config->spinner.step > 0) { + spinBox->setSingleStep(config->spinner.step); + } + spinBox->setValue(value); + dc.ui->formLayout->addRow(config->description, spinBox); + break; + } + case CONFIG_FNAME: + { + auto* fileName = config_get_string(device_context.name, const_cast(config->name), const_cast(config->default_string)); + auto* fileField = new FileField(); + fileField->setObjectName(config->name); + fileField->setFileName(fileName); + dc.ui->formLayout->addRow(config->description, fileField); + break; + } + } + ++config; + } + + int res = dc.exec(); + if (res == QDialog::Accepted) { + config = device->config; + while (config->type != -1) { + switch (config->type) { + case CONFIG_BINARY: + { + auto* cbox = dc.findChild(config->name); + config_set_int(device_context.name, const_cast(config->name), cbox->isChecked() ? 1 : 0); + break; + } + case CONFIG_SELECTION: + case CONFIG_MIDI: + case CONFIG_MIDI_IN: + case CONFIG_HEX16: + case CONFIG_HEX20: + { + auto* cbox = dc.findChild(config->name); + config_set_int(device_context.name, const_cast(config->name), cbox->currentData().toInt()); + break; + } + case CONFIG_FNAME: + { + auto* fbox = dc.findChild(config->name); + auto fileName = fbox->fileName().toUtf8(); + config_set_string(device_context.name, const_cast(config->name), fileName.data()); + break; + } + case CONFIG_SPINNER: + { + auto* spinBox = dc.findChild(config->name); + config_set_int(device_context.name, const_cast(config->name), spinBox->value()); + break; + } + } + config++; + } + } +} + +QString DeviceConfig::DeviceName(const _device_* device, const char *internalName, int bus) { + if (QStringLiteral("none") == internalName) { + return "None"; + } else if (QStringLiteral("internal") == internalName) { + return "Internal"; + } else if (device == nullptr) { + return QString(); + } else { + char temp[512]; + device_get_name(device, bus, temp); + return temp; + } +} diff --git a/src/qt/qt_deviceconfig.hpp b/src/qt/qt_deviceconfig.hpp new file mode 100644 index 000000000..3090c96a7 --- /dev/null +++ b/src/qt/qt_deviceconfig.hpp @@ -0,0 +1,28 @@ +#ifndef QT_DEVICECONFIG_HPP +#define QT_DEVICECONFIG_HPP + +#include + +extern "C" { +struct _device_; +} + +namespace Ui { +class DeviceConfig; +} + +class DeviceConfig : public QDialog +{ + Q_OBJECT + +public: + explicit DeviceConfig(QWidget *parent = nullptr); + ~DeviceConfig(); + + static void ConfigureDevice(const _device_* device); + static QString DeviceName(const _device_* device, const char* internalName, int bus); +private: + Ui::DeviceConfig *ui; +}; + +#endif // QT_DEVICECONFIG_HPP diff --git a/src/qt/qt_deviceconfig.ui b/src/qt/qt_deviceconfig.ui new file mode 100644 index 000000000..0a7e7d974 --- /dev/null +++ b/src/qt/qt_deviceconfig.ui @@ -0,0 +1,74 @@ + + + DeviceConfig + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + DeviceConfig + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DeviceConfig + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/qt/qt_filefield.cpp b/src/qt/qt_filefield.cpp new file mode 100644 index 000000000..39387fb05 --- /dev/null +++ b/src/qt/qt_filefield.cpp @@ -0,0 +1,34 @@ +#include "qt_filefield.hpp" +#include "ui_qt_filefield.h" + +#include + +FileField::FileField(QWidget *parent) : + QWidget(parent), + ui(new Ui::FileField) +{ + ui->setupUi(this); +} + +FileField::~FileField() +{ + delete ui; +} + +void FileField::setFileName(const QString &fileName) { + fileName_ = fileName; + ui->label->setText(fileName); +} + +void FileField::on_pushButton_clicked() { + QString fileName; + if (createFile_) { + fileName = QFileDialog::getSaveFileName(this, "Create..."); + } else { + fileName = QFileDialog::getOpenFileName(this, "Open..."); + } + + fileName_ = fileName; + ui->label->setText(fileName); + emit fileSelected(fileName); +} diff --git a/src/qt/qt_filefield.hpp b/src/qt/qt_filefield.hpp new file mode 100644 index 000000000..24e3870a3 --- /dev/null +++ b/src/qt/qt_filefield.hpp @@ -0,0 +1,35 @@ +#ifndef QT_FILEFIELD_HPP +#define QT_FILEFIELD_HPP + +#include + +namespace Ui { +class FileField; +} + +class FileField : public QWidget +{ + Q_OBJECT + +public: + explicit FileField(QWidget *parent = nullptr); + ~FileField(); + + QString fileName() const { return fileName_; } + void setFileName(const QString& fileName); + + void setCreateFile(bool createFile) { createFile_ = createFile; } + +signals: + void fileSelected(const QString& fileName); + +private slots: + void on_pushButton_clicked(); + +private: + Ui::FileField *ui; + QString fileName_; + bool createFile_ = false; +}; + +#endif // QT_FILEFIELD_HPP diff --git a/src/qt/qt_filefield.ui b/src/qt/qt_filefield.ui new file mode 100644 index 000000000..b595378e1 --- /dev/null +++ b/src/qt/qt_filefield.ui @@ -0,0 +1,47 @@ + + + FileField + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + Browse + + + + + + + + diff --git a/src/qt/qt_harddiskdialog.cpp b/src/qt/qt_harddiskdialog.cpp new file mode 100644 index 000000000..659c27ea4 --- /dev/null +++ b/src/qt/qt_harddiskdialog.cpp @@ -0,0 +1,706 @@ +#include "qt_harddiskdialog.hpp" +#include "ui_qt_harddiskdialog.h" + +extern "C" { +#include <86box/86box.h> +#include <86box/hdd.h> +#include "../disk/minivhd/minivhd.h" +#include "../disk/minivhd/minivhd_util.h" +} + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "qt_harddrive_common.hpp" +#include "qt_models_common.hpp" + +HarddiskDialog::HarddiskDialog(bool existing, QWidget *parent) : + QDialog(parent), + ui(new Ui::HarddiskDialog) +{ + ui->setupUi(this); + + if (existing) { + setWindowTitle("Add Existing Hard Disk"); + ui->lineEditCylinders->setEnabled(false); + ui->lineEditHeads->setEnabled(false); + ui->lineEditSectors->setEnabled(false); + ui->lineEditSize->setEnabled(false); + ui->comboBoxType->setEnabled(false); + + ui->comboBoxFormat->hide(); + ui->labelFormat->hide(); + + connect(ui->fileField, &FileField::fileSelected, this, &HarddiskDialog::onExistingFileSelected); + } else { + setWindowTitle("Add New Hard Disk"); + ui->fileField->setCreateFile(true); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &HarddiskDialog::onCreateNewFile); + } + + auto* model = ui->comboBoxFormat->model(); + model->insertRows(0, 6); + model->setData(model->index(0, 0), "Raw image (.img)"); + model->setData(model->index(1, 0), "HDI image (.hdi)"); + model->setData(model->index(2, 0), "HDX image (.hdx)"); + model->setData(model->index(3, 0), "Fixed-size VHD (.vhd)"); + model->setData(model->index(4, 0), "Dynamic-size VHD (.vhd)"); + model->setData(model->index(5, 0), "Differencing VHD (.vhd)"); + + model = ui->comboBoxBlockSize->model(); + model->insertRows(0, 2); + model->setData(model->index(0, 0), "Large blocks (2 MiB)"); + model->setData(model->index(1, 0), "Small blocks (512 KiB)"); + + ui->comboBoxBlockSize->hide(); + ui->labelBlockSize->hide(); + + Harddrives::populateBuses(ui->comboBoxBus->model()); + ui->comboBoxBus->setCurrentIndex(3); + + model = ui->comboBoxType->model(); + for (int i = 0; i < 127; i++) { + uint64_t size = ((uint64_t) hdd_table[i][0]) * hdd_table[i][1] * hdd_table[i][2]; + uint64_t size_mb = size >> 11LL; + QString text = QString("%1 MiB (CHS: %2, %3, %4)").arg(size_mb).arg(hdd_table[i][0]).arg(hdd_table[i][1]).arg(hdd_table[i][2]); + Models::AddEntry(model, text, i); + } + Models::AddEntry(model, "Custom...", 127); + Models::AddEntry(model, "Custom (large)...", 128); + + ui->lineEditSize->setValidator(new QIntValidator()); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + connect(ui->fileField, &FileField::fileSelected, this, [this] { + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + }); +} + +HarddiskDialog::~HarddiskDialog() +{ + delete ui; +} + +uint8_t HarddiskDialog::bus() const { + return static_cast(ui->comboBoxBus->currentData().toUInt()); +} + +uint8_t HarddiskDialog::channel() const { + return static_cast(ui->comboBoxChannel->currentData().toUInt()); +} + +QString HarddiskDialog::fileName() const { + return ui->fileField->fileName(); +} + +void HarddiskDialog::on_comboBoxFormat_currentIndexChanged(int index) { + bool enabled; + if (index == 5) { /* They switched to a diff VHD; disable the geometry fields. */ + enabled = false; + ui->lineEditCylinders->setText(QStringLiteral("(N/A)")); + ui->lineEditHeads->setText(QStringLiteral("(N/A)")); + ui->lineEditSectors->setText(QStringLiteral("(N/A)")); + ui->lineEditSize->setText(QStringLiteral("(N/A)")); + } else { + enabled = true; + ui->lineEditCylinders->setText(QString::number(cylinders_)); + ui->lineEditHeads->setText(QString::number(heads_)); + ui->lineEditSectors->setText(QString::number(sectors_)); + recalcSize(); + } + ui->lineEditCylinders->setEnabled(enabled); + ui->lineEditHeads->setEnabled(enabled); + ui->lineEditSectors->setEnabled(enabled); + ui->lineEditSize->setEnabled(enabled); + ui->comboBoxType->setEnabled(enabled); + + if (index < 4) { + ui->comboBoxBlockSize->hide(); + ui->labelBlockSize->hide(); + } else { + ui->comboBoxBlockSize->show(); + ui->labelBlockSize->show(); + } +} + +/* If the disk geometry requested in the 86Box GUI is not compatible with the internal VHD geometry, + * we adjust it to the next-largest size that is compatible. On average, this will be a difference + * of about 21 MB, and should only be necessary for VHDs larger than 31.5 GB, so should never be more + * than a tenth of a percent change in size. + */ +static void adjust_86box_geometry_for_vhd(MVHDGeom *_86box_geometry, MVHDGeom *vhd_geometry) +{ + if (_86box_geometry->cyl <= 65535) { + vhd_geometry->cyl = _86box_geometry->cyl; + vhd_geometry->heads = _86box_geometry->heads; + vhd_geometry->spt = _86box_geometry->spt; + return; + } + + int desired_sectors = _86box_geometry->cyl * _86box_geometry->heads * _86box_geometry->spt; + if (desired_sectors > 267321600) + desired_sectors = 267321600; + + int remainder = desired_sectors % 85680; /* 8560 is the LCM of 1008 (63*16) and 4080 (255*16) */ + if (remainder > 0) + desired_sectors += (85680 - remainder); + + _86box_geometry->cyl = desired_sectors / (16 * 63); + _86box_geometry->heads = 16; + _86box_geometry->spt = 63; + + vhd_geometry->cyl = desired_sectors / (16 * 255); + vhd_geometry->heads = 16; + vhd_geometry->spt = 255; +} + +static HarddiskDialog* callbackPtr = nullptr; +static MVHDGeom create_drive_vhd_fixed(const QString& fileName, HarddiskDialog* p, uint16_t cyl, uint8_t heads, uint8_t spt) { + MVHDGeom _86box_geometry = { .cyl = cyl, .heads = heads, .spt = spt }; + MVHDGeom vhd_geometry; + adjust_86box_geometry_for_vhd(&_86box_geometry, &vhd_geometry); + + int vhd_error = 0; + QByteArray filenameBytes = fileName.toUtf8(); + callbackPtr = p; + MVHDMeta *vhd = mvhd_create_fixed(filenameBytes.data(), vhd_geometry, &vhd_error, [](uint32_t current_sector, uint32_t total_sectors) { + callbackPtr->fileProgress((current_sector * 100) / total_sectors); + }); + callbackPtr = nullptr; + + if (vhd == NULL) { + _86box_geometry.cyl = 0; + _86box_geometry.heads = 0; + _86box_geometry.spt = 0; + } else { + mvhd_close(vhd); + } + + return _86box_geometry; +} + +static MVHDGeom create_drive_vhd_dynamic(const QString& fileName, uint16_t cyl, uint8_t heads, uint8_t spt, int blocksize) { + MVHDGeom _86box_geometry = { .cyl = cyl, .heads = heads, .spt = spt }; + MVHDGeom vhd_geometry; + adjust_86box_geometry_for_vhd(&_86box_geometry, &vhd_geometry); + int vhd_error = 0; + QByteArray filenameBytes = fileName.toUtf8(); + MVHDCreationOptions options; + options.block_size_in_sectors = blocksize; + options.path = filenameBytes.data(); + options.size_in_bytes = 0; + options.geometry = vhd_geometry; + options.type = MVHD_TYPE_DYNAMIC; + + MVHDMeta *vhd = mvhd_create_ex(options, &vhd_error); + if (vhd == NULL) { + _86box_geometry.cyl = 0; + _86box_geometry.heads = 0; + _86box_geometry.spt = 0; + } else { + mvhd_close(vhd); + } + + return _86box_geometry; +} + +static MVHDGeom create_drive_vhd_diff(const QString& fileName, const QString& parentFileName, int blocksize) { + int vhd_error = 0; + QByteArray filenameBytes = fileName.toUtf8(); + QByteArray parentFilenameBytes = fileName.toUtf8(); + MVHDCreationOptions options; + options.block_size_in_sectors = blocksize; + options.path = filenameBytes.data(); + options.parent_path = parentFilenameBytes.data(); + options.type = MVHD_TYPE_DIFF; + + MVHDMeta *vhd = mvhd_create_ex(options, &vhd_error); + MVHDGeom vhd_geometry; + if (vhd == NULL) { + vhd_geometry.cyl = 0; + vhd_geometry.heads = 0; + vhd_geometry.spt = 0; + } else { + vhd_geometry = mvhd_get_geometry(vhd); + + if (vhd_geometry.spt > 63) { + vhd_geometry.cyl = mvhd_calc_size_sectors(&vhd_geometry) / (16 * 63); + vhd_geometry.heads = 16; + vhd_geometry.spt = 63; + } + + mvhd_close(vhd); + } + + return vhd_geometry; +} + +void HarddiskDialog::onCreateNewFile() { + qint64 size = ui->lineEditSize->text().toUInt() << 20U; + if (size > 0x1FFFFFFE00ll) { + QMessageBox::critical(this, "Disk image too large", "Disk images cannot be larger than 127 GiB"); + return; + } + + int img_format = ui->comboBoxFormat->currentIndex(); + uint32_t zero = 0; + uint32_t base = 0x1000; + uint32_t sector_size = 512; + + auto fileName = ui->fileField->fileName(); + QString expectedSuffix; + switch (img_format) { + case 1: + expectedSuffix = "hdi"; + break; + case 2: + expectedSuffix = "hdx"; + break; + case 3: + case 4: + case 5: + expectedSuffix = "vhd"; + break; + } + if (! expectedSuffix.isEmpty()) { + QFileInfo fileInfo(fileName); + if (fileInfo.suffix().compare(expectedSuffix, Qt::CaseInsensitive) != 0) { + fileName = QString("%1.%2").arg(fileName, expectedSuffix); + ui->fileField->setFileName(fileName); + } + } + + QFile file(fileName); + if (! file.open(QIODevice::WriteOnly)) { + QMessageBox::critical(this, "Unable to write file", "Make sure the file is being saved to a writable directory"); + return; + } + + if (img_format == 1) { /* HDI file */ + QDataStream stream(&file); + stream.setByteOrder(QDataStream::LittleEndian); + if (size >= 0x100000000ll) { + QMessageBox::critical(this, "Disk image too large", "HDI disk images cannot be larger than 4 GiB"); + return; + } + uint32_t s = static_cast(size); + stream << zero; /* 00000000: Zero/unknown */ + stream << zero; /* 00000004: Zero/unknown */ + stream << base; /* 00000008: Offset at which data starts */ + stream << s; /* 0000000C: Full size of the data (32-bit) */ + stream << sector_size; /* 00000010: Sector size in bytes */ + stream << sectors_; /* 00000014: Sectors per cylinder */ + stream << heads_; /* 00000018: Heads per cylinder */ + stream << cylinders_; /* 0000001C: Cylinders */ + + for (int i = 0; i < 0x3f8; i++) { + stream << zero; + } + } else if (img_format == 2) { /* HDX file */ + QDataStream stream(&file); + stream.setByteOrder(QDataStream::LittleEndian); + quint64 signature = 0xD778A82044445459; + stream << signature; /* 00000000: Signature */ + stream << size; /* 00000008: Full size of the data (64-bit) */ + stream << sector_size; /* 00000010: Sector size in bytes */ + stream << sectors_; /* 00000014: Sectors per cylinder */ + stream << heads_; /* 00000018: Heads per cylinder */ + stream << cylinders_; /* 0000001C: Cylinders */ + stream << zero; /* 00000020: [Translation] Sectors per cylinder */ + stream << zero; /* 00000004: [Translation] Heads per cylinder */ + } else if (img_format >= 3) { /* VHD file */ + file.close(); + + MVHDGeom _86box_geometry; + int block_size = ui->comboBoxBlockSize->currentIndex() == 0 ? MVHD_BLOCK_LARGE : MVHD_BLOCK_SMALL; + switch (img_format) { + case 3: + { + QProgressDialog progress("Creating disk image", QString(), 0, 100, this); + connect(this, &HarddiskDialog::fileProgress, &progress, &QProgressDialog::setValue); + std::thread writer([&_86box_geometry, fileName, this] { + _86box_geometry = create_drive_vhd_fixed(fileName, this, cylinders_, heads_, sectors_); + }); + progress.exec(); + writer.join(); + } + break; + case 4: + _86box_geometry = create_drive_vhd_dynamic(fileName, cylinders_, heads_, sectors_, block_size); + break; + case 5: + QString vhdParent = QFileDialog::getOpenFileName(this, "Select the parent VHD", QString(), "VHD files (*.vhd);;All files (*.*)"); + if (vhdParent.isEmpty()) { + return; + } + _86box_geometry = create_drive_vhd_diff(fileName, vhdParent, block_size); + break; + } + + if (img_format != 5) { + QMessageBox::information(this, "Disk image created", "Remember to partition and format the newly-created drive"); + } + + ui->lineEditCylinders->setText(QString::number(_86box_geometry.cyl)); + ui->lineEditHeads->setText(QString::number(_86box_geometry.heads)); + ui->lineEditSectors->setText(QString::number(_86box_geometry.spt)); + cylinders_ = _86box_geometry.cyl; + heads_ = _86box_geometry.heads; + sectors_ = _86box_geometry.spt; + + return; + } + + // formats 0, 1 and 2 + QProgressDialog progress("Creating disk image", QString(), 0, 100, this); + connect(this, &HarddiskDialog::fileProgress, &progress, &QProgressDialog::setValue); + std::thread writer([size, &file, this] { + QDataStream stream(&file); + stream.setByteOrder(QDataStream::LittleEndian); + + QByteArray buf(1048576, 0); + uint64_t mibBlocks = size >> 20; + uint64_t restBlock = size & 0xfffff; + + if (restBlock) { + stream << QByteArray::fromRawData(buf.data(), restBlock); + } + + if (mibBlocks) { + for (uint64_t i = 0; i < mibBlocks; ++i) { + stream << buf; + emit fileProgress(static_cast((i * 100) / mibBlocks)); + } + } + emit fileProgress(100); + }); + + progress.exec(); + writer.join(); + QMessageBox::information(this, "Disk image created", "Remember to partition and format the newly-created drive"); +} + +static void adjust_vhd_geometry_for_86box(MVHDGeom *vhd_geometry) { + if (vhd_geometry->spt <= 63) + return; + + int desired_sectors = vhd_geometry->cyl * vhd_geometry->heads * vhd_geometry->spt; + if (desired_sectors > 267321600) + desired_sectors = 267321600; + + int remainder = desired_sectors % 85680; /* 8560 is the LCM of 1008 (63*16) and 4080 (255*16) */ + if (remainder > 0) + desired_sectors -= remainder; + + vhd_geometry->cyl = desired_sectors / (16 * 63); + vhd_geometry->heads = 16; + vhd_geometry->spt = 63; +} + +void HarddiskDialog::recalcSelection() { + int selection = 127; + for (int i = 0; i < 127; i++) { + if ((cylinders_ == hdd_table[i][0]) && + (heads_ == hdd_table[i][1]) && + (sectors_ == hdd_table[i][2])) + selection = i; + } + if ((selection == 127) && (heads_ == 16) && (sectors_ == 63)) { + selection = 128; + } + ui->comboBoxType->setCurrentIndex(selection); +} + +void HarddiskDialog::onExistingFileSelected(const QString &fileName) { + // TODO : Over to non-existing file selected + /* + if (!(existing & 1)) { + f = _wfopen(wopenfilestring, L"rb"); + if (f != NULL) { + fclose(f); + if (settings_msgbox_ex(MBX_QUESTION_YN, (wchar_t *) IDS_4111, (wchar_t *) IDS_4118, (wchar_t *) IDS_4120, (wchar_t *) IDS_4121, NULL) != 0) / * yes * / + return FALSE; + } + } + + f = _wfopen(wopenfilestring, (existing & 1) ? L"rb" : L"wb"); + if (f == NULL) { + hdd_add_file_open_error: + fclose(f); + settings_msgbox_header(MBX_ERROR, (existing & 1) ? (wchar_t *) IDS_4114 : (wchar_t *) IDS_4115, (existing & 1) ? (wchar_t *) IDS_4107 : (wchar_t *) IDS_4108); + return TRUE; + } + */ + + uint64_t size = 0; + uint32_t sector_size = 0; + uint32_t sectors = 0; + uint32_t heads = 0; + uint32_t cylinders = 0; + int vhd_error = 0; + + QFile file(fileName); + if (! file.open(QIODevice::ReadOnly)) { + QMessageBox::critical(this, "Unable to read file", "Make sure the file exists and is readable"); + return; + } + QByteArray fileNameUtf8 = fileName.toUtf8(); + + QFileInfo fi(file); + if (image_is_hdi(fileNameUtf8.data()) || image_is_hdx(fileNameUtf8.data(), 1)) { + file.seek(0x10); + QDataStream stream(&file); + stream.setByteOrder(QDataStream::LittleEndian); + stream >> sector_size; + if (sector_size != 512) { + QMessageBox::critical(this, "Unsupported disk image", "HDI or HDX images with a sector size other than 512 are not supported"); + return; + } + + sectors = heads = cylinders = 0; + stream >> sectors; + stream >> heads; + stream >> cylinders; + } else if (image_is_vhd(fileNameUtf8.data(), 1)) { + MVHDMeta* vhd = mvhd_open(fileNameUtf8.data(), 0, &vhd_error); + if (vhd == nullptr) { + QMessageBox::critical(this, "Unable to read file", "Make sure the file exists and is readable"); + return; + } else if (vhd_error == MVHD_ERR_TIMESTAMP) { + QMessageBox::StandardButton btn = QMessageBox::warning(this, "Parent and child disk timestamps do not match", "This could mean that the parent image was modified after the differencing image was created.\n\nIt can also happen if the image files were moved or copied, or by a bug in the program that created this disk.\n\nDo you want to fix the timestamps?", QMessageBox::Yes | QMessageBox::No); + if (btn == QMessageBox::Yes) { + int ts_res = mvhd_diff_update_par_timestamp(vhd, &vhd_error); + if (ts_res != 0) { + QMessageBox::critical(this, "Error", "Could not fix VHD timestamp"); + mvhd_close(vhd); + return; + } + } else { + mvhd_close(vhd); + return; + } + } + + MVHDGeom vhd_geom = mvhd_get_geometry(vhd); + adjust_vhd_geometry_for_86box(&vhd_geom); + cylinders = vhd_geom.cyl; + heads = vhd_geom.heads; + sectors = vhd_geom.spt; + size = static_cast(cylinders * heads * sectors * 512); + mvhd_close(vhd); + } else { + size = file.size(); + if (((size % 17) == 0) && (size <= 142606336)) { + sectors = 17; + if (size <= 26738688) + heads = 4; + else if (((size % 3072) == 0) && (size <= 53477376)) + heads = 6; + else { + int i; + for (i = 5; i < 16; i++) { + if (((size % (i << 9)) == 0) && (size <= ((i * 17) << 19))) + break; + if (i == 5) + i++; + } + heads = i; + } + } else { + sectors = 63; + heads = 16; + } + + cylinders = ((size >> 9) / heads) / sectors; + } + + if ((sectors > max_sectors) || (heads > max_heads) || (cylinders > max_cylinders)) { + QMessageBox::critical(this, "Unable to read file", "Make sure the file exists and is readable"); + return; + } + + heads_ = heads; + sectors_ = sectors; + cylinders_ = cylinders; + ui->lineEditCylinders->setText(QString::number(cylinders)); + ui->lineEditHeads->setText(QString::number(heads)); + ui->lineEditSectors->setText(QString::number(sectors)); + recalcSize(); + recalcSelection(); + + ui->lineEditCylinders->setEnabled(true); + ui->lineEditHeads->setEnabled(true); + ui->lineEditSectors->setEnabled(true); + ui->lineEditSize->setEnabled(true); + ui->comboBoxType->setEnabled(true); +} + +void HarddiskDialog::recalcSize() { + uint64_t size = (static_cast(cylinders_) * static_cast(heads_) * static_cast(sectors_)) << 9; + ui->lineEditSize->setText(QString::number(size >> 20)); +} + +bool HarddiskDialog::checkAndAdjustSectors() { + if (sectors_ > max_sectors) { + sectors_ = max_sectors; + ui->lineEditSectors->setText(QString::number(max_sectors)); + recalcSize(); + recalcSelection(); + return false; + } + return true; +} + +bool HarddiskDialog::checkAndAdjustHeads() { + if (heads_ > max_heads) { + heads_ = max_heads; + ui->lineEditHeads->setText(QString::number(max_heads)); + recalcSize(); + recalcSelection(); + return false; + } + return true; +} + +bool HarddiskDialog::checkAndAdjustCylinders() { + if (cylinders_ > max_cylinders) { + cylinders_ = max_cylinders; + ui->lineEditCylinders->setText(QString::number(max_cylinders)); + recalcSize(); + recalcSelection(); + return false; + } + return true; +} + + +void HarddiskDialog::on_comboBoxBus_currentIndexChanged(int index) { + if (index < 0) { + return; + } + + switch (ui->comboBoxBus->currentData().toInt()) { + case HDD_BUS_DISABLED: + default: + max_sectors = max_heads = max_cylinders = 0; + break; + case HDD_BUS_MFM: + max_sectors = 26; /* 17 for MFM, 26 for RLL. */ + max_heads = 15; + max_cylinders = 2047; + break; + case HDD_BUS_XTA: + max_sectors = 63; + max_heads = 16; + max_cylinders = 1023; + break; + case HDD_BUS_ESDI: + max_sectors = 99; /* ESDI drives usually had 32 to 43 sectors per track. */ + max_heads = 16; + max_cylinders = 266305; + break; + case HDD_BUS_IDE: + max_sectors = 63; + max_heads = 255; + max_cylinders = 266305; + break; + case HDD_BUS_ATAPI: + case HDD_BUS_SCSI: + max_sectors = 99; + max_heads = 255; + max_cylinders = 266305; + break; + } + + checkAndAdjustCylinders(); + checkAndAdjustHeads(); + checkAndAdjustSectors(); + + if (ui->lineEditCylinders->validator() != nullptr) { + delete ui->lineEditCylinders->validator(); + } + if (ui->lineEditHeads->validator() != nullptr) { + delete ui->lineEditHeads->validator(); + } + if (ui->lineEditSectors->validator() != nullptr) { + delete ui->lineEditSectors->validator(); + } + + ui->lineEditCylinders->setValidator(new QIntValidator(1, max_cylinders, this)); + ui->lineEditHeads->setValidator(new QIntValidator(1, max_heads, this)); + ui->lineEditSectors->setValidator(new QIntValidator(1, max_sectors, this)); + + Harddrives::populateBusChannels(ui->comboBoxChannel->model(), ui->comboBoxBus->currentData().toInt()); +} + +void HarddiskDialog::on_lineEditSize_textEdited(const QString &text) { + uint32_t size = text.toUInt(); + /* This is needed to ensure VHD standard compliance. */ + hdd_image_calc_chs(&cylinders_, &heads_, §ors_, size); + ui->lineEditCylinders->setText(QString::number(cylinders_)); + ui->lineEditHeads->setText(QString::number(heads_)); + ui->lineEditSectors->setText(QString::number(sectors_)); + recalcSelection(); + + checkAndAdjustCylinders(); + checkAndAdjustHeads(); + checkAndAdjustSectors(); +} + +void HarddiskDialog::on_lineEditCylinders_textEdited(const QString &text) { + cylinders_ = text.toUInt(); + if (checkAndAdjustCylinders()) { + recalcSize(); + recalcSelection(); + } +} + +void HarddiskDialog::on_lineEditHeads_textEdited(const QString &text) { + heads_ = text.toUInt(); + if (checkAndAdjustHeads()) { + recalcSize(); + recalcSelection(); + } +} + +void HarddiskDialog::on_lineEditSectors_textEdited(const QString &text) { + sectors_ = text.toUInt(); + if (checkAndAdjustSectors()) { + recalcSize(); + recalcSelection(); + } +} + +void HarddiskDialog::on_comboBoxType_currentIndexChanged(int index) { + if (index < 0) { + return; + } + + if ((index != 127) && (index != 128)) { + cylinders_ = hdd_table[index][0]; + heads_ = hdd_table[index][1]; + sectors_ = hdd_table[index][2]; + ui->lineEditCylinders->setText(QString::number(cylinders_)); + ui->lineEditHeads->setText(QString::number(heads_)); + ui->lineEditSectors->setText(QString::number(sectors_)); + recalcSize(); + } else if (index == 128) { + heads_ = 16; + sectors_ = 63; + ui->lineEditHeads->setText(QString::number(heads_)); + ui->lineEditSectors->setText(QString::number(sectors_)); + recalcSize(); + } + + checkAndAdjustCylinders(); + checkAndAdjustHeads(); + checkAndAdjustSectors(); +} diff --git a/src/qt/qt_harddiskdialog.hpp b/src/qt/qt_harddiskdialog.hpp new file mode 100644 index 000000000..e6ced50fb --- /dev/null +++ b/src/qt/qt_harddiskdialog.hpp @@ -0,0 +1,57 @@ +#ifndef QT_HARDDISKDIALOG_HPP +#define QT_HARDDISKDIALOG_HPP + +#include + +namespace Ui { +class HarddiskDialog; +} + +class HarddiskDialog : public QDialog +{ + Q_OBJECT + +public: + explicit HarddiskDialog(bool existing, QWidget *parent = nullptr); + ~HarddiskDialog(); + + uint8_t bus() const; + uint8_t channel() const; + QString fileName() const; + uint32_t cylinders() const { return cylinders_; } + uint32_t heads() const { return heads_; } + uint32_t sectors() const { return sectors_; } + +signals: + void fileProgress(int i); + +private slots: + void on_comboBoxType_currentIndexChanged(int index); + void on_lineEditSectors_textEdited(const QString &arg1); + void on_lineEditHeads_textEdited(const QString &arg1); + void on_lineEditCylinders_textEdited(const QString &arg1); + void on_lineEditSize_textEdited(const QString &arg1); + void on_comboBoxBus_currentIndexChanged(int index); + void on_comboBoxFormat_currentIndexChanged(int index); + void onCreateNewFile(); + void onExistingFileSelected(const QString& fileName); + +private: + Ui::HarddiskDialog *ui; + + uint32_t cylinders_; + uint32_t heads_; + uint32_t sectors_; + + uint32_t max_sectors = 0; + uint32_t max_heads = 0; + uint32_t max_cylinders = 0; + + bool checkAndAdjustCylinders(); + bool checkAndAdjustHeads(); + bool checkAndAdjustSectors(); + void recalcSize(); + void recalcSelection(); +}; + +#endif // QT_HARDDISKDIALOG_HPP diff --git a/src/qt/qt_harddiskdialog.ui b/src/qt/qt_harddiskdialog.ui new file mode 100644 index 000000000..01cbb43ca --- /dev/null +++ b/src/qt/qt_harddiskdialog.ui @@ -0,0 +1,230 @@ + + + HarddiskDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + 0 + + + + + File name + + + + + + + + + + + + + + Cylinders + + + + + + + Sectors + + + + + + + + + + + + + Size (MiB) + + + + + + + Heads + + + + + + + 32767 + + + + + + + Type + + + + + + + 32767 + + + + + + + + + + + + + + Bus + + + + + + + + + + Channel + + + + + + + + + + + + + + Format + + + + + + + + + + + + 5 + + + + + Block Size + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + FileField + QWidget +
qt_filefield.hpp
+ 1 +
+
+ + lineEditCylinders + lineEditHeads + lineEditSectors + lineEditSize + comboBoxType + comboBoxBus + comboBoxChannel + comboBoxFormat + comboBoxBlockSize + + + + + buttonBox + accepted() + HarddiskDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + HarddiskDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/qt/qt_harddrive_common.cpp b/src/qt/qt_harddrive_common.cpp new file mode 100644 index 000000000..8c6b478f3 --- /dev/null +++ b/src/qt/qt_harddrive_common.cpp @@ -0,0 +1,101 @@ +#include "qt_harddrive_common.hpp" + +#include + +extern "C" { +#include <86box/hdd.h> +} + +#include + +void Harddrives::populateBuses(QAbstractItemModel *model) { + model->removeRows(0, model->rowCount()); + model->insertRows(0, 6); + model->setData(model->index(0, 0), "MFM/RLL"); + model->setData(model->index(1, 0), "XT IDE"); + model->setData(model->index(2, 0), "ESDI"); + model->setData(model->index(3, 0), "IDE"); + model->setData(model->index(4, 0), "ATAPI"); + model->setData(model->index(5, 0), "SCSI"); + + model->setData(model->index(0, 0), HDD_BUS_MFM, Qt::UserRole); + model->setData(model->index(1, 0), HDD_BUS_XTA, Qt::UserRole); + model->setData(model->index(2, 0), HDD_BUS_ESDI, Qt::UserRole); + model->setData(model->index(3, 0), HDD_BUS_IDE, Qt::UserRole); + model->setData(model->index(4, 0), HDD_BUS_ATAPI, Qt::UserRole); + model->setData(model->index(5, 0), HDD_BUS_SCSI, Qt::UserRole); +} + +void Harddrives::populateRemovableBuses(QAbstractItemModel *model) { + model->removeRows(0, model->rowCount()); + model->insertRows(0, 3); + model->setData(model->index(0, 0), "Disabled"); + model->setData(model->index(1, 0), "ATAPI"); + model->setData(model->index(2, 0), "SCSI"); + + model->setData(model->index(0, 0), HDD_BUS_DISABLED, Qt::UserRole); + model->setData(model->index(1, 0), HDD_BUS_ATAPI, Qt::UserRole); + model->setData(model->index(2, 0), HDD_BUS_SCSI, Qt::UserRole); +} + +void Harddrives::populateBusChannels(QAbstractItemModel *model, int bus) { + model->removeRows(0, model->rowCount()); + + int busRows = 0; + int shifter = 1; + int orer = 1; + int subChannelWidth = 1; + switch (bus) { + case HDD_BUS_MFM: + case HDD_BUS_XTA: + case HDD_BUS_ESDI: + busRows = 2; + break; + case HDD_BUS_IDE: + case HDD_BUS_ATAPI: + busRows = 8; + break; + case HDD_BUS_SCSI: + shifter = 4; + orer = 15; + busRows = 64; + subChannelWidth = 2; + break; + } + + model->insertRows(0, busRows); + for (int i = 0; i < busRows; ++i) { + auto idx = model->index(i, 0); + model->setData(idx, QString("%1:%2").arg(i >> shifter).arg(i & orer, subChannelWidth, 10, QChar('0'))); + model->setData(idx, ((i >> shifter) << shifter) | (i & orer), Qt::UserRole); + } +} + +QString Harddrives::BusChannelName(uint8_t bus, uint8_t channel) { + QString busName; + switch(bus) { + case HDD_BUS_DISABLED: + busName = QString("Disabled"); + break; + case HDD_BUS_MFM: + busName = QString("MFM/RLL (%1:%2)").arg(channel >> 1).arg(channel & 1); + break; + case HDD_BUS_XTA: + busName = QString("XT IDE (%1:%2)").arg(channel >> 1).arg(channel & 1); + break; + case HDD_BUS_ESDI: + busName = QString("ESDI (%1:%2)").arg(channel >> 1).arg(channel & 1); + break; + case HDD_BUS_IDE: + busName = QString("IDE (%1:%2)").arg(channel >> 1).arg(channel & 1); + break; + case HDD_BUS_ATAPI: + busName = QString("ATAPI (%1:%2)").arg(channel >> 1).arg(channel & 1); + break; + case HDD_BUS_SCSI: + busName = QString("SCSI (%1:%2)").arg(channel >> 4).arg(channel & 15, 2, 10, QChar('0')); + break; + } + + return busName; +} diff --git a/src/qt/qt_harddrive_common.hpp b/src/qt/qt_harddrive_common.hpp new file mode 100644 index 000000000..0e0164d54 --- /dev/null +++ b/src/qt/qt_harddrive_common.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +class QString; +class QAbstractItemModel; + +namespace Harddrives { + void populateBuses(QAbstractItemModel* model); + void populateRemovableBuses(QAbstractItemModel* model); + void populateBusChannels(QAbstractItemModel* model, int bus); + QString BusChannelName(uint8_t bus, uint8_t channel); +}; diff --git a/src/qt/qt_main.cpp b/src/qt/qt_main.cpp new file mode 100644 index 000000000..4d6062917 --- /dev/null +++ b/src/qt/qt_main.cpp @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include + +#include <86box/86box.h> +#include <86box/plat.h> +#include <86box/ui.h> +#include <86box/video.h> + +#include + +#include "qt_mainwindow.hpp" +#include "qt_sdl.h" + + +// Void Cast +#define VC(x) const_cast(x) + +extern QElapsedTimer elapsed_timer; +extern int nvr_dosave; +extern MainWindow* main_window; + +extern "C" { + extern int qt_nvr_save(void); +} + +void +main_thread_fn() +{ + uint64_t old_time, new_time; + int drawits, frames; + + QThread::currentThread()->setPriority(QThread::HighestPriority); + framecountx = 0; + //title_update = 1; + old_time = elapsed_timer.elapsed(); + drawits = frames = 0; + while (!is_quit && cpu_thread_run) { + /* See if it is time to run a frame of code. */ + new_time = elapsed_timer.elapsed(); + drawits += (new_time - old_time); + old_time = new_time; + if (drawits > 0 && !dopause) { + /* Yes, so do one frame now. */ + drawits -= 10; + if (drawits > 50) + drawits = 0; + + /* Run a block of code. */ + pc_run(); + + /* Every 200 frames we save the machine status. */ + if (++frames >= 200 && nvr_dosave) { + qt_nvr_save(); + nvr_dosave = 0; + frames = 0; + } + } else /* Just so we dont overload the host OS. */ + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + /* If needed, handle a screen resize. */ + if (doresize && !video_fullscreen && !is_quit) { + if (vid_resize & 2) + plat_resize(fixed_size_x, fixed_size_y); + else + plat_resize(scrnsz_x, scrnsz_y); + doresize = 0; + } + } + + is_quit = 1; +} + +int main(int argc, char* argv[]) { + QApplication app(argc, argv); + elapsed_timer.start(); + + pc_init(argc, argv); + if (! pc_init_modules()) { + ui_msgbox_header(MBX_FATAL, VC(L"No ROMs found."), VC(L"86Box could not find any usable ROM images.\n\nPlease download a ROM set and extract it into the \"roms\" directory.")); + return 6; + } + + main_window = new MainWindow(); + main_window->show(); + + pc_reset_hard_init(); + + /* Set the PAUSE mode depending on the renderer. */ + // plat_pause(0); + + /* Initialize the rendering window, or fullscreen. */ + auto main_thread = std::thread([] { + main_thread_fn(); + }); + + auto ret = app.exec(); + cpu_thread_run = 0; + main_thread.join(); + + return ret; +} diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp new file mode 100644 index 000000000..d9ae9500d --- /dev/null +++ b/src/qt/qt_mainwindow.cpp @@ -0,0 +1,299 @@ +#include "qt_mainwindow.hpp" +#include "ui_qt_mainwindow.h" + +extern "C" { +#include <86box/86box.h> +#include <86box/keyboard.h> +#include <86box/mouse.h> +#include <86box/config.h> +#include <86box/plat.h> + +#include "qt_sdl.h" +}; + +#include +#include +#include +#include + +#include "qt_settings.hpp" + +CentralWidget::CentralWidget(QWidget *parent) : QWidget(parent) {} +CentralWidget::~CentralWidget() = default; + +MainWindowLabel::MainWindowLabel(QWidget *parent) : QLabel(parent) { + setMouseTracking(true); +} +MainWindowLabel::~MainWindowLabel() = default; +void MainWindowLabel::mouseMoveEvent(QMouseEvent *event) { + pos_ = event->pos(); +} +void MainWindowLabel::mousePressEvent(QMouseEvent *event) { + buttons_ = event->buttons(); +} +void MainWindowLabel::mouseReleaseEvent(QMouseEvent *event) { + buttons_ = event->buttons(); +} + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + Q_INIT_RESOURCE(qt_resources); + + ui->setupUi(this); + + connect(this, &MainWindow::pollMouse, this, [] { + sdl_mouse_poll(); + }); + + connect(this, &MainWindow::setMouseCapture, this, [](bool state) { + mouse_capture = state ? 1 : 0; + sdl_mouse_capture(mouse_capture); + }); + + connect(this, &MainWindow::setFullscreen, this, [](bool state) { + video_fullscreen = state ? 1 : 0; + sdl_set_fs(video_fullscreen); + }); + + connect(this, &MainWindow::resizeContents, this, [this](int w, int h) { + sdl_resize(w, h); + }); + + connect(ui->menubar, &QMenuBar::triggered, this, [] { + config_save(); + }); + + ui->actionKeyboard_requires_capture->setChecked(kbd_req_capture); + ui->actionRight_CTRL_is_left_ALT->setChecked(rctrl_is_lalt); + + sdl_inits(); + sdl_timer = new QTimer(this); + connect(sdl_timer, &QTimer::timeout, this, [] { + auto status = sdl_main(); + if (status == SdlMainQuit) { + QApplication::quit(); + } + }); + sdl_timer->start(5); +} + +MainWindow::~MainWindow() { + sdl_close(); + delete ui; +} + +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() { + 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() { + Settings settings; + 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; + } +} + +static const int keycode_entries = 136; +// xmodmap -pk +static const uint16_t xfree86_keycode_table[keycode_entries] = { + /* 0 */ 0, + /* 1 */ 0, + /* 2 */ 0, + /* 3 */ 0, + /* 4 */ 0, + /* 5 */ 0, + /* 6 */ 0, + /* 7 */ 0, + /* 8 */ 0, + /* 9 */ 0x01, // Esc + /* 10 */ 0x02, // 1 + /* 11 */ 0x03, // 2 + /* 12 */ 0x04, // 3 + /* 13 */ 0x05, // 4 + /* 14 */ 0x06, // 5 + /* 15 */ 0x07, // 6 + /* 16 */ 0x08, // 7 + /* 17 */ 0x09, // 8 + /* 18 */ 0x0a, // 9 + /* 19 */ 0x0b, // 0 + /* 20 */ 0x0c, // - + /* 21 */ 0x0d, // = + /* 22 */ 0x0e, // BackSpace + /* 23 */ 0x0f, // Tab + /* 24 */ 0x10, // Q + /* 25 */ 0x11, // W + /* 26 */ 0x12, // E + /* 27 */ 0x13, // R + /* 28 */ 0x14, // T + /* 29 */ 0x15, // Y + /* 30 */ 0x16, // U + /* 31 */ 0x17, // I + /* 32 */ 0x18, // O + /* 33 */ 0x19, // P + /* 34 */ 0x1a, // [ + /* 35 */ 0x1b, // ] + /* 36 */ 0x1c, // Return + /* 37 */ 0x1d, // LeftControl + /* 38 */ 0x1e, // A + /* 39 */ 0x1f, // S + /* 40 */ 0x20, // D + /* 41 */ 0x21, // F + /* 42 */ 0x22, // G + /* 43 */ 0x23, // H + /* 44 */ 0x24, // J + /* 45 */ 0x25, // K + /* 46 */ 0x26, // L + /* 47 */ 0x27, // ; + /* 48 */ 0x28, // ' + /* 49 */ 0x29, // ` (???) + /* 50 */ 0x2a, // LeftShift + /* 51 */ 0x2b, // BackSlash + /* 52 */ 0x2c, // Z + /* 53 */ 0x2d, // X + /* 54 */ 0x2e, // C + /* 55 */ 0x2f, // V + /* 56 */ 0x30, // B + /* 57 */ 0x31, // N + /* 58 */ 0x32, // M + /* 59 */ 0x33, // , + /* 60 */ 0x34, // . + /* 61 */ 0x35, // - + /* 62 */ 0x36, // RightShift + /* 63 */ 0x37, // KeyPad Multiply + /* 64 */ 0x38, // LeftAlt + /* 65 */ 0x39, // Space + /* 66 */ 0x3a, // CapsLock + /* 67 */ 0x3b, // F01 + /* 68 */ 0x3c, // F02 + /* 69 */ 0x3d, // F03 + /* 70 */ 0x3e, // F04 + /* 71 */ 0x3f, // F05 + /* 72 */ 0x40, // F06 + /* 73 */ 0x41, // F07 + /* 74 */ 0x42, // F08 + /* 75 */ 0x43, // F09 + /* 76 */ 0x44, // F10 + /* 77 */ 0x45, // NumLock + /* 78 */ 0x46, // ScrollLock + /* 79 */ 0x47, // KeyPad7 + /* 80 */ 0x48, // KeyPad8 + /* 81 */ 0x49, // KeyPad9 + /* 82 */ 0x4a, // KeyPad Minus + /* 83 */ 0x4b, // KeyPad4 + /* 84 */ 0x4c, // KeyPad5 + /* 85 */ 0x4d, // KeyPad6 + /* 86 */ 0x4e, // KeyPad Plus + /* 87 */ 0x4f, // KeyPad1 + /* 88 */ 0x50, // KeyPad2 + /* 89 */ 0x51, // KeyPad3 + /* 90 */ 0x52, // KeyPad0 + /* 91 */ 0x53, // KeyPad . + /* 92 */ 0, + /* 93 */ 0, + /* 94 */ 0x56, // Less/Great + /* 95 */ 0x57, // F11 + /* 96 */ 0x58, // F12 + /* 97 */ 0, + /* 98 */ 0, + /* 99 */ 0, + /* 100 */ 0, + /* 101 */ 0, + /* 102 */ 0, + /* 103 */ 0, + /* 104 */ 0x11c, // KeyPad Enter + /* 105 */ 0x11d, // RightControl + /* 106 */ 0x135, // KeyPad Divide + /* 107 */ 0x137, // PrintScreen / SysReq + /* 108 */ 0x138, // RightAlt + /* 109 */ 0, + /* 110 */ 0x147, // Home + /* 111 */ 0x148, // Up + /* 112 */ 0x149, // PageUp + /* 113 */ 0x14b, // Left + /* 114 */ 0x14d, // Right + /* 115 */ 0x14f, // End + /* 116 */ 0x150, // Down + /* 117 */ 0x151, // PageDown + /* 118 */ 0x152, // Insert + /* 119 */ 0x153, // Delete + /* 120 */ 0, + /* 121 */ 0, + /* 122 */ 0, + /* 123 */ 0, + /* 124 */ 0, + /* 125 */ 0, + /* 126 */ 0, + /* 127 */ 0, + /* 128 */ 0, + /* 129 */ 0, + /* 130 */ 0, + /* 131 */ 0, + /* 132 */ 0, + /* 133 */ 0x15b, // SuperLeft + /* 134 */ 0x15c, // SuperRight + /* 135 */ 0x15d, // Application +}; + +static void handle_keypress_event(int state, quint32 native_scancode) { + if (native_scancode > keycode_entries) { + return; + } + uint16_t translated_code = xfree86_keycode_table[native_scancode]; + if (translated_code == 0) { + return; + } + keyboard_input(state, translated_code); + + if (keyboard_isfsexit() > 0) { + plat_setfullscreen(0); + } + + if (keyboard_ismsexit() > 0) { + plat_mouse_capture(0); + } +} + +void MainWindow::on_actionFullscreen_triggered() { + setFullscreen(true); +} diff --git a/src/qt/qt_mainwindow.hpp b/src/qt/qt_mainwindow.hpp new file mode 100644 index 000000000..25227d04b --- /dev/null +++ b/src/qt/qt_mainwindow.hpp @@ -0,0 +1,83 @@ +#ifndef QT_MAINWINDOW_HPP +#define QT_MAINWINDOW_HPP + +#include +#include + +namespace Ui { +class MainWindow; +} + +class MainWindowLabel : public QLabel +{ + Q_OBJECT +public: + explicit MainWindowLabel(QWidget *parent = nullptr); + ~MainWindowLabel(); + + const QPoint& pos() { return pos_; } + Qt::MouseButtons buttons() { return buttons_; } +protected: + void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; +private: + QPoint pos_; + Qt::MouseButtons buttons_; +}; + +class CentralWidget : public QWidget +{ + Q_OBJECT +public: + explicit CentralWidget(QWidget *parent = nullptr); + ~CentralWidget(); + + void setSizeHint(QSize size) { size_ = size; } + QSize sizeHint() const override { return size_; } +private: + QSize size_; +}; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); +signals: + void paint(const QImage& image); + void resizeContents(int w, int h); + void pollMouse(); + + void setFullscreen(bool state); + void setMouseCapture(bool state); + +private slots: + void on_actionFullscreen_triggered(); + +private slots: + void on_actionSettings_triggered(); + void on_actionExit_triggered(); + void on_actionPause_triggered(); + void on_actionCtrl_Alt_Del_triggered(); + void on_actionCtrl_Alt_Esc_triggered(); + void on_actionHard_Reset_triggered(); + void on_actionRight_CTRL_is_left_ALT_triggered(); + void on_actionKeyboard_requires_capture_triggered(); + +private: + struct DeltaPos { + int x = 0; + int y = 0; + int z = 0; + }; + Ui::MainWindow *ui; + DeltaPos mouseDelta; + QWindow* sdl_wrapped_window; + QWidget* sdl_wrapped_widget; + QTimer* sdl_timer; +}; + +#endif // QT_MAINWINDOW_HPP diff --git a/src/qt/qt_mainwindow.ui b/src/qt/qt_mainwindow.ui new file mode 100644 index 000000000..9e77eee55 --- /dev/null +++ b/src/qt/qt_mainwindow.ui @@ -0,0 +1,143 @@ + + + MainWindow + + + + 0 + 0 + 724 + 274 + + + + 86Box + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + 0 + 0 + 724 + 20 + + + + + Action + + + + + + + + + + + + + + + + Tools + + + + + + View + + + + + + + + + + + true + + + Keyboard requires capture + + + + + true + + + Right CTRL is left ALT + + + + + Hard Reset + + + + + Ctrl+Alt+Del + + + + + Ctrl+Alt+Esc + + + + + true + + + Pause + + + + + Exit + + + + + Settings + + + + + Fullscreen + + + Ctrl+Alt+PgUp + + + + + + diff --git a/src/qt/qt_midi.cpp b/src/qt/qt_midi.cpp new file mode 100644 index 000000000..0027e5b5a --- /dev/null +++ b/src/qt/qt_midi.cpp @@ -0,0 +1,32 @@ +#include + +extern "C" { + +void plat_midi_play_msg(uint8_t *msg) +{} + +void plat_midi_play_sysex(uint8_t *sysex, unsigned int len) +{} + +void plat_midi_input_init(void) +{} + +void plat_midi_input_close(void) +{} + +int plat_midi_write(uint8_t val) +{ return 0; } + +void plat_midi_init() +{} + +void plat_midi_close() +{} + +int plat_midi_get_num_devs() +{ return 0; } + +int plat_midi_in_get_num_devs(void) +{ return 0; } + +} diff --git a/src/qt/qt_models_common.cpp b/src/qt/qt_models_common.cpp new file mode 100644 index 000000000..892048605 --- /dev/null +++ b/src/qt/qt_models_common.cpp @@ -0,0 +1,15 @@ +#include "qt_models_common.hpp" + +#include + +int Models::AddEntry(QAbstractItemModel *model, const QString& displayRole, int userRole) +{ + int row = model->rowCount(); + model->insertRow(row); + auto idx = model->index(row, 0); + + model->setData(idx, displayRole, Qt::DisplayRole); + model->setData(idx, userRole, Qt::UserRole); + + return row; +} diff --git a/src/qt/qt_models_common.hpp b/src/qt/qt_models_common.hpp new file mode 100644 index 000000000..91cda3836 --- /dev/null +++ b/src/qt/qt_models_common.hpp @@ -0,0 +1,8 @@ +#pragma once + +class QString; +class QAbstractItemModel; +namespace Models +{ + int AddEntry(QAbstractItemModel* model, const QString& displayRole, int userRole); +}; diff --git a/src/qt/qt_platform.cpp b/src/qt/qt_platform.cpp new file mode 100644 index 000000000..45366560c --- /dev/null +++ b/src/qt/qt_platform.cpp @@ -0,0 +1,382 @@ +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#ifdef Q_OS_UNIX +#include +#endif + +// static QByteArray buf; +extern QElapsedTimer elapsed_timer; +QElapsedTimer elapsed_timer; +static std::mutex blitmx; + +class CharPointer { +public: + CharPointer(char* buf, int size) : b(buf), s(size) {} + CharPointer& operator=(const QByteArray &ba) { + if (s > 0) { + strncpy(b, ba.data(), s-1); + b[s] = 0; + } else { + // if we haven't been told the length of b, just assume enough + // because we didn't get it from emulator code + strcpy(b, ba.data()); + b[ba.size()] = 0; + } + return *this; + } +private: + char* b; + int s; +}; + +extern "C" { +#include <86box/86box.h> +#include <86box/device.h> +#include <86box/gameport.h> +#include <86box/plat_dynld.h> +#include <86box/config.h> +#include <86box/ui.h> + +#include "../cpu/cpu.h" +#include <86box/plat.h> + +volatile int cpu_thread_run = 1; +int mouse_capture = 0; +int fixed_size_x = 640; +int fixed_size_y = 480; +int rctrl_is_lalt = 0; +int update_icons = 0; +int kbd_req_capture = 0; +int hide_status_bar = 0; +uint32_t lang_id = 0x0409, lang_sys = 0x0409; // Multilangual UI variables, for now all set to LCID of en-US + +plat_joystick_t plat_joystick_state[MAX_PLAT_JOYSTICKS]; +joystick_t joystick_state[MAX_JOYSTICKS]; + +int stricmp(const char* s1, const char* s2) +{ + return QByteArray(s1).compare(s2, Qt::CaseInsensitive); +} + +int strnicmp(const char *s1, const char *s2, size_t n) +{ + QByteArray b1(s1, std::min(strlen(s1), n)); + QByteArray b2(s2, std::min(strlen(s2), n)); + return b1.compare(b2, Qt::CaseInsensitive); +} + +void +do_stop(void) +{ + QCoreApplication::quit(); +} + +void plat_get_exe_name(char *s, int size) +{ + CharPointer(s, size) = QCoreApplication::applicationFilePath().toUtf8(); +} + +uint32_t +plat_get_ticks(void) +{ + return elapsed_timer.elapsed(); +} + +uint64_t +plat_timer_read(void) +{ + return elapsed_timer.elapsed(); +} + +FILE * +plat_fopen(const char *path, const char *mode) +{ + /* + QString filepath(path); + if (filepath.isEmpty()) { + return nullptr; + } + + qWarning() << "plat_fopen" << filepath; + bool ok = false; + QFile file(filepath); + auto mode_len = strlen(mode); + for (size_t i = 0; i < mode_len; ++i) { + switch (mode[i]) { + case 'r': + ok = file.open(QIODevice::ReadOnly); + break; + case 'w': + ok = file.open(QIODevice::ReadWrite); + break; + case 'b': + case 't': + break; + default: + qWarning() << "Unhandled open mode" << mode[i]; + } + } + + if (ok) { + qDebug() << "filehandle" << file.handle(); + QFile returned; + qDebug() << "\t" << returned.open(file.handle(), file.openMode(), QFileDevice::FileHandleFlag::DontCloseHandle); + return fdopen(returned.handle(), mode); + } else { + return nullptr; + } + */ +#ifdef Q_OS_WINDOWS + wchar_t *pathw, *modew; + int len; + FILE *fp; + + if (acp_utf8) + return fopen(path, mode); + else { + len = mbstoc16s(NULL, path, 0) + 1; + pathw = malloc(sizeof(wchar_t) * len); + mbstoc16s(pathw, path, len); + + len = mbstoc16s(NULL, mode, 0) + 1; + modew = malloc(sizeof(wchar_t) * len); + mbstoc16s(modew, mode, len); + + fp = _wfopen(pathw, modew); + + free(pathw); + free(modew); + + return fp; + } +#endif +#ifdef Q_OS_UNIX + return fopen(path, mode); +#endif +} + +FILE * +plat_fopen64(const char *path, const char *mode) +{ + return fopen(path, mode); +} + +int +plat_dir_create(char *path) +{ + return QDir().mkdir(path) ? 0 : -1; +} + +int +plat_dir_check(char *path) +{ + QFileInfo fi(path); + return fi.isDir() ? 1 : 0; +} + +int +plat_getcwd(char *bufp, int max) +{ + CharPointer(bufp, max) = QDir::currentPath().toUtf8(); + return 0; +} + +void +plat_get_dirname(char *dest, const char *path) +{ + QFileInfo fi(path); + CharPointer(dest, -1) = fi.dir().path().toUtf8(); +} + +char * +plat_get_extension(char *s) +{ + auto len = strlen(s); + auto idx = QByteArray::fromRawData(s, len).lastIndexOf('.'); + if (idx >= 0) { + return s+idx+1; + } + return s+len; +} + +char * +plat_get_filename(char *s) +{ + auto idx = QByteArray::fromRawData(s, strlen(s)).lastIndexOf(QDir::separator().toLatin1()); + if (idx >= 0) { + return s+idx+1; + } + return s; +} + +int +plat_path_abs(char *path) +{ + QFileInfo fi(path); + return fi.isAbsolute() ? 1 : 0; +} + +void +plat_path_slash(char *path) +{ + auto len = strlen(path); + auto separator = QDir::separator().toLatin1(); + if (path[len-1] != separator) { + path[len] = separator; + path[len+1] = 0; + } +} + +void +plat_append_filename(char *dest, const char *s1, const char *s2) +{ + strcpy(dest, s1); + plat_path_slash(dest); + strcat(dest, s2); +} + +void +plat_tempfile(char *bufp, char *prefix, char *suffix) +{ + QString name; + + if (prefix != nullptr) { + name.append(QString("%1-").arg(prefix)); + } + + name.append("XXXXXX"); + + if (suffix != nullptr) { + name.append(suffix); + } + QTemporaryFile temp(name); + QByteArray buf(bufp); + buf = temp.fileName().toUtf8(); +} + +void plat_remove(char* path) +{ + QFile(path).remove(); +} + +void * +plat_mmap(size_t size, uint8_t executable) +{ +#if defined Q_OS_WINDOWS + return VirtualAlloc(NULL, size, MEM_COMMIT, executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE); +#elif defined Q_OS_UNIX +#if defined Q_OS_DARWIN && defined MAP_JIT + void *ret = mmap(0, size, PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0), MAP_ANON | MAP_PRIVATE | (executable ? MAP_JIT : 0), 0, 0); +#else + void *ret = mmap(0, size, PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0), MAP_ANON | MAP_PRIVATE, 0, 0); + auto retval = *reinterpret_cast(ret); + return (retval < 0) ? nullptr : ret; +#endif +#endif +} + +void +plat_munmap(void *ptr, size_t size) +{ + munmap(ptr, size); +} + +void +plat_pause(int p) +{ + static wchar_t oldtitle[512]; + wchar_t title[512]; + + dopause = p; + if (p) { + wcsncpy(oldtitle, ui_window_title(NULL), sizeof_w(oldtitle) - 1); + wcscpy(title, oldtitle); + wcscat(title, L" - PAUSED -"); + ui_window_title(title); + } else { + ui_window_title(oldtitle); + } +} + +// because we can't include nvr.h because it's got fields named new +extern int nvr_save(void); + +void +plat_power_off(void) +{ + confirm_exit = 0; + nvr_save(); + config_save(); + + /* Deduct a sufficiently large number of cycles that no instructions will + run before the main thread is terminated */ + cycles -= 99999999; + + cpu_thread_run = 0; +} + +void set_language(uint32_t id) { + lang_id = id; +} + +/* Sets up the program language before initialization. */ +uint32_t plat_language_code(char* langcode) { + /* or maybe not */ + return 0; +} + +/* Converts back the language code to LCID */ +void plat_language_code_r(uint32_t lcid, char* outbuf, int len) { + /* or maybe not */ + return; +} + +void* dynld_module(const char *name, dllimp_t *table) +{ + auto lib = std::unique_ptr(new QLibrary(name)); + if (lib->load()) { + for (auto imp = table; imp->name != nullptr; imp++) + { + if ((imp->func = reinterpret_cast(lib->resolve(imp->name))) != nullptr) + { + return nullptr; + } + } + } + + return lib.release(); +} + +void dynld_close(void *handle) +{ + delete reinterpret_cast(handle); +} + +void joystick_init(void) {} +void joystick_close(void) {} +void joystick_process(void) {} +void startblit() +{ + blitmx.lock(); +} + +void endblit() +{ + blitmx.unlock(); +} + +} diff --git a/src/qt/qt_sdl.c b/src/qt/qt_sdl.c new file mode 100644 index 000000000..30c2aff9d --- /dev/null +++ b/src/qt/qt_sdl.c @@ -0,0 +1,743 @@ +/* + * 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. + * + * Rendering module for libSDL2 + * + * NOTE: Given all the problems reported with FULLSCREEN use of SDL, + * we will not use that, but, instead, use a new window which + * coverrs the entire desktop. + * + * + * + * Authors: Fred N. van Kempen, + * Michael Dr�ing, + * + * Copyright 2018-2020 Fred N. van Kempen. + * Copyright 2018-2020 Michael Dr�ing. + * + * Redistribution and use in source and binary forms, with + * or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the entire + * above notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names + * of its contributors may be used to endorse or promote + * products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include + +#include +#include +#include +#include +/* This #undef is needed because a SDL include header redefines HAVE_STDARG_H. */ +#undef HAVE_STDARG_H +#define HAVE_STDARG_H +#include <86box/86box.h> +#include <86box/mouse.h> +#include <86box/keyboard.h> +#include <86box/device.h> +#include <86box/plat.h> +#include <86box/plat_dynld.h> +#include <86box/video.h> +#include <86box/ui.h> +#include <86box/version.h> + +#include "qt_sdl.h" + +#define RENDERER_FULL_SCREEN 1 +#define RENDERER_HARDWARE 2 +#define RENDERER_OPENGL 4 + + +static SDL_Window *sdl_win = NULL; +static SDL_Renderer *sdl_render = NULL; +static SDL_Texture *sdl_tex = NULL; +static int sdl_w, sdl_h; +static int sdl_fs, sdl_flags = -1; +static int cur_w, cur_h; +static int cur_ww = 0, cur_wh = 0; +static volatile int sdl_enabled = 0; +static SDL_mutex* sdl_mutex = NULL; +static int blit_w = 0, blit_h = 0, blit_tex_updated = 0; + +static const uint16_t sdl_to_xt[0x200] = + { + [SDL_SCANCODE_ESCAPE] = 0x01, + [SDL_SCANCODE_1] = 0x02, + [SDL_SCANCODE_2] = 0x03, + [SDL_SCANCODE_3] = 0x04, + [SDL_SCANCODE_4] = 0x05, + [SDL_SCANCODE_5] = 0x06, + [SDL_SCANCODE_6] = 0x07, + [SDL_SCANCODE_7] = 0x08, + [SDL_SCANCODE_8] = 0x09, + [SDL_SCANCODE_9] = 0x0A, + [SDL_SCANCODE_0] = 0x0B, + [SDL_SCANCODE_MINUS] = 0x0C, + [SDL_SCANCODE_EQUALS] = 0x0D, + [SDL_SCANCODE_BACKSPACE] = 0x0E, + [SDL_SCANCODE_TAB] = 0x0F, + [SDL_SCANCODE_Q] = 0x10, + [SDL_SCANCODE_W] = 0x11, + [SDL_SCANCODE_E] = 0x12, + [SDL_SCANCODE_R] = 0x13, + [SDL_SCANCODE_T] = 0x14, + [SDL_SCANCODE_Y] = 0x15, + [SDL_SCANCODE_U] = 0x16, + [SDL_SCANCODE_I] = 0x17, + [SDL_SCANCODE_O] = 0x18, + [SDL_SCANCODE_P] = 0x19, + [SDL_SCANCODE_LEFTBRACKET] = 0x1A, + [SDL_SCANCODE_RIGHTBRACKET] = 0x1B, + [SDL_SCANCODE_RETURN] = 0x1C, + [SDL_SCANCODE_LCTRL] = 0x1D, + [SDL_SCANCODE_A] = 0x1E, + [SDL_SCANCODE_S] = 0x1F, + [SDL_SCANCODE_D] = 0x20, + [SDL_SCANCODE_F] = 0x21, + [SDL_SCANCODE_G] = 0x22, + [SDL_SCANCODE_H] = 0x23, + [SDL_SCANCODE_J] = 0x24, + [SDL_SCANCODE_K] = 0x25, + [SDL_SCANCODE_L] = 0x26, + [SDL_SCANCODE_SEMICOLON] = 0x27, + [SDL_SCANCODE_APOSTROPHE] = 0x28, + [SDL_SCANCODE_GRAVE] = 0x29, + [SDL_SCANCODE_LSHIFT] = 0x2A, + [SDL_SCANCODE_BACKSLASH] = 0x2B, + [SDL_SCANCODE_Z] = 0x2C, + [SDL_SCANCODE_X] = 0x2D, + [SDL_SCANCODE_C] = 0x2E, + [SDL_SCANCODE_V] = 0x2F, + [SDL_SCANCODE_B] = 0x30, + [SDL_SCANCODE_N] = 0x31, + [SDL_SCANCODE_M] = 0x32, + [SDL_SCANCODE_COMMA] = 0x33, + [SDL_SCANCODE_PERIOD] = 0x34, + [SDL_SCANCODE_SLASH] = 0x35, + [SDL_SCANCODE_RSHIFT] = 0x36, + [SDL_SCANCODE_KP_MULTIPLY] = 0x37, + [SDL_SCANCODE_LALT] = 0x38, + [SDL_SCANCODE_SPACE] = 0x39, + [SDL_SCANCODE_CAPSLOCK] = 0x3A, + [SDL_SCANCODE_F1] = 0x3B, + [SDL_SCANCODE_F2] = 0x3C, + [SDL_SCANCODE_F3] = 0x3D, + [SDL_SCANCODE_F4] = 0x3E, + [SDL_SCANCODE_F5] = 0x3F, + [SDL_SCANCODE_F6] = 0x40, + [SDL_SCANCODE_F7] = 0x41, + [SDL_SCANCODE_F8] = 0x42, + [SDL_SCANCODE_F9] = 0x43, + [SDL_SCANCODE_F10] = 0x44, + [SDL_SCANCODE_NUMLOCKCLEAR] = 0x45, + [SDL_SCANCODE_SCROLLLOCK] = 0x46, + [SDL_SCANCODE_HOME] = 0x147, + [SDL_SCANCODE_UP] = 0x148, + [SDL_SCANCODE_PAGEUP] = 0x149, + [SDL_SCANCODE_KP_MINUS] = 0x4A, + [SDL_SCANCODE_LEFT] = 0x14B, + [SDL_SCANCODE_KP_5] = 0x4C, + [SDL_SCANCODE_RIGHT] = 0x14D, + [SDL_SCANCODE_KP_PLUS] = 0x4E, + [SDL_SCANCODE_END] = 0x14F, + [SDL_SCANCODE_DOWN] = 0x150, + [SDL_SCANCODE_PAGEDOWN] = 0x151, + [SDL_SCANCODE_INSERT] = 0x152, + [SDL_SCANCODE_DELETE] = 0x153, + [SDL_SCANCODE_F11] = 0x57, + [SDL_SCANCODE_F12] = 0x58, + + [SDL_SCANCODE_KP_ENTER] = 0x11c, + [SDL_SCANCODE_RCTRL] = 0x11d, + [SDL_SCANCODE_KP_DIVIDE] = 0x135, + [SDL_SCANCODE_RALT] = 0x138, + [SDL_SCANCODE_KP_9] = 0x49, + [SDL_SCANCODE_KP_8] = 0x48, + [SDL_SCANCODE_KP_7] = 0x47, + [SDL_SCANCODE_KP_6] = 0x4D, + [SDL_SCANCODE_KP_4] = 0x4B, + [SDL_SCANCODE_KP_3] = 0x51, + [SDL_SCANCODE_KP_2] = 0x50, + [SDL_SCANCODE_KP_1] = 0x4F, + [SDL_SCANCODE_KP_0] = 0x52, + [SDL_SCANCODE_KP_PERIOD] = 0x53, + + [SDL_SCANCODE_LGUI] = 0x15B, + [SDL_SCANCODE_RGUI] = 0x15C, + [SDL_SCANCODE_APPLICATION] = 0x15D, + [SDL_SCANCODE_PRINTSCREEN] = 0x137, + [SDL_SCANCODE_NONUSBACKSLASH] = 0x56, +}; + +typedef struct mouseinputdata +{ + int deltax, deltay, deltaz; + int mousebuttons; +} mouseinputdata; +static mouseinputdata mousedata; + +// #define ENABLE_SDL_LOG 3 +#ifdef ENABLE_SDL_LOG +int sdl_do_log = ENABLE_SDL_LOG; + +static void +sdl_log(const char *fmt, ...) +{ + va_list ap; + + if (sdl_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +#define sdl_log(fmt, ...) +#endif + + +static void +sdl_integer_scale(double *d, double *g) +{ + double ratio; + + if (*d > *g) { + ratio = floor(*d / *g); + *d = *g * ratio; + } else { + ratio = ceil(*d / *g); + *d = *g / ratio; + } +} + + +static void +sdl_stretch(int *w, int *h, int *x, int *y) +{ + double hw, gw, hh, gh, dx, dy, dw, dh, gsr, hsr; + + hw = (double) sdl_w; + hh = (double) sdl_h; + gw = (double) *w; + gh = (double) *h; + hsr = hw / hh; + + switch (video_fullscreen_scale) { + case FULLSCR_SCALE_FULL: + default: + *w = sdl_w; + *h = sdl_h; + *x = 0; + *y = 0; + break; + case FULLSCR_SCALE_43: + case FULLSCR_SCALE_KEEPRATIO: + if (video_fullscreen_scale == FULLSCR_SCALE_43) + gsr = 4.0 / 3.0; + else + gsr = gw / gh; + if (gsr <= hsr) { + dw = hh * gsr; + dh = hh; + } else { + dw = hw; + dh = hw / gsr; + } + dx = (hw - dw) / 2.0; + dy = (hh - dh) / 2.0; + *w = (int) dw; + *h = (int) dh; + *x = (int) dx; + *y = (int) dy; + break; + case FULLSCR_SCALE_INT: + gsr = gw / gh; + if (gsr <= hsr) { + dw = hh * gsr; + dh = hh; + } else { + dw = hw; + dh = hw / gsr; + } + sdl_integer_scale(&dw, &gw); + sdl_integer_scale(&dh, &gh); + dx = (hw - dw) / 2.0; + dy = (hh - dh) / 2.0; + *w = (int) dw; + *h = (int) dh; + *x = (int) dx; + *y = (int) dy; + break; + } +} + +static void +sdl_blit(int x, int y, int w, int h) +{ + void *pixeldata; + int pitch; + + if (!sdl_enabled || (x < 0) || (y < 0) || (w <= 0) || (h <= 0) || (w > 2048) || (h > 2048) || (buffer32 == NULL) || (sdl_render == NULL) || (sdl_tex == NULL)) { + video_blit_complete(); + return; + } + + SDL_LockMutex(sdl_mutex); + SDL_LockTexture(sdl_tex, 0, &pixeldata, &pitch); + + video_copy(pixeldata, &(buffer32->line[y][x]), h * (2048 + 64) * sizeof(uint32_t)); + + if (screenshots) + video_screenshot((uint32_t *) pixeldata, 0, 0, (2048 + 64)); + + blit_w = w; + blit_h = h; + blit_tex_updated = 1; + + SDL_UnlockTexture(sdl_tex); + SDL_UnlockMutex(sdl_mutex); + video_blit_complete(); +} + + +static void +sdl_destroy_window(void) +{ + if (sdl_win != NULL) { + SDL_DestroyWindow(sdl_win); + sdl_win = NULL; + } +} + + +static void +sdl_destroy_texture(void) +{ + if (sdl_tex != NULL) { + SDL_DestroyTexture(sdl_tex); + sdl_tex = NULL; + } + + /* SDL_DestroyRenderer also automatically destroys all associated textures. */ + if (sdl_render != NULL) { + SDL_DestroyRenderer(sdl_render); + sdl_render = NULL; + } +} + + +void +sdl_close(void) +{ + if (sdl_mutex != NULL) + SDL_LockMutex(sdl_mutex); + + /* Unregister our renderer! */ + video_setblit(NULL); + + if (sdl_enabled) + sdl_enabled = 0; + + if (sdl_mutex != NULL) { + SDL_DestroyMutex(sdl_mutex); + sdl_mutex = NULL; + } + + sdl_destroy_texture(); + sdl_destroy_window(); + + /* Quit. */ + SDL_Quit(); + sdl_flags = -1; +} + +static void sdl_select_best_hw_driver(void) { + int i; + SDL_RendererInfo renderInfo; + + for (i = 0; i < SDL_GetNumRenderDrivers(); ++i) { + SDL_GetRenderDriverInfo(i, &renderInfo); + if (renderInfo.flags & SDL_RENDERER_ACCELERATED) { + SDL_SetHint(SDL_HINT_RENDER_DRIVER, renderInfo.name); + return; + } + } +} + +static void +sdl_init_texture(void) +{ + if (sdl_flags & RENDERER_HARDWARE) { + sdl_render = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_ACCELERATED); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, video_filter_method ? "1" : "0"); + } else { + sdl_render = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_SOFTWARE); + } + + sdl_tex = SDL_CreateTexture(sdl_render, SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, (2048 + 64), (2048 + 64)); + + if (sdl_render == NULL) { + sdl_log("SDL: unable to SDL_CreateRenderer (%s)\n", SDL_GetError()); + } + if (sdl_tex == NULL) { + sdl_log("SDL: unable to SDL_CreateTexture (%s)\n", SDL_GetError()); + } +} + + +static void +sdl_reinit_texture(void) +{ + if (sdl_flags == -1) + return; + + sdl_destroy_texture(); + sdl_init_texture(); +} + + +void sdl_set_fs(int fs) { + SDL_SetWindowFullscreen(sdl_win, fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + SDL_SetRelativeMouseMode((SDL_bool)mouse_capture); + + sdl_fs = fs; + + if (fs) { + sdl_flags |= RENDERER_FULL_SCREEN; + } else { + sdl_flags &= ~RENDERER_FULL_SCREEN; + } + + sdl_reinit_texture(); +} + + +static int +sdl_init_common(void* win, int flags) +{ + wchar_t temp[128]; + SDL_version ver; + + sdl_log("SDL: init (fs=%d)\n", 0); + + /* Get and log the version of the DLL we are using. */ + SDL_GetVersion(&ver); + sdl_log("SDL: version %d.%d.%d\n", ver.major, ver.minor, ver.patch); + + /* Initialize the SDL system. */ + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + sdl_log("SDL: initialization failed (%s)\n", SDL_GetError()); + return(0); + } + + if (flags & RENDERER_HARDWARE) { + if (flags & RENDERER_OPENGL) + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "OpenGL"); + else + sdl_select_best_hw_driver(); + } + + /* Get the size of the (current) desktop. */ + SDL_DisplayMode dm; + if (SDL_GetDesktopDisplayMode(0, &dm) != 0) { + sdl_log("SDL: SDL_GetDesktopDisplayMode failed (%s)\n", SDL_GetError()); + return(0); + } + sdl_w = dm.w; + sdl_h = dm.h; + sdl_flags = flags; + + sdl_win = SDL_CreateWindow("86Box renderer", 640, 480, 100, 100, sdl_flags); + if (sdl_win == NULL) { + sdl_log("SDL: unable to CreateWindowFrom (%s)\n", SDL_GetError()); + } + sdl_init_texture(); + sdl_set_fs(video_fullscreen & 1); + + /* Make sure we get a clean exit. */ + atexit(sdl_close); + + /* Register our renderer! */ + video_setblit(sdl_blit); + + sdl_enabled = 1; + sdl_mutex = SDL_CreateMutex(); + + return(1); +} + + +int +sdl_inits(void* win) +{ + return sdl_init_common(win, 0); +} + + +int +sdl_inith(void* win) +{ + return sdl_init_common(win, RENDERER_HARDWARE); +} + + +int +sdl_initho(void* win) +{ + return sdl_init_common(win, RENDERER_HARDWARE | RENDERER_OPENGL); +} + + +int +sdl_pause(void) +{ + return(0); +} + + +void +sdl_resize(int w, int h) +{ + int ww = 0, wh = 0; + + if (video_fullscreen & 2) + return; + + if ((w == cur_w) && (h == cur_h)) + return; + + SDL_LockMutex(sdl_mutex); + + ww = w; + wh = h; + + if (sdl_fs) { +// sdl_stretch(&ww, &wh, &wx, &wy); +// MoveWindow(hwndRender, wx, wy, ww, wh, TRUE); + } + + cur_w = w; + cur_h = h; + + cur_ww = ww; + cur_wh = wh; + + SDL_SetWindowSize(sdl_win, cur_ww, cur_wh); + sdl_reinit_texture(); + + SDL_UnlockMutex(sdl_mutex); +} + + +void +sdl_enable(int enable) +{ + if (sdl_flags == -1) + return; + + SDL_LockMutex(sdl_mutex); + sdl_enabled = !!enable; + + if (enable == 1) { + SDL_SetWindowSize(sdl_win, cur_ww, cur_wh); + sdl_reinit_texture(); + } + + SDL_UnlockMutex(sdl_mutex); +} + + +void +sdl_reload(void) +{ + if (sdl_flags & RENDERER_HARDWARE) { + SDL_LockMutex(sdl_mutex); + + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, video_filter_method ? "1" : "0"); + sdl_reinit_texture(); + + SDL_UnlockMutex(sdl_mutex); + } +} + +static int mouse_inside = 0; +enum sdl_main_status sdl_main() { + int ret = SdlMainOk; + SDL_Rect r_src; + SDL_Event event; + + while (SDL_PollEvent(&event)) + { + switch(event.type) + { + case SDL_QUIT: + ret = SdlMainQuit; + break; + case SDL_MOUSEWHEEL: + { + if (mouse_capture || video_fullscreen) + { + if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) + { + event.wheel.x *= -1; + event.wheel.y *= -1; + } + mousedata.deltaz = event.wheel.y; + } + break; + } + case SDL_MOUSEMOTION: + { + if (mouse_capture || video_fullscreen) + { + mousedata.deltax += event.motion.xrel; + mousedata.deltay += event.motion.yrel; + } + break; + } + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + { + if ((event.button.button == SDL_BUTTON_LEFT) + && !(mouse_capture || video_fullscreen) + && event.button.state == SDL_RELEASED + && mouse_inside) + { + plat_mouse_capture(1); + break; + } + if (mouse_get_buttons() < 3 && event.button.button == SDL_BUTTON_MIDDLE && !video_fullscreen) + { + plat_mouse_capture(0); + break; + } + if (mouse_capture || video_fullscreen) + { + int buttonmask = 0; + + switch(event.button.button) + { + case SDL_BUTTON_LEFT: + buttonmask = 1; + break; + case SDL_BUTTON_RIGHT: + buttonmask = 2; + break; + case SDL_BUTTON_MIDDLE: + buttonmask = 4; + break; + } + if (event.button.state == SDL_PRESSED) + { + mousedata.mousebuttons |= buttonmask; + } + else mousedata.mousebuttons &= ~buttonmask; + } + break; + } + case SDL_RENDER_DEVICE_RESET: + case SDL_RENDER_TARGETS_RESET: + { + sdl_reinit_texture(); + break; + } + case SDL_KEYDOWN: + case SDL_KEYUP: + { + uint16_t xtkey = 0; + switch(event.key.keysym.scancode) + { + default: + xtkey = sdl_to_xt[event.key.keysym.scancode]; + break; + } + keyboard_input(event.key.state == SDL_PRESSED, xtkey); + } + break; + case SDL_WINDOWEVENT: + { + switch (event.window.event) + { + case SDL_WINDOWEVENT_ENTER: + mouse_inside = 1; + break; + case SDL_WINDOWEVENT_LEAVE: + mouse_inside = 0; + break; + } + } + } + } + + if (blit_tex_updated > 0) { + SDL_LockMutex(sdl_mutex); + int status = SDL_RenderClear(sdl_render); + if (status) { + sdl_log("SDL: unable to SDL_RenderClear (%s)\n", SDL_GetError()); + } + + r_src.x = 0; + r_src.y = 0; + r_src.w = blit_w; + r_src.h = blit_h; + + status = SDL_RenderCopy(sdl_render, sdl_tex, &r_src, 0); + if (status) { + sdl_log("SDL: unable to copy texture to renderer (%s)\n", SDL_GetError()); + } + + SDL_RenderPresent(sdl_render); + blit_tex_updated = 0; + SDL_UnlockMutex(sdl_mutex); + } + + if (mouse_capture && keyboard_ismsexit()) { + plat_mouse_capture(0); + } + if (video_fullscreen && keyboard_isfsexit()) { + plat_setfullscreen(0); + } + + return ret; +} + +void sdl_mouse_capture(int on) { + SDL_SetRelativeMouseMode((SDL_bool)on); +} + +void sdl_mouse_poll() { + mouse_x = mousedata.deltax; + mouse_y = mousedata.deltay; + mouse_z = mousedata.deltaz; + mousedata.deltax = mousedata.deltay = mousedata.deltaz = 0; + mouse_buttons = mousedata.mousebuttons; +} diff --git a/src/qt/qt_sdl.h b/src/qt/qt_sdl.h new file mode 100644 index 000000000..02fd47ddf --- /dev/null +++ b/src/qt/qt_sdl.h @@ -0,0 +1,73 @@ +/* + * 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. + * + * Definitions for the libSDL2 rendering module. + * + * + * + * Authors: Fred N. van Kempen, + * Michael Drüing, + * + * Copyright 2018,2019 Fred N. van Kempen. + * Copyright 2018,2019 Michael Drüing. + * + * Redistribution and use in source and binary forms, with + * or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the entire + * above notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names + * of its contributors may be used to endorse or promote + * products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef WIN_SDL_H +# define WIN_SDL_H + +extern void* sdl_win_handle; +extern void sdl_close(void); +extern int sdl_inits(); +extern int sdl_inith(); +extern int sdl_initho(); +extern int sdl_pause(void); +extern void sdl_resize(int w, int h); +extern void sdl_enable(int enable); +extern void sdl_set_fs(int fs); +extern void sdl_reload(void); + +enum sdl_main_status { + SdlMainOk, + SdlMainQuit, +}; + +extern enum sdl_main_status sdl_main(); + +extern void sdl_mouse_capture(int on); +extern void sdl_mouse_poll(); + +#endif /*WIN_SDL_H*/ diff --git a/src/qt/qt_settings.cpp b/src/qt/qt_settings.cpp new file mode 100644 index 000000000..b2716b1e0 --- /dev/null +++ b/src/qt/qt_settings.cpp @@ -0,0 +1,131 @@ +#include "qt_settings.hpp" +#include "ui_qt_settings.h" + +#include "qt_settingsmachine.hpp" +#include "qt_settingsdisplay.hpp" +#include "qt_settingsinput.hpp" +#include "qt_settingssound.hpp" +#include "qt_settingsnetwork.hpp" +#include "qt_settingsports.hpp" +#include "qt_settingsstoragecontrollers.hpp" +#include "qt_settingsharddisks.hpp" +#include "qt_settingsfloppycdrom.hpp" +#include "qt_settingsotherremovable.hpp" +#include "qt_settingsotherperipherals.hpp" + +#include + +class SettingsModel : public QAbstractListModel { +public: + SettingsModel(QObject* parent) : QAbstractListModel(parent) {} + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; +private: + QStringList pages = { + "Machine", + "Display", + "Input Devices", + "Sound", + "Network", + "Ports (COM & LPT)", + "Storage Controllers", + "Hard Disks", + "Floppy & CD-ROM Drives", + "Other Removable Devices", + "Other Peripherals", + }; + QStringList page_icons = { + "machine", + "display", + "input_devices", + "sound", + "network", + "ports", + "storage_controllers", + "hard_disk", + "floppy_and_cdrom_drives", + "other_removable_devices", + "other_peripherals", + }; +}; + +QVariant SettingsModel::data(const QModelIndex &index, int role) const { + Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::ParentIsInvalid)); + + switch (role) { + case Qt::DisplayRole: + return pages.at(index.row()); + case Qt::DecorationRole: + return QIcon(QString(":/settings/win/icons/%1.ico").arg(page_icons[index.row()])); + default: + return {}; + } +} + +int SettingsModel::rowCount(const QModelIndex &parent) const { + (void) parent; + return pages.size(); +} + +Settings::Settings(QWidget *parent) : + QDialog(parent), + ui(new Ui::Settings) +{ + ui->setupUi(this); + + ui->listView->setModel(new SettingsModel(this)); + + machine = new SettingsMachine(this); + display = new SettingsDisplay(this); + input = new SettingsInput(this); + sound = new SettingsSound(this); + network = new SettingsNetwork(this); + ports = new SettingsPorts(this); + storageControllers = new SettingsStorageControllers(this); + harddisks = new SettingsHarddisks(this); + floppyCdrom = new SettingsFloppyCDROM(this); + otherRemovable = new SettingsOtherRemovable(this); + otherPeripherals = new SettingsOtherPeripherals(this); + + ui->stackedWidget->addWidget(machine); + ui->stackedWidget->addWidget(display); + ui->stackedWidget->addWidget(input); + ui->stackedWidget->addWidget(sound); + ui->stackedWidget->addWidget(network); + ui->stackedWidget->addWidget(ports); + ui->stackedWidget->addWidget(storageControllers); + ui->stackedWidget->addWidget(harddisks); + ui->stackedWidget->addWidget(floppyCdrom); + ui->stackedWidget->addWidget(otherRemovable); + ui->stackedWidget->addWidget(otherPeripherals); + + connect(machine, &SettingsMachine::currentMachineChanged, display, &SettingsDisplay::onCurrentMachineChanged); + connect(machine, &SettingsMachine::currentMachineChanged, input, &SettingsInput::onCurrentMachineChanged); + connect(machine, &SettingsMachine::currentMachineChanged, sound, &SettingsSound::onCurrentMachineChanged); + connect(machine, &SettingsMachine::currentMachineChanged, network, &SettingsNetwork::onCurrentMachineChanged); + connect(machine, &SettingsMachine::currentMachineChanged, storageControllers, &SettingsStorageControllers::onCurrentMachineChanged); + + connect(ui->listView->selectionModel(), &QItemSelectionModel::currentChanged, this, [this](const QModelIndex ¤t, const QModelIndex &previous) { + ui->stackedWidget->setCurrentIndex(current.row()); + }); +} + +Settings::~Settings() +{ + delete ui; +} + +void Settings::save() { + machine->save(); + display->save(); + input->save(); + sound->save(); + network->save(); + ports->save(); + storageControllers->save(); + harddisks->save(); + floppyCdrom->save(); + otherRemovable->save(); + otherPeripherals->save(); +} diff --git a/src/qt/qt_settings.hpp b/src/qt/qt_settings.hpp new file mode 100644 index 000000000..2fa189186 --- /dev/null +++ b/src/qt/qt_settings.hpp @@ -0,0 +1,46 @@ +#ifndef QT_SETTINGS_HPP +#define QT_SETTINGS_HPP + +#include + +namespace Ui { +class Settings; +} + +class SettingsMachine; +class SettingsDisplay; +class SettingsInput; +class SettingsSound; +class SettingsNetwork; +class SettingsPorts; +class SettingsStorageControllers; +class SettingsHarddisks; +class SettingsFloppyCDROM; +class SettingsOtherRemovable; +class SettingsOtherPeripherals; + +class Settings : public QDialog +{ + Q_OBJECT + +public: + explicit Settings(QWidget *parent = nullptr); + ~Settings(); + void save(); + +private: + Ui::Settings *ui; + SettingsMachine* machine; + SettingsDisplay* display; + SettingsInput* input; + SettingsSound* sound; + SettingsNetwork* network; + SettingsPorts* ports; + SettingsStorageControllers* storageControllers; + SettingsHarddisks* harddisks; + SettingsFloppyCDROM* floppyCdrom; + SettingsOtherRemovable* otherRemovable; + SettingsOtherPeripherals* otherPeripherals; +}; + +#endif // QT_SETTINGS_HPP diff --git a/src/qt/qt_settings.ui b/src/qt/qt_settings.ui new file mode 100644 index 000000000..5bfca9748 --- /dev/null +++ b/src/qt/qt_settings.ui @@ -0,0 +1,87 @@ + + + Settings + + + + 0 + 0 + 900 + 595 + + + + 86Box Settings + + + + + + + + + + + + -1 + + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Settings + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Settings + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/qt/qt_settingsdisplay.cpp b/src/qt/qt_settingsdisplay.cpp new file mode 100644 index 000000000..70ec09c89 --- /dev/null +++ b/src/qt/qt_settingsdisplay.cpp @@ -0,0 +1,105 @@ +#include "qt_settingsdisplay.hpp" +#include "ui_qt_settingsdisplay.h" + +#include + +extern "C" { +#include <86box/86box.h> +#include <86box/device.h> +#include <86box/machine.h> +#include <86box/video.h> +} + +#include "qt_deviceconfig.hpp" +#include "qt_models_common.hpp" + +SettingsDisplay::SettingsDisplay(QWidget *parent) : + QWidget(parent), + ui(new Ui::SettingsDisplay) +{ + ui->setupUi(this); + + onCurrentMachineChanged(machine); +} + +SettingsDisplay::~SettingsDisplay() +{ + delete ui; +} + +void SettingsDisplay::save() { + gfxcard = ui->comboBoxVideo->currentData().toInt(); + voodoo_enabled = ui->checkBoxVoodoo->isChecked() ? 1 : 0; +} + +void SettingsDisplay::onCurrentMachineChanged(int machineId) { + // win_settings_video_proc, WM_INITDIALOG + this->machineId = machineId; + + auto* machine = &machines[machineId]; + auto* model = ui->comboBoxVideo->model(); + auto removeRows = model->rowCount(); + + int c = 0; + int selectedRow = 0; + while (true) { + /* Skip "internal" if machine doesn't have it. */ + if ((c == 1) && !(machine->flags & MACHINE_VIDEO)) { + c++; + continue; + } + + const device_t* video_dev = video_card_getdevice(c); + QString name = DeviceConfig::DeviceName(video_dev, video_get_internal_name(c), 1); + if (name.isEmpty()) { + break; + } + + if (video_card_available(c) && + device_is_valid(video_dev, machine->flags)) { + int row = Models::AddEntry(model, name, c); + if (c == gfxcard) { + selectedRow = row - removeRows; + } + } + + c++; + } + model->removeRows(0, removeRows); + + if (machine->flags & MACHINE_VIDEO_ONLY) { + ui->comboBoxVideo->setEnabled(false); + selectedRow = 1; + } else { + ui->comboBoxVideo->setEnabled(true); + } + ui->comboBoxVideo->setCurrentIndex(selectedRow); +} + +void SettingsDisplay::on_pushButtonConfigure_clicked() { + auto* device = video_card_getdevice(ui->comboBoxVideo->currentData().toInt()); + DeviceConfig::ConfigureDevice(device); +} + +void SettingsDisplay::on_pushButtonConfigureVoodoo_clicked() { + DeviceConfig::ConfigureDevice(&voodoo_device); +} + +void SettingsDisplay::on_comboBoxVideo_currentIndexChanged(int index) { + if (index < 0) { + return; + } + int videoCard = ui->comboBoxVideo->currentData().toInt(); + ui->pushButtonConfigure->setEnabled(video_card_has_config(videoCard) > 0); + + bool machineHasPci = machines[machineId].flags & MACHINE_BUS_PCI; + ui->checkBoxVoodoo->setEnabled(machineHasPci); + if (machineHasPci) { + ui->checkBoxVoodoo->setChecked(voodoo_enabled); + } + ui->pushButtonConfigureVoodoo->setEnabled(machineHasPci && ui->checkBoxVoodoo->isChecked()); +} + +void SettingsDisplay::on_checkBoxVoodoo_stateChanged(int state) { + ui->pushButtonConfigureVoodoo->setEnabled(state == Qt::Checked); +} diff --git a/src/qt/qt_settingsdisplay.hpp b/src/qt/qt_settingsdisplay.hpp new file mode 100644 index 000000000..768f40311 --- /dev/null +++ b/src/qt/qt_settingsdisplay.hpp @@ -0,0 +1,34 @@ +#ifndef QT_SETTINGSDISPLAY_HPP +#define QT_SETTINGSDISPLAY_HPP + +#include + +namespace Ui { +class SettingsDisplay; +} + +class SettingsDisplay : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsDisplay(QWidget *parent = nullptr); + ~SettingsDisplay(); + + void save(); + +public slots: + void onCurrentMachineChanged(int machineId); + +private slots: + void on_checkBoxVoodoo_stateChanged(int state); + void on_comboBoxVideo_currentIndexChanged(int index); + void on_pushButtonConfigureVoodoo_clicked(); + void on_pushButtonConfigure_clicked(); + +private: + Ui::SettingsDisplay *ui; + int machineId = 0; +}; + +#endif // QT_SETTINGSDISPLAY_HPP diff --git a/src/qt/qt_settingsdisplay.ui b/src/qt/qt_settingsdisplay.ui new file mode 100644 index 000000000..02ee3a1a9 --- /dev/null +++ b/src/qt/qt_settingsdisplay.ui @@ -0,0 +1,77 @@ + + + SettingsDisplay + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Video + + + + + + + Configure + + + + + + + Configure + + + + + + + Voodoo Graphics + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/qt/qt_settingsfloppycdrom.cpp b/src/qt/qt_settingsfloppycdrom.cpp new file mode 100644 index 000000000..5a578ca61 --- /dev/null +++ b/src/qt/qt_settingsfloppycdrom.cpp @@ -0,0 +1,217 @@ +#include "qt_settingsfloppycdrom.hpp" +#include "ui_qt_settingsfloppycdrom.h" + +extern "C" { +#include <86box/timer.h> +#include <86box/fdd.h> +#include <86box/cdrom.h> +} + +#include + +#include "qt_models_common.hpp" +#include "qt_harddrive_common.hpp" + +static void setFloppyType(QAbstractItemModel* model, const QModelIndex& idx, int type) { + QIcon icon; + if (type == 0) { + icon = QIcon(":/settings/win/icons/floppy_disabled.ico"); + } else if (type >= 1 && type <= 6) { + icon = QIcon(":/settings/win/icons/floppy_525.ico"); + } else { + icon = QIcon(":/settings/win/icons/floppy_35.ico"); + } + + model->setData(idx, fdd_getname(type)); + model->setData(idx, type, Qt::UserRole); + model->setData(idx, icon, Qt::DecorationRole); +} + +static void setCDROMBus(QAbstractItemModel* model, const QModelIndex& idx, uint8_t bus, uint8_t channel) { + QIcon icon; + switch (bus) { + case CDROM_BUS_DISABLED: + icon = QIcon(":/settings/win/icons/cdrom_disabled.ico"); + break; + case CDROM_BUS_ATAPI: + case CDROM_BUS_SCSI: + icon = QIcon(":/settings/win/icons/cdrom.ico"); + break; + } + + auto i = idx.siblingAtColumn(0); + model->setData(i, Harddrives::BusChannelName(bus, channel)); + model->setData(i, bus, Qt::UserRole); + model->setData(i, channel, Qt::UserRole + 1); + model->setData(i, icon, Qt::DecorationRole); +} + +static void setCDROMSpeed(QAbstractItemModel* model, const QModelIndex& idx, uint8_t speed) { + auto i = idx.siblingAtColumn(1); + model->setData(i, QString("%1x").arg(speed)); + model->setData(i, speed, Qt::UserRole); +} + +SettingsFloppyCDROM::SettingsFloppyCDROM(QWidget *parent) : + QWidget(parent), + ui(new Ui::SettingsFloppyCDROM) +{ + ui->setupUi(this); + + auto* model = ui->comboBoxFloppyType->model(); + int i = 0; + while (true) { + QString name = fdd_getname(i); + if (name.isEmpty()) { + break; + } + + Models::AddEntry(model, name, i); + ++i; + } + + model = new QStandardItemModel(0, 3, this); + ui->tableViewFloppy->setModel(model); + model->setHeaderData(0, Qt::Horizontal, "Type"); + model->setHeaderData(1, Qt::Horizontal, "Turbo"); + model->setHeaderData(2, Qt::Horizontal, "Check BPB"); + + model->insertRows(0, FDD_NUM); + /* Floppy drives category */ + for (int i = 0; i < FDD_NUM; i++) { + auto idx = model->index(i, 0); + int type = fdd_get_type(i); + setFloppyType(model, idx, type); + model->setData(idx.siblingAtColumn(1), fdd_get_turbo(i) > 0 ? "On" : "Off"); + model->setData(idx.siblingAtColumn(2), fdd_get_check_bpb(i) > 0 ? "On" : "Off"); + } + + ui->tableViewFloppy->resizeColumnsToContents(); + ui->tableViewFloppy->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + + connect(ui->tableViewFloppy->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &SettingsFloppyCDROM::onFloppyRowChanged); + ui->tableViewFloppy->setCurrentIndex(model->index(0, 0)); + + + Harddrives::populateRemovableBuses(ui->comboBoxBus->model()); + model = ui->comboBoxSpeed->model(); + for (int i = 0; i <= 72; i++) { + Models::AddEntry(model, QString("%1x").arg(i), i); + } + + model = new QStandardItemModel(0, 2, this); + ui->tableViewCDROM->setModel(model); + model->setHeaderData(0, Qt::Horizontal, "Bus"); + model->setHeaderData(1, Qt::Horizontal, "Speed"); + model->insertRows(0, CDROM_NUM); + for (int i = 0; i < CDROM_NUM; i++) { + auto idx = model->index(i, 0); + setCDROMBus(model, idx, cdrom[i].bus_type, cdrom[i].res); + setCDROMSpeed(model, idx.siblingAtColumn(1), cdrom[i].speed); + } + ui->tableViewCDROM->resizeColumnsToContents(); + ui->tableViewCDROM->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + + connect(ui->tableViewCDROM->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &SettingsFloppyCDROM::onCDROMRowChanged); + ui->tableViewCDROM->setCurrentIndex(model->index(0, 0)); +} + +SettingsFloppyCDROM::~SettingsFloppyCDROM() +{ + delete ui; +} + +void SettingsFloppyCDROM::save() { + auto* model = ui->tableViewFloppy->model(); + for (int i = 0; i < FDD_NUM; i++) { + fdd_set_type(i, model->index(i, 0).data(Qt::UserRole).toInt()); + fdd_set_turbo(i, model->index(i, 1).data() == "On" ? 1 : 0); + fdd_set_check_bpb(i, model->index(i, 2).data() == "On" ? 1 : 0); + } + + /* Removable devices category */ + model = ui->tableViewCDROM->model(); + memset(cdrom, 0, sizeof(cdrom)); + for (int i = 0; i < CDROM_NUM; i++) { + cdrom[i].bus_type = model->index(i, 0).data(Qt::UserRole).toUInt(); + cdrom[i].res = model->index(i, 0).data(Qt::UserRole + 1).toUInt(); + cdrom[i].speed = model->index(i, 1).data(Qt::UserRole).toUInt(); + } +} + +void SettingsFloppyCDROM::onFloppyRowChanged(const QModelIndex ¤t) { + int type = current.siblingAtColumn(0).data(Qt::UserRole).toInt(); + ui->comboBoxFloppyType->setCurrentIndex(type); + ui->checkBoxTurboTimings->setChecked(current.siblingAtColumn(1).data() == "On"); + ui->checkBoxCheckBPB->setChecked(current.siblingAtColumn(2).data() == "On"); +} + +void SettingsFloppyCDROM::onCDROMRowChanged(const QModelIndex ¤t) { + uint8_t bus = current.siblingAtColumn(0).data(Qt::UserRole).toUInt(); + uint8_t channel = current.siblingAtColumn(0).data(Qt::UserRole + 1).toUInt(); + uint8_t speed = current.siblingAtColumn(1).data(Qt::UserRole).toUInt(); + + ui->comboBoxBus->setCurrentIndex(-1); + auto* model = ui->comboBoxBus->model(); + auto match = model->match(model->index(0, 0), Qt::UserRole, bus); + if (! match.isEmpty()) { + ui->comboBoxBus->setCurrentIndex(match.first().row()); + } + + model = ui->comboBoxChannel->model(); + match = model->match(model->index(0, 0), Qt::UserRole, channel); + if (! match.isEmpty()) { + ui->comboBoxChannel->setCurrentIndex(match.first().row()); + } + ui->comboBoxSpeed->setCurrentIndex(speed); +} + +void SettingsFloppyCDROM::on_checkBoxTurboTimings_stateChanged(int arg1) { + auto idx = ui->tableViewFloppy->selectionModel()->currentIndex(); + ui->tableViewFloppy->model()->setData(idx.siblingAtColumn(1), arg1 == Qt::Checked ? "On" : "Off"); +} + +void SettingsFloppyCDROM::on_checkBoxCheckBPB_stateChanged(int arg1) { + auto idx = ui->tableViewFloppy->selectionModel()->currentIndex(); + ui->tableViewFloppy->model()->setData(idx.siblingAtColumn(2), arg1 == Qt::Checked ? "On" : "Off"); +} + +void SettingsFloppyCDROM::on_comboBoxFloppyType_activated(int index) { + setFloppyType(ui->tableViewFloppy->model(), ui->tableViewFloppy->selectionModel()->currentIndex(), index); +} + +void SettingsFloppyCDROM::on_comboBoxBus_currentIndexChanged(int index) { + if (index < 0) { + return; + } + + int bus = ui->comboBoxBus->currentData().toInt(); + bool enabled = (bus != CDROM_BUS_DISABLED); + ui->comboBoxChannel->setEnabled(enabled); + ui->comboBoxSpeed->setEnabled(enabled); + Harddrives::populateBusChannels(ui->comboBoxChannel->model(), bus); +} + +void SettingsFloppyCDROM::on_comboBoxSpeed_activated(int index) { + auto idx = ui->tableViewCDROM->selectionModel()->currentIndex(); + setCDROMSpeed(ui->tableViewCDROM->model(), idx.siblingAtColumn(1), index); +} + + +void SettingsFloppyCDROM::on_comboBoxBus_activated(int) { + setCDROMBus( + ui->tableViewCDROM->model(), + ui->tableViewCDROM->selectionModel()->currentIndex(), + ui->comboBoxBus->currentData().toUInt(), + ui->comboBoxChannel->currentData().toUInt()); +} + + +void SettingsFloppyCDROM::on_comboBoxChannel_activated(int) { + setCDROMBus( + ui->tableViewCDROM->model(), + ui->tableViewCDROM->selectionModel()->currentIndex(), + ui->comboBoxBus->currentData().toUInt(), + ui->comboBoxChannel->currentData().toUInt()); +} + diff --git a/src/qt/qt_settingsfloppycdrom.hpp b/src/qt/qt_settingsfloppycdrom.hpp new file mode 100644 index 000000000..f68646ab0 --- /dev/null +++ b/src/qt/qt_settingsfloppycdrom.hpp @@ -0,0 +1,35 @@ +#ifndef QT_SETTINGSFLOPPYCDROM_HPP +#define QT_SETTINGSFLOPPYCDROM_HPP + +#include + +namespace Ui { +class SettingsFloppyCDROM; +} + +class SettingsFloppyCDROM : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsFloppyCDROM(QWidget *parent = nullptr); + ~SettingsFloppyCDROM(); + + void save(); + +private slots: + void on_comboBoxChannel_activated(int index); + void on_comboBoxBus_activated(int index); + void on_comboBoxSpeed_activated(int index); + void on_comboBoxBus_currentIndexChanged(int index); + void on_comboBoxFloppyType_activated(int index); + void on_checkBoxCheckBPB_stateChanged(int arg1); + void on_checkBoxTurboTimings_stateChanged(int arg1); + void onFloppyRowChanged(const QModelIndex ¤t); + void onCDROMRowChanged(const QModelIndex ¤t); + +private: + Ui::SettingsFloppyCDROM *ui; +}; + +#endif // QT_SETTINGSFLOPPYCDROM_HPP diff --git a/src/qt/qt_settingsfloppycdrom.ui b/src/qt/qt_settingsfloppycdrom.ui new file mode 100644 index 000000000..222728356 --- /dev/null +++ b/src/qt/qt_settingsfloppycdrom.ui @@ -0,0 +1,148 @@ + + + SettingsFloppyCDROM + + + + 0 + 0 + 544 + 617 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Floppy Drives + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + + + + + + + Type + + + + + + + + + + Turbo Timings + + + + + + + Check BPB + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + CD-ROM Drives + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + + + + + + + Channel + + + + + + + + + + Speed + + + + + + + Bus + + + + + + + + + + + + + + + + diff --git a/src/qt/qt_settingsharddisks.cpp b/src/qt/qt_settingsharddisks.cpp new file mode 100644 index 000000000..ddd419636 --- /dev/null +++ b/src/qt/qt_settingsharddisks.cpp @@ -0,0 +1,225 @@ +#include "qt_settingsharddisks.hpp" +#include "ui_qt_settingsharddisks.h" + +extern "C" { +#include <86box/86box.h> +#include <86box/hdd.h> +} + +#include + +#include "qt_harddiskdialog.hpp" +#include "qt_harddrive_common.hpp" + +const int ColumnBus = 0; +const int ColumnFilename = 1; +const int ColumnCylinders = 2; +const int ColumnHeads = 3; +const int ColumnSectors = 4; +const int ColumnSize = 5; + +const int DataBus = Qt::UserRole; +const int DataBusChannel = Qt::UserRole + 1; + +/* +static void +normalize_hd_list() +{ + hard_disk_t ihdd[HDD_NUM]; + int i, j; + + j = 0; + memset(ihdd, 0x00, HDD_NUM * sizeof(hard_disk_t)); + + for (i = 0; i < HDD_NUM; i++) { + if (temp_hdd[i].bus != HDD_BUS_DISABLED) { + memcpy(&(ihdd[j]), &(temp_hdd[i]), sizeof(hard_disk_t)); + j++; + } + } + + memcpy(temp_hdd, ihdd, HDD_NUM * sizeof(hard_disk_t)); +} +*/ + +static QString busChannelName(const QModelIndex& idx) { + return Harddrives::BusChannelName(idx.data(DataBus).toUInt(), idx.data(DataBusChannel).toUInt()); +} + +static void addRow(QAbstractItemModel* model, hard_disk_t* hd) { + const QString userPath = usr_path; + + int row = model->rowCount(); + model->insertRow(row); + + QString busName = Harddrives::BusChannelName(hd->bus, hd->channel); + model->setData(model->index(row, ColumnBus), busName); + model->setData(model->index(row, ColumnBus), QIcon(":/settings/win/icons/hard_disk.ico"), Qt::DecorationRole); + model->setData(model->index(row, ColumnBus), hd->bus, DataBus); + model->setData(model->index(row, ColumnBus), hd->channel, DataBusChannel); + QString fileName = hd->fn; + if (fileName.startsWith(userPath, Qt::CaseInsensitive)) { + model->setData(model->index(row, ColumnFilename), fileName.mid(userPath.size())); + } else { + model->setData(model->index(row, ColumnFilename), fileName); + } + model->setData(model->index(row, ColumnFilename), fileName, Qt::UserRole); + + model->setData(model->index(row, ColumnCylinders), hd->tracks); + model->setData(model->index(row, ColumnHeads), hd->hpc); + model->setData(model->index(row, ColumnSectors), hd->spt); + model->setData(model->index(row, ColumnSize), (hd->tracks * hd->hpc * hd->spt) >> 11); +} + +SettingsHarddisks::SettingsHarddisks(QWidget *parent) : + QWidget(parent), + ui(new Ui::SettingsHarddisks) +{ + ui->setupUi(this); + + QAbstractItemModel* model = new QStandardItemModel(0, 6, this); + model->setHeaderData(ColumnBus, Qt::Horizontal, "Bus"); + model->setHeaderData(ColumnFilename, Qt::Horizontal, "File"); + model->setHeaderData(ColumnCylinders, Qt::Horizontal, "C"); + model->setHeaderData(ColumnHeads, Qt::Horizontal, "H"); + model->setHeaderData(ColumnSectors, Qt::Horizontal, "S"); + model->setHeaderData(ColumnSize, Qt::Horizontal, "MiB"); + ui->tableView->setModel(model); + + for (int i = 0; i < HDD_NUM; i++) { + if (hdd[i].bus > 0) { + addRow(model, &hdd[i]); + } + } + ui->tableView->resizeColumnsToContents(); + ui->tableView->horizontalHeader()->setSectionResizeMode(ColumnFilename, QHeaderView::Stretch); + + auto* tableSelectionModel = ui->tableView->selectionModel(); + connect(tableSelectionModel, &QItemSelectionModel::currentRowChanged, this, &SettingsHarddisks::onTableRowChanged); + onTableRowChanged(QModelIndex()); + + Harddrives::populateBuses(ui->comboBoxBus->model()); + on_comboBoxBus_currentIndexChanged(0); +} + +SettingsHarddisks::~SettingsHarddisks() +{ + delete ui; +} + +void SettingsHarddisks::save() { + memset(hdd, 0, sizeof(hdd)); + + auto* model = ui->tableView->model(); + int rows = model->rowCount(); + for (int i = 0; i < rows; ++i) { + auto idx = model->index(i, ColumnBus); + hdd[i].bus = idx.data(DataBus).toUInt(); + hdd[i].channel = idx.data(DataBusChannel).toUInt(); + hdd[i].tracks = idx.siblingAtColumn(ColumnCylinders).data().toUInt(); + hdd[i].hpc = idx.siblingAtColumn(ColumnHeads).data().toUInt(); + hdd[i].spt = idx.siblingAtColumn(ColumnSectors).data().toUInt(); + + QByteArray fileName = idx.siblingAtColumn(ColumnFilename).data(Qt::UserRole).toString().toUtf8(); + strncpy(hdd[i].fn, fileName.data(), sizeof(hdd[i].fn)); + hdd[i].priv = nullptr; + } +} + +void SettingsHarddisks::on_comboBoxBus_currentIndexChanged(int index) { + if (index < 0) { + return; + } + + auto idx = ui->tableView->selectionModel()->currentIndex(); + if (idx.isValid()) { + auto* model = ui->tableView->model(); + auto col = idx.siblingAtColumn(ColumnBus); + model->setData(col, ui->comboBoxBus->currentData(Qt::UserRole), DataBus); + model->setData(col, busChannelName(col), Qt::DisplayRole); + } + + Harddrives::populateBusChannels(ui->comboBoxChannel->model(), ui->comboBoxBus->currentData().toInt()); +} + +void SettingsHarddisks::on_comboBoxChannel_currentIndexChanged(int index) { + if (index < 0) { + return; + } + + auto idx = ui->tableView->selectionModel()->currentIndex(); + if (idx.isValid()) { + auto* model = ui->tableView->model(); + auto col = idx.siblingAtColumn(ColumnBus); + model->setData(col, ui->comboBoxChannel->currentData(Qt::UserRole), DataBusChannel); + model->setData(col, busChannelName(col), Qt::DisplayRole); + } +} + +void SettingsHarddisks::onTableRowChanged(const QModelIndex ¤t) { + bool hidden = !current.isValid(); + ui->labelBus->setHidden(hidden); + ui->labelChannel->setHidden(hidden); + ui->comboBoxBus->setHidden(hidden); + ui->comboBoxChannel->setHidden(hidden); + + uint32_t bus = current.siblingAtColumn(ColumnBus).data(DataBus).toUInt(); + uint32_t busChannel = current.siblingAtColumn(ColumnBus).data(DataBusChannel).toUInt(); + + auto* model = ui->comboBoxBus->model(); + auto match = model->match(model->index(0, 0), Qt::UserRole, bus); + if (! match.isEmpty()) { + ui->comboBoxBus->setCurrentIndex(match.first().row()); + } + model = ui->comboBoxChannel->model(); + match = model->match(model->index(0, 0), Qt::UserRole, busChannel); + if (! match.isEmpty()) { + ui->comboBoxChannel->setCurrentIndex(match.first().row()); + } +} + +static void addDriveFromDialog(Ui::SettingsHarddisks* ui, const HarddiskDialog& dlg) { + QByteArray fn = dlg.fileName().toUtf8(); + + hard_disk_t hd; + hd.bus = dlg.bus(); + hd.channel = dlg.channel(); + hd.tracks = dlg.cylinders(); + hd.hpc = dlg.heads(); + hd.spt = dlg.sectors(); + strncpy(hd.fn, fn.data(), sizeof(hd.fn)); + + addRow(ui->tableView->model(), &hd); + ui->tableView->resizeColumnsToContents(); + ui->tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); +} + +void SettingsHarddisks::on_pushButtonNew_clicked() { + HarddiskDialog dialog(false, this); + switch (dialog.exec()) { + case QDialog::Accepted: + addDriveFromDialog(ui, dialog); + break; + } +} + + +void SettingsHarddisks::on_pushButtonExisting_clicked() { + HarddiskDialog dialog(true, this); + switch (dialog.exec()) { + case QDialog::Accepted: + addDriveFromDialog(ui, dialog); + break; + } +} + +void SettingsHarddisks::on_pushButtonRemove_clicked() { + auto idx = ui->tableView->selectionModel()->currentIndex(); + if (! idx.isValid()) { + return; + } + + auto* model = ui->tableView->model(); + model->removeRow(idx.row()); +} + diff --git a/src/qt/qt_settingsharddisks.hpp b/src/qt/qt_settingsharddisks.hpp new file mode 100644 index 000000000..0289d2ccb --- /dev/null +++ b/src/qt/qt_settingsharddisks.hpp @@ -0,0 +1,35 @@ +#ifndef QT_SETTINGSHARDDISKS_HPP +#define QT_SETTINGSHARDDISKS_HPP + +#include + +namespace Ui { +class SettingsHarddisks; +} + +class SettingsHarddisks : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsHarddisks(QWidget *parent = nullptr); + ~SettingsHarddisks(); + + void save(); + +private slots: + void on_comboBoxChannel_currentIndexChanged(int index); + +private slots: + void on_pushButtonRemove_clicked(); + void on_pushButtonExisting_clicked(); + void on_pushButtonNew_clicked(); + void on_comboBoxBus_currentIndexChanged(int index); + + void onTableRowChanged(const QModelIndex& current); + +private: + Ui::SettingsHarddisks *ui; +}; + +#endif // QT_SETTINGSHARDDISKS_HPP diff --git a/src/qt/qt_settingsharddisks.ui b/src/qt/qt_settingsharddisks.ui new file mode 100644 index 000000000..4bded8aab --- /dev/null +++ b/src/qt/qt_settingsharddisks.ui @@ -0,0 +1,95 @@ + + + SettingsHarddisks + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + + + + + + + Bus + + + + + + + + + + ID + + + + + + + + + + + + + + New + + + + + + + Existing + + + + + + + Remove + + + + + + + + + + diff --git a/src/qt/qt_settingsinput.cpp b/src/qt/qt_settingsinput.cpp new file mode 100644 index 000000000..8a8d8e1bf --- /dev/null +++ b/src/qt/qt_settingsinput.cpp @@ -0,0 +1,114 @@ +#include "qt_settingsinput.hpp" +#include "ui_qt_settingsinput.h" + +#include + +extern "C" { +#include <86box/86box.h> +#include <86box/device.h> +#include <86box/machine.h> +#include <86box/mouse.h> +#include <86box/gameport.h> +} + +#include "qt_deviceconfig.hpp" + +SettingsInput::SettingsInput(QWidget *parent) : + QWidget(parent), + ui(new Ui::SettingsInput) +{ + ui->setupUi(this); + + onCurrentMachineChanged(machine); +} + +SettingsInput::~SettingsInput() +{ + delete ui; +} + +void SettingsInput::save() { + mouse_type = ui->comboBoxMouse->currentData().toInt(); + joystick_type = ui->comboBoxJoystick->currentData().toInt(); +} + +void SettingsInput::onCurrentMachineChanged(int machineId) { + // win_settings_video_proc, WM_INITDIALOG + this->machineId = machineId; + + const auto* machine = &machines[machineId]; + auto* mouseModel = ui->comboBoxMouse->model(); + auto removeRows = mouseModel->rowCount(); + + int selectedRow = 0; + for (int i = 0; i < mouse_get_ndev(); ++i) { + const auto* dev = mouse_get_device(i); + if ((i == MOUSE_TYPE_INTERNAL) && !(machines[machineId].flags & MACHINE_MOUSE)) { + continue; + } + + if (device_is_valid(dev, machine->flags) == 0) { + continue; + } + + QString name = DeviceConfig::DeviceName(dev, mouse_get_internal_name(i), 0); + int row = mouseModel->rowCount(); + mouseModel->insertRow(row); + auto idx = mouseModel->index(row, 0); + + mouseModel->setData(idx, name, Qt::DisplayRole); + mouseModel->setData(idx, i, Qt::UserRole); + + if (i == mouse_type) { + selectedRow = row - removeRows; + } + } + mouseModel->removeRows(0, removeRows); + ui->comboBoxMouse->setCurrentIndex(selectedRow); + + + int i = 0; + char* joyName = joystick_get_name(i); + auto* joystickModel = ui->comboBoxJoystick->model(); + removeRows = joystickModel->rowCount(); + selectedRow = 0; + while (joyName) { + int row = joystickModel->rowCount(); + joystickModel->insertRow(row); + auto idx = joystickModel->index(row, 0); + + joystickModel->setData(idx, joyName, Qt::DisplayRole); + joystickModel->setData(idx, i, Qt::UserRole); + + if (i == joystick_type) { + selectedRow = row - removeRows; + } + + ++i; + joyName = joystick_get_name(i); + } + joystickModel->removeRows(0, removeRows); + ui->comboBoxJoystick->setCurrentIndex(selectedRow); +} + +void SettingsInput::on_comboBoxMouse_currentIndexChanged(int index) { + int mouseId = ui->comboBoxMouse->currentData().toInt(); + ui->pushButtonConfigureMouse->setEnabled(mouse_has_config(mouseId) > 0); +} + + +void SettingsInput::on_comboBoxJoystick_currentIndexChanged(int index) { + int joystickId = ui->comboBoxJoystick->currentData().toInt(); + for (int i = 0; i < 4; ++i) { + auto* btn = findChild(QString("pushButtonJoystick%1").arg(i+1)); + if (btn == nullptr) { + continue; + } + btn->setEnabled(joystick_get_max_joysticks(joystickId) > i); + } +} + +void SettingsInput::on_pushButtonConfigureMouse_clicked() { + int mouseId = ui->comboBoxMouse->currentData().toInt(); + DeviceConfig::ConfigureDevice(mouse_get_device(mouseId)); +} diff --git a/src/qt/qt_settingsinput.hpp b/src/qt/qt_settingsinput.hpp new file mode 100644 index 000000000..f69453dea --- /dev/null +++ b/src/qt/qt_settingsinput.hpp @@ -0,0 +1,33 @@ +#ifndef QT_SETTINGSINPUT_HPP +#define QT_SETTINGSINPUT_HPP + +#include + +namespace Ui { +class SettingsInput; +} + +class SettingsInput : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsInput(QWidget *parent = nullptr); + ~SettingsInput(); + + void save(); + +public slots: + void onCurrentMachineChanged(int machineId); + +private slots: + void on_pushButtonConfigureMouse_clicked(); + void on_comboBoxJoystick_currentIndexChanged(int index); + void on_comboBoxMouse_currentIndexChanged(int index); + +private: + Ui::SettingsInput *ui; + int machineId = 0; +}; + +#endif // QT_SETTINGSINPUT_HPP diff --git a/src/qt/qt_settingsinput.ui b/src/qt/qt_settingsinput.ui new file mode 100644 index 000000000..28bf16eb8 --- /dev/null +++ b/src/qt/qt_settingsinput.ui @@ -0,0 +1,134 @@ + + + SettingsInput + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Mouse + + + + + + + + + + Configure + + + + + + + Joystick + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Joystick 1... + + + + + + + Joystick 2... + + + + + + + Joystick 3... + + + + + + + Joystick 4... + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/qt/qt_settingsmachine.cpp b/src/qt/qt_settingsmachine.cpp new file mode 100644 index 000000000..d42216107 --- /dev/null +++ b/src/qt/qt_settingsmachine.cpp @@ -0,0 +1,348 @@ +#include "qt_settingsmachine.hpp" +#include "ui_qt_settingsmachine.h" + +#include +#include +#include +#include +#include + +extern "C" { +#include "../cpu/cpu.h" + +#include <86box/86box.h> +#include <86box/config.h> +#include <86box/device.h> +#include <86box/machine.h> +} + +// from nvr.h, which we can't import into CPP code +#define TIME_SYNC_DISABLED 0 +#define TIME_SYNC_ENABLED 1 +#define TIME_SYNC_UTC 2 + +#include "qt_deviceconfig.hpp" +#include "qt_models_common.hpp" + +/* +class MachineModel : public QAbstractListModel { +public: + MachineModel(QObject* parent) : QAbstractListModel(parent) {} + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; +private: + struct Entry { + QString name; + int id; + }; + QList entries; +}; + +bool MachineModel::insertRows(int row, int count, const QModelIndex &parent) { + beginInsertRows(parent, row, row + count - 1); + for (int i = 0; i < count; ++i) { + entries.insert(row, Entry{}); + } + endInsertRows(); + + return true; +} + +bool MachineModel::removeRows(int row, int count, const QModelIndex &parent) { + beginRemoveRows(parent, row, row + count - 1); + for (int i = 0; i < count; ++i) { + entries.removeAt(row); + } + endRemoveRows(); + + return true; +} + +QVariant MachineModel::data(const QModelIndex &index, int role) const { + Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::ParentIsInvalid)); + + switch (role) { + case Qt::DisplayRole: + return entries.at(index.row()).name; + case Qt::UserRole: + return entries.at(index.row()).id; + default: + return {}; + } +} + +int MachineModel::rowCount(const QModelIndex &parent) const { + (void) parent; + return entries.size(); +} + +bool MachineModel::setData(const QModelIndex &index, const QVariant &value, int role) { + Entry* entry = nullptr; + if (index.row() < entries.size()) { + entry = &entries[index.row()]; + } else if (index.row() == entries.size()) { + entries.append(Entry{}); + entry = &entries.back(); + } + + bool ret = true; + if (entry != nullptr) { + switch (role) { + case Qt::DisplayRole: + entry->name = value.toString(); + case Qt::UserRole: + entry->id = value.toInt(); + default: + ret = false; + break; + } + } + return ret; +} +*/ + +SettingsMachine::SettingsMachine(QWidget *parent) : + QWidget(parent), + ui(new Ui::SettingsMachine) +{ + ui->setupUi(this); + + switch (time_sync) { + case TIME_SYNC_ENABLED: + ui->radioButtonLocalTime->setChecked(true); + break; + case TIME_SYNC_ENABLED | TIME_SYNC_UTC: + ui->radioButtonUTC->setChecked(true); + break; + case TIME_SYNC_DISABLED: + default: + ui->radioButtonDisabled->setChecked(true); + break; + } + + auto* waitStatesModel = ui->comboBoxWaitStates->model(); + waitStatesModel->insertRows(0, 9); + auto idx = waitStatesModel->index(0, 0); + waitStatesModel->setData(idx, "Default", Qt::DisplayRole); + waitStatesModel->setData(idx, 0, Qt::UserRole); + for (int i = 0; i < 8; ++i) { + idx = waitStatesModel->index(i+1, 0); + waitStatesModel->setData(idx, QString("%1 Wait State(s)").arg(i), Qt::DisplayRole); + waitStatesModel->setData(idx, i+1, Qt::UserRole); + } + + int selectedMachineType = 0; + auto* machineTypesModel = ui->comboBoxMachineType->model(); + for (int i = 0; i < MACHINE_TYPE_MAX; ++i) { + Models::AddEntry(machineTypesModel, machine_types[i].name, machine_types[i].id); + if (machine_types[i].id == machines[machine].type) { + selectedMachineType = i; + } + } + ui->comboBoxMachineType->setCurrentIndex(selectedMachineType); +} + +SettingsMachine::~SettingsMachine() { + delete ui; +} + +void SettingsMachine::save() { + machine = ui->comboBoxMachine->currentData().toInt(); + cpu_f = const_cast(&cpu_families[ui->comboBoxCPU->currentData().toInt()]); + cpu = ui->comboBoxSpeed->currentData().toInt(); + fpu_type = ui->comboBoxFPU->currentData().toInt(); + cpu_use_dynarec = ui->checkBoxDynamicRecompiler->isChecked() ? 1 : 0; + if (machines[machine].ram_granularity < 1024) { + mem_size = ui->spinBoxRAM->value(); + } else { + mem_size = ui->spinBoxRAM->value() * 1024; + } + if (ui->comboBoxWaitStates->isEnabled()) { + cpu_waitstates = ui->comboBoxWaitStates->currentData().toInt(); + } else { + cpu_waitstates = 0; + } + + time_sync = 0; + if (ui->radioButtonLocalTime->isChecked()) { + time_sync = TIME_SYNC_ENABLED; + } + if (ui->radioButtonUTC->isChecked()) { + time_sync = TIME_SYNC_ENABLED | TIME_SYNC_UTC; + } +} + +void SettingsMachine::on_comboBoxMachineType_currentIndexChanged(int index) { + auto* model = ui->comboBoxMachine->model(); + int removeRows = model->rowCount(); + + int selectedMachineRow = 0; + for (int i = 0; i < machine_count(); ++i) { + if ((machines[i].type == index) && machine_available(i)) { + int row = Models::AddEntry(model, machines[i].name, i); + if (i == machine) { + selectedMachineRow = row - removeRows; + } + } + } + model->removeRows(0, removeRows); + + ui->comboBoxMachine->setCurrentIndex(-1); + ui->comboBoxMachine->setCurrentIndex(selectedMachineRow); +} + + +void SettingsMachine::on_comboBoxMachine_currentIndexChanged(int index) { + // win_settings_machine_recalc_machine + if (index < 0) { + return; + } + + int machineId = ui->comboBoxMachine->currentData().toInt(); + const auto* device = machine_getdevice(machineId); + ui->pushButtonConfigure->setEnabled((device != nullptr) && (device->config != nullptr)); + + auto* modelCpu = ui->comboBoxCPU->model(); + int removeRows = modelCpu->rowCount(); + + int i = 0; + int eligibleRows = 0; + int selectedCpuFamilyRow = 0; + while (cpu_families[i].package != 0) { + if (cpu_family_is_eligible(&cpu_families[i], machineId)) { + Models::AddEntry(modelCpu, QString("%1 %2").arg(cpu_families[i].manufacturer, cpu_families[i].name), i); + if (&cpu_families[i] == cpu_f) { + selectedCpuFamilyRow = eligibleRows; + } + ++eligibleRows; + } + ++i; + } + modelCpu->removeRows(0, removeRows); + ui->comboBoxCPU->setEnabled(eligibleRows > 1); + ui->comboBoxCPU->setCurrentIndex(-1); + ui->comboBoxCPU->setCurrentIndex(selectedCpuFamilyRow); + + auto* machine = &machines[machineId]; + if ((machine->ram_granularity < 1024)) { + ui->spinBoxRAM->setMinimum(machine->min_ram); + ui->spinBoxRAM->setMaximum(machine->max_ram); + ui->spinBoxRAM->setSingleStep(machine->ram_granularity); + ui->spinBoxRAM->setSuffix(" KiB"); + ui->spinBoxRAM->setValue(mem_size); + } else { + uint maxram; +#if (!(defined __amd64__ || defined _M_X64 || defined __aarch64__ || defined _M_ARM64)) + maxram = std::min(machine->max_ram, 2097152U); +#else + maxram = std::min(machine->max_ram, 3145728U); +#endif + ui->spinBoxRAM->setMinimum(machine->min_ram / 1024); + ui->spinBoxRAM->setMaximum(maxram / 1024); + ui->spinBoxRAM->setSingleStep(machine->ram_granularity / 1024); + ui->spinBoxRAM->setSuffix(" MiB"); + ui->spinBoxRAM->setValue(mem_size / 1024); + } + ui->spinBoxRAM->setEnabled(machine->min_ram != machine->max_ram); + ui->spinBoxRAM->setEnabled(machine->min_ram != machine->max_ram); + + emit currentMachineChanged(machineId); +} + + +void SettingsMachine::on_comboBoxCPU_currentIndexChanged(int index) { + if (index < 0) { + return; + } + + int machineId = ui->comboBoxMachine->currentData().toInt(); + int cpuFamilyId = ui->comboBoxCPU->currentData().toInt(); + const auto* cpuFamily = &cpu_families[cpuFamilyId]; + + auto* modelSpeed = ui->comboBoxSpeed->model(); + int removeRows = modelSpeed->rowCount(); + + // win_settings_machine_recalc_cpu_m + int i = 0; + int eligibleRows = 0; + int selectedSpeedRow = 0; + while (cpuFamily->cpus[i].cpu_type != 0) { + if (cpu_is_eligible(cpuFamily, i, machineId)) { + Models::AddEntry(modelSpeed, QString("%1").arg(cpuFamily->cpus[i].name), i); + if (cpu == i) { + selectedSpeedRow = eligibleRows; + } + ++eligibleRows; + } + ++i; + } + modelSpeed->removeRows(0, removeRows); + ui->comboBoxSpeed->setEnabled(eligibleRows > 1); + ui->comboBoxSpeed->setCurrentIndex(-1); + ui->comboBoxSpeed->setCurrentIndex(selectedSpeedRow); +} + + +void SettingsMachine::on_comboBoxSpeed_currentIndexChanged(int index) { + if (index < 0) { + return; + } + + // win_settings_machine_recalc_cpu + int cpuFamilyId = ui->comboBoxCPU->currentData().toInt(); + const auto* cpuFamily = &cpu_families[cpuFamilyId]; + int cpuId = ui->comboBoxSpeed->currentData().toInt(); + uint cpuType = cpuFamily->cpus[cpuId].cpu_type; + + if ((cpuType >= CPU_286) && (cpuType <= CPU_386DX)) { + ui->comboBoxWaitStates->setEnabled(true); + ui->comboBoxWaitStates->setCurrentIndex(cpu_waitstates); + } else { + ui->comboBoxWaitStates->setCurrentIndex(0); + ui->comboBoxWaitStates->setEnabled(false); + } + +#ifdef USE_DYNAREC + uint8_t flags = cpuFamily->cpus[cpuId].cpu_flags; + if (! (flags & CPU_SUPPORTS_DYNAREC)) { + ui->checkBoxDynamicRecompiler->setChecked(false); + ui->checkBoxDynamicRecompiler->setEnabled(false); + } else if (flags & CPU_REQUIRES_DYNAREC) { + ui->checkBoxDynamicRecompiler->setChecked(true); + ui->checkBoxDynamicRecompiler->setEnabled(false); + } else { + ui->checkBoxDynamicRecompiler->setChecked(cpu_use_dynarec); + ui->checkBoxDynamicRecompiler->setEnabled(true); + } +#endif + + // win_settings_machine_recalc_fpu + auto* modelFpu = ui->comboBoxFPU->model(); + int removeRows = modelFpu->rowCount(); + + int i = 0; + int selectedFpuRow = 0; + for (const char* fpuName = fpu_get_name_from_index(cpuFamily, cpuId, i); fpuName != nullptr; fpuName = fpu_get_name_from_index(cpuFamily, cpuId, ++i)) { + auto fpuType = fpu_get_type_from_index(cpuFamily, cpuId, i); + Models::AddEntry(modelFpu, QString("%1").arg(fpuName), fpuType); + if (fpu_type == fpuType) { + selectedFpuRow = i; + } + } + + modelFpu->removeRows(0, removeRows); + ui->comboBoxFPU->setEnabled(modelFpu->rowCount() > 1); + ui->comboBoxFPU->setCurrentIndex(-1); + ui->comboBoxFPU->setCurrentIndex(selectedFpuRow); +} + +void SettingsMachine::on_pushButtonConfigure_clicked() { + // deviceconfig_inst_open + int machineId = ui->comboBoxMachine->currentData().toInt(); + const auto* device = machine_getdevice(machineId); + DeviceConfig::ConfigureDevice(device); +} diff --git a/src/qt/qt_settingsmachine.hpp b/src/qt/qt_settingsmachine.hpp new file mode 100644 index 000000000..d0d4aabd2 --- /dev/null +++ b/src/qt/qt_settingsmachine.hpp @@ -0,0 +1,41 @@ +#ifndef QT_SETTINGSMACHINE_HPP +#define QT_SETTINGSMACHINE_HPP + +#include + +namespace Ui { +class SettingsMachine; +} + +class SettingsMachine : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsMachine(QWidget *parent = nullptr); + ~SettingsMachine(); + + void save(); + +signals: + void currentMachineChanged(int machineId); +private slots: + void on_pushButtonConfigure_clicked(); + +private slots: + void on_comboBoxSpeed_currentIndexChanged(int index); + +private slots: + void on_comboBoxCPU_currentIndexChanged(int index); + +private slots: + void on_comboBoxMachine_currentIndexChanged(int index); + +private slots: + void on_comboBoxMachineType_currentIndexChanged(int index); + +private: + Ui::SettingsMachine *ui; +}; + +#endif // QT_SETTINGSMACHINE_HPP diff --git a/src/qt/qt_settingsmachine.ui b/src/qt/qt_settingsmachine.ui new file mode 100644 index 000000000..5e902a751 --- /dev/null +++ b/src/qt/qt_settingsmachine.ui @@ -0,0 +1,229 @@ + + + SettingsMachine + + + + 0 + 0 + 458 + 390 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Machine Type + + + + + + + + + + Machine + + + + + + + CPU + + + + + + + FPU + + + + + + + Wait States + + + + + + + Memory + + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Speed + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + 0 + + + + Configure + + + + + + + + + + + + + + 2 + 2 + + + + Dynamic Recompiler + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Time Synchronization + + + + + + Disabled + + + + + + + Enabled (Local Time) + + + + + + + Enabled (UTC) + + + + + + + + + + + diff --git a/src/qt/qt_settingsnetwork.cpp b/src/qt/qt_settingsnetwork.cpp new file mode 100644 index 000000000..18d06a520 --- /dev/null +++ b/src/qt/qt_settingsnetwork.cpp @@ -0,0 +1,113 @@ +#include "qt_settingsnetwork.hpp" +#include "ui_qt_settingsnetwork.h" + +extern "C" { +#include <86box/86box.h> +#include <86box/device.h> +#include <86box/machine.h> +#include <86box/network.h> +} + +#include "qt_models_common.hpp" +#include "qt_deviceconfig.hpp" + +static void enableElements(Ui::SettingsNetwork *ui) { + int netType = ui->comboBoxNetwork->currentData().toInt(); + ui->comboBoxPcap->setEnabled(netType == NET_TYPE_PCAP); + + bool adaptersEnabled = netType == NET_TYPE_SLIRP || + (netType == NET_TYPE_PCAP && ui->comboBoxPcap->currentData().toInt() > 0); + ui->comboBoxAdapter->setEnabled(adaptersEnabled); + ui->pushButtonConfigure->setEnabled(adaptersEnabled && ui->comboBoxAdapter->currentIndex() > 0); +} + +SettingsNetwork::SettingsNetwork(QWidget *parent) : + QWidget(parent), + ui(new Ui::SettingsNetwork) +{ + ui->setupUi(this); + + auto* model = ui->comboBoxNetwork->model(); + Models::AddEntry(model, "None", NET_TYPE_NONE); + Models::AddEntry(model, "PCap", NET_TYPE_PCAP); + Models::AddEntry(model, "SLiRP", NET_TYPE_SLIRP); + ui->comboBoxNetwork->setCurrentIndex(network_type); + + int selectedRow = 0; + model = ui->comboBoxPcap->model(); + QString currentPcapDevice = network_host; + for (int c = 0; c < network_ndev; c++) { + + Models::AddEntry(model, network_devs[c].description, c); + if (QString(network_devs[c].device) == currentPcapDevice) { + selectedRow = c; + } + } + ui->comboBoxPcap->setCurrentIndex(-1); + ui->comboBoxPcap->setCurrentIndex(selectedRow); + + onCurrentMachineChanged(machine); + enableElements(ui); +} + +SettingsNetwork::~SettingsNetwork() +{ + delete ui; +} + +void SettingsNetwork::save() { + network_type = ui->comboBoxNetwork->currentData().toInt(); + memset(network_host, '\0', sizeof(network_host)); + strcpy(network_host, network_devs[ui->comboBoxPcap->currentData().toInt()].device); + network_card = ui->comboBoxAdapter->currentData().toInt(); +} + +void SettingsNetwork::onCurrentMachineChanged(int machineId) { + this->machineId = machineId; + auto* machine = &machines[machineId]; + + auto* model = ui->comboBoxAdapter->model(); + auto removeRows = model->rowCount(); + int c = 0; + int selectedRow = 0; + while (true) { + auto name = DeviceConfig::DeviceName(network_card_getdevice(c), network_card_get_internal_name(c), 1); + if (name.isEmpty()) { + break; + } + + if (network_card_available(c) && device_is_valid(network_card_getdevice(c), machine->flags)) { + int row = Models::AddEntry(model, name, c); + if (c == network_card) { + selectedRow = row - removeRows; + } + } + + c++; + } + model->removeRows(0, removeRows); + ui->comboBoxAdapter->setEnabled(model->rowCount() > 0); + ui->comboBoxAdapter->setCurrentIndex(-1); + ui->comboBoxAdapter->setCurrentIndex(selectedRow); +} + +void SettingsNetwork::on_comboBoxNetwork_currentIndexChanged(int index) { + if (index < 0) { + return; + } + + enableElements(ui); +} + +void SettingsNetwork::on_comboBoxAdapter_currentIndexChanged(int index) { + if (index < 0) { + return; + } + + enableElements(ui); +} + +void SettingsNetwork::on_pushButtonConfigure_clicked() { + DeviceConfig::ConfigureDevice(network_card_getdevice(ui->comboBoxAdapter->currentData().toInt())); +} + diff --git a/src/qt/qt_settingsnetwork.hpp b/src/qt/qt_settingsnetwork.hpp new file mode 100644 index 000000000..f17eb98f1 --- /dev/null +++ b/src/qt/qt_settingsnetwork.hpp @@ -0,0 +1,33 @@ +#ifndef QT_SETTINGSNETWORK_HPP +#define QT_SETTINGSNETWORK_HPP + +#include + +namespace Ui { +class SettingsNetwork; +} + +class SettingsNetwork : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsNetwork(QWidget *parent = nullptr); + ~SettingsNetwork(); + + void save(); + +public slots: + void onCurrentMachineChanged(int machineId); + +private slots: + void on_pushButtonConfigure_clicked(); + void on_comboBoxAdapter_currentIndexChanged(int index); + void on_comboBoxNetwork_currentIndexChanged(int index); + +private: + Ui::SettingsNetwork *ui; + int machineId = 0; +}; + +#endif // QT_SETTINGSNETWORK_HPP diff --git a/src/qt/qt_settingsnetwork.ui b/src/qt/qt_settingsnetwork.ui new file mode 100644 index 000000000..045ddbbae --- /dev/null +++ b/src/qt/qt_settingsnetwork.ui @@ -0,0 +1,101 @@ + + + SettingsNetwork + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + PCap Device + + + + + + + Network Adapter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Network Type + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Configure + + + + + + + + + + + diff --git a/src/qt/qt_settingsotherperipherals.cpp b/src/qt/qt_settingsotherperipherals.cpp new file mode 100644 index 000000000..e52c3ead1 --- /dev/null +++ b/src/qt/qt_settingsotherperipherals.cpp @@ -0,0 +1,133 @@ +#include "qt_settingsotherperipherals.hpp" +#include "ui_qt_settingsotherperipherals.h" + +extern "C" { +#include <86box/86box.h> +#include <86box/device.h> +#include <86box/isamem.h> +#include <86box/isartc.h> +} + +#include "qt_deviceconfig.hpp" +#include "qt_models_common.hpp" + +SettingsOtherPeripherals::SettingsOtherPeripherals(QWidget *parent) : + QWidget(parent), + ui(new Ui::SettingsOtherPeripherals) +{ + ui->setupUi(this); + + ui->checkBoxISABugger->setChecked(bugger_enabled > 0 ? true : false); + ui->checkBoxPOSTCard->setChecked(postcard_enabled > 0 ? true : false); + + auto* model = ui->comboBoxRTC->model(); + int d = 0; + int selectedRow = 0; + while (true) { + QString name = DeviceConfig::DeviceName(isartc_get_device(d), isartc_get_internal_name(d), 0); + if (name.isEmpty()) { + break; + } + + int row = Models::AddEntry(model, name, d); + if (d == isartc_type) { + selectedRow = row; + } + ++d; + } + ui->comboBoxRTC->setCurrentIndex(selectedRow); + + for (int c = 0; c < ISAMEM_MAX; c++) { + auto* cbox = findChild(QString("comboBoxCard%1").arg(c + 1)); + model = cbox->model(); + d = 0; + selectedRow = 0; + while (true) { + QString name = DeviceConfig::DeviceName(isamem_get_device(d), isamem_get_internal_name(d), 0); + if (name.isEmpty()) { + break; + } + + int row = Models::AddEntry(model, name, d); + if (d == isartc_type) { + selectedRow = row; + } + ++d; + } + cbox->setCurrentIndex(-1); + cbox->setCurrentIndex(selectedRow); + } +} + +SettingsOtherPeripherals::~SettingsOtherPeripherals() +{ + delete ui; +} + +void SettingsOtherPeripherals::save() { + /* Other peripherals category */ + bugger_enabled = ui->checkBoxISABugger->isChecked() ? 1 : 0; + postcard_enabled = ui->checkBoxPOSTCard->isChecked() ? 1 : 0; + isartc_type = ui->comboBoxRTC->currentData().toInt(); + + /* ISA memory boards. */ + for (int i = 0; i < ISAMEM_MAX; i++) { + auto* cbox = findChild(QString("comboBoxCard%1").arg(i + 1)); + isamem_type[i] = cbox->currentData().toInt(); + } +} + +void SettingsOtherPeripherals::on_comboBoxRTC_currentIndexChanged(int index) { + if (index < 0) { + return; + } + ui->pushButtonConfigureRTC->setEnabled(index != 0); +} + +void SettingsOtherPeripherals::on_pushButtonConfigureRTC_clicked() { + DeviceConfig::ConfigureDevice(isartc_get_device(ui->comboBoxRTC->currentData().toInt())); +} + +void SettingsOtherPeripherals::on_comboBoxCard1_currentIndexChanged(int index) { + if (index < 0) { + return; + } + ui->pushButtonConfigureCard1->setEnabled(index != 0); +} + +void SettingsOtherPeripherals::on_pushButtonConfigureCard1_clicked() { + DeviceConfig::ConfigureDevice(isamem_get_device(ui->comboBoxCard1->currentData().toInt())); +} + +void SettingsOtherPeripherals::on_comboBoxCard2_currentIndexChanged(int index) { + if (index < 0) { + return; + } + ui->pushButtonConfigureCard2->setEnabled(index != 0); +} + +void SettingsOtherPeripherals::on_pushButtonConfigureCard2_clicked() { + DeviceConfig::ConfigureDevice(isamem_get_device(ui->comboBoxCard2->currentData().toInt())); +} + +void SettingsOtherPeripherals::on_comboBoxCard3_currentIndexChanged(int index) { + if (index < 0) { + return; + } + ui->pushButtonConfigureCard3->setEnabled(index != 0); +} + +void SettingsOtherPeripherals::on_pushButtonConfigureCard3_clicked() { + DeviceConfig::ConfigureDevice(isamem_get_device(ui->comboBoxCard3->currentData().toInt())); +} + +void SettingsOtherPeripherals::on_comboBoxCard4_currentIndexChanged(int index) { + if (index < 0) { + return; + } + ui->pushButtonConfigureCard4->setEnabled(index != 0); +} + +void SettingsOtherPeripherals::on_pushButtonConfigureCard4_clicked() { + DeviceConfig::ConfigureDevice(isamem_get_device(ui->comboBoxCard4->currentData().toInt())); +} diff --git a/src/qt/qt_settingsotherperipherals.hpp b/src/qt/qt_settingsotherperipherals.hpp new file mode 100644 index 000000000..605b629ba --- /dev/null +++ b/src/qt/qt_settingsotherperipherals.hpp @@ -0,0 +1,53 @@ +#ifndef QT_SETTINGSOTHERPERIPHERALS_HPP +#define QT_SETTINGSOTHERPERIPHERALS_HPP + +#include + +namespace Ui { +class SettingsOtherPeripherals; +} + +class SettingsOtherPeripherals : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsOtherPeripherals(QWidget *parent = nullptr); + ~SettingsOtherPeripherals(); + + void save(); +private slots: + void on_pushButtonConfigureCard4_clicked(); + +private slots: + void on_comboBoxCard4_currentIndexChanged(int index); + +private slots: + void on_pushButtonConfigureCard3_clicked(); + +private slots: + void on_comboBoxCard3_currentIndexChanged(int index); + +private slots: + void on_pushButtonConfigureCard2_clicked(); + +private slots: + void on_comboBoxCard2_currentIndexChanged(int index); + +private slots: + void on_pushButtonConfigureCard1_clicked(); + +private slots: + void on_comboBoxCard1_currentIndexChanged(int index); + +private slots: + void on_pushButtonConfigureRTC_clicked(); + +private slots: + void on_comboBoxRTC_currentIndexChanged(int index); + +private: + Ui::SettingsOtherPeripherals *ui; +}; + +#endif // QT_SETTINGSOTHERPERIPHERALS_HPP diff --git a/src/qt/qt_settingsotherperipherals.ui b/src/qt/qt_settingsotherperipherals.ui new file mode 100644 index 000000000..a47b79425 --- /dev/null +++ b/src/qt/qt_settingsotherperipherals.ui @@ -0,0 +1,162 @@ + + + SettingsOtherPeripherals + + + + 0 + 0 + 421 + 458 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + ISA RTC + + + + + + + + + + Configure + + + + + + + + + ISA Memory Expansion + + + + + + Configure + + + + + + + + + + Configure + + + + + + + Card 2 + + + + + + + Card 3 + + + + + + + Configure + + + + + + + + + + Card 1 + + + + + + + + + + + + + Configure + + + + + + + Card 4 + + + + + + + + + + + + ISABugger Device + + + + + + + POST card + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/qt/qt_settingsotherremovable.cpp b/src/qt/qt_settingsotherremovable.cpp new file mode 100644 index 000000000..2e989cda1 --- /dev/null +++ b/src/qt/qt_settingsotherremovable.cpp @@ -0,0 +1,267 @@ +#include "qt_settingsotherremovable.hpp" +#include "ui_qt_settingsotherremovable.h" + +extern "C" { +#include <86box/timer.h> +#include <86box/scsi_device.h> +#include <86box/mo.h> +#include <86box/zip.h> +} + +#include + +#include "qt_models_common.hpp" +#include "qt_harddrive_common.hpp" + +static QString moDriveTypeName(int i) { + return QString("%1 %2 %3").arg(mo_drive_types[i].vendor, mo_drive_types[i].model, mo_drive_types[i].revision); +} + +static void setMOBus(QAbstractItemModel* model, const QModelIndex& idx, uint8_t bus, uint8_t channel) { + QIcon icon; + switch (bus) { + case MO_BUS_DISABLED: + icon = QIcon(":/settings/win/icons/mo_disabled.ico"); + break; + case MO_BUS_ATAPI: + case MO_BUS_SCSI: + icon = QIcon(":/settings/win/icons/mo.ico"); + break; + } + + auto i = idx.siblingAtColumn(0); + model->setData(i, Harddrives::BusChannelName(bus, channel)); + model->setData(i, bus, Qt::UserRole); + model->setData(i, channel, Qt::UserRole + 1); + model->setData(i, icon, Qt::DecorationRole); +} + +static void setMOType(QAbstractItemModel* model, const QModelIndex& idx, uint32_t type) { + auto i = idx.siblingAtColumn(1); + if (idx.siblingAtColumn(0).data(Qt::UserRole).toUInt() == MO_BUS_DISABLED) { + model->setData(i, "None"); + } else { + model->setData(i, moDriveTypeName(type)); + } + model->setData(i, type, Qt::UserRole); +} + +static void setZIPBus(QAbstractItemModel* model, const QModelIndex& idx, uint8_t bus, uint8_t channel) { + QIcon icon; + switch (bus) { + case ZIP_BUS_DISABLED: + icon = QIcon(":/settings/win/icons/zip_disabled.ico"); + break; + case ZIP_BUS_ATAPI: + case ZIP_BUS_SCSI: + icon = QIcon(":/settings/win/icons/zip.ico"); + break; + } + + auto i = idx.siblingAtColumn(0); + model->setData(i, Harddrives::BusChannelName(bus, channel)); + model->setData(i, bus, Qt::UserRole); + model->setData(i, channel, Qt::UserRole + 1); + model->setData(i, icon, Qt::DecorationRole); +} + +static void setZIPType(QAbstractItemModel* model, const QModelIndex& idx, bool is250) { + auto i = idx.siblingAtColumn(1); + model->setData(i, is250 ? "ZIP 250" : "ZIP 100"); + model->setData(i, is250, Qt::UserRole); +} + +SettingsOtherRemovable::SettingsOtherRemovable(QWidget *parent) : + QWidget(parent), + ui(new Ui::SettingsOtherRemovable) +{ + ui->setupUi(this); + + Harddrives::populateRemovableBuses(ui->comboBoxMOBus->model()); + auto* model = ui->comboBoxMOType->model(); + for (uint32_t i = 0; i < KNOWN_MO_DRIVE_TYPES; i++) { + Models::AddEntry(model, moDriveTypeName(i), i); + } + + model = new QStandardItemModel(0, 2, this); + ui->tableViewMO->setModel(model); + model->setHeaderData(0, Qt::Horizontal, "Bus"); + model->setHeaderData(1, Qt::Horizontal, "Type"); + model->insertRows(0, MO_NUM); + for (int i = 0; i < MO_NUM; i++) { + auto idx = model->index(i, 0); + setMOBus(model, idx, mo_drives[i].bus_type, mo_drives[i].res); + setMOType(model, idx.siblingAtColumn(1), mo_drives[i].type); + } + ui->tableViewMO->resizeColumnsToContents(); + ui->tableViewMO->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + + connect(ui->tableViewMO->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &SettingsOtherRemovable::onMORowChanged); + ui->tableViewMO->setCurrentIndex(model->index(0, 0)); + + + + + Harddrives::populateRemovableBuses(ui->comboBoxZIPBus->model()); + + model = new QStandardItemModel(0, 2, this); + ui->tableViewZIP->setModel(model); + model->setHeaderData(0, Qt::Horizontal, "Bus"); + model->setHeaderData(1, Qt::Horizontal, "Type"); + model->insertRows(0, ZIP_NUM); + for (int i = 0; i < ZIP_NUM; i++) { + auto idx = model->index(i, 0); + setZIPBus(model, idx, zip_drives[i].bus_type, zip_drives[i].res); + setZIPType(model, idx, zip_drives[i].is_250 > 0); + } + ui->tableViewZIP->resizeColumnsToContents(); + ui->tableViewZIP->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + + connect(ui->tableViewZIP->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &SettingsOtherRemovable::onZIPRowChanged); + ui->tableViewZIP->setCurrentIndex(model->index(0, 0)); +} + +SettingsOtherRemovable::~SettingsOtherRemovable() +{ + delete ui; +} + +void SettingsOtherRemovable::save() { + auto* model = ui->tableViewMO->model(); + memset(mo_drives, 0, sizeof(mo_drives)); + for (int i = 0; i < MO_NUM; i++) { + mo_drives[i].f = NULL; + mo_drives[i].priv = NULL; + mo_drives[i].bus_type = model->index(i, 0).data(Qt::UserRole).toUInt(); + mo_drives[i].res = model->index(i, 0).data(Qt::UserRole + 1).toUInt(); + mo_drives[i].type = model->index(i, 1).data(Qt::UserRole).toUInt(); + } + + model = ui->tableViewZIP->model(); + memset(zip_drives, 0, sizeof(zip_drives)); + for (int i = 0; i < ZIP_NUM; i++) { + zip_drives[i].f = NULL; + zip_drives[i].priv = NULL; + zip_drives[i].bus_type = model->index(i, 0).data(Qt::UserRole).toUInt(); + zip_drives[i].res = model->index(i, 0).data(Qt::UserRole + 1).toUInt(); + zip_drives[i].is_250 = model->index(i, 1).data(Qt::UserRole).toBool() ? 1 : 0; + } +} + +void SettingsOtherRemovable::onMORowChanged(const QModelIndex ¤t) { + uint8_t bus = current.siblingAtColumn(0).data(Qt::UserRole).toUInt(); + uint8_t channel = current.siblingAtColumn(0).data(Qt::UserRole + 1).toUInt(); + uint8_t type = current.siblingAtColumn(1).data(Qt::UserRole).toUInt(); + + ui->comboBoxMOBus->setCurrentIndex(-1); + auto* model = ui->comboBoxMOBus->model(); + auto match = model->match(model->index(0, 0), Qt::UserRole, bus); + if (! match.isEmpty()) { + ui->comboBoxMOBus->setCurrentIndex(match.first().row()); + } + + model = ui->comboBoxMOChannel->model(); + match = model->match(model->index(0, 0), Qt::UserRole, channel); + if (! match.isEmpty()) { + ui->comboBoxMOChannel->setCurrentIndex(match.first().row()); + } + ui->comboBoxMOType->setCurrentIndex(type); +} + +void SettingsOtherRemovable::onZIPRowChanged(const QModelIndex ¤t) { + uint8_t bus = current.siblingAtColumn(0).data(Qt::UserRole).toUInt(); + uint8_t channel = current.siblingAtColumn(0).data(Qt::UserRole + 1).toUInt(); + bool is250 = current.siblingAtColumn(1).data(Qt::UserRole).toBool(); + + ui->comboBoxZIPBus->setCurrentIndex(-1); + auto* model = ui->comboBoxZIPBus->model(); + auto match = model->match(model->index(0, 0), Qt::UserRole, bus); + if (! match.isEmpty()) { + ui->comboBoxZIPBus->setCurrentIndex(match.first().row()); + } + + model = ui->comboBoxZIPChannel->model(); + match = model->match(model->index(0, 0), Qt::UserRole, channel); + if (! match.isEmpty()) { + ui->comboBoxZIPChannel->setCurrentIndex(match.first().row()); + } + ui->checkBoxZIP250->setChecked(is250); +} + +void SettingsOtherRemovable::on_comboBoxMOBus_currentIndexChanged(int index) { + if (index < 0) { + return; + } + + int bus = ui->comboBoxMOBus->currentData().toInt(); + bool enabled = (bus != MO_BUS_DISABLED); + ui->comboBoxMOChannel->setEnabled(enabled); + ui->comboBoxMOType->setEnabled(enabled); + Harddrives::populateBusChannels(ui->comboBoxMOChannel->model(), bus); +} + +void SettingsOtherRemovable::on_comboBoxMOBus_activated(int) { + setMOBus( + ui->tableViewMO->model(), + ui->tableViewMO->selectionModel()->currentIndex(), + ui->comboBoxMOBus->currentData().toUInt(), + ui->comboBoxMOChannel->currentData().toUInt()); + setMOType( + ui->tableViewMO->model(), + ui->tableViewMO->selectionModel()->currentIndex(), + ui->comboBoxMOType->currentData().toUInt()); + ui->tableViewMO->resizeColumnsToContents(); + ui->tableViewMO->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); +} + +void SettingsOtherRemovable::on_comboBoxMOChannel_activated(int) { + setMOBus( + ui->tableViewMO->model(), + ui->tableViewMO->selectionModel()->currentIndex(), + ui->comboBoxMOBus->currentData().toUInt(), + ui->comboBoxMOChannel->currentData().toUInt()); +} + +void SettingsOtherRemovable::on_comboBoxMOType_activated(int) { + setMOType( + ui->tableViewMO->model(), + ui->tableViewMO->selectionModel()->currentIndex(), + ui->comboBoxMOType->currentData().toUInt()); + ui->tableViewMO->resizeColumnsToContents(); + ui->tableViewMO->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); +} + +void SettingsOtherRemovable::on_comboBoxZIPBus_currentIndexChanged(int index) { + if (index < 0) { + return; + } + + int bus = ui->comboBoxZIPBus->currentData().toInt(); + bool enabled = (bus != ZIP_BUS_DISABLED); + ui->comboBoxZIPChannel->setEnabled(enabled); + ui->checkBoxZIP250->setEnabled(enabled); + Harddrives::populateBusChannels(ui->comboBoxZIPChannel->model(), bus); +} + +void SettingsOtherRemovable::on_comboBoxZIPBus_activated(int) { + setZIPBus( + ui->tableViewZIP->model(), + ui->tableViewZIP->selectionModel()->currentIndex(), + ui->comboBoxZIPBus->currentData().toUInt(), + ui->comboBoxZIPChannel->currentData().toUInt()); +} + +void SettingsOtherRemovable::on_comboBoxZIPChannel_activated(int) { + setZIPBus( + ui->tableViewZIP->model(), + ui->tableViewZIP->selectionModel()->currentIndex(), + ui->comboBoxZIPBus->currentData().toUInt(), + ui->comboBoxZIPChannel->currentData().toUInt()); +} + +void SettingsOtherRemovable::on_checkBoxZIP250_stateChanged(int state) { + setZIPType( + ui->tableViewZIP->model(), + ui->tableViewZIP->selectionModel()->currentIndex(), + state == Qt::Checked); +} diff --git a/src/qt/qt_settingsotherremovable.hpp b/src/qt/qt_settingsotherremovable.hpp new file mode 100644 index 000000000..c48f6f819 --- /dev/null +++ b/src/qt/qt_settingsotherremovable.hpp @@ -0,0 +1,52 @@ +#ifndef QT_SETTINGSOTHERREMOVABLE_HPP +#define QT_SETTINGSOTHERREMOVABLE_HPP + +#include + +namespace Ui { +class SettingsOtherRemovable; +} + +class SettingsOtherRemovable : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsOtherRemovable(QWidget *parent = nullptr); + ~SettingsOtherRemovable(); + + void save(); + +private slots: + void on_checkBoxZIP250_stateChanged(int arg1); + +private slots: + void on_comboBoxZIPChannel_activated(int index); + +private slots: + void on_comboBoxZIPBus_activated(int index); + +private slots: + void on_comboBoxZIPBus_currentIndexChanged(int index); + +private slots: + void on_comboBoxMOType_activated(int index); + +private slots: + void on_comboBoxMOChannel_activated(int index); + +private slots: + void on_comboBoxMOBus_activated(int index); + +private slots: + void on_comboBoxMOBus_currentIndexChanged(int index); + +private slots: + void onMORowChanged(const QModelIndex ¤t); + void onZIPRowChanged(const QModelIndex ¤t); + +private: + Ui::SettingsOtherRemovable *ui; +}; + +#endif // QT_SETTINGSOTHERREMOVABLE_HPP diff --git a/src/qt/qt_settingsotherremovable.ui b/src/qt/qt_settingsotherremovable.ui new file mode 100644 index 000000000..7d2b7a6d7 --- /dev/null +++ b/src/qt/qt_settingsotherremovable.ui @@ -0,0 +1,151 @@ + + + SettingsOtherRemovable + + + + 0 + 0 + 418 + 433 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + MO Drives + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + + + + + + + Bus + + + + + + + Channel + + + + + + + + + + + + + Type + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + ZIP Drives + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + + + + + + + Bus + + + + + + + + + + Channel + + + + + + + + + + ZIP 250 + + + + + + + + + + diff --git a/src/qt/qt_settingsports.cpp b/src/qt/qt_settingsports.cpp new file mode 100644 index 000000000..4c6446ef4 --- /dev/null +++ b/src/qt/qt_settingsports.cpp @@ -0,0 +1,81 @@ +#include "qt_settingsports.hpp" +#include "ui_qt_settingsports.h" + +extern "C" { +#include <86box/86box.h> +#include <86box/timer.h> +#include <86box/device.h> +#include <86box/machine.h> +#include <86box/lpt.h> +} + +#include "qt_deviceconfig.hpp" +#include "qt_models_common.hpp" + +SettingsPorts::SettingsPorts(QWidget *parent) : + QWidget(parent), + ui(new Ui::SettingsPorts) +{ + ui->setupUi(this); + + for (int i = 0; i < 3; i++) { + auto* cbox = findChild(QString("comboBoxLpt%1").arg(i+1)); + auto* model = cbox->model(); + int c = 0; + int selectedRow = 0; + while (true) { + const char* lptName = lpt_device_get_name(c); + if (lptName == nullptr) { + break; + } + + Models::AddEntry(model, lptName, c); + if (c == lpt_ports[i].device) { + selectedRow = c; + } + c++; + } + cbox->setCurrentIndex(selectedRow); + + auto* checkBox = findChild(QString("checkBoxParallel%1").arg(i+1)); + checkBox->setChecked(lpt_ports[i].enabled > 0); + cbox->setEnabled(lpt_ports[i].enabled > 0); + } + + for (int i = 0; i < 4; i++) { + auto* checkBox = findChild(QString("checkBoxSerial%1").arg(i+1)); + checkBox->setChecked(serial_enabled[i] > 0); + } +} + +SettingsPorts::~SettingsPorts() +{ + delete ui; +} + +void SettingsPorts::save() { + for (int i = 0; i < 3; i++) { + auto* cbox = findChild(QString("comboBoxLpt%1").arg(i+1)); + auto* checkBox = findChild(QString("checkBoxParallel%1").arg(i+1)); + lpt_ports[i].device = cbox->currentData().toInt(); + lpt_ports[i].enabled = checkBox->isChecked() ? 1 : 0; + } + + for (int i = 0; i < 4; i++) { + auto* checkBox = findChild(QString("checkBoxSerial%1").arg(i+1)); + serial_enabled[i] = checkBox->isChecked() ? 1 : 0; + } +} + +void SettingsPorts::on_checkBoxParallel1_stateChanged(int state) { + ui->comboBoxLpt1->setEnabled(state == Qt::Checked); +} + +void SettingsPorts::on_checkBoxParallel2_stateChanged(int state) { + ui->comboBoxLpt2->setEnabled(state == Qt::Checked); +} + +void SettingsPorts::on_checkBoxParallel3_stateChanged(int state) { + ui->comboBoxLpt3->setEnabled(state == Qt::Checked); +} + diff --git a/src/qt/qt_settingsports.hpp b/src/qt/qt_settingsports.hpp new file mode 100644 index 000000000..e4a8a050a --- /dev/null +++ b/src/qt/qt_settingsports.hpp @@ -0,0 +1,28 @@ +#ifndef QT_SETTINGSPORTS_HPP +#define QT_SETTINGSPORTS_HPP + +#include + +namespace Ui { +class SettingsPorts; +} + +class SettingsPorts : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsPorts(QWidget *parent = nullptr); + ~SettingsPorts(); + + void save(); +private slots: + void on_checkBoxParallel3_stateChanged(int arg1); + void on_checkBoxParallel2_stateChanged(int arg1); + void on_checkBoxParallel1_stateChanged(int arg1); + +private: + Ui::SettingsPorts *ui; +}; + +#endif // QT_SETTINGSPORTS_HPP diff --git a/src/qt/qt_settingsports.ui b/src/qt/qt_settingsports.ui new file mode 100644 index 000000000..4831948dc --- /dev/null +++ b/src/qt/qt_settingsports.ui @@ -0,0 +1,133 @@ + + + SettingsPorts + + + + 0 + 0 + 398 + 341 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + LPT1 Device + + + + + + + + + + LPT2 Device + + + + + + + + + + LPT3 Device + + + + + + + + + + + + + + Serial Port 1 + + + + + + + Parallel Port 1 + + + + + + + Serial Port 2 + + + + + + + Parallel Port 2 + + + + + + + Serial Port 3 + + + + + + + Parallel Port 3 + + + + + + + Serial Port 4 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/qt/qt_settingssound.cpp b/src/qt/qt_settingssound.cpp new file mode 100644 index 000000000..7dd3fc51a --- /dev/null +++ b/src/qt/qt_settingssound.cpp @@ -0,0 +1,223 @@ +#include "qt_settingssound.hpp" +#include "ui_qt_settingssound.h" + +extern "C" { +#include <86box/86box.h> +#include <86box/timer.h> +#include <86box/device.h> +#include <86box/machine.h> +#include <86box/sound.h> +#include <86box/midi.h> +#include <86box/snd_mpu401.h> +} + +#include "qt_deviceconfig.hpp" +#include "qt_models_common.hpp" + +SettingsSound::SettingsSound(QWidget *parent) : + QWidget(parent), + ui(new Ui::SettingsSound) +{ + ui->setupUi(this); + onCurrentMachineChanged(machine); +} + +SettingsSound::~SettingsSound() +{ + delete ui; +} + +void SettingsSound::save() { + sound_card_current = ui->comboBoxSoundCard->currentData().toInt(); + midi_device_current = ui->comboBoxMidiOut->currentData().toInt(); + midi_input_device_current = ui->comboBoxMidiIn->currentData().toInt(); + mpu401_standalone_enable = ui->checkBoxMPU401->isChecked() ? 1 : 0; + SSI2001 = ui->checkBoxSSI2001->isChecked() ? 1 : 0;; + GAMEBLASTER = ui->checkBoxCMS->isChecked() ? 1 : 0; + GUS = ui->checkBoxGUS->isChecked() ? 1 : 0;; + sound_is_float = ui->checkBoxFloat32->isChecked() ? 1 : 0;; +} + +void SettingsSound::onCurrentMachineChanged(int machineId) { + this->machineId = machineId; + auto* machine = &machines[machineId]; + + auto* model = ui->comboBoxSoundCard->model(); + auto removeRows = model->rowCount(); + int c = 0; + int selectedRow = 0; + while (true) { + /* Skip "internal" if machine doesn't have it. */ + if ((c == 1) && !(machine->flags & MACHINE_SOUND)) { + c++; + continue; + } + + auto* sound_dev = sound_card_getdevice(c); + QString name = DeviceConfig::DeviceName(sound_dev, sound_card_get_internal_name(c), 1); + if (name.isEmpty()) { + break; + } + + if (sound_card_available(c)) { + if (device_is_valid(sound_dev, machine->flags)) { + int row = Models::AddEntry(model, name, c); + if (c == sound_card_current) { + selectedRow = row - removeRows; + } + } + } + + c++; + } + model->removeRows(0, removeRows); + ui->comboBoxSoundCard->setEnabled(model->rowCount() > 0); + ui->comboBoxSoundCard->setCurrentIndex(-1); + ui->comboBoxSoundCard->setCurrentIndex(selectedRow); + + model = ui->comboBoxMidiOut->model(); + removeRows = model->rowCount(); + c = 0; + selectedRow = 0; + while (true) { + QString name = DeviceConfig::DeviceName(midi_device_getdevice(c), midi_device_get_internal_name(c), 0); + if (name.isEmpty()) { + break; + } + + if (midi_device_available(c)) { + int row = Models::AddEntry(model, name, c); + if (c == midi_input_device_current) { + selectedRow = row - removeRows; + } + } + c++; + } + model->removeRows(0, removeRows); + ui->comboBoxMidiOut->setEnabled(model->rowCount() > 0); + ui->comboBoxMidiOut->setCurrentIndex(-1); + ui->comboBoxMidiOut->setCurrentIndex(selectedRow); + + model = ui->comboBoxMidiIn->model(); + removeRows = model->rowCount(); + c = 0; + selectedRow = 0; + while (true) { + QString name = DeviceConfig::DeviceName(midi_in_device_getdevice(c), midi_device_get_internal_name(c), 0); + if (name.isEmpty()) { + break; + } + + if (midi_in_device_available(c)) { + int row = Models::AddEntry(model, name, c); + if (c == midi_device_current) { + selectedRow = row - removeRows; + } + } + + c++; + } + model->removeRows(0, removeRows); + ui->comboBoxMidiIn->setEnabled(model->rowCount() > 0); + ui->comboBoxMidiIn->setCurrentIndex(-1); + ui->comboBoxMidiIn->setCurrentIndex(selectedRow); + + ui->checkBoxMPU401->setChecked(mpu401_standalone_enable > 0); + ui->checkBoxSSI2001->setChecked(SSI2001 > 0); + ui->checkBoxCMS->setChecked(GAMEBLASTER > 0); + ui->checkBoxGUS->setChecked(GUS > 0); + ui->checkBoxFloat32->setChecked(sound_is_float > 0); + + ui->pushButtonConfigureSSI2001->setEnabled((SSI2001 > 0) && (machine->flags & MACHINE_BUS_ISA)); + ui->pushButtonConfigureCMS->setEnabled((GAMEBLASTER > 0) && (machine->flags & MACHINE_BUS_ISA)); + ui->pushButtonConfigureGUS->setEnabled((GUS > 0) && (machine->flags & MACHINE_BUS_ISA16)); +} + +static bool allowMpu401(Ui::SettingsSound *ui) { + QString midiOut = midi_device_get_internal_name(ui->comboBoxMidiOut->currentData().toInt()); + QString midiIn = midi_in_device_get_internal_name(ui->comboBoxMidiIn->currentData().toInt()); + + if (midiOut.isEmpty()) { + return false; + } + + if (midiOut == QStringLiteral("none") && midiIn == QStringLiteral("none")) { + return false; + } + + return true; +} + +void SettingsSound::on_comboBoxSoundCard_currentIndexChanged(int index) { + if (index < 0) { + return; + } + ui->pushButtonConfigureSoundCard->setEnabled(sound_card_has_config(ui->comboBoxSoundCard->currentData().toInt())); +} + + +void SettingsSound::on_pushButtonConfigureSoundCard_clicked() { + DeviceConfig::ConfigureDevice(sound_card_getdevice(ui->comboBoxSoundCard->currentData().toInt())); +} + +void SettingsSound::on_comboBoxMidiOut_currentIndexChanged(int index) { + if (index < 0) { + return; + } + ui->pushButtonConfigureMidiOut->setEnabled(midi_device_has_config(ui->comboBoxMidiOut->currentData().toInt())); + ui->checkBoxMPU401->setEnabled(allowMpu401(ui)); + ui->pushButtonConfigureMPU401->setEnabled(allowMpu401(ui) && ui->checkBoxMPU401->isChecked()); +} + +void SettingsSound::on_pushButtonConfigureMidiOut_clicked() { + DeviceConfig::ConfigureDevice(midi_device_getdevice(ui->comboBoxMidiOut->currentData().toInt())); +} + +void SettingsSound::on_comboBoxMidiIn_currentIndexChanged(int index) { + if (index < 0) { + return; + } + ui->pushButtonConfigureMidiIn->setEnabled(midi_in_device_has_config(ui->comboBoxMidiIn->currentData().toInt())); + ui->checkBoxMPU401->setEnabled(allowMpu401(ui)); + ui->pushButtonConfigureMPU401->setEnabled(allowMpu401(ui) && ui->checkBoxMPU401->isChecked()); +} + +void SettingsSound::on_pushButtonConfigureMidiIn_clicked() { + DeviceConfig::ConfigureDevice(midi_in_device_getdevice(ui->comboBoxMidiIn->currentData().toInt())); +} + +void SettingsSound::on_checkBoxMPU401_stateChanged(int state) { + ui->pushButtonConfigureMPU401->setEnabled(state == Qt::Checked); +} + +void SettingsSound::on_checkBoxSSI2001_stateChanged(int state) { + ui->pushButtonConfigureSSI2001->setEnabled(state == Qt::Checked); +} + +void SettingsSound::on_checkBoxCMS_stateChanged(int state) { + ui->pushButtonConfigureCMS->setEnabled(state == Qt::Checked); +} + +void SettingsSound::on_checkBoxGUS_stateChanged(int state) { + ui->pushButtonConfigureGUS->setEnabled(state == Qt::Checked); +} + +void SettingsSound::on_pushButtonConfigureMPU401_clicked() { + if (machines[machineId].flags & MACHINE_MCA) { + DeviceConfig::ConfigureDevice(&mpu401_mca_device); + } else { + DeviceConfig::ConfigureDevice(&mpu401_device); + } +} + +void SettingsSound::on_pushButtonConfigureSSI2001_clicked() { + DeviceConfig::ConfigureDevice(&ssi2001_device); +} + +void SettingsSound::on_pushButtonConfigureCMS_clicked() { + DeviceConfig::ConfigureDevice(&cms_device); +} + +void SettingsSound::on_pushButtonConfigureGUS_clicked() { + DeviceConfig::ConfigureDevice(&gus_device); +} diff --git a/src/qt/qt_settingssound.hpp b/src/qt/qt_settingssound.hpp new file mode 100644 index 000000000..c649eb2a2 --- /dev/null +++ b/src/qt/qt_settingssound.hpp @@ -0,0 +1,44 @@ +#ifndef QT_SETTINGSSOUND_HPP +#define QT_SETTINGSSOUND_HPP + +#include + +namespace Ui { +class SettingsSound; +} + +class SettingsSound : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsSound(QWidget *parent = nullptr); + ~SettingsSound(); + + void save(); + +public slots: + void onCurrentMachineChanged(int machineId); + +private slots: + void on_pushButtonConfigureGUS_clicked(); + void on_pushButtonConfigureCMS_clicked(); + void on_pushButtonConfigureSSI2001_clicked(); + void on_pushButtonConfigureMPU401_clicked(); + void on_checkBoxGUS_stateChanged(int arg1); + void on_checkBoxCMS_stateChanged(int arg1); + void on_checkBoxSSI2001_stateChanged(int arg1); + void on_checkBoxMPU401_stateChanged(int arg1); + void on_pushButtonConfigureMidiIn_clicked(); + void on_pushButtonConfigureMidiOut_clicked(); + void on_comboBoxMidiIn_currentIndexChanged(int index); + void on_comboBoxMidiOut_currentIndexChanged(int index); + void on_pushButtonConfigureSoundCard_clicked(); + void on_comboBoxSoundCard_currentIndexChanged(int index); + +private: + Ui::SettingsSound *ui; + int machineId = 0; +}; + +#endif // QT_SETTINGSSOUND_HPP diff --git a/src/qt/qt_settingssound.ui b/src/qt/qt_settingssound.ui new file mode 100644 index 000000000..6290f63aa --- /dev/null +++ b/src/qt/qt_settingssound.ui @@ -0,0 +1,160 @@ + + + SettingsSound + + + + 0 + 0 + 387 + 332 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + MIDI In + + + + + + + Innovation SSI-2001 + + + + + + + Gravis Ultrasound + + + + + + + Sound Card + + + + + + + Configure + + + + + + + + + + MIDI Out + + + + + + + Standalone MPU-401 + + + + + + + Configure + + + + + + + Configure + + + + + + + Configure + + + + + + + CMS / Game Blaster + + + + + + + + + + + + + Configure + + + + + + + Use FLOAT32 sound + + + + + + + Configure + + + + + + + Configure + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/qt/qt_settingsstoragecontrollers.cpp b/src/qt/qt_settingsstoragecontrollers.cpp new file mode 100644 index 000000000..64bb0e488 --- /dev/null +++ b/src/qt/qt_settingsstoragecontrollers.cpp @@ -0,0 +1,233 @@ +#include "qt_settingsstoragecontrollers.hpp" +#include "ui_qt_settingsstoragecontrollers.h" + +extern "C" { +#include <86box/86box.h> +#include <86box/timer.h> +#include <86box/device.h> +#include <86box/machine.h> +#include <86box/hdc.h> +#include <86box/hdc_ide.h> +#include <86box/fdc_ext.h> +#include <86box/scsi.h> +#include <86box/scsi_device.h> +#include <86box/cassette.h> +} + +#include "qt_deviceconfig.hpp" +#include "qt_models_common.hpp" + +SettingsStorageControllers::SettingsStorageControllers(QWidget *parent) : + QWidget(parent), + ui(new Ui::SettingsStorageControllers) +{ + ui->setupUi(this); + + ui->checkBoxCassette->setChecked(cassette_enable > 0); + + onCurrentMachineChanged(machine); +} + +SettingsStorageControllers::~SettingsStorageControllers() +{ + delete ui; +} + +void SettingsStorageControllers::save() { + /* Storage devices category */ + for (int i = 0; i < SCSI_BUS_MAX; ++i) { + auto* cbox = findChild(QString("comboBoxSCSI%1").arg(i+1)); + scsi_card_current[i] = cbox->currentData().toInt(); + } + hdc_current = ui->comboBoxHD->currentData().toInt(); + fdc_type = ui->comboBoxFD->currentData().toInt(); + ide_ter_enabled = ui->checkBoxTertiaryIDE->isChecked() ? 1 : 0; + ide_qua_enabled = ui->checkBoxQuaternaryIDE->isChecked() ? 1 : 0; + cassette_enable = ui->checkBoxCassette->isChecked() ? 1 : 0; +} + +void SettingsStorageControllers::onCurrentMachineChanged(int machineId) { + this->machineId = machineId; + auto* machine = &machines[machineId]; + + /*HD controller config*/ + auto* model = ui->comboBoxHD->model(); + auto removeRows = model->rowCount(); + int c = 0; + int selectedRow = 0; + while (true) { + /* Skip "internal" if machine doesn't have it. */ + if ((c == 1) && !(machine->flags & MACHINE_HDC)) { + c++; + continue; + } + + QString name = DeviceConfig::DeviceName(hdc_get_device(c), hdc_get_internal_name(c), 1); + if (name.isEmpty()) { + break; + } + + if (hdc_available(c)) { + auto* hdc_dev = hdc_get_device(c); + + if (device_is_valid(hdc_dev, machine->flags)) { + int row = Models::AddEntry(model, name, c); + if (c == hdc_current) { + selectedRow = row - removeRows; + } + } + } + c++; + } + model->removeRows(0, removeRows); + ui->comboBoxHD->setEnabled(model->rowCount() > 0); + ui->comboBoxHD->setCurrentIndex(-1); + ui->comboBoxHD->setCurrentIndex(selectedRow); + + /*FD controller config*/ + model = ui->comboBoxFD->model(); + removeRows = model->rowCount(); + c = 0; + selectedRow = 0; + while (true) { + QString name = DeviceConfig::DeviceName(fdc_card_getdevice(c), fdc_card_get_internal_name(c), 1); + if (name.isEmpty()) { + break; + } + + if (fdc_card_available(c)) { + auto* fdc_dev = fdc_card_getdevice(c); + + if (device_is_valid(fdc_dev, machine->flags)) { + int row = Models::AddEntry(model, name, c); + if (c == fdc_type) { + selectedRow = row - removeRows; + } + } + } + c++; + } + model->removeRows(0, removeRows); + ui->comboBoxFD->setEnabled(model->rowCount() > 0); + ui->comboBoxFD->setCurrentIndex(-1); + ui->comboBoxFD->setCurrentIndex(selectedRow); + + for (int i = 0; i < SCSI_BUS_MAX; ++i) { + auto* cbox = findChild(QString("comboBoxSCSI%1").arg(i+1)); + model = cbox->model(); + removeRows = model->rowCount(); + c = 0; + selectedRow = 0; + + while (true) { + auto name = DeviceConfig::DeviceName(scsi_card_getdevice(c), scsi_card_get_internal_name(c), 1); + if (name.isEmpty()) { + break; + } + + if (scsi_card_available(c)) { + auto* scsi_dev = scsi_card_getdevice(c); + if (device_is_valid(scsi_dev, machine->flags)) { + int row = Models::AddEntry(model, name, c); + if (c == scsi_card_current[i]) { + selectedRow = row - removeRows; + } + } + } + c++; + } + + model->removeRows(0, removeRows); + cbox->setEnabled(model->rowCount() > 0); + cbox->setCurrentIndex(-1); + cbox->setCurrentIndex(selectedRow); + } + + int is_at = IS_AT(machineId); + ui->checkBoxTertiaryIDE->setEnabled(is_at > 0); + ui->checkBoxQuaternaryIDE->setEnabled(is_at > 0); +} + +void SettingsStorageControllers::on_comboBoxHD_currentIndexChanged(int index) { + if (index < 0) { + return; + } + ui->pushButtonHD->setEnabled(hdc_has_config(ui->comboBoxHD->currentData().toInt()) > 0); +} + +void SettingsStorageControllers::on_comboBoxFD_currentIndexChanged(int index) { + if (index < 0) { + return; + } + ui->pushButtonFD->setEnabled(hdc_has_config(ui->comboBoxFD->currentData().toInt()) > 0); +} + +void SettingsStorageControllers::on_checkBoxTertiaryIDE_stateChanged(int arg1) { + ui->pushButtonTertiaryIDE->setEnabled(arg1 == Qt::Checked); +} + + +void SettingsStorageControllers::on_checkBoxQuaternaryIDE_stateChanged(int arg1) { + ui->pushButtonQuaternaryIDE->setEnabled(arg1 == Qt::Checked); +} + +void SettingsStorageControllers::on_pushButtonHD_clicked() { + DeviceConfig::ConfigureDevice(hdc_get_device(ui->comboBoxHD->currentData().toInt())); +} + +void SettingsStorageControllers::on_pushButtonFD_clicked() { + DeviceConfig::ConfigureDevice(fdc_card_getdevice(ui->comboBoxFD->currentData().toInt())); +} + +void SettingsStorageControllers::on_pushButtonTertiaryIDE_clicked() { + DeviceConfig::ConfigureDevice(&ide_ter_device); +} + +void SettingsStorageControllers::on_pushButtonQuaternaryIDE_clicked() { + DeviceConfig::ConfigureDevice(&ide_qua_device); +} + +void SettingsStorageControllers::on_comboBoxSCSI1_currentIndexChanged(int index) { + if (index < 0) { + return; + } + ui->pushButtonSCSI1->setEnabled(scsi_card_has_config(ui->comboBoxSCSI1->currentData().toInt()) > 0); +} + +void SettingsStorageControllers::on_comboBoxSCSI2_currentIndexChanged(int index) { + if (index < 0) { + return; + } + ui->pushButtonSCSI2->setEnabled(scsi_card_has_config(ui->comboBoxSCSI2->currentData().toInt()) > 0); +} + +void SettingsStorageControllers::on_comboBoxSCSI3_currentIndexChanged(int index) { + if (index < 0) { + return; + } + ui->pushButtonSCSI3->setEnabled(scsi_card_has_config(ui->comboBoxSCSI3->currentData().toInt()) > 0); +} + +void SettingsStorageControllers::on_comboBoxSCSI4_currentIndexChanged(int index) { + if (index < 0) { + return; + } + ui->pushButtonSCSI4->setEnabled(scsi_card_has_config(ui->comboBoxSCSI4->currentData().toInt()) > 0); +} + + +void SettingsStorageControllers::on_pushButtonSCSI1_clicked() { + DeviceConfig::ConfigureDevice(scsi_card_getdevice(ui->comboBoxSCSI1->currentData().toInt())); +} + +void SettingsStorageControllers::on_pushButtonSCSI2_clicked() { + DeviceConfig::ConfigureDevice(scsi_card_getdevice(ui->comboBoxSCSI2->currentData().toInt())); +} + +void SettingsStorageControllers::on_pushButtonSCSI3_clicked() { + DeviceConfig::ConfigureDevice(scsi_card_getdevice(ui->comboBoxSCSI3->currentData().toInt())); +} + +void SettingsStorageControllers::on_pushButtonSCSI4_clicked() { + DeviceConfig::ConfigureDevice(scsi_card_getdevice(ui->comboBoxSCSI4->currentData().toInt())); +} diff --git a/src/qt/qt_settingsstoragecontrollers.hpp b/src/qt/qt_settingsstoragecontrollers.hpp new file mode 100644 index 000000000..de2e36646 --- /dev/null +++ b/src/qt/qt_settingsstoragecontrollers.hpp @@ -0,0 +1,76 @@ +#ifndef QT_SETTINGSSTORAGECONTROLLERS_HPP +#define QT_SETTINGSSTORAGECONTROLLERS_HPP + +#include + +namespace Ui { +class SettingsStorageControllers; +} + +class SettingsStorageControllers : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsStorageControllers(QWidget *parent = nullptr); + ~SettingsStorageControllers(); + + void save(); + +public slots: + void onCurrentMachineChanged(int machineId); + +private slots: + void on_pushButtonSCSI4_clicked(); + +private slots: + void on_pushButtonSCSI3_clicked(); + +private slots: + void on_pushButtonSCSI2_clicked(); + +private slots: + void on_pushButtonSCSI1_clicked(); + +private slots: + void on_comboBoxSCSI4_currentIndexChanged(int index); + +private slots: + void on_comboBoxSCSI3_currentIndexChanged(int index); + +private slots: + void on_comboBoxSCSI2_currentIndexChanged(int index); + +private slots: + void on_comboBoxSCSI1_currentIndexChanged(int index); + +private slots: + void on_pushButtonQuaternaryIDE_clicked(); + +private slots: + void on_pushButtonTertiaryIDE_clicked(); + +private slots: + void on_pushButtonFD_clicked(); + +private slots: + void on_pushButtonHD_clicked(); + +private slots: + void on_checkBoxQuaternaryIDE_stateChanged(int arg1); + +private slots: + void on_checkBoxTertiaryIDE_stateChanged(int arg1); + +private slots: + void on_comboBoxFD_currentIndexChanged(int index); + +private slots: + void on_comboBoxHD_currentIndexChanged(int index); + +private: + Ui::SettingsStorageControllers *ui; + int machineId = 0; +}; + +#endif // QT_SETTINGSSTORAGECONTROLLERS_HPP diff --git a/src/qt/qt_settingsstoragecontrollers.ui b/src/qt/qt_settingsstoragecontrollers.ui new file mode 100644 index 000000000..3d1bf7dba --- /dev/null +++ b/src/qt/qt_settingsstoragecontrollers.ui @@ -0,0 +1,202 @@ + + + SettingsStorageControllers + + + + 0 + 0 + 496 + 449 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + HD Controller + + + + + + + Configure + + + + + + + FD Controller + + + + + + + + + + Configure + + + + + + + + + + Tertiary IDE Controller + + + + + + + Quaternary IDE Controller + + + + + + + false + + + Configure + + + + + + + false + + + Configure + + + + + + + + + SCSI + + + + + + + + + Configure + + + + + + + Controller 1 + + + + + + + + + + + + + Controller 4 + + + + + + + Controller 2 + + + + + + + Configure + + + + + + + Configure + + + + + + + Controller 3 + + + + + + + + + + Configure + + + + + + + + + + Cassette + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/qt/qt_ui.cpp b/src/qt/qt_ui.cpp new file mode 100644 index 000000000..05e14332e --- /dev/null +++ b/src/qt/qt_ui.cpp @@ -0,0 +1,111 @@ +#include + +#include +#include + +#include + +#include "qt_mainwindow.hpp" + +std::atomic_int resize_pending = 0; +std::atomic_int resize_w = 0; +std::atomic_int resize_h = 0; + +MainWindow* main_window = nullptr; + +extern "C" { + +#include <86box/plat.h> + +void +ui_sb_update_icon_state(int tag, int state) +{ + +} + +void +ui_sb_update_icon(int tag, int active) +{ + +} + +void +plat_delay_ms(uint32_t count) +{ + QThread::msleep(count); +} + +void +ui_sb_update_tip(int arg) +{ + +} + +void +ui_sb_update_panes() +{ + +} + +void ui_sb_bugui(char *str) +{ + +} + +void ui_sb_set_ready(int ready) {} + +wchar_t* ui_window_title(wchar_t* str) +{ + if (str == nullptr) { + static wchar_t title[512]; + int chars = main_window->windowTitle().toWCharArray(title); + title[chars] = 0; + str = title; + } else { + main_window->setWindowTitle(QString::fromWCharArray(str)); + } + return str; +} + +void mouse_poll() { + main_window->pollMouse(); +} + +void plat_resize(int w, int h) { + resize_w = w; + resize_h = h; + resize_pending = 1; + + main_window->resizeContents(w, h); +} + +void plat_setfullscreen(int on) { + main_window->setFullscreen(on > 0 ? true : false); +} + +void plat_mouse_capture(int on) { + main_window->setMouseCapture(on > 0 ? true : false); +} + +int ui_msgbox_header(int flags, void *header, void* message) +{ + if (header <= (void*)7168) header = plat_get_string(reinterpret_cast(header)); + if (message <= (void*)7168) message = plat_get_string(reinterpret_cast(message)); + + auto hdr = QString::fromWCharArray(reinterpret_cast(header)); + auto msg = QString::fromWCharArray(reinterpret_cast(message)); + + QMessageBox box(QMessageBox::Warning, hdr, msg); + box.exec(); + return 0; +} + +int ui_msgbox(int flags, void *message) { + return ui_msgbox_header(flags, nullptr, message); +} + +void ui_sb_set_text_w(wchar_t *wstr) { + main_window->statusBar()->showMessage(QString::fromWCharArray(wstr)); +} + +} diff --git a/src/qt_resources.qrc b/src/qt_resources.qrc new file mode 100644 index 000000000..b542fb305 --- /dev/null +++ b/src/qt_resources.qrc @@ -0,0 +1,24 @@ + + + win/icons/machine.ico + win/icons/display.ico + win/icons/input_devices.ico + win/icons/sound.ico + win/icons/network.ico + win/icons/ports.ico + win/icons/storage_controllers.ico + win/icons/hard_disk.ico + win/icons/floppy_and_cdrom_drives.ico + win/icons/other_peripherals.ico + win/icons/other_removable_devices.ico + win/icons/floppy_35.ico + win/icons/floppy_525.ico + win/icons/floppy_disabled.ico + win/icons/cdrom_disabled.ico + win/icons/cdrom.ico + win/icons/mo.ico + win/icons/mo_disabled.ico + win/icons/zip.ico + win/icons/zip_disabled.ico + + From 4d7434809f8dceec22a04a7dffccc48af2b0c297 Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Thu, 25 Nov 2021 10:27:09 +0100 Subject: [PATCH 03/30] some branch notes --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 3dcf0f3a6..fce7599c5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,23 @@ 86Box ===== +**This Branch** I've added initial Qt support to 86Box because I wanted to +have the configuration dialogs available on Linux, similar to what was +available on PCem. + +This is work-in-progress! + +Implemented +----------- +* The settings dialog +* Full screen switching +* Keyboard and mouse + +Missing +------- +* Status updates (activity lights) +* Display output options (like forced 4:3) +* Entering full screen from within the emulated screen + **86Box** is a low level x86 emulator that runs older operating systems and software designed for IBM PC systems and compatibles from 1981 through fairly recent system designs based on the PCI bus. Features From 6c81074f0a87f215a00f22dfad8122ccfc3aad32 Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Sun, 28 Nov 2021 20:49:05 +0100 Subject: [PATCH 04/30] added status icons --- src/qt/CMakeLists.txt | 5 +- src/qt/qt_machinestatus.cpp | 408 ++++++++++++++++++++++++++++++++++++ src/qt/qt_machinestatus.hpp | 32 +++ src/qt/qt_machinestatus.ui | 44 ++++ src/qt/qt_mainwindow.cpp | 57 ++--- src/qt/qt_mainwindow.hpp | 36 +--- src/qt/qt_mainwindow.ui | 13 +- src/qt/qt_ui.cpp | 63 +++--- src/qt_resources.qrc | 51 +++-- 9 files changed, 593 insertions(+), 116 deletions(-) create mode 100644 src/qt/qt_machinestatus.cpp create mode 100644 src/qt/qt_machinestatus.hpp create mode 100644 src/qt/qt_machinestatus.ui diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index ab923e042..a9a80d042 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -1,5 +1,3 @@ -find_package(Qt5 COMPONENTS Core Widgets REQUIRED) - # Find includes in corresponding build directories set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) @@ -15,6 +13,9 @@ add_library(ui STATIC qt_mainwindow.cpp qt_mainwindow.hpp qt_mainwindow.ui + qt_machinestatus.cpp + qt_machinestatus.hpp + qt_machinestatus.ui qt_settings.cpp qt_settings.hpp diff --git a/src/qt/qt_machinestatus.cpp b/src/qt/qt_machinestatus.cpp new file mode 100644 index 000000000..d876343d7 --- /dev/null +++ b/src/qt/qt_machinestatus.cpp @@ -0,0 +1,408 @@ +#include "qt_machinestatus.hpp" +#include "ui_qt_machinestatus.h" + +extern "C" { +#define EMU_CPU_H // superhack - don't want timer.h to include cpu.h here, and some combo is preventing a compile +extern uint64_t tsc; + +#include <86box/hdd.h> +#include <86box/timer.h> +#include <86box/86box.h> +#include <86box/device.h> +#include <86box/cartridge.h> +#include <86box/cassette.h> +#include <86box/cdrom.h> +#include <86box/fdd.h> +#include <86box/hdc.h> +#include <86box/scsi.h> +#include <86box/scsi_device.h> +#include <86box/zip.h> +#include <86box/mo.h> +#include <86box/plat.h> +#include <86box/machine.h> +#include <86box/network.h> +#include <86box/ui.h> +}; + +#include +#include +#include +#include + +namespace { + struct PixmapSetActive { + QPixmap normal; + QPixmap active; + void load(const QString& basePath); + }; + struct PixmapSetEmpty { + QPixmap normal; + QPixmap empty; + void load(const QString& basePath); + }; + struct PixmapSetEmptyActive { + QPixmap normal; + QPixmap active; + QPixmap empty; + QPixmap empty_active; + void load(const QString& basePath); + }; + struct Pixmaps { + PixmapSetEmpty cartridge; + PixmapSetEmptyActive cassette; + PixmapSetEmptyActive floppy_disabled; + PixmapSetEmptyActive floppy_525; + PixmapSetEmptyActive floppy_35; + PixmapSetEmptyActive cdrom; + PixmapSetEmptyActive zip; + PixmapSetEmptyActive mo; + PixmapSetActive hd; + PixmapSetActive net; + QPixmap sound; + }; + + struct StateActive { + QLabel label; + QTimer timer; + PixmapSetActive* pixmaps = nullptr; + bool active = false; + + void setActive(bool b) { + active = b; + label.setPixmap(active ? pixmaps->active : pixmaps->normal); + timer.start(75); + } + }; + struct StateEmpty { + QLabel label; + PixmapSetEmpty* pixmaps = nullptr; + bool empty = false; + + void setEmpty(bool e) { + empty = e; + label.setPixmap(empty ? pixmaps->empty : pixmaps->normal); + } + }; + struct StateEmptyActive { + QLabel label; + QTimer timer; + PixmapSetEmptyActive* pixmaps = nullptr; + bool empty = false; + bool active = false; + + void setActive(bool b) { + active = b; + refresh(); + timer.start(75); + } + void setEmpty(bool b) { + empty = b; + refresh(); + } + void refresh() { + if (empty) { + label.setPixmap(active ? pixmaps->empty_active : pixmaps->empty); + } else { + label.setPixmap(active ? pixmaps->active : pixmaps->normal); + } + } + }; + + static const QSize pixmap_size(32, 32); + static const QString pixmap_empty = QStringLiteral("_empty"); + static const QString pixmap_active = QStringLiteral("_active"); + static const QString pixmap_empty_active = QStringLiteral("_empty_active"); + void PixmapSetEmpty::load(const QString &basePath) { + normal = QIcon(basePath.arg(QStringLiteral(""))).pixmap(pixmap_size); + empty = QIcon(basePath.arg(pixmap_empty)).pixmap(pixmap_size); + } + + void PixmapSetActive::load(const QString &basePath) { + normal = QIcon(basePath.arg(QStringLiteral(""))).pixmap(pixmap_size); + active = QIcon(basePath.arg(pixmap_active)).pixmap(pixmap_size); + } + + void PixmapSetEmptyActive::load(const QString &basePath) { + normal = QIcon(basePath.arg(QStringLiteral(""))).pixmap(pixmap_size); + active = QIcon(basePath.arg(pixmap_active)).pixmap(pixmap_size); + empty = QIcon(basePath.arg(pixmap_empty)).pixmap(pixmap_size); + empty_active = QIcon(basePath.arg(pixmap_empty_active)).pixmap(pixmap_size); + } +} + +struct MachineStatus::States { + Pixmaps pixmaps; + + States(QObject* parent) { + pixmaps.cartridge.load(":/settings/win/icons/cartridge%1.ico"); + pixmaps.cassette.load(":/settings/win/icons/cassette%1.ico"); + pixmaps.floppy_disabled.normal = QIcon(QStringLiteral(":/settings/win/icons/floppy_disabled.ico")).pixmap(pixmap_size); + pixmaps.floppy_disabled.active = pixmaps.floppy_disabled.normal; + pixmaps.floppy_disabled.empty = pixmaps.floppy_disabled.normal; + pixmaps.floppy_disabled.empty_active = pixmaps.floppy_disabled.normal; + pixmaps.floppy_525.load(":/settings/win/icons/floppy_525%1.ico"); + pixmaps.floppy_35.load(":/settings/win/icons/floppy_35%1.ico"); + pixmaps.cdrom.load(":/settings/win/icons/cdrom%1.ico"); + pixmaps.zip.load(":/settings/win/icons/zip%1.ico"); + pixmaps.mo.load(":/settings/win/icons/mo%1.ico"); + pixmaps.hd.load(":/settings/win/icons/hard_disk%1.ico"); + pixmaps.net.load(":/settings/win/icons/network%1.ico"); + pixmaps.sound = QIcon(":/settings/win/icons/sound.ico").pixmap(pixmap_size); + + cartridge[0].pixmaps = &pixmaps.cartridge; + cartridge[1].pixmaps = &pixmaps.cartridge; + cassette.pixmaps = &pixmaps.cassette; + QObject::connect(&cassette.timer, &QTimer::timeout, parent, [&]{ cassette.setActive(false); }); + for (auto& f : fdd) { + f.pixmaps = &pixmaps.floppy_disabled; + QObject::connect(&f.timer, &QTimer::timeout, parent, [&]{ f.setActive(false); }); + } + for (auto& c : cdrom) { + c.pixmaps = &pixmaps.cdrom; + QObject::connect(&c.timer, &QTimer::timeout, parent, [&]{ c.setActive(false); }); + } + for (auto& z : zip) { + z.pixmaps = &pixmaps.zip; + QObject::connect(&z.timer, &QTimer::timeout, parent, [&]{ z.setActive(false); }); + } + for (auto& m : mo) { + m.pixmaps = &pixmaps.mo; + QObject::connect(&m.timer, &QTimer::timeout, parent, [&]{ m.setActive(false); }); + } + for (auto& h : hdds) { + h.pixmaps = &pixmaps.hd; + QObject::connect(&h.timer, &QTimer::timeout, parent, [&]{ h.setActive(false); }); + } + net.pixmaps = &pixmaps.net; + sound.setPixmap(pixmaps.sound); + } + + std::array cartridge; + StateEmptyActive cassette; + std::array fdd; + std::array cdrom; + std::array zip; + std::array mo; + std::array hdds; + StateActive net; + QLabel sound; +}; + +MachineStatus::MachineStatus(QWidget *parent) : + QWidget(parent), + ui(new Ui::MachineStatus) +{ + ui->setupUi(this); + d = std::make_unique(this); +} + +MachineStatus::~MachineStatus() +{ + delete ui; +} + +static int hdd_count(int bus) { + int c = 0; + int i; + + for (i = 0; i < HDD_NUM; i++) { + if (hdd[i].bus == bus) { + c++; + } + } + + return(c); +} + +void MachineStatus::refresh() { + bool has_cart = machines[machine].flags & MACHINE_CARTRIDGE; + bool has_mfm = machines[machine].flags & MACHINE_MFM; + bool has_xta = machines[machine].flags & MACHINE_XTA; + bool has_esdi = machines[machine].flags & MACHINE_ESDI; + bool has_ide = machines[machine].flags & MACHINE_IDE_QUAD; + bool has_scsi = machines[machine].flags & MACHINE_SCSI_DUAL; + + int c_mfm = hdd_count(HDD_BUS_MFM); + int c_esdi = hdd_count(HDD_BUS_ESDI); + int c_xta = hdd_count(HDD_BUS_XTA); + int c_ide = hdd_count(HDD_BUS_IDE); + int c_scsi = hdd_count(HDD_BUS_SCSI); + int do_net = (network_type == NET_TYPE_NONE) || (network_card == 0); + + while (ui->statusIcons->count() > 0) { + auto item = ui->statusIcons->itemAt(0); + ui->statusIcons->removeItem(item); + delete item; + } + + if (cassette_enable) { + d->cassette.setEmpty(QString(cassette_fname).isEmpty()); + ui->statusIcons->addWidget(&d->cassette.label); + } + + if (has_cart) { + for (int i = 0; i < 2; ++i) { + d->cartridge[i].setEmpty(QString(cart_fns[i]).isEmpty()); + ui->statusIcons->addWidget(&d->cartridge[i].label); + } + } + + for (size_t i = 0; i < FDD_NUM; ++i) { + if (fdd_get_type(i) != 0) { + int t = fdd_get_type(i); + if (t == 0) { + d->fdd[i].pixmaps = &d->pixmaps.floppy_disabled; + } else if (t >= 1 && t <= 6) { + d->fdd[i].pixmaps = &d->pixmaps.floppy_525; + } else { + d->fdd[i].pixmaps = &d->pixmaps.floppy_35; + } + d->fdd[i].setEmpty(QString(floppyfns[i]).isEmpty()); + d->fdd[i].setActive(false); + ui->statusIcons->addWidget(&d->fdd[i].label); + } + } + + auto hdc_name = QString(hdc_get_internal_name(hdc_current)); + for (size_t i = 0; i < CDROM_NUM; i++) { + /* Could be Internal or External IDE.. */ + if ((cdrom[i].bus_type == CDROM_BUS_ATAPI) && + !has_ide && hdc_name != QStringLiteral("ide")) + continue; + if ((cdrom[i].bus_type == CDROM_BUS_SCSI) && !has_scsi && + (scsi_card_current[0] == 0) && (scsi_card_current[1] == 0) && + (scsi_card_current[2] == 0) && (scsi_card_current[3] == 0)) + continue; + if (cdrom[i].bus_type != 0) { + d->cdrom[i].setEmpty(cdrom[i].host_drive != 200 || QString(cdrom[i].image_path).isEmpty()); + d->cdrom[i].setActive(false); + ui->statusIcons->addWidget(&d->cdrom[i].label); + } + } + for (size_t i = 0; i < ZIP_NUM; i++) { + /* Could be Internal or External IDE.. */ + if ((zip_drives[i].bus_type == ZIP_BUS_ATAPI) && + !has_ide && hdc_name != QStringLiteral("ide")) + continue; + if ((zip_drives[i].bus_type == ZIP_BUS_SCSI) && !has_scsi && + (scsi_card_current[0] == 0) && (scsi_card_current[1] == 0) && + (scsi_card_current[2] == 0) && (scsi_card_current[3] == 0)) + continue; + if (zip_drives[i].bus_type != 0) { + d->zip[i].setEmpty(QString(zip_drives[i].image_path).isEmpty()); + d->zip[i].setActive(false); + ui->statusIcons->addWidget(&d->zip[i].label); + } + } + for (size_t i = 0; i < MO_NUM; i++) { + /* Could be Internal or External IDE.. */ + if ((mo_drives[i].bus_type == MO_BUS_ATAPI) && + !has_ide && hdc_name != QStringLiteral("ide")) + continue; + if ((mo_drives[i].bus_type == MO_BUS_SCSI) && !has_scsi && + (scsi_card_current[0] == 0) && (scsi_card_current[1] == 0) && + (scsi_card_current[2] == 0) && (scsi_card_current[3] == 0)) + continue; + if (mo_drives[i].bus_type != 0) { + d->mo[i].setEmpty(QString(mo_drives[i].image_path).isEmpty()); + d->mo[i].setActive(false); + ui->statusIcons->addWidget(&d->mo[i].label); + } + } + + if ((has_mfm || hdc_name == QStringLiteral("st506")) && c_mfm > 0) { + d->hdds[HDD_BUS_MFM].setActive(false); + ui->statusIcons->addWidget(&d->hdds[HDD_BUS_MFM].label); + } + if ((has_esdi || hdc_name == QStringLiteral("esdi")) && c_esdi > 0) { + d->hdds[HDD_BUS_ESDI].setActive(false); + ui->statusIcons->addWidget(&d->hdds[HDD_BUS_ESDI].label); + } + if ((has_xta || hdc_name == QStringLiteral("xta")) && c_xta > 0) { + d->hdds[HDD_BUS_XTA].setActive(false); + ui->statusIcons->addWidget(&d->hdds[HDD_BUS_XTA].label); + } + if ((has_ide || hdc_name == QStringLiteral("xtide") || hdc_name == QStringLiteral("ide")) && c_ide > 0) { + d->hdds[HDD_BUS_IDE].setActive(false); + ui->statusIcons->addWidget(&d->hdds[HDD_BUS_IDE].label); + } + if ((has_scsi || (scsi_card_current[0] != 0) || (scsi_card_current[1] != 0) || + (scsi_card_current[2] != 0) || (scsi_card_current[3] != 0)) && c_scsi > 0) { + d->hdds[HDD_BUS_SCSI].setActive(false); + ui->statusIcons->addWidget(&d->hdds[HDD_BUS_SCSI].label); + } + + if (do_net) { + d->net.setActive(false); + ui->statusIcons->addWidget(&d->net.label); + } + ui->statusIcons->addWidget(&d->sound); + ui->statusIcons->addItem(new QSpacerItem(20, 40, QSizePolicy::Expanding, QSizePolicy::Minimum)); +} + +void MachineStatus::setActivity(int tag, bool active) { + int category = tag & 0xfffffff0; + int item = tag & 0xf; + switch (category) { + case SB_CASSETTE: + break; + case SB_CARTRIDGE: + break; + case SB_FLOPPY: + d->fdd[item].setActive(active); + break; + case SB_CDROM: + d->cdrom[item].setActive(active); + break; + case SB_ZIP: + d->zip[item].setActive(active); + break; + case SB_MO: + d->mo[item].setActive(active); + break; + case SB_HDD: + d->hdds[item].setActive(active); + break; + case SB_NETWORK: + d->net.setActive(active); + break; + case SB_SOUND: + break; + case SB_TEXT: + break; + } +} + +void MachineStatus::setEmpty(int tag, bool empty) { + int category = tag & 0xfffffff0; + int item = tag & 0xf; + switch (category) { + case SB_CASSETTE: + d->cassette.setEmpty(empty); + break; + case SB_CARTRIDGE: + d->cartridge[item].setEmpty(empty); + break; + case SB_FLOPPY: + d->fdd[item].setEmpty(empty); + break; + case SB_CDROM: + d->cdrom[item].setEmpty(empty); + break; + case SB_ZIP: + d->zip[item].setEmpty(empty); + break; + case SB_MO: + d->mo[item].setEmpty(empty); + break; + case SB_HDD: + break; + case SB_NETWORK: + break; + case SB_SOUND: + break; + case SB_TEXT: + break; + } +} + diff --git a/src/qt/qt_machinestatus.hpp b/src/qt/qt_machinestatus.hpp new file mode 100644 index 000000000..e3f0a6409 --- /dev/null +++ b/src/qt/qt_machinestatus.hpp @@ -0,0 +1,32 @@ +#ifndef QT_MACHINESTATUS_HPP +#define QT_MACHINESTATUS_HPP + +#include + +class QLabel; + +namespace Ui { +class MachineStatus; +} + +class MachineStatus : public QWidget +{ + Q_OBJECT + +public: + explicit MachineStatus(QWidget *parent = nullptr); + ~MachineStatus(); + +public slots: + void refresh(); + void setActivity(int tag, bool active); + void setEmpty(int tag, bool active); + +private: + Ui::MachineStatus *ui; + + struct States; + std::unique_ptr d; +}; + +#endif // QT_MACHINESTATUS_HPP diff --git a/src/qt/qt_machinestatus.ui b/src/qt/qt_machinestatus.ui new file mode 100644 index 000000000..b05f1f8c3 --- /dev/null +++ b/src/qt/qt_machinestatus.ui @@ -0,0 +1,44 @@ + + + MachineStatus + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + + diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index d9ae9500d..8c179bb2c 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -3,8 +3,8 @@ extern "C" { #include <86box/86box.h> -#include <86box/keyboard.h> -#include <86box/mouse.h> +//#include <86box/keyboard.h> +//#include <86box/mouse.h> #include <86box/config.h> #include <86box/plat.h> @@ -18,23 +18,6 @@ extern "C" { #include "qt_settings.hpp" -CentralWidget::CentralWidget(QWidget *parent) : QWidget(parent) {} -CentralWidget::~CentralWidget() = default; - -MainWindowLabel::MainWindowLabel(QWidget *parent) : QLabel(parent) { - setMouseTracking(true); -} -MainWindowLabel::~MainWindowLabel() = default; -void MainWindowLabel::mouseMoveEvent(QMouseEvent *event) { - pos_ = event->pos(); -} -void MainWindowLabel::mousePressEvent(QMouseEvent *event) { - buttons_ = event->buttons(); -} -void MainWindowLabel::mouseReleaseEvent(QMouseEvent *event) { - buttons_ = event->buttons(); -} - MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) @@ -65,6 +48,10 @@ MainWindow::MainWindow(QWidget *parent) : config_save(); }); + connect(this, &MainWindow::updateStatusBarPanes, ui->machineStatus, &MachineStatus::refresh); + connect(this, &MainWindow::updateStatusBarActivity, ui->machineStatus, &MachineStatus::setActivity); + connect(this, &MainWindow::updateStatusBarEmpty, ui->machineStatus, &MachineStatus::setEmpty); + ui->actionKeyboard_requires_capture->setChecked(kbd_req_capture); ui->actionRight_CTRL_is_left_ALT->setChecked(rctrl_is_lalt); @@ -275,24 +262,24 @@ static const uint16_t xfree86_keycode_table[keycode_entries] = { /* 135 */ 0x15d, // Application }; -static void handle_keypress_event(int state, quint32 native_scancode) { - if (native_scancode > keycode_entries) { - return; - } - uint16_t translated_code = xfree86_keycode_table[native_scancode]; - if (translated_code == 0) { - return; - } - keyboard_input(state, translated_code); +//static void handle_keypress_event(int state, quint32 native_scancode) { +// if (native_scancode > keycode_entries) { +// return; +// } +// uint16_t translated_code = xfree86_keycode_table[native_scancode]; +// if (translated_code == 0) { +// return; +// } +// keyboard_input(state, translated_code); - if (keyboard_isfsexit() > 0) { - plat_setfullscreen(0); - } +// if (keyboard_isfsexit() > 0) { +// plat_setfullscreen(0); +// } - if (keyboard_ismsexit() > 0) { - plat_mouse_capture(0); - } -} +// if (keyboard_ismsexit() > 0) { +// plat_mouse_capture(0); +// } +//} void MainWindow::on_actionFullscreen_triggered() { setFullscreen(true); diff --git a/src/qt/qt_mainwindow.hpp b/src/qt/qt_mainwindow.hpp index 25227d04b..3428ca098 100644 --- a/src/qt/qt_mainwindow.hpp +++ b/src/qt/qt_mainwindow.hpp @@ -8,37 +8,6 @@ namespace Ui { class MainWindow; } -class MainWindowLabel : public QLabel -{ - Q_OBJECT -public: - explicit MainWindowLabel(QWidget *parent = nullptr); - ~MainWindowLabel(); - - const QPoint& pos() { return pos_; } - Qt::MouseButtons buttons() { return buttons_; } -protected: - void mouseMoveEvent(QMouseEvent *event) override; - void mousePressEvent(QMouseEvent *event) override; - void mouseReleaseEvent(QMouseEvent *event) override; -private: - QPoint pos_; - Qt::MouseButtons buttons_; -}; - -class CentralWidget : public QWidget -{ - Q_OBJECT -public: - explicit CentralWidget(QWidget *parent = nullptr); - ~CentralWidget(); - - void setSizeHint(QSize size) { size_ = size; } - QSize sizeHint() const override { return size_; } -private: - QSize size_; -}; - class MainWindow : public QMainWindow { Q_OBJECT @@ -50,14 +19,15 @@ signals: void paint(const QImage& image); void resizeContents(int w, int h); void pollMouse(); + void updateStatusBarPanes(); + void updateStatusBarActivity(int tag, bool active); + void updateStatusBarEmpty(int tag, bool empty); void setFullscreen(bool state); void setMouseCapture(bool state); private slots: void on_actionFullscreen_triggered(); - -private slots: void on_actionSettings_triggered(); void on_actionExit_triggered(); void on_actionPause_triggered(); diff --git a/src/qt/qt_mainwindow.ui b/src/qt/qt_mainwindow.ui index 9e77eee55..0e854a8f2 100644 --- a/src/qt/qt_mainwindow.ui +++ b/src/qt/qt_mainwindow.ui @@ -7,7 +7,7 @@ 0 0 724 - 274 + 427 @@ -36,6 +36,9 @@ 0 + + + @@ -138,6 +141,14 @@ + + + MachineStatus + QWidget +
qt_machinestatus.hpp
+ 1 +
+
diff --git a/src/qt/qt_ui.cpp b/src/qt/qt_ui.cpp index 05e14332e..08614a48e 100644 --- a/src/qt/qt_ui.cpp +++ b/src/qt/qt_ui.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -17,43 +18,12 @@ extern "C" { #include <86box/plat.h> -void -ui_sb_update_icon_state(int tag, int state) -{ - -} - -void -ui_sb_update_icon(int tag, int active) -{ - -} - void plat_delay_ms(uint32_t count) { QThread::msleep(count); } -void -ui_sb_update_tip(int arg) -{ - -} - -void -ui_sb_update_panes() -{ - -} - -void ui_sb_bugui(char *str) -{ - -} - -void ui_sb_set_ready(int ready) {} - wchar_t* ui_window_title(wchar_t* str) { if (str == nullptr) { @@ -108,4 +78,35 @@ void ui_sb_set_text_w(wchar_t *wstr) { main_window->statusBar()->showMessage(QString::fromWCharArray(wstr)); } +void +ui_sb_update_tip(int arg) { + qDebug() << Q_FUNC_INFO << arg; +} + +void +ui_sb_update_panes() { + main_window->updateStatusBarPanes(); +} + +void ui_sb_bugui(char *str) { + main_window->statusBar()->showMessage(str); +} + +void ui_sb_set_ready(int ready) { + qDebug() << Q_FUNC_INFO << ready; +} + +void +ui_sb_update_icon_state(int tag, int state) { + if (main_window == nullptr) { + return; + } + main_window->updateStatusBarEmpty(tag, state > 0 ? true : false); +} + +void +ui_sb_update_icon(int tag, int active) { + main_window->updateStatusBarActivity(tag, active > 0 ? true : false); +} + } diff --git a/src/qt_resources.qrc b/src/qt_resources.qrc index b542fb305..8c66e5ca2 100644 --- a/src/qt_resources.qrc +++ b/src/qt_resources.qrc @@ -1,24 +1,47 @@ - win/icons/machine.ico + win/icons/cartridge.ico + win/icons/cartridge_empty.ico + win/icons/cassette.ico + win/icons/cassette_active.ico + win/icons/cassette_empty.ico + win/icons/cassette_empty_active.ico + win/icons/cdrom.ico + win/icons/cdrom_active.ico + win/icons/cdrom_disabled.ico + win/icons/cdrom_empty.ico + win/icons/cdrom_empty_active.ico win/icons/display.ico - win/icons/input_devices.ico - win/icons/sound.ico - win/icons/network.ico - win/icons/ports.ico - win/icons/storage_controllers.ico - win/icons/hard_disk.ico + win/icons/floppy_35.ico + win/icons/floppy_35_active.ico + win/icons/floppy_35_empty.ico + win/icons/floppy_35_empty_active.ico + win/icons/floppy_525.ico + win/icons/floppy_525_active.ico + win/icons/floppy_525_empty.ico + win/icons/floppy_525_empty_active.ico win/icons/floppy_and_cdrom_drives.ico + win/icons/floppy_disabled.ico + win/icons/hard_disk.ico + win/icons/hard_disk_active.ico + win/icons/input_devices.ico + win/icons/machine.ico + win/icons/mo.ico + win/icons/mo_active.ico + win/icons/mo_disabled.ico + win/icons/mo_empty.ico + win/icons/mo_empty_active.ico + win/icons/network.ico + win/icons/network_active.ico win/icons/other_peripherals.ico win/icons/other_removable_devices.ico - win/icons/floppy_35.ico - win/icons/floppy_525.ico - win/icons/floppy_disabled.ico - win/icons/cdrom_disabled.ico - win/icons/cdrom.ico - win/icons/mo.ico - win/icons/mo_disabled.ico + win/icons/ports.ico + win/icons/sound.ico + win/icons/storage_controllers.ico win/icons/zip.ico + win/icons/zip_active.ico win/icons/zip_disabled.ico + win/icons/zip_empty.ico + win/icons/zip_empty_active.ico From 859ee888cea555850beab86ad19239d356dbb740 Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Sun, 28 Nov 2021 20:50:02 +0100 Subject: [PATCH 05/30] fixed macos bundling (thanks to @dob205) --- src/CMakeLists.txt | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f5b3c99f8..bf5a46bc3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -129,6 +129,10 @@ if(APPLE) endif() +if(QT) + find_package(Qt5 COMPONENTS Core Widgets REQUIRED) +endif() + find_package(Freetype REQUIRED) include_directories(${FREETYPE_INCLUDE_DIRS}) if(APPLE) @@ -189,14 +193,28 @@ else() install(TARGETS 86Box) endif() - # adjustments for macOS app bundles if(APPLE) set(APPS ${CMAKE_CURRENT_BINARY_DIR}/86Box.app) install(CODE " + include(InstallRequiredSystemLibraries) include(BundleUtilities) fixup_bundle(\"${APPS}\" \"\" \"\")" COMPONENT Runtime) + + # needed for Qt packaging + # get the macdeployqt path + get_target_property(_qmake_executable Qt5::qmake IMPORTED_LOCATION) + get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY) + find_program(MACDEPLOYQT_EXECUTABLE macdeployqt HINTS "${_qt_bin_dir}") + + # excecute macdeployqt + add_custom_command(TARGET 86Box POST_BUILD + COMMAND "${MACDEPLOYQT_EXECUTABLE}" + "$/../.." + -always-overwrite + COMMENT "Running macdeployqt..." + ) endif() if(VCPKG_TOOLCHAIN) @@ -221,14 +239,14 @@ add_subdirectory(sio) add_subdirectory(scsi) add_subdirectory(sound) add_subdirectory(video) +if (APPLE) + add_subdirectory(mac) +endif() if (QT) add_subdirectory(qt) -elseif(APPLE) - add_subdirectory(mac) - add_subdirectory(unix) elseif(WIN32) add_subdirectory(win) else() add_subdirectory(unix) -endif() +endif() \ No newline at end of file From 6aef88c957f670a6ed47cf1e8c1cd92d3e5ec532 Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Sun, 28 Nov 2021 20:54:32 +0100 Subject: [PATCH 06/30] set project cpp-files to c++17 when qt is included, search and link to a threading library --- src/CMakeLists.txt | 5 ++++- src/qt/CMakeLists.txt | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bf5a46bc3..3a6477dfe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -130,6 +130,9 @@ if(APPLE) endif() if(QT) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) find_package(Qt5 COMPONENTS Core Widgets REQUIRED) endif() @@ -249,4 +252,4 @@ elseif(WIN32) add_subdirectory(win) else() add_subdirectory(unix) -endif() \ No newline at end of file +endif() diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index a9a80d042..0f119f8c5 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -4,6 +4,8 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) +find_package(Threads REQUIRED) + add_library(plat STATIC qt.c qt_main.cpp qt_platform.cpp qt_midi.cpp cpp11_thread.cpp) add_library(ui STATIC qt_ui.cpp @@ -78,10 +80,12 @@ target_link_libraries( plat PRIVATE Qt5::Widgets + Threads::Threads ) target_link_libraries( ui PRIVATE Qt5::Widgets + Threads::Threads ) From ccb851343ebefdfde3dfca3b4a21545cf67cecc7 Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Sun, 28 Nov 2021 21:00:41 +0100 Subject: [PATCH 07/30] replaced blitter function with a copy from win_sdl --- src/qt/qt_sdl.c | 47 ++++++++++++++++++----------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/src/qt/qt_sdl.c b/src/qt/qt_sdl.c index 30c2aff9d..072531bd6 100644 --- a/src/qt/qt_sdl.c +++ b/src/qt/qt_sdl.c @@ -85,7 +85,6 @@ static int cur_w, cur_h; static int cur_ww = 0, cur_wh = 0; static volatile int sdl_enabled = 0; static SDL_mutex* sdl_mutex = NULL; -static int blit_w = 0, blit_h = 0, blit_tex_updated = 0; static const uint16_t sdl_to_xt[0x200] = { @@ -302,8 +301,9 @@ sdl_stretch(int *w, int *h, int *x, int *y) static void sdl_blit(int x, int y, int w, int h) { + SDL_Rect r_src; void *pixeldata; - int pitch; + int ret, pitch; if (!sdl_enabled || (x < 0) || (y < 0) || (w <= 0) || (h <= 0) || (w > 2048) || (h > 2048) || (buffer32 == NULL) || (sdl_render == NULL) || (sdl_tex == NULL)) { video_blit_complete(); @@ -318,13 +318,24 @@ sdl_blit(int x, int y, int w, int h) if (screenshots) video_screenshot((uint32_t *) pixeldata, 0, 0, (2048 + 64)); - blit_w = w; - blit_h = h; - blit_tex_updated = 1; - SDL_UnlockTexture(sdl_tex); - SDL_UnlockMutex(sdl_mutex); + video_blit_complete(); + + SDL_RenderClear(sdl_render); + + r_src.x = 0; + r_src.y = 0; + r_src.w = w; + r_src.h = h; + + ret = SDL_RenderCopy(sdl_render, sdl_tex, &r_src, 0); + if (ret) + sdl_log("SDL: unable to copy texture to renderer (%s)\n", sdl_GetError()); + + SDL_RenderPresent(sdl_render); + + SDL_UnlockMutex(sdl_mutex); } @@ -698,28 +709,6 @@ enum sdl_main_status sdl_main() { } } - if (blit_tex_updated > 0) { - SDL_LockMutex(sdl_mutex); - int status = SDL_RenderClear(sdl_render); - if (status) { - sdl_log("SDL: unable to SDL_RenderClear (%s)\n", SDL_GetError()); - } - - r_src.x = 0; - r_src.y = 0; - r_src.w = blit_w; - r_src.h = blit_h; - - status = SDL_RenderCopy(sdl_render, sdl_tex, &r_src, 0); - if (status) { - sdl_log("SDL: unable to copy texture to renderer (%s)\n", SDL_GetError()); - } - - SDL_RenderPresent(sdl_render); - blit_tex_updated = 0; - SDL_UnlockMutex(sdl_mutex); - } - if (mouse_capture && keyboard_ismsexit()) { plat_mouse_capture(0); } From 6211834a219dff290a48f311605b0ae0f9d3b564 Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Sun, 28 Nov 2021 21:06:39 +0100 Subject: [PATCH 08/30] status updates are done --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fce7599c5..c66286455 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ 86Box ===== -**This Branch** I've added initial Qt support to 86Box because I wanted to +**This Branch:** I've added initial Qt support to 86Box because I wanted to have the configuration dialogs available on Linux, similar to what was available on PCem. @@ -11,13 +11,16 @@ Implemented * The settings dialog * Full screen switching * Keyboard and mouse - -Missing -------- * Status updates (activity lights) + +TODO +---- +* Emulation state and updates (titlebar in windows) * Display output options (like forced 4:3) * Entering full screen from within the emulated screen +Original Readme +=============== **86Box** is a low level x86 emulator that runs older operating systems and software designed for IBM PC systems and compatibles from 1981 through fairly recent system designs based on the PCI bus. Features From 22c89177d59c9b02224c212dc0227a8e8c81112f Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Mon, 29 Nov 2021 10:51:29 +0100 Subject: [PATCH 09/30] use real glib when compiling for Qt on Unix platforms --- src/network/slirp/CMakeLists.txt | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/network/slirp/CMakeLists.txt b/src/network/slirp/CMakeLists.txt index 2a6a8adb9..afc64a382 100644 --- a/src/network/slirp/CMakeLists.txt +++ b/src/network/slirp/CMakeLists.txt @@ -13,12 +13,28 @@ # Copyright 2020,2021 David Hrdlička. # -add_library(slirp STATIC tinyglib.c arp_table.c bootp.c cksum.c dnssearch.c if.c ip_icmp.c - ip_input.c ip_output.c mbuf.c misc.c sbuf.c slirp.c socket.c tcp_input.c - tcp_output.c tcp_subr.c tcp_timer.c udp.c util.c version.c) +add_library(slirp STATIC arp_table.c bootp.c cksum.c dnssearch.c if.c ip_icmp.c + ip_input.c ip_output.c mbuf.c misc.c sbuf.c slirp.c socket.c tcp_input.c + tcp_output.c tcp_subr.c tcp_timer.c udp.c util.c version.c) -#target_link_libraries(slirp wsock32 iphlpapi) -#target_link_libraries(slirp) if (CMAKE_SYSTEM_NAME MATCHES "Windows") -target_link_libraries(slirp wsock32 iphlpapi) + target_link_libraries(slirp wsock32 iphlpapi) +endif() + +if(QT) + if(UNIX AND NOT APPLE) + find_package(PkgConfig REQUIRED) + pkg_check_modules(GLIB_PKG glib-2.0) + if (GLIB_PKG_FOUND) + include_directories(${GLIB_PKG_INCLUDE_DIRS}) + target_link_libraries(slirp glib-2.0) + target_compile_definitions(slirp PRIVATE TINYGLIB_USE_GLIB) + else() + message(ERROR "GLib development headers are required when compiling with Qt on Unix") + endif() + else() + target_sources(slirp PRIVATE tinyglib.c) + endif() +else() + target_sources(slirp PRIVATE tinyglib.c) endif() From 97d844c3fc4d8e844fca07a504d8cb4459443b10 Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Mon, 29 Nov 2021 10:51:44 +0100 Subject: [PATCH 10/30] match g_strlcpy with glib proper --- src/network/slirp/misc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/slirp/misc.c b/src/network/slirp/misc.c index 4b520fc43..3bea32ac1 100644 --- a/src/network/slirp/misc.c +++ b/src/network/slirp/misc.c @@ -11,7 +11,7 @@ #ifdef BSD #define g_strlcpy strlcpy #else -extern int g_strlcpy(gchar* dest, const gchar* src, gsize dest_size); +extern gsize g_strlcpy(gchar* dest, const gchar* src, gsize dest_size); #endif #endif From 238fb7ef6222316c133904f032a49cb2ebdfb28b Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Mon, 29 Nov 2021 12:25:27 +0100 Subject: [PATCH 11/30] handle messageboxes inside qt's thread --- src/qt/qt_main.cpp | 7 +++---- src/qt/qt_mainwindow.cpp | 8 ++++++-- src/qt/qt_mainwindow.hpp | 1 + src/qt/qt_ui.cpp | 7 ++----- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/qt/qt_main.cpp b/src/qt/qt_main.cpp index 4d6062917..3fb6dd2ca 100644 --- a/src/qt/qt_main.cpp +++ b/src/qt/qt_main.cpp @@ -77,15 +77,14 @@ int main(int argc, char* argv[]) { QApplication app(argc, argv); elapsed_timer.start(); + main_window = new MainWindow(); + main_window->show(); + pc_init(argc, argv); if (! pc_init_modules()) { ui_msgbox_header(MBX_FATAL, VC(L"No ROMs found."), VC(L"86Box could not find any usable ROM images.\n\nPlease download a ROM set and extract it into the \"roms\" directory.")); return 6; } - - main_window = new MainWindow(); - main_window->show(); - pc_reset_hard_init(); /* Set the PAUSE mode depending on the renderer. */ diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index 8c179bb2c..4f7b9fc9b 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -3,8 +3,6 @@ extern "C" { #include <86box/86box.h> -//#include <86box/keyboard.h> -//#include <86box/mouse.h> #include <86box/config.h> #include <86box/plat.h> @@ -15,6 +13,7 @@ extern "C" { #include #include #include +#include #include "qt_settings.hpp" @@ -26,6 +25,11 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); + connect(this, &MainWindow::showMessage, this, [this](const QString& header, const QString& message) { + QMessageBox box(QMessageBox::Warning, header, message, QMessageBox::NoButton, this); + box.exec(); + }, Qt::BlockingQueuedConnection); + connect(this, &MainWindow::pollMouse, this, [] { sdl_mouse_poll(); }); diff --git a/src/qt/qt_mainwindow.hpp b/src/qt/qt_mainwindow.hpp index 3428ca098..a22088830 100644 --- a/src/qt/qt_mainwindow.hpp +++ b/src/qt/qt_mainwindow.hpp @@ -25,6 +25,7 @@ signals: void setFullscreen(bool state); void setMouseCapture(bool state); + void showMessage(const QString& header, const QString& message); private slots: void on_actionFullscreen_triggered(); diff --git a/src/qt/qt_ui.cpp b/src/qt/qt_ui.cpp index 08614a48e..decca9f24 100644 --- a/src/qt/qt_ui.cpp +++ b/src/qt/qt_ui.cpp @@ -2,7 +2,6 @@ #include #include -#include #include @@ -57,16 +56,14 @@ void plat_mouse_capture(int on) { main_window->setMouseCapture(on > 0 ? true : false); } -int ui_msgbox_header(int flags, void *header, void* message) -{ +int ui_msgbox_header(int flags, void *header, void* message) { if (header <= (void*)7168) header = plat_get_string(reinterpret_cast(header)); if (message <= (void*)7168) message = plat_get_string(reinterpret_cast(message)); auto hdr = QString::fromWCharArray(reinterpret_cast(header)); auto msg = QString::fromWCharArray(reinterpret_cast(message)); - QMessageBox box(QMessageBox::Warning, hdr, msg); - box.exec(); + main_window->showMessage(hdr, msg); return 0; } From 8385051f6a6a834bb9a55cf0c3c9c52f1d683f26 Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Mon, 29 Nov 2021 17:25:31 +0100 Subject: [PATCH 12/30] fixup showMessage from both qt thread and external thread, while blocking --- src/qt/qt_mainwindow.cpp | 19 +++++++++++++++---- src/qt/qt_mainwindow.hpp | 4 +++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index 4f7b9fc9b..34f3a0340 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -12,6 +12,7 @@ extern "C" { #include #include #include +#include #include #include @@ -25,10 +26,7 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); - connect(this, &MainWindow::showMessage, this, [this](const QString& header, const QString& message) { - QMessageBox box(QMessageBox::Warning, header, message, QMessageBox::NoButton, this); - box.exec(); - }, Qt::BlockingQueuedConnection); + connect(this, &MainWindow::showMessageForNonQtThread, this, &MainWindow::showMessage_, Qt::BlockingQueuedConnection); connect(this, &MainWindow::pollMouse, this, [] { sdl_mouse_poll(); @@ -288,3 +286,16 @@ static const uint16_t xfree86_keycode_table[keycode_entries] = { void MainWindow::on_actionFullscreen_triggered() { setFullscreen(true); } + +void MainWindow::showMessage(const QString& header, const QString& message) { + if (QThread::currentThread() == this->thread()) { + showMessage_(header, message); + } else { + emit showMessageForNonQtThread(header, message); + } +} + +void MainWindow::showMessage_(const QString &header, const QString &message) { + QMessageBox box(QMessageBox::Warning, header, message, QMessageBox::NoButton, this); + box.exec(); +} diff --git a/src/qt/qt_mainwindow.hpp b/src/qt/qt_mainwindow.hpp index a22088830..655a8edc8 100644 --- a/src/qt/qt_mainwindow.hpp +++ b/src/qt/qt_mainwindow.hpp @@ -15,6 +15,8 @@ class MainWindow : public QMainWindow public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); + + void showMessage(const QString& header, const QString& message); signals: void paint(const QImage& image); void resizeContents(int w, int h); @@ -25,8 +27,8 @@ signals: void setFullscreen(bool state); void setMouseCapture(bool state); - void showMessage(const QString& header, const QString& message); + void showMessageForNonQtThread(const QString& header, const QString& message); private slots: void on_actionFullscreen_triggered(); void on_actionSettings_triggered(); From 668887a30abf0adb0c382c1e8bff34bcf47f6bf9 Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Mon, 29 Nov 2021 19:03:00 +0100 Subject: [PATCH 13/30] missed showMessage_ declaration --- src/qt/qt_mainwindow.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qt/qt_mainwindow.hpp b/src/qt/qt_mainwindow.hpp index 655a8edc8..ba7867120 100644 --- a/src/qt/qt_mainwindow.hpp +++ b/src/qt/qt_mainwindow.hpp @@ -40,6 +40,7 @@ private slots: void on_actionRight_CTRL_is_left_ALT_triggered(); void on_actionKeyboard_requires_capture_triggered(); + void showMessage_(const QString& header, const QString& message); private: struct DeltaPos { int x = 0; From 0108c93e2d88055df129f3b8669a21273218032f Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Tue, 30 Nov 2021 16:26:49 +0600 Subject: [PATCH 14/30] Render emulator output on main window Introduce hardware renderer --- src/qt/CMakeLists.txt | 14 + src/qt/cocoa_mouse.hpp | 10 + src/qt/macos_event_filter.mm | 107 ++++++ src/qt/qt_gleswidget.cpp | 154 ++++++++ src/qt/qt_gleswidget.hpp | 41 ++ src/qt/qt_main.cpp | 11 + src/qt/qt_mainwindow.cpp | 703 +++++++++++++++++++++++++++-------- src/qt/qt_mainwindow.hpp | 6 + src/qt/qt_ui.cpp | 5 + 9 files changed, 888 insertions(+), 163 deletions(-) create mode 100644 src/qt/cocoa_mouse.hpp create mode 100644 src/qt/macos_event_filter.mm create mode 100644 src/qt/qt_gleswidget.cpp create mode 100644 src/qt/qt_gleswidget.hpp diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index 0f119f8c5..9bda243b2 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -6,6 +6,11 @@ set(CMAKE_AUTORCC ON) find_package(Threads REQUIRED) +set(UI_EXTRA_FILES) +if (APPLE) + set(UI_EXTRA_FILES "macos_event_filter.mm") +endif() + add_library(plat STATIC qt.c qt_main.cpp qt_platform.cpp qt_midi.cpp cpp11_thread.cpp) add_library(ui STATIC qt_ui.cpp @@ -18,6 +23,10 @@ add_library(ui STATIC qt_machinestatus.cpp qt_machinestatus.hpp qt_machinestatus.ui + qt_gleswidget.cpp + qt_gleswidget.hpp + + ${UI_EXTRA_FILES} qt_settings.cpp qt_settings.hpp @@ -89,3 +98,8 @@ target_link_libraries( Qt5::Widgets Threads::Threads ) + +if (NOT APPLE) + find_package(X11 REQUIRED) + target_link_libraries(ui PRIVATE X11::X11) +endif() \ No newline at end of file diff --git a/src/qt/cocoa_mouse.hpp b/src/qt/cocoa_mouse.hpp new file mode 100644 index 000000000..83939a233 --- /dev/null +++ b/src/qt/cocoa_mouse.hpp @@ -0,0 +1,10 @@ +#include +#include + +class CocoaEventFilter : public QAbstractNativeEventFilter +{ +public: + CocoaEventFilter() {}; + ~CocoaEventFilter(); + virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override; +}; \ No newline at end of file diff --git a/src/qt/macos_event_filter.mm b/src/qt/macos_event_filter.mm new file mode 100644 index 000000000..e942b5f49 --- /dev/null +++ b/src/qt/macos_event_filter.mm @@ -0,0 +1,107 @@ +#include +#include "86box/plat.h" +#include "cocoa_mouse.hpp" +#import +extern "C" +{ +#include <86box/86box.h> +#include <86box/keyboard.h> +#include <86box/mouse.h> +#include <86box/config.h> +//#include <86box/plat.h> +#include <86box/plat_dynld.h> +#include <86box/device.h> +#include <86box/timer.h> +#include <86box/ui.h> +#include <86box/video.h> +extern int mouse_capture; +} + +typedef struct mouseinputdata +{ + int deltax, deltay, deltaz; + int mousebuttons; +} mouseinputdata; + +extern SDL_mutex* mousemutex; +extern mouseinputdata mousedata; + +CocoaEventFilter::~CocoaEventFilter() +{ + +} + +bool CocoaEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result) +{ + if (mouse_capture) + { + if (eventType == "mac_generic_NSEvent") + { + NSEvent* event = (NSEvent*)message; + if ([event type] == NSEventTypeMouseMoved) + { + SDL_LockMutex(mousemutex); + mousedata.deltax += [event deltaX]; + mousedata.deltay += [event deltaY]; + SDL_UnlockMutex(mousemutex); + return true; + } + if ([event type] == NSEventTypeScrollWheel) + { + SDL_LockMutex(mousemutex); + mousedata.deltaz += [event deltaY]; + SDL_UnlockMutex(mousemutex); + return true; + } + switch ([event type]) + { + default: return false; + case NSEventTypeLeftMouseDown: + { + SDL_LockMutex(mousemutex); + mousedata.mousebuttons |= 1; + SDL_UnlockMutex(mousemutex); + break; + } + case NSEventTypeLeftMouseUp: + { + SDL_LockMutex(mousemutex); + mousedata.mousebuttons &= ~1; + SDL_UnlockMutex(mousemutex); + break; + } + case NSEventTypeRightMouseDown: + { + SDL_LockMutex(mousemutex); + mousedata.mousebuttons |= 2; + SDL_UnlockMutex(mousemutex); + break; + } + case NSEventTypeRightMouseUp: + { + SDL_LockMutex(mousemutex); + mousedata.mousebuttons &= ~2; + SDL_UnlockMutex(mousemutex); + break; + } + case NSEventTypeOtherMouseDown: + { + SDL_LockMutex(mousemutex); + mousedata.mousebuttons |= 4; + SDL_UnlockMutex(mousemutex); + break; + } + case NSEventTypeOtherMouseUp: + { + if (mouse_get_buttons() < 3) { plat_mouse_capture(0); return true; } + SDL_LockMutex(mousemutex); + mousedata.mousebuttons &= ~4; + SDL_UnlockMutex(mousemutex); + break; + } + } + return true; + } + } + return false; +} diff --git a/src/qt/qt_gleswidget.cpp b/src/qt/qt_gleswidget.cpp new file mode 100644 index 000000000..d7078db45 --- /dev/null +++ b/src/qt/qt_gleswidget.cpp @@ -0,0 +1,154 @@ +#include +#include +#include "qt_gleswidget.hpp" +#ifdef __APPLE__ +#include +#endif + +extern "C" +{ +#include <86box/mouse.h> +#include <86box/plat.h> +#include <86box/video.h> +} + +typedef struct mouseinputdata +{ + int deltax, deltay, deltaz; + int mousebuttons; +} mouseinputdata; + +mouseinputdata mousedata; +SDL_mutex* mousemutex; + +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 qt_mouse_poll() +{ + SDL_LockMutex(mousemutex); + mouse_x = mousedata.deltax; + mouse_y = mousedata.deltay; + mouse_z = mousedata.deltaz; + mousedata.deltax = mousedata.deltay = mousedata.deltaz = 0; + mouse_buttons = mousedata.mousebuttons; + SDL_UnlockMutex(mousemutex); +} + +void GLESWidget::resizeGL(int w, int h) +{ + glViewport(0, 0, w, h); +} + +void GLESWidget::initializeGL() +{ + initializeOpenGLFunctions(); +} +void GLESWidget::paintGL() +{ + QPainter painter(this); + //painter.fillRect(rect, QColor(0, 0, 0)); + painter.drawImage(QRect(0, 0, width(), height()), m_image.convertToFormat(QImage::Format_RGBA8888), QRect(sx, sy, sw, sh)); + painter.end(); + update(); +} + +void GLESWidget::mouseReleaseEvent(QMouseEvent *event) +{ + if (this->geometry().contains(event->pos()) && event->button() == Qt::LeftButton && !mouse_capture) + { + this->grabMouse(); + return; + } + if (mouse_capture && event->button() == Qt::MiddleButton && mouse_get_buttons() < 3) + { + this->releaseMouse(); + return; + } + if (mouse_capture) + { + SDL_LockMutex(mousemutex); + mousedata.mousebuttons &= ~event->button(); + SDL_UnlockMutex(mousemutex); + } +} +void GLESWidget::mousePressEvent(QMouseEvent *event) +{ + if (mouse_capture) + { + SDL_LockMutex(mousemutex); + mousedata.mousebuttons |= event->button(); + SDL_UnlockMutex(mousemutex); + } +} +void GLESWidget::wheelEvent(QWheelEvent *event) +{ + if (mouse_capture) + { + SDL_LockMutex(mousemutex); + mousedata.deltay += event->pixelDelta().y(); + SDL_UnlockMutex(mousemutex); + } +} + +int ignoreNextMouseEvent = 0; +void GLESWidget::mouseMoveEvent(QMouseEvent *event) +{ + if (!mouse_capture) { event->ignore(); return; } +#ifdef __APPLE__ + event->accept(); + return; +#else + static QPoint oldPos = QCursor::pos(); + if (ignoreNextMouseEvent) { oldPos = event->pos(); ignoreNextMouseEvent--; event->accept(); return; } + SDL_LockMutex(mousemutex); + mousedata.deltax += event->pos().x() - oldPos.x(); + mousedata.deltay += event->pos().y() - oldPos.y(); + SDL_UnlockMutex(mousemutex); + QCursor::setPos(mapToGlobal(QPoint(width() / 2, height() / 2))); + oldPos = event->pos(); + ignoreNextMouseEvent = 1; +#endif +} + +void GLESWidget::qt_real_blit(int x, int y, int w, int h) +{ + // printf("Offpainter thread ID: %X\n", SDL_ThreadID()); + if ((w <= 0) || (h <= 0) || (w > 2048) || (h > 2048) || (buffer32 == NULL)) + { + video_blit_complete(); + return; + } + sx = x; + sy = y; + sw = this->w = w; + sh = this->h = h; + auto imagebits = m_image.bits(); + for (int y1 = y; y1 < (y + h - 1); y1++) + { + auto scanline = imagebits + (y1 * (2048 + 64) * 4); + video_copy(scanline + (x * 4), &(buffer32->line[y1][x]), w * 4); + } + if (screenshots) + { + video_screenshot((uint32_t *)imagebits, 0, 0, 2048 + 64); + } + video_blit_complete(); +} \ No newline at end of file diff --git a/src/qt/qt_gleswidget.hpp b/src/qt/qt_gleswidget.hpp new file mode 100644 index 000000000..8689cdf1d --- /dev/null +++ b/src/qt/qt_gleswidget.hpp @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include + +class GLESWidget : public QOpenGLWidget, protected QOpenGLFunctions +{ + Q_OBJECT + +private: + QImage m_image{QSize(2048 + 64, 2048 + 64), QImage::Format_RGB32}; + int x, y, w, h, sx, sy, sw, sh; +public: + void resizeGL(int w, int h) override; + void initializeGL() override; + void paintGL() override; + GLESWidget(QWidget* parent = nullptr) + : QOpenGLWidget(parent) + { + setMinimumSize(16, 16); + } + ~GLESWidget() + { + makeCurrent(); + } + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void wheelEvent(QWheelEvent *event) override; + void keyPressEvent(QKeyEvent* event) override + { + event->ignore(); + } + void keyReleaseEvent(QKeyEvent* event) override + { + event->ignore(); + } +public slots: + void qt_real_blit(int x, int y, int w, int h); +}; \ No newline at end of file diff --git a/src/qt/qt_main.cpp b/src/qt/qt_main.cpp index 3fb6dd2ca..56a486ac8 100644 --- a/src/qt/qt_main.cpp +++ b/src/qt/qt_main.cpp @@ -11,8 +11,11 @@ #include +#include "SDL.h" +#include "SDL_mutex.h" #include "qt_mainwindow.hpp" #include "qt_sdl.h" +#include "cocoa_mouse.hpp" // Void Cast @@ -73,9 +76,16 @@ main_thread_fn() is_quit = 1; } +extern SDL_mutex* mousemutex; int main(int argc, char* argv[]) { QApplication app(argc, argv); +#ifdef __APPLE__ + CocoaEventFilter cocoafilter; + app.installNativeEventFilter(&cocoafilter); +#endif elapsed_timer.start(); + SDL_Init(SDL_INIT_TIMER); + mousemutex = SDL_CreateMutex(); main_window = new MainWindow(); main_window->show(); @@ -91,6 +101,7 @@ int main(int argc, char* argv[]) { // plat_pause(0); /* Initialize the rendering window, or fullscreen. */ + QTimer::singleShot(50, []() { plat_resize(640, 480); } ); auto main_thread = std::thread([] { main_thread_fn(); }); diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index 34f3a0340..fdde83dc5 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -1,10 +1,14 @@ #include "qt_mainwindow.hpp" +#include "ui_qt_machinestatus.h" #include "ui_qt_mainwindow.h" +#include extern "C" { #include <86box/86box.h> #include <86box/config.h> +#include <86box/keyboard.h> #include <86box/plat.h> +#include <86box/video.h> #include "qt_sdl.h" }; @@ -16,7 +20,19 @@ extern "C" { #include #include +#include + #include "qt_settings.hpp" +#include "qt_gleswidget.hpp" + +#ifdef __unix__ +#include +#include +#endif + +extern void qt_mouse_poll(); +extern void qt_mouse_capture(int); +extern "C" void qt_blit(int x, int y, int w, int h); MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), @@ -25,25 +41,36 @@ MainWindow::MainWindow(QWidget *parent) : Q_INIT_RESOURCE(qt_resources); ui->setupUi(this); + video_setblit(qt_blit); + + this->hw_widget = new GLESWidget(this); + this->hw_widget->setMouseTracking(true); + this->hw_widget->setGeometry(QRect(this->menuWidget() ? QPoint(0,this->menuWidget()->size().height()) : QPoint(0,0), QSize(640, 480))); + this->setCentralWidget(this->hw_widget); + connect(this, &MainWindow::blitToWidget, (GLESWidget*)this->hw_widget, &GLESWidget::qt_real_blit); connect(this, &MainWindow::showMessageForNonQtThread, this, &MainWindow::showMessage_, Qt::BlockingQueuedConnection); connect(this, &MainWindow::pollMouse, this, [] { - sdl_mouse_poll(); + qt_mouse_poll(); }); - connect(this, &MainWindow::setMouseCapture, this, [](bool state) { + connect(this, &MainWindow::setMouseCapture, this, [this](bool state) { mouse_capture = state ? 1 : 0; - sdl_mouse_capture(mouse_capture); + qt_mouse_capture(mouse_capture); + if (mouse_capture) hw_widget->grabMouse(); + else hw_widget->releaseMouse(); }); - connect(this, &MainWindow::setFullscreen, this, [](bool state) { + connect(this, &MainWindow::setFullscreen, this, [this](bool state) { video_fullscreen = state ? 1 : 0; - sdl_set_fs(video_fullscreen); + //sdl_set_fs(video_fullscreen); + this->setFullscreen(state); }); connect(this, &MainWindow::resizeContents, this, [this](int w, int h) { - sdl_resize(w, h); + this->hw_widget->resize(w, h); + this->resize(w, h + menuBar()->height() + statusBar()->height()); }); connect(ui->menubar, &QMenuBar::triggered, this, [] { @@ -56,7 +83,7 @@ MainWindow::MainWindow(QWidget *parent) : ui->actionKeyboard_requires_capture->setChecked(kbd_req_capture); ui->actionRight_CTRL_is_left_ALT->setChecked(rctrl_is_lalt); - +#if 0 sdl_inits(); sdl_timer = new QTimer(this); connect(sdl_timer, &QTimer::timeout, this, [] { @@ -66,10 +93,13 @@ MainWindow::MainWindow(QWidget *parent) : } }); sdl_timer->start(5); +#endif } MainWindow::~MainWindow() { - sdl_close(); + //sdl_close(); + startblit(); + //delete hw_widget; delete ui; } @@ -123,165 +153,494 @@ void MainWindow::on_actionSettings_triggered() { } } -static const int keycode_entries = 136; -// xmodmap -pk -static const uint16_t xfree86_keycode_table[keycode_entries] = { - /* 0 */ 0, - /* 1 */ 0, - /* 2 */ 0, - /* 3 */ 0, - /* 4 */ 0, - /* 5 */ 0, - /* 6 */ 0, - /* 7 */ 0, - /* 8 */ 0, - /* 9 */ 0x01, // Esc - /* 10 */ 0x02, // 1 - /* 11 */ 0x03, // 2 - /* 12 */ 0x04, // 3 - /* 13 */ 0x05, // 4 - /* 14 */ 0x06, // 5 - /* 15 */ 0x07, // 6 - /* 16 */ 0x08, // 7 - /* 17 */ 0x09, // 8 - /* 18 */ 0x0a, // 9 - /* 19 */ 0x0b, // 0 - /* 20 */ 0x0c, // - - /* 21 */ 0x0d, // = - /* 22 */ 0x0e, // BackSpace - /* 23 */ 0x0f, // Tab - /* 24 */ 0x10, // Q - /* 25 */ 0x11, // W - /* 26 */ 0x12, // E - /* 27 */ 0x13, // R - /* 28 */ 0x14, // T - /* 29 */ 0x15, // Y - /* 30 */ 0x16, // U - /* 31 */ 0x17, // I - /* 32 */ 0x18, // O - /* 33 */ 0x19, // P - /* 34 */ 0x1a, // [ - /* 35 */ 0x1b, // ] - /* 36 */ 0x1c, // Return - /* 37 */ 0x1d, // LeftControl - /* 38 */ 0x1e, // A - /* 39 */ 0x1f, // S - /* 40 */ 0x20, // D - /* 41 */ 0x21, // F - /* 42 */ 0x22, // G - /* 43 */ 0x23, // H - /* 44 */ 0x24, // J - /* 45 */ 0x25, // K - /* 46 */ 0x26, // L - /* 47 */ 0x27, // ; - /* 48 */ 0x28, // ' - /* 49 */ 0x29, // ` (???) - /* 50 */ 0x2a, // LeftShift - /* 51 */ 0x2b, // BackSlash - /* 52 */ 0x2c, // Z - /* 53 */ 0x2d, // X - /* 54 */ 0x2e, // C - /* 55 */ 0x2f, // V - /* 56 */ 0x30, // B - /* 57 */ 0x31, // N - /* 58 */ 0x32, // M - /* 59 */ 0x33, // , - /* 60 */ 0x34, // . - /* 61 */ 0x35, // - - /* 62 */ 0x36, // RightShift - /* 63 */ 0x37, // KeyPad Multiply - /* 64 */ 0x38, // LeftAlt - /* 65 */ 0x39, // Space - /* 66 */ 0x3a, // CapsLock - /* 67 */ 0x3b, // F01 - /* 68 */ 0x3c, // F02 - /* 69 */ 0x3d, // F03 - /* 70 */ 0x3e, // F04 - /* 71 */ 0x3f, // F05 - /* 72 */ 0x40, // F06 - /* 73 */ 0x41, // F07 - /* 74 */ 0x42, // F08 - /* 75 */ 0x43, // F09 - /* 76 */ 0x44, // F10 - /* 77 */ 0x45, // NumLock - /* 78 */ 0x46, // ScrollLock - /* 79 */ 0x47, // KeyPad7 - /* 80 */ 0x48, // KeyPad8 - /* 81 */ 0x49, // KeyPad9 - /* 82 */ 0x4a, // KeyPad Minus - /* 83 */ 0x4b, // KeyPad4 - /* 84 */ 0x4c, // KeyPad5 - /* 85 */ 0x4d, // KeyPad6 - /* 86 */ 0x4e, // KeyPad Plus - /* 87 */ 0x4f, // KeyPad1 - /* 88 */ 0x50, // KeyPad2 - /* 89 */ 0x51, // KeyPad3 - /* 90 */ 0x52, // KeyPad0 - /* 91 */ 0x53, // KeyPad . - /* 92 */ 0, - /* 93 */ 0, - /* 94 */ 0x56, // Less/Great - /* 95 */ 0x57, // F11 - /* 96 */ 0x58, // F12 - /* 97 */ 0, - /* 98 */ 0, - /* 99 */ 0, - /* 100 */ 0, - /* 101 */ 0, - /* 102 */ 0, - /* 103 */ 0, - /* 104 */ 0x11c, // KeyPad Enter - /* 105 */ 0x11d, // RightControl - /* 106 */ 0x135, // KeyPad Divide - /* 107 */ 0x137, // PrintScreen / SysReq - /* 108 */ 0x138, // RightAlt - /* 109 */ 0, - /* 110 */ 0x147, // Home - /* 111 */ 0x148, // Up - /* 112 */ 0x149, // PageUp - /* 113 */ 0x14b, // Left - /* 114 */ 0x14d, // Right - /* 115 */ 0x14f, // End - /* 116 */ 0x150, // Down - /* 117 */ 0x151, // PageDown - /* 118 */ 0x152, // Insert - /* 119 */ 0x153, // Delete - /* 120 */ 0, - /* 121 */ 0, - /* 122 */ 0, - /* 123 */ 0, - /* 124 */ 0, - /* 125 */ 0, - /* 126 */ 0, - /* 127 */ 0, - /* 128 */ 0, - /* 129 */ 0, - /* 130 */ 0, - /* 131 */ 0, - /* 132 */ 0, - /* 133 */ 0x15b, // SuperLeft - /* 134 */ 0x15c, // SuperRight - /* 135 */ 0x15d, // Application +std::array 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, }; -//static void handle_keypress_event(int state, quint32 native_scancode) { -// if (native_scancode > keycode_entries) { -// return; -// } -// uint16_t translated_code = xfree86_keycode_table[native_scancode]; -// if (translated_code == 0) { -// return; -// } -// keyboard_input(state, translated_code); +std::array 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, + 0x54, + 0x55, + 0x56, + 0x57, + 0x58, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0x11C, + 0x11D, + 0x135, + 0x137, + 0x138, + 0, + 0x147, + 0x148, + 0x149, + 0x14B, + 0x14D, + 0x14F, + 0x150, + 0x151, + 0x152, + 0x153 +}; -// if (keyboard_isfsexit() > 0) { -// plat_setfullscreen(0); -// } +std::array x11_to_xt_vnc +{ + 0, + 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, +}; -// if (keyboard_ismsexit() > 0) { -// plat_mouse_capture(0); -// } -//} +std::array 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, +}; + +static std::array& selected_keycode = x11_to_xt_base; + +uint16_t x11_keycode_to_keysym(uint32_t keycode) +{ +#ifdef __APPLE__ + return darwin_to_xt[keycode]; +#else + 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; + } + } + return selected_keycode[keycode]; +#endif +} void MainWindow::on_actionFullscreen_triggered() { setFullscreen(true); @@ -299,3 +658,21 @@ void MainWindow::showMessage_(const QString &header, const QString &message) { QMessageBox box(QMessageBox::Warning, header, message, QMessageBox::NoButton, this); box.exec(); } + +void MainWindow::keyPressEvent(QKeyEvent* event) +{ +#ifdef __APPLE__ + keyboard_input(1, x11_keycode_to_keysym(event->nativeVirtualKey())); +#else + keyboard_input(1, x11_keycode_to_keysym(event->nativeScanCode())); +#endif +} + +void MainWindow::keyReleaseEvent(QKeyEvent* event) +{ +#ifdef __APPLE__ + keyboard_input(0, x11_keycode_to_keysym(event->nativeVirtualKey())); +#else + keyboard_input(0, x11_keycode_to_keysym(event->nativeScanCode())); +#endif +} diff --git a/src/qt/qt_mainwindow.hpp b/src/qt/qt_mainwindow.hpp index ba7867120..7a075a174 100644 --- a/src/qt/qt_mainwindow.hpp +++ b/src/qt/qt_mainwindow.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace Ui { class MainWindow; @@ -19,6 +20,7 @@ public: void showMessage(const QString& header, const QString& message); signals: void paint(const QImage& image); + void blitToWidget(int x, int y, int w, int h); void resizeContents(int w, int h); void pollMouse(); void updateStatusBarPanes(); @@ -41,6 +43,9 @@ private slots: void on_actionKeyboard_requires_capture_triggered(); void showMessage_(const QString& header, const QString& message); +protected: + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; private: struct DeltaPos { int x = 0; @@ -51,6 +56,7 @@ private: DeltaPos mouseDelta; QWindow* sdl_wrapped_window; QWidget* sdl_wrapped_widget; + QWidget* hw_widget; QTimer* sdl_timer; }; diff --git a/src/qt/qt_ui.cpp b/src/qt/qt_ui.cpp index decca9f24..1e24ac792 100644 --- a/src/qt/qt_ui.cpp +++ b/src/qt/qt_ui.cpp @@ -36,6 +36,11 @@ wchar_t* ui_window_title(wchar_t* str) return str; } +extern "C" void qt_blit(int x, int y, int w, int h) +{ + main_window->blitToWidget(x, y, w, h); +} + void mouse_poll() { main_window->pollMouse(); } From 563869a06e314f922266e7bb0e6f688a884fee93 Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Wed, 1 Dec 2021 01:00:41 +0600 Subject: [PATCH 15/30] Fix macOS builds --- src/include/86box/plat.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/include/86box/plat.h b/src/include/86box/plat.h index ca116a684..de5daf1ff 100644 --- a/src/include/86box/plat.h +++ b/src/include/86box/plat.h @@ -20,6 +20,8 @@ #ifndef EMU_PLAT_H # define EMU_PLAT_H +#include "86box/device.h" +#include "86box/machine.h" #ifndef GLOBAL # define GLOBAL extern #endif @@ -158,6 +160,25 @@ extern int ioctl_open(uint8_t id, char d); extern void ioctl_reset(uint8_t id); extern void ioctl_close(uint8_t id); +#ifdef __APPLE__ +#define thread_t plat_thread_t +#define event_t plat_event_t +#define mutex_t plat_mutex_t + +#define thread_create plat_thread_create +#define thread_wait plat_thread_wait +#define thread_create_event plat_thread_create_event +#define thread_set_event plat_thread_set_event +#define thread_reset_event plat_thread_reset_event +#define thread_wait_event plat_thread_wait_event +#define thread_destroy_event plat_thread_destroy_event + +#define thread_create_mutex plat_thread_create_mutex +#define thread_create_mutex_with_spin_count plat_thread_create_mutex_with_spin_count +#define thread_close_mutex plat_thread_close_mutex +#define thread_wait_mutex plat_thread_wait_mutex +#define thread_release_mutex plat_thread_release_mutex +#endif /* Thread support. */ typedef void thread_t; From de88caf4b19313673cb63537640ea46c8042c9bc Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Wed, 1 Dec 2021 01:11:06 +0600 Subject: [PATCH 16/30] Accidentally left mouse capture half-baked --- src/qt/qt_gleswidget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qt/qt_gleswidget.cpp b/src/qt/qt_gleswidget.cpp index d7078db45..cf44b80fe 100644 --- a/src/qt/qt_gleswidget.cpp +++ b/src/qt/qt_gleswidget.cpp @@ -74,12 +74,12 @@ void GLESWidget::mouseReleaseEvent(QMouseEvent *event) { if (this->geometry().contains(event->pos()) && event->button() == Qt::LeftButton && !mouse_capture) { - this->grabMouse(); + plat_mouse_capture(1); return; } if (mouse_capture && event->button() == Qt::MiddleButton && mouse_get_buttons() < 3) { - this->releaseMouse(); + plat_mouse_capture(0); return; } if (mouse_capture) From 6fb22514fae2787fc9c2b1c81d4e6544d5b107ae Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Tue, 30 Nov 2021 20:18:13 +0100 Subject: [PATCH 17/30] simplified macos_event_filter.mm inclusion, link to X11 if UNIX-and-not-Apple --- src/qt/CMakeLists.txt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index 9bda243b2..bf4b561c0 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -6,11 +6,6 @@ set(CMAKE_AUTORCC ON) find_package(Threads REQUIRED) -set(UI_EXTRA_FILES) -if (APPLE) - set(UI_EXTRA_FILES "macos_event_filter.mm") -endif() - add_library(plat STATIC qt.c qt_main.cpp qt_platform.cpp qt_midi.cpp cpp11_thread.cpp) add_library(ui STATIC qt_ui.cpp @@ -26,8 +21,6 @@ add_library(ui STATIC qt_gleswidget.cpp qt_gleswidget.hpp - ${UI_EXTRA_FILES} - qt_settings.cpp qt_settings.hpp qt_settings.ui @@ -84,6 +77,9 @@ add_library(ui STATIC ../qt_resources.qrc ) +if (APPLE) + target_sources(ui PRIVATE macos_event_filter.mm) +endif() target_link_libraries( plat @@ -99,7 +95,7 @@ target_link_libraries( Threads::Threads ) -if (NOT APPLE) +if (UNIX AND NOT APPLE) find_package(X11 REQUIRED) target_link_libraries(ui PRIVATE X11::X11) -endif() \ No newline at end of file +endif() From 4cd5cb76d946315f342d4fb82afdce73b76a901a Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Tue, 30 Nov 2021 20:20:53 +0100 Subject: [PATCH 18/30] return null when dynld_module fails to load library --- src/qt/qt_platform.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/qt/qt_platform.cpp b/src/qt/qt_platform.cpp index 45366560c..11cded7be 100644 --- a/src/qt/qt_platform.cpp +++ b/src/qt/qt_platform.cpp @@ -356,6 +356,8 @@ void* dynld_module(const char *name, dllimp_t *table) return nullptr; } } + } else { + return nullptr; } return lib.release(); From 82f6f6f5e8177b051245c66555159cd7d476ae2e Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Tue, 30 Nov 2021 20:34:07 +0100 Subject: [PATCH 19/30] fixed up dynld_module, actually loads and sets pointers correct now (tested against libpcap) --- src/qt/qt_platform.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/qt/qt_platform.cpp b/src/qt/qt_platform.cpp index 11cded7be..47a771442 100644 --- a/src/qt/qt_platform.cpp +++ b/src/qt/qt_platform.cpp @@ -347,14 +347,23 @@ void plat_language_code_r(uint32_t lcid, char* outbuf, int len) { void* dynld_module(const char *name, dllimp_t *table) { - auto lib = std::unique_ptr(new QLibrary(name)); + QString libraryName = name; + QFileInfo fi(libraryName); + QStringList removeSuffixes = {"dll", "dylib", "so"}; + if (removeSuffixes.contains(fi.suffix())) { + libraryName = fi.completeBaseName(); + } + + auto lib = std::unique_ptr(new QLibrary(libraryName)); if (lib->load()) { for (auto imp = table; imp->name != nullptr; imp++) { - if ((imp->func = reinterpret_cast(lib->resolve(imp->name))) != nullptr) - { + auto ptr = lib->resolve(imp->name); + if (ptr == nullptr) { return nullptr; } + auto imp_ptr = reinterpret_cast(imp->func); + *imp_ptr = reinterpret_cast(ptr); } } else { return nullptr; From 9cdb4f90cade793fc9110eac8257c82aca58f707 Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Tue, 30 Nov 2021 20:52:14 +0100 Subject: [PATCH 20/30] only need to signal update when we've been blitted --- src/qt/qt_gleswidget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qt/qt_gleswidget.cpp b/src/qt/qt_gleswidget.cpp index cf44b80fe..71ad041d6 100644 --- a/src/qt/qt_gleswidget.cpp +++ b/src/qt/qt_gleswidget.cpp @@ -67,7 +67,6 @@ void GLESWidget::paintGL() //painter.fillRect(rect, QColor(0, 0, 0)); painter.drawImage(QRect(0, 0, width(), height()), m_image.convertToFormat(QImage::Format_RGBA8888), QRect(sx, sy, sw, sh)); painter.end(); - update(); } void GLESWidget::mouseReleaseEvent(QMouseEvent *event) @@ -151,4 +150,5 @@ void GLESWidget::qt_real_blit(int x, int y, int w, int h) video_screenshot((uint32_t *)imagebits, 0, 0, 2048 + 64); } video_blit_complete(); -} \ No newline at end of file + update(); +} From 7e38ff7d4da627e07da9adf496d5a0e893861274 Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Tue, 30 Nov 2021 21:04:55 +0100 Subject: [PATCH 21/30] removed sdl from gleswidget, use slots to update mouse movement --- src/qt/qt_gleswidget.cpp | 21 +-------------------- src/qt/qt_gleswidget.hpp | 11 ++++++++++- src/qt/qt_main.cpp | 2 -- src/qt/qt_mainwindow.cpp | 15 +-------------- src/qt/qt_mainwindow.hpp | 4 +++- 5 files changed, 15 insertions(+), 38 deletions(-) diff --git a/src/qt/qt_gleswidget.cpp b/src/qt/qt_gleswidget.cpp index 71ad041d6..3cd91c5a7 100644 --- a/src/qt/qt_gleswidget.cpp +++ b/src/qt/qt_gleswidget.cpp @@ -1,4 +1,3 @@ -#include #include #include "qt_gleswidget.hpp" #ifdef __APPLE__ @@ -12,14 +11,6 @@ extern "C" #include <86box/video.h> } -typedef struct mouseinputdata -{ - int deltax, deltay, deltaz; - int mousebuttons; -} mouseinputdata; - -mouseinputdata mousedata; -SDL_mutex* mousemutex; void qt_mouse_capture(int on) @@ -41,15 +32,13 @@ qt_mouse_capture(int on) return; } -void qt_mouse_poll() +void GLESWidget::qt_mouse_poll() { - SDL_LockMutex(mousemutex); mouse_x = mousedata.deltax; mouse_y = mousedata.deltay; mouse_z = mousedata.deltaz; mousedata.deltax = mousedata.deltay = mousedata.deltaz = 0; mouse_buttons = mousedata.mousebuttons; - SDL_UnlockMutex(mousemutex); } void GLESWidget::resizeGL(int w, int h) @@ -83,27 +72,21 @@ void GLESWidget::mouseReleaseEvent(QMouseEvent *event) } if (mouse_capture) { - SDL_LockMutex(mousemutex); mousedata.mousebuttons &= ~event->button(); - SDL_UnlockMutex(mousemutex); } } void GLESWidget::mousePressEvent(QMouseEvent *event) { if (mouse_capture) { - SDL_LockMutex(mousemutex); mousedata.mousebuttons |= event->button(); - SDL_UnlockMutex(mousemutex); } } void GLESWidget::wheelEvent(QWheelEvent *event) { if (mouse_capture) { - SDL_LockMutex(mousemutex); mousedata.deltay += event->pixelDelta().y(); - SDL_UnlockMutex(mousemutex); } } @@ -117,10 +100,8 @@ void GLESWidget::mouseMoveEvent(QMouseEvent *event) #else static QPoint oldPos = QCursor::pos(); if (ignoreNextMouseEvent) { oldPos = event->pos(); ignoreNextMouseEvent--; event->accept(); return; } - SDL_LockMutex(mousemutex); mousedata.deltax += event->pos().x() - oldPos.x(); mousedata.deltay += event->pos().y() - oldPos.y(); - SDL_UnlockMutex(mousemutex); QCursor::setPos(mapToGlobal(QPoint(width() / 2, height() / 2))); oldPos = event->pos(); ignoreNextMouseEvent = 1; diff --git a/src/qt/qt_gleswidget.hpp b/src/qt/qt_gleswidget.hpp index 8689cdf1d..76701c917 100644 --- a/src/qt/qt_gleswidget.hpp +++ b/src/qt/qt_gleswidget.hpp @@ -38,4 +38,13 @@ public: } public slots: void qt_real_blit(int x, int y, int w, int h); -}; \ No newline at end of file + void qt_mouse_poll(); + +private: + struct mouseinputdata { + int deltax, deltay, deltaz; + int mousebuttons; + }; + mouseinputdata mousedata; + +}; diff --git a/src/qt/qt_main.cpp b/src/qt/qt_main.cpp index 56a486ac8..bbdbc6dc3 100644 --- a/src/qt/qt_main.cpp +++ b/src/qt/qt_main.cpp @@ -76,7 +76,6 @@ main_thread_fn() is_quit = 1; } -extern SDL_mutex* mousemutex; int main(int argc, char* argv[]) { QApplication app(argc, argv); #ifdef __APPLE__ @@ -85,7 +84,6 @@ int main(int argc, char* argv[]) { #endif elapsed_timer.start(); SDL_Init(SDL_INIT_TIMER); - mousemutex = SDL_CreateMutex(); main_window = new MainWindow(); main_window->show(); diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index fdde83dc5..44c07fc58 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -51,9 +51,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(this, &MainWindow::showMessageForNonQtThread, this, &MainWindow::showMessage_, Qt::BlockingQueuedConnection); - connect(this, &MainWindow::pollMouse, this, [] { - qt_mouse_poll(); - }); + connect(this, &MainWindow::pollMouse, hw_widget, &GLESWidget::qt_mouse_poll); connect(this, &MainWindow::setMouseCapture, this, [this](bool state) { mouse_capture = state ? 1 : 0; @@ -83,17 +81,6 @@ MainWindow::MainWindow(QWidget *parent) : ui->actionKeyboard_requires_capture->setChecked(kbd_req_capture); ui->actionRight_CTRL_is_left_ALT->setChecked(rctrl_is_lalt); -#if 0 - sdl_inits(); - sdl_timer = new QTimer(this); - connect(sdl_timer, &QTimer::timeout, this, [] { - auto status = sdl_main(); - if (status == SdlMainQuit) { - QApplication::quit(); - } - }); - sdl_timer->start(5); -#endif } MainWindow::~MainWindow() { diff --git a/src/qt/qt_mainwindow.hpp b/src/qt/qt_mainwindow.hpp index 7a075a174..173caa8b1 100644 --- a/src/qt/qt_mainwindow.hpp +++ b/src/qt/qt_mainwindow.hpp @@ -9,6 +9,8 @@ namespace Ui { class MainWindow; } +class GLESWidget; + class MainWindow : public QMainWindow { Q_OBJECT @@ -56,7 +58,7 @@ private: DeltaPos mouseDelta; QWindow* sdl_wrapped_window; QWidget* sdl_wrapped_widget; - QWidget* hw_widget; + GLESWidget* hw_widget; QTimer* sdl_timer; }; From 6c2d7bef4a657e2f86649042c4dc65f827ee5390 Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Tue, 30 Nov 2021 21:18:25 +0100 Subject: [PATCH 22/30] promote the gleswidget to centralwidget via the .ui file --- src/qt/qt_gleswidget.hpp | 4 +++- src/qt/qt_mainwindow.cpp | 23 ++++++++++------------- src/qt/qt_mainwindow.hpp | 12 ------------ src/qt/qt_mainwindow.ui | 9 ++++----- 4 files changed, 17 insertions(+), 31 deletions(-) diff --git a/src/qt/qt_gleswidget.hpp b/src/qt/qt_gleswidget.hpp index 76701c917..2ac888724 100644 --- a/src/qt/qt_gleswidget.hpp +++ b/src/qt/qt_gleswidget.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #include #include @@ -16,7 +18,7 @@ public: void initializeGL() override; void paintGL() override; GLESWidget(QWidget* parent = nullptr) - : QOpenGLWidget(parent) + : QOpenGLWidget(parent), QOpenGLFunctions() { setMinimumSize(16, 16); } diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index 44c07fc58..c7c643ce6 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -42,22 +42,19 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); video_setblit(qt_blit); + ui->glesWidget->setMouseTracking(true); - this->hw_widget = new GLESWidget(this); - this->hw_widget->setMouseTracking(true); - this->hw_widget->setGeometry(QRect(this->menuWidget() ? QPoint(0,this->menuWidget()->size().height()) : QPoint(0,0), QSize(640, 480))); - this->setCentralWidget(this->hw_widget); - connect(this, &MainWindow::blitToWidget, (GLESWidget*)this->hw_widget, &GLESWidget::qt_real_blit); + connect(this, &MainWindow::blitToWidget, ui->glesWidget, &GLESWidget::qt_real_blit); connect(this, &MainWindow::showMessageForNonQtThread, this, &MainWindow::showMessage_, Qt::BlockingQueuedConnection); - connect(this, &MainWindow::pollMouse, hw_widget, &GLESWidget::qt_mouse_poll); + connect(this, &MainWindow::pollMouse, ui->glesWidget, &GLESWidget::qt_mouse_poll); connect(this, &MainWindow::setMouseCapture, this, [this](bool state) { mouse_capture = state ? 1 : 0; qt_mouse_capture(mouse_capture); - if (mouse_capture) hw_widget->grabMouse(); - else hw_widget->releaseMouse(); + if (mouse_capture) ui->glesWidget->grabMouse(); + else ui->glesWidget->releaseMouse(); }); connect(this, &MainWindow::setFullscreen, this, [this](bool state) { @@ -67,17 +64,17 @@ MainWindow::MainWindow(QWidget *parent) : }); connect(this, &MainWindow::resizeContents, this, [this](int w, int h) { - this->hw_widget->resize(w, h); - this->resize(w, h + menuBar()->height() + statusBar()->height()); + ui->glesWidget->resize(w, h); + resize(w, h + menuBar()->height() + statusBar()->height()); }); connect(ui->menubar, &QMenuBar::triggered, this, [] { config_save(); }); - connect(this, &MainWindow::updateStatusBarPanes, ui->machineStatus, &MachineStatus::refresh); - connect(this, &MainWindow::updateStatusBarActivity, ui->machineStatus, &MachineStatus::setActivity); - connect(this, &MainWindow::updateStatusBarEmpty, ui->machineStatus, &MachineStatus::setEmpty); +// connect(this, &MainWindow::updateStatusBarPanes, ui->machineStatus, &MachineStatus::refresh); +// connect(this, &MainWindow::updateStatusBarActivity, ui->machineStatus, &MachineStatus::setActivity); +// connect(this, &MainWindow::updateStatusBarEmpty, ui->machineStatus, &MachineStatus::setEmpty); ui->actionKeyboard_requires_capture->setChecked(kbd_req_capture); ui->actionRight_CTRL_is_left_ALT->setChecked(rctrl_is_lalt); diff --git a/src/qt/qt_mainwindow.hpp b/src/qt/qt_mainwindow.hpp index 173caa8b1..d3356b8e6 100644 --- a/src/qt/qt_mainwindow.hpp +++ b/src/qt/qt_mainwindow.hpp @@ -9,8 +9,6 @@ namespace Ui { class MainWindow; } -class GLESWidget; - class MainWindow : public QMainWindow { Q_OBJECT @@ -49,17 +47,7 @@ protected: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; private: - struct DeltaPos { - int x = 0; - int y = 0; - int z = 0; - }; Ui::MainWindow *ui; - DeltaPos mouseDelta; - QWindow* sdl_wrapped_window; - QWidget* sdl_wrapped_widget; - GLESWidget* hw_widget; - QTimer* sdl_timer; }; #endif // QT_MAINWINDOW_HPP diff --git a/src/qt/qt_mainwindow.ui b/src/qt/qt_mainwindow.ui index 0e854a8f2..9c055ef62 100644 --- a/src/qt/qt_mainwindow.ui +++ b/src/qt/qt_mainwindow.ui @@ -37,7 +37,7 @@ 0 - + @@ -143,10 +143,9 @@ - MachineStatus - QWidget -
qt_machinestatus.hpp
- 1 + GLESWidget + QOpenGLWidget +
qt_gleswidget.hpp
From 358feb19ccbc60375cb1a5cbc007ac02cb9a5960 Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Tue, 30 Nov 2021 21:26:51 +0100 Subject: [PATCH 23/30] fixed up fullscreen --- src/qt/qt_mainwindow.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index c7c643ce6..292a50d00 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -57,12 +57,6 @@ MainWindow::MainWindow(QWidget *parent) : else ui->glesWidget->releaseMouse(); }); - connect(this, &MainWindow::setFullscreen, this, [this](bool state) { - video_fullscreen = state ? 1 : 0; - //sdl_set_fs(video_fullscreen); - this->setFullscreen(state); - }); - connect(this, &MainWindow::resizeContents, this, [this](int w, int h) { ui->glesWidget->resize(w, h); resize(w, h + menuBar()->height() + statusBar()->height()); @@ -627,7 +621,13 @@ uint16_t x11_keycode_to_keysym(uint32_t keycode) } void MainWindow::on_actionFullscreen_triggered() { - setFullscreen(true); + if (video_fullscreen > 0) { + showNormal(); + video_fullscreen = 0; + } else { + showFullScreen(); + video_fullscreen = 1; + } } void MainWindow::showMessage(const QString& header, const QString& message) { From a6f8e6b68e802cf79a0a20d2036ef720ce17d21a Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Tue, 30 Nov 2021 21:29:58 +0100 Subject: [PATCH 24/30] plugged in keyboard_isfsexit and keyboard_ismsexit to ungrab mouse and leave fullscreen --- src/qt/qt_mainwindow.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index 292a50d00..6e17e3cc8 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -650,6 +650,14 @@ void MainWindow::keyPressEvent(QKeyEvent* event) #else keyboard_input(1, x11_keycode_to_keysym(event->nativeScanCode())); #endif + + if (keyboard_isfsexit()) { + ui->actionFullscreen->trigger(); + } + + if (keyboard_ismsexit()) { + plat_mouse_capture(0); + } } void MainWindow::keyReleaseEvent(QKeyEvent* event) From cee1c93d49e35234aa2b665f8d907fba75d12404 Mon Sep 17 00:00:00 2001 From: "Joakim L. Gilje" Date: Tue, 30 Nov 2021 22:06:41 +0100 Subject: [PATCH 25/30] refactored machinestatus to update the qstatusbar --- src/qt/qt_machinestatus.cpp | 108 +++++++++++++++++++++++------------- src/qt/qt_machinestatus.hpp | 14 ++--- src/qt/qt_machinestatus.ui | 44 --------------- src/qt/qt_mainwindow.cpp | 16 ++++-- src/qt/qt_mainwindow.hpp | 5 +- 5 files changed, 89 insertions(+), 98 deletions(-) delete mode 100644 src/qt/qt_machinestatus.ui diff --git a/src/qt/qt_machinestatus.cpp b/src/qt/qt_machinestatus.cpp index d876343d7..d61e2f04a 100644 --- a/src/qt/qt_machinestatus.cpp +++ b/src/qt/qt_machinestatus.cpp @@ -1,5 +1,4 @@ #include "qt_machinestatus.hpp" -#include "ui_qt_machinestatus.h" extern "C" { #define EMU_CPU_H // superhack - don't want timer.h to include cpu.h here, and some combo is preventing a compile @@ -28,6 +27,7 @@ extern uint64_t tsc; #include #include #include +#include namespace { struct PixmapSetActive { @@ -62,29 +62,35 @@ namespace { }; struct StateActive { - QLabel label; + std::unique_ptr label; QTimer timer; PixmapSetActive* pixmaps = nullptr; bool active = false; void setActive(bool b) { active = b; - label.setPixmap(active ? pixmaps->active : pixmaps->normal); + if (! label) { + return; + } + label->setPixmap(active ? pixmaps->active : pixmaps->normal); timer.start(75); } }; struct StateEmpty { - QLabel label; + std::unique_ptr label; PixmapSetEmpty* pixmaps = nullptr; bool empty = false; void setEmpty(bool e) { empty = e; - label.setPixmap(empty ? pixmaps->empty : pixmaps->normal); + if (! label) { + return; + } + label->setPixmap(empty ? pixmaps->empty : pixmaps->normal); } }; struct StateEmptyActive { - QLabel label; + std::unique_ptr label; QTimer timer; PixmapSetEmptyActive* pixmaps = nullptr; bool empty = false; @@ -100,15 +106,18 @@ namespace { refresh(); } void refresh() { + if (! label) { + return; + } if (empty) { - label.setPixmap(active ? pixmaps->empty_active : pixmaps->empty); + label->setPixmap(active ? pixmaps->empty_active : pixmaps->empty); } else { - label.setPixmap(active ? pixmaps->active : pixmaps->normal); + label->setPixmap(active ? pixmaps->active : pixmaps->normal); } } }; - static const QSize pixmap_size(32, 32); + static const QSize pixmap_size(16, 16); static const QString pixmap_empty = QStringLiteral("_empty"); static const QString pixmap_active = QStringLiteral("_active"); static const QString pixmap_empty_active = QStringLiteral("_empty_active"); @@ -174,7 +183,6 @@ struct MachineStatus::States { QObject::connect(&h.timer, &QTimer::timeout, parent, [&]{ h.setActive(false); }); } net.pixmaps = &pixmaps.net; - sound.setPixmap(pixmaps.sound); } std::array cartridge; @@ -185,21 +193,16 @@ struct MachineStatus::States { std::array mo; std::array hdds; StateActive net; - QLabel sound; + std::unique_ptr sound; }; -MachineStatus::MachineStatus(QWidget *parent) : - QWidget(parent), - ui(new Ui::MachineStatus) +MachineStatus::MachineStatus(QObject *parent) : + QObject(parent) { - ui->setupUi(this); d = std::make_unique(this); } -MachineStatus::~MachineStatus() -{ - delete ui; -} +MachineStatus::~MachineStatus() = default; static int hdd_count(int bus) { int c = 0; @@ -214,7 +217,7 @@ static int hdd_count(int bus) { return(c); } -void MachineStatus::refresh() { +void MachineStatus::refresh(QStatusBar* sbar) { bool has_cart = machines[machine].flags & MACHINE_CARTRIDGE; bool has_mfm = machines[machine].flags & MACHINE_MFM; bool has_xta = machines[machine].flags & MACHINE_XTA; @@ -229,26 +232,45 @@ void MachineStatus::refresh() { int c_scsi = hdd_count(HDD_BUS_SCSI); int do_net = (network_type == NET_TYPE_NONE) || (network_card == 0); - while (ui->statusIcons->count() > 0) { - auto item = ui->statusIcons->itemAt(0); - ui->statusIcons->removeItem(item); - delete item; + sbar->removeWidget(d->cassette.label.get()); + for (int i = 0; i < 2; ++i) { + sbar->removeWidget(d->cartridge[i].label.get()); } + for (size_t i = 0; i < FDD_NUM; ++i) { + sbar->removeWidget(d->fdd[i].label.get()); + } + for (size_t i = 0; i < CDROM_NUM; i++) { + sbar->removeWidget(d->cdrom[i].label.get()); + } + for (size_t i = 0; i < ZIP_NUM; i++) { + sbar->removeWidget(d->zip[i].label.get()); + } + for (size_t i = 0; i < MO_NUM; i++) { + sbar->removeWidget(d->mo[i].label.get()); + } + for (size_t i = 0; i < HDD_BUS_USB; i++) { + sbar->removeWidget(d->hdds[i].label.get()); + } + sbar->removeWidget(d->net.label.get()); + sbar->removeWidget(d->sound.get()); if (cassette_enable) { + d->cassette.label = std::make_unique(); d->cassette.setEmpty(QString(cassette_fname).isEmpty()); - ui->statusIcons->addWidget(&d->cassette.label); + sbar->addWidget(d->cassette.label.get()); } if (has_cart) { for (int i = 0; i < 2; ++i) { + d->cartridge[i].label = std::make_unique(); d->cartridge[i].setEmpty(QString(cart_fns[i]).isEmpty()); - ui->statusIcons->addWidget(&d->cartridge[i].label); + sbar->addWidget(d->cartridge[i].label.get()); } } for (size_t i = 0; i < FDD_NUM; ++i) { if (fdd_get_type(i) != 0) { + d->fdd[i].label = std::make_unique(); int t = fdd_get_type(i); if (t == 0) { d->fdd[i].pixmaps = &d->pixmaps.floppy_disabled; @@ -259,7 +281,7 @@ void MachineStatus::refresh() { } d->fdd[i].setEmpty(QString(floppyfns[i]).isEmpty()); d->fdd[i].setActive(false); - ui->statusIcons->addWidget(&d->fdd[i].label); + sbar->addWidget(d->fdd[i].label.get()); } } @@ -274,9 +296,10 @@ void MachineStatus::refresh() { (scsi_card_current[2] == 0) && (scsi_card_current[3] == 0)) continue; if (cdrom[i].bus_type != 0) { + d->cdrom[i].label = std::make_unique(); d->cdrom[i].setEmpty(cdrom[i].host_drive != 200 || QString(cdrom[i].image_path).isEmpty()); d->cdrom[i].setActive(false); - ui->statusIcons->addWidget(&d->cdrom[i].label); + sbar->addWidget(d->cdrom[i].label.get()); } } for (size_t i = 0; i < ZIP_NUM; i++) { @@ -289,9 +312,10 @@ void MachineStatus::refresh() { (scsi_card_current[2] == 0) && (scsi_card_current[3] == 0)) continue; if (zip_drives[i].bus_type != 0) { + d->zip[i].label = std::make_unique(); d->zip[i].setEmpty(QString(zip_drives[i].image_path).isEmpty()); d->zip[i].setActive(false); - ui->statusIcons->addWidget(&d->zip[i].label); + sbar->addWidget(d->zip[i].label.get()); } } for (size_t i = 0; i < MO_NUM; i++) { @@ -304,40 +328,48 @@ void MachineStatus::refresh() { (scsi_card_current[2] == 0) && (scsi_card_current[3] == 0)) continue; if (mo_drives[i].bus_type != 0) { + d->mo[i].label = std::make_unique(); d->mo[i].setEmpty(QString(mo_drives[i].image_path).isEmpty()); d->mo[i].setActive(false); - ui->statusIcons->addWidget(&d->mo[i].label); + sbar->addWidget(d->mo[i].label.get()); } } if ((has_mfm || hdc_name == QStringLiteral("st506")) && c_mfm > 0) { + d->hdds[HDD_BUS_MFM].label = std::make_unique(); d->hdds[HDD_BUS_MFM].setActive(false); - ui->statusIcons->addWidget(&d->hdds[HDD_BUS_MFM].label); + sbar->addWidget(d->hdds[HDD_BUS_MFM].label.get()); } if ((has_esdi || hdc_name == QStringLiteral("esdi")) && c_esdi > 0) { + d->hdds[HDD_BUS_ESDI].label = std::make_unique(); d->hdds[HDD_BUS_ESDI].setActive(false); - ui->statusIcons->addWidget(&d->hdds[HDD_BUS_ESDI].label); + sbar->addWidget(d->hdds[HDD_BUS_ESDI].label.get()); } if ((has_xta || hdc_name == QStringLiteral("xta")) && c_xta > 0) { + d->hdds[HDD_BUS_XTA].label = std::make_unique(); d->hdds[HDD_BUS_XTA].setActive(false); - ui->statusIcons->addWidget(&d->hdds[HDD_BUS_XTA].label); + sbar->addWidget(d->hdds[HDD_BUS_XTA].label.get()); } if ((has_ide || hdc_name == QStringLiteral("xtide") || hdc_name == QStringLiteral("ide")) && c_ide > 0) { + d->hdds[HDD_BUS_IDE].label = std::make_unique(); d->hdds[HDD_BUS_IDE].setActive(false); - ui->statusIcons->addWidget(&d->hdds[HDD_BUS_IDE].label); + sbar->addWidget(d->hdds[HDD_BUS_IDE].label.get()); } if ((has_scsi || (scsi_card_current[0] != 0) || (scsi_card_current[1] != 0) || (scsi_card_current[2] != 0) || (scsi_card_current[3] != 0)) && c_scsi > 0) { + d->hdds[HDD_BUS_SCSI].label = std::make_unique(); d->hdds[HDD_BUS_SCSI].setActive(false); - ui->statusIcons->addWidget(&d->hdds[HDD_BUS_SCSI].label); + sbar->addWidget(d->hdds[HDD_BUS_SCSI].label.get()); } if (do_net) { + d->net.label = std::make_unique(); d->net.setActive(false); - ui->statusIcons->addWidget(&d->net.label); + sbar->addWidget(d->net.label.get()); } - ui->statusIcons->addWidget(&d->sound); - ui->statusIcons->addItem(new QSpacerItem(20, 40, QSizePolicy::Expanding, QSizePolicy::Minimum)); + d->sound = std::make_unique(); + d->sound->setPixmap(d->pixmaps.sound); + sbar->addWidget(d->sound.get()); } void MachineStatus::setActivity(int tag, bool active) { diff --git a/src/qt/qt_machinestatus.hpp b/src/qt/qt_machinestatus.hpp index e3f0a6409..7ca07255c 100644 --- a/src/qt/qt_machinestatus.hpp +++ b/src/qt/qt_machinestatus.hpp @@ -3,28 +3,22 @@ #include -class QLabel; +class QStatusBar; -namespace Ui { -class MachineStatus; -} - -class MachineStatus : public QWidget +class MachineStatus : public QObject { Q_OBJECT public: - explicit MachineStatus(QWidget *parent = nullptr); + explicit MachineStatus(QObject *parent = nullptr); ~MachineStatus(); public slots: - void refresh(); + void refresh(QStatusBar* sbar); void setActivity(int tag, bool active); void setEmpty(int tag, bool active); private: - Ui::MachineStatus *ui; - struct States; std::unique_ptr d; }; diff --git a/src/qt/qt_machinestatus.ui b/src/qt/qt_machinestatus.ui deleted file mode 100644 index b05f1f8c3..000000000 --- a/src/qt/qt_machinestatus.ui +++ /dev/null @@ -1,44 +0,0 @@ - - - MachineStatus - - - - 0 - 0 - 400 - 300 - - - - Form - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - - - - diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index 6e17e3cc8..e8c56e530 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -1,7 +1,5 @@ #include "qt_mainwindow.hpp" -#include "ui_qt_machinestatus.h" #include "ui_qt_mainwindow.h" -#include extern "C" { #include <86box/86box.h> @@ -24,6 +22,7 @@ extern "C" { #include "qt_settings.hpp" #include "qt_gleswidget.hpp" +#include "qt_machinestatus.hpp" #ifdef __unix__ #include @@ -39,6 +38,7 @@ MainWindow::MainWindow(QWidget *parent) : ui(new Ui::MainWindow) { Q_INIT_RESOURCE(qt_resources); + status = std::make_unique(this); ui->setupUi(this); video_setblit(qt_blit); @@ -66,9 +66,15 @@ MainWindow::MainWindow(QWidget *parent) : config_save(); }); -// connect(this, &MainWindow::updateStatusBarPanes, ui->machineStatus, &MachineStatus::refresh); -// connect(this, &MainWindow::updateStatusBarActivity, ui->machineStatus, &MachineStatus::setActivity); -// connect(this, &MainWindow::updateStatusBarEmpty, ui->machineStatus, &MachineStatus::setEmpty); + connect(this, &MainWindow::updateStatusBarPanes, this, [this] { + status->refresh(ui->statusbar); + }); + connect(this, &MainWindow::updateStatusBarActivity, this, [this](int i, bool b) { + status->setActivity(i, b); + }); + connect(this, &MainWindow::updateStatusBarEmpty, this, [this](int i, bool b) { + status->setEmpty(i, b); + }); ui->actionKeyboard_requires_capture->setChecked(kbd_req_capture); ui->actionRight_CTRL_is_left_ALT->setChecked(rctrl_is_lalt); diff --git a/src/qt/qt_mainwindow.hpp b/src/qt/qt_mainwindow.hpp index d3356b8e6..9f3dee140 100644 --- a/src/qt/qt_mainwindow.hpp +++ b/src/qt/qt_mainwindow.hpp @@ -3,12 +3,14 @@ #include #include -#include +#include namespace Ui { class MainWindow; } +class MachineStatus; + class MainWindow : public QMainWindow { Q_OBJECT @@ -48,6 +50,7 @@ protected: void keyReleaseEvent(QKeyEvent* event) override; private: Ui::MainWindow *ui; + std::unique_ptr status; }; #endif // QT_MAINWINDOW_HPP From 2297c337513d4facaf7fe326b647fe100818cc7d Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Wed, 1 Dec 2021 11:15:42 +0600 Subject: [PATCH 26/30] Fix macOS builds --- src/qt/CMakeLists.txt | 2 +- src/qt/cocoa_mouse.hpp | 2 +- src/qt/macos_event_filter.mm | 31 ++++++++++++------------------- src/qt/qt_gleswidget.cpp | 6 +++++- src/qt/qt_machinestatus.cpp | 2 ++ src/qt/qt_platform.cpp | 2 +- 6 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index bf4b561c0..b3ce7121b 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -17,7 +17,7 @@ add_library(ui STATIC qt_mainwindow.ui qt_machinestatus.cpp qt_machinestatus.hpp - qt_machinestatus.ui + #qt_machinestatus.ui qt_gleswidget.cpp qt_gleswidget.hpp diff --git a/src/qt/cocoa_mouse.hpp b/src/qt/cocoa_mouse.hpp index 83939a233..6d481fd89 100644 --- a/src/qt/cocoa_mouse.hpp +++ b/src/qt/cocoa_mouse.hpp @@ -7,4 +7,4 @@ public: CocoaEventFilter() {}; ~CocoaEventFilter(); virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override; -}; \ No newline at end of file +}; diff --git a/src/qt/macos_event_filter.mm b/src/qt/macos_event_filter.mm index e942b5f49..4dd7ef2ae 100644 --- a/src/qt/macos_event_filter.mm +++ b/src/qt/macos_event_filter.mm @@ -1,5 +1,5 @@ #include -#include "86box/plat.h" +//#include "86box/plat.h" #include "cocoa_mouse.hpp" #import extern "C" @@ -15,6 +15,7 @@ extern "C" #include <86box/ui.h> #include <86box/video.h> extern int mouse_capture; +extern void plat_mouse_capture(int); } typedef struct mouseinputdata @@ -23,8 +24,7 @@ typedef struct mouseinputdata int mousebuttons; } mouseinputdata; -extern SDL_mutex* mousemutex; -extern mouseinputdata mousedata; +static mouseinputdata mousedata; CocoaEventFilter::~CocoaEventFilter() { @@ -40,17 +40,13 @@ bool CocoaEventFilter::nativeEventFilter(const QByteArray &eventType, void *mess NSEvent* event = (NSEvent*)message; if ([event type] == NSEventTypeMouseMoved) { - SDL_LockMutex(mousemutex); mousedata.deltax += [event deltaX]; mousedata.deltay += [event deltaY]; - SDL_UnlockMutex(mousemutex); return true; } if ([event type] == NSEventTypeScrollWheel) { - SDL_LockMutex(mousemutex); mousedata.deltaz += [event deltaY]; - SDL_UnlockMutex(mousemutex); return true; } switch ([event type]) @@ -58,45 +54,33 @@ bool CocoaEventFilter::nativeEventFilter(const QByteArray &eventType, void *mess default: return false; case NSEventTypeLeftMouseDown: { - SDL_LockMutex(mousemutex); mousedata.mousebuttons |= 1; - SDL_UnlockMutex(mousemutex); break; } case NSEventTypeLeftMouseUp: { - SDL_LockMutex(mousemutex); mousedata.mousebuttons &= ~1; - SDL_UnlockMutex(mousemutex); break; } case NSEventTypeRightMouseDown: { - SDL_LockMutex(mousemutex); mousedata.mousebuttons |= 2; - SDL_UnlockMutex(mousemutex); break; } case NSEventTypeRightMouseUp: { - SDL_LockMutex(mousemutex); mousedata.mousebuttons &= ~2; - SDL_UnlockMutex(mousemutex); break; } case NSEventTypeOtherMouseDown: { - SDL_LockMutex(mousemutex); mousedata.mousebuttons |= 4; - SDL_UnlockMutex(mousemutex); break; } case NSEventTypeOtherMouseUp: { if (mouse_get_buttons() < 3) { plat_mouse_capture(0); return true; } - SDL_LockMutex(mousemutex); mousedata.mousebuttons &= ~4; - SDL_UnlockMutex(mousemutex); break; } } @@ -105,3 +89,12 @@ bool CocoaEventFilter::nativeEventFilter(const QByteArray &eventType, void *mess } return false; } + +extern "C" void macos_poll_mouse() +{ + mouse_x = mousedata.deltax; + mouse_y = mousedata.deltay; + mouse_z = mousedata.deltaz; + mousedata.deltax = mousedata.deltay = mousedata.deltaz = 0; + mouse_buttons = mousedata.mousebuttons; +} diff --git a/src/qt/qt_gleswidget.cpp b/src/qt/qt_gleswidget.cpp index 3cd91c5a7..24c9e4a36 100644 --- a/src/qt/qt_gleswidget.cpp +++ b/src/qt/qt_gleswidget.cpp @@ -11,7 +11,7 @@ extern "C" #include <86box/video.h> } - +extern "C" void macos_poll_mouse(); void qt_mouse_capture(int on) { @@ -34,11 +34,15 @@ qt_mouse_capture(int on) void GLESWidget::qt_mouse_poll() { +#ifdef __APPLE__ + return macos_poll_mouse(); +#else mouse_x = mousedata.deltax; mouse_y = mousedata.deltay; mouse_z = mousedata.deltaz; mousedata.deltax = mousedata.deltay = mousedata.deltaz = 0; mouse_buttons = mousedata.mousebuttons; +#endif } void GLESWidget::resizeGL(int w, int h) diff --git a/src/qt/qt_machinestatus.cpp b/src/qt/qt_machinestatus.cpp index d61e2f04a..b2c8d0ad1 100644 --- a/src/qt/qt_machinestatus.cpp +++ b/src/qt/qt_machinestatus.cpp @@ -29,6 +29,8 @@ extern uint64_t tsc; #include #include +#include + namespace { struct PixmapSetActive { QPixmap normal; diff --git a/src/qt/qt_platform.cpp b/src/qt/qt_platform.cpp index 47a771442..6be563814 100644 --- a/src/qt/qt_platform.cpp +++ b/src/qt/qt_platform.cpp @@ -283,10 +283,10 @@ plat_mmap(size_t size, uint8_t executable) void *ret = mmap(0, size, PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0), MAP_ANON | MAP_PRIVATE | (executable ? MAP_JIT : 0), 0, 0); #else void *ret = mmap(0, size, PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0), MAP_ANON | MAP_PRIVATE, 0, 0); +#endif auto retval = *reinterpret_cast(ret); return (retval < 0) ? nullptr : ret; #endif -#endif } void From ac52b32adb804d1c5b6b78f7a007b055773848d4 Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Wed, 1 Dec 2021 14:50:49 +0600 Subject: [PATCH 27/30] Keep emulator paused while settings is open Reduces crashes --- src/qt/qt_mainwindow.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index e8c56e530..f70e55147 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -116,7 +116,11 @@ void MainWindow::on_actionExit_triggered() { } void MainWindow::on_actionSettings_triggered() { - Settings settings; + int currentPause = dopause; + plat_pause(1); + Settings settings(this); + settings.setModal(true); + settings.setWindowModality(Qt::WindowModal); settings.exec(); switch (settings.result()) { @@ -135,6 +139,7 @@ void MainWindow::on_actionSettings_triggered() { case QDialog::Rejected: break; } + plat_pause(currentPause); } std::array x11_to_xt_base From 88452f7957ff6fd4304e25df6839bd82c82ce507 Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Wed, 1 Dec 2021 15:55:41 +0600 Subject: [PATCH 28/30] Implement title bar statistics --- src/qt/qt_main.cpp | 8 ++++++++ src/qt/qt_mainwindow.cpp | 31 +++++++++++++++++++++++++++++++ src/qt/qt_mainwindow.hpp | 6 ++++++ src/qt/qt_ui.cpp | 6 +++--- 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/qt/qt_main.cpp b/src/qt/qt_main.cpp index bbdbc6dc3..f011fd9f7 100644 --- a/src/qt/qt_main.cpp +++ b/src/qt/qt_main.cpp @@ -13,6 +13,7 @@ #include "SDL.h" #include "SDL_mutex.h" +#include "SDL_timer.h" #include "qt_mainwindow.hpp" #include "qt_sdl.h" #include "cocoa_mouse.hpp" @@ -76,6 +77,12 @@ main_thread_fn() is_quit = 1; } +uint32_t timer_onesec(uint32_t interval, void* param) +{ + pc_onesec(); + return interval; +} + int main(int argc, char* argv[]) { QApplication app(argc, argv); #ifdef __APPLE__ @@ -97,6 +104,7 @@ int main(int argc, char* argv[]) { /* Set the PAUSE mode depending on the renderer. */ // plat_pause(0); + SDL_AddTimer(1000, timer_onesec, nullptr); /* Initialize the rendering window, or fullscreen. */ QTimer::singleShot(50, []() { plat_resize(640, 480); } ); diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index f70e55147..9adad9656 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -48,6 +48,9 @@ MainWindow::MainWindow(QWidget *parent) : connect(this, &MainWindow::showMessageForNonQtThread, this, &MainWindow::showMessage_, Qt::BlockingQueuedConnection); + connect(this, &MainWindow::setTitleForNonQtThread, this, &MainWindow::setTitle_, Qt::BlockingQueuedConnection); + connect(this, &MainWindow::getTitleForNonQtThread, this, &MainWindow::getTitle_, Qt::BlockingQueuedConnection); + connect(this, &MainWindow::pollMouse, ui->glesWidget, &GLESWidget::qt_mouse_poll); connect(this, &MainWindow::setMouseCapture, this, [this](bool state) { @@ -641,6 +644,34 @@ void MainWindow::on_actionFullscreen_triggered() { } } +void MainWindow::setTitle_(const wchar_t *title) +{ + this->setWindowTitle(QString::fromWCharArray(title)); +} + +void MainWindow::setTitle(const wchar_t *title) +{ + if (QThread::currentThread() == this->thread()) { + setTitle_(title); + } else { + emit setTitleForNonQtThread(title); + } +} + +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); + } +} + void MainWindow::showMessage(const QString& header, const QString& message) { if (QThread::currentThread() == this->thread()) { showMessage_(header, message); diff --git a/src/qt/qt_mainwindow.hpp b/src/qt/qt_mainwindow.hpp index 9f3dee140..ba93df369 100644 --- a/src/qt/qt_mainwindow.hpp +++ b/src/qt/qt_mainwindow.hpp @@ -20,6 +20,8 @@ public: ~MainWindow(); void showMessage(const QString& header, const QString& message); + void setTitle(const wchar_t* title); + void getTitle(wchar_t* title); signals: void paint(const QImage& image); void blitToWidget(int x, int y, int w, int h); @@ -33,6 +35,8 @@ signals: void setMouseCapture(bool state); void showMessageForNonQtThread(const QString& header, const QString& message); + void setTitleForNonQtThread(const wchar_t* title); + void getTitleForNonQtThread(wchar_t* title); private slots: void on_actionFullscreen_triggered(); void on_actionSettings_triggered(); @@ -45,6 +49,8 @@ private slots: void on_actionKeyboard_requires_capture_triggered(); void showMessage_(const QString& header, const QString& message); + void setTitle_(const wchar_t* title); + void getTitle_(wchar_t* title); protected: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; diff --git a/src/qt/qt_ui.cpp b/src/qt/qt_ui.cpp index 1e24ac792..c4de70282 100644 --- a/src/qt/qt_ui.cpp +++ b/src/qt/qt_ui.cpp @@ -27,11 +27,11 @@ wchar_t* ui_window_title(wchar_t* str) { if (str == nullptr) { static wchar_t title[512]; - int chars = main_window->windowTitle().toWCharArray(title); - title[chars] = 0; + memset(title, 0, sizeof(title)); + main_window->getTitle(title); str = title; } else { - main_window->setWindowTitle(QString::fromWCharArray(str)); + main_window->setTitle(str); } return str; } From 23dbb85fa8c267fb008a4a15dde8ad5f91b939e0 Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Thu, 2 Dec 2021 00:47:02 +0600 Subject: [PATCH 29/30] Significantly improve renderer performance --- src/qt/qt_gleswidget.cpp | 19 +++++++++++++------ src/qt/qt_gleswidget.hpp | 8 +++++++- src/qt/qt_mainwindow.cpp | 7 +++++-- src/qt/qt_mainwindow.hpp | 2 +- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/qt/qt_gleswidget.cpp b/src/qt/qt_gleswidget.cpp index 24c9e4a36..c30e5cefa 100644 --- a/src/qt/qt_gleswidget.cpp +++ b/src/qt/qt_gleswidget.cpp @@ -1,4 +1,5 @@ #include +#include #include "qt_gleswidget.hpp" #ifdef __APPLE__ #include @@ -53,13 +54,20 @@ void GLESWidget::resizeGL(int w, int h) void GLESWidget::initializeGL() { initializeOpenGLFunctions(); + connect(this, &GLESWidget::reqUpdate, this, &GLESWidget::reqUpdate_); } + void GLESWidget::paintGL() { QPainter painter(this); - //painter.fillRect(rect, QColor(0, 0, 0)); - painter.drawImage(QRect(0, 0, width(), height()), m_image.convertToFormat(QImage::Format_RGBA8888), QRect(sx, sy, sw, sh)); + painter.drawImage(QRect(0, 0, width(), height()), m_image.convertToFormat(QImage::Format_RGBX8888), QRect(sx, sy, sw, sh)); painter.end(); + firstupdate = true; +} + +void GLESWidget::reqUpdate_() +{ + update(); } void GLESWidget::mouseReleaseEvent(QMouseEvent *event) @@ -114,8 +122,7 @@ void GLESWidget::mouseMoveEvent(QMouseEvent *event) void GLESWidget::qt_real_blit(int x, int y, int w, int h) { - // printf("Offpainter thread ID: %X\n", SDL_ThreadID()); - if ((w <= 0) || (h <= 0) || (w > 2048) || (h > 2048) || (buffer32 == NULL)) + if ((w <= 0) || (h <= 0) || (w > 2048) || (h > 2048) || (buffer32 == NULL) || !firstupdate) { video_blit_complete(); return; @@ -124,7 +131,7 @@ void GLESWidget::qt_real_blit(int x, int y, int w, int h) sy = y; sw = this->w = w; sh = this->h = h; - auto imagebits = m_image.bits(); + static auto imagebits = m_image.bits(); for (int y1 = y; y1 < (y + h - 1); y1++) { auto scanline = imagebits + (y1 * (2048 + 64) * 4); @@ -135,5 +142,5 @@ void GLESWidget::qt_real_blit(int x, int y, int w, int h) video_screenshot((uint32_t *)imagebits, 0, 0, 2048 + 64); } video_blit_complete(); - update(); + this->reqUpdate(); } diff --git a/src/qt/qt_gleswidget.hpp b/src/qt/qt_gleswidget.hpp index 2ac888724..2fa8d8d67 100644 --- a/src/qt/qt_gleswidget.hpp +++ b/src/qt/qt_gleswidget.hpp @@ -6,6 +6,8 @@ #include #include +#include + class GLESWidget : public QOpenGLWidget, protected QOpenGLFunctions { Q_OBJECT @@ -38,9 +40,13 @@ public: { event->ignore(); } +signals: + void reqUpdate(); + public slots: void qt_real_blit(int x, int y, int w, int h); void qt_mouse_poll(); + void reqUpdate_(); private: struct mouseinputdata { @@ -48,5 +54,5 @@ private: int mousebuttons; }; mouseinputdata mousedata; - + std::atomic firstupdate{false}; }; diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index 9adad9656..ad3a4df14 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -44,8 +44,6 @@ MainWindow::MainWindow(QWidget *parent) : video_setblit(qt_blit); ui->glesWidget->setMouseTracking(true); - connect(this, &MainWindow::blitToWidget, ui->glesWidget, &GLESWidget::qt_real_blit); - connect(this, &MainWindow::showMessageForNonQtThread, this, &MainWindow::showMessage_, Qt::BlockingQueuedConnection); connect(this, &MainWindow::setTitleForNonQtThread, this, &MainWindow::setTitle_, Qt::BlockingQueuedConnection); @@ -702,6 +700,11 @@ void MainWindow::keyPressEvent(QKeyEvent* event) } } +void MainWindow::blitToWidget(int x, int y, int w, int h) +{ + ui->glesWidget->qt_real_blit(x, y, w, h); +} + void MainWindow::keyReleaseEvent(QKeyEvent* event) { #ifdef __APPLE__ diff --git a/src/qt/qt_mainwindow.hpp b/src/qt/qt_mainwindow.hpp index ba93df369..3c5055027 100644 --- a/src/qt/qt_mainwindow.hpp +++ b/src/qt/qt_mainwindow.hpp @@ -22,9 +22,9 @@ public: void showMessage(const QString& header, const QString& message); void setTitle(const wchar_t* title); void getTitle(wchar_t* title); + void blitToWidget(int x, int y, int w, int h); signals: void paint(const QImage& image); - void blitToWidget(int x, int y, int w, int h); void resizeContents(int w, int h); void pollMouse(); void updateStatusBarPanes(); From fc9d73b541b5347dd00fa9b0328eec92e6b95165 Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Thu, 2 Dec 2021 16:26:33 +0600 Subject: [PATCH 30/30] Wayland mouse support Fix moving items with mouse when captured on macOS --- CMakeLists.txt | 2 +- src/qt/CMakeLists.txt | 19 + src/qt/macos_event_filter.mm | 5 +- src/qt/qt_gleswidget.cpp | 14 + src/qt/qt_gleswidget.hpp | 12 + src/qt/qt_mainwindow.cpp | 20 +- src/qt/wl_mouse.cpp | 89 +++++ src/qt/wl_mouse.hpp | 5 + .../pointer-constraints-unstable-v1.xml | 339 ++++++++++++++++++ wl_protocols/relative-pointer-unstable-v1.xml | 136 +++++++ 10 files changed, 636 insertions(+), 5 deletions(-) create mode 100644 src/qt/wl_mouse.cpp create mode 100644 src/qt/wl_mouse.hpp create mode 100644 wl_protocols/pointer-constraints-unstable-v1.xml create mode 100644 wl_protocols/relative-pointer-unstable-v1.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index 64d0087e5..76f55f86c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ # Copyright 2020,2021 David Hrdlička. # -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.16) cmake_policy(SET CMP0091 NEW) cmake_policy(SET CMP0079 NEW) diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index b3ce7121b..9a2be5a7f 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -85,6 +85,7 @@ target_link_libraries( plat PRIVATE Qt5::Widgets + Qt5::Gui Threads::Threads ) @@ -92,10 +93,28 @@ target_link_libraries( ui PRIVATE Qt5::Widgets + Qt5::Gui Threads::Threads ) if (UNIX AND NOT APPLE) find_package(X11 REQUIRED) target_link_libraries(ui PRIVATE X11::X11) + find_package(ECM NO_MODULE) + if (ECM_FOUND) + list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) + find_package(Wayland COMPONENTS Client) + if (Wayland_FOUND) + target_link_libraries(ui PRIVATE Wayland::Client) + find_package(WaylandScanner REQUIRED) + if (WaylandScanner_FOUND) + set(WL_SOURCE_VAR) + ecm_add_wayland_client_protocol(WL_SOURCE_VAR PROTOCOL ${CMAKE_SOURCE_DIR}/wl_protocols/relative-pointer-unstable-v1.xml BASENAME relative-pointer-unstable-v1) + ecm_add_wayland_client_protocol(WL_SOURCE_VAR PROTOCOL ${CMAKE_SOURCE_DIR}/wl_protocols/pointer-constraints-unstable-v1.xml BASENAME pointer-constraints-unstable-v1) + target_include_directories(ui PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) + target_sources(ui PRIVATE ${WL_SOURCE_VAR} wl_mouse.cpp) + target_compile_definitions(ui PRIVATE WAYLAND) + endif() + endif() + endif() endif() diff --git a/src/qt/macos_event_filter.mm b/src/qt/macos_event_filter.mm index 4dd7ef2ae..a3200aa8e 100644 --- a/src/qt/macos_event_filter.mm +++ b/src/qt/macos_event_filter.mm @@ -38,7 +38,10 @@ bool CocoaEventFilter::nativeEventFilter(const QByteArray &eventType, void *mess if (eventType == "mac_generic_NSEvent") { NSEvent* event = (NSEvent*)message; - if ([event type] == NSEventTypeMouseMoved) + if ([event type] == NSEventTypeMouseMoved + || [event type] == NSEventTypeLeftMouseDragged + || [event type] == NSEventTypeRightMouseDragged + || [event type] == NSEventTypeOtherMouseDragged) { mousedata.deltax += [event deltaX]; mousedata.deltay += [event deltaY]; diff --git a/src/qt/qt_gleswidget.cpp b/src/qt/qt_gleswidget.cpp index c30e5cefa..829b484b9 100644 --- a/src/qt/qt_gleswidget.cpp +++ b/src/qt/qt_gleswidget.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include "qt_gleswidget.hpp" #ifdef __APPLE__ #include @@ -43,6 +45,10 @@ void GLESWidget::qt_mouse_poll() mouse_z = mousedata.deltaz; mousedata.deltax = mousedata.deltay = mousedata.deltaz = 0; mouse_buttons = mousedata.mousebuttons; +#ifdef WAYLAND + if (wayland) + wl_mouse_poll(); +#endif #endif } @@ -75,11 +81,13 @@ void GLESWidget::mouseReleaseEvent(QMouseEvent *event) if (this->geometry().contains(event->pos()) && event->button() == Qt::LeftButton && !mouse_capture) { plat_mouse_capture(1); + this->setCursor(Qt::BlankCursor); return; } if (mouse_capture && event->button() == Qt::MiddleButton && mouse_get_buttons() < 3) { plat_mouse_capture(0); + this->setCursor(Qt::ArrowCursor); return; } if (mouse_capture) @@ -93,6 +101,7 @@ void GLESWidget::mousePressEvent(QMouseEvent *event) { mousedata.mousebuttons |= event->button(); } + event->accept(); } void GLESWidget::wheelEvent(QWheelEvent *event) { @@ -105,6 +114,11 @@ void GLESWidget::wheelEvent(QWheelEvent *event) int ignoreNextMouseEvent = 0; void GLESWidget::mouseMoveEvent(QMouseEvent *event) { + if (QApplication::platformName().contains("wayland")) + { + event->accept(); + return; + } if (!mouse_capture) { event->ignore(); return; } #ifdef __APPLE__ event->accept(); diff --git a/src/qt/qt_gleswidget.hpp b/src/qt/qt_gleswidget.hpp index 2fa8d8d67..9c7e9a6cd 100644 --- a/src/qt/qt_gleswidget.hpp +++ b/src/qt/qt_gleswidget.hpp @@ -7,6 +7,11 @@ #include #include +#include + +#ifdef WAYLAND +#include "wl_mouse.hpp" +#endif class GLESWidget : public QOpenGLWidget, protected QOpenGLFunctions { @@ -15,6 +20,7 @@ class GLESWidget : public QOpenGLWidget, protected QOpenGLFunctions private: QImage m_image{QSize(2048 + 64, 2048 + 64), QImage::Format_RGB32}; int x, y, w, h, sx, sy, sw, sh; + bool wayland = false; public: void resizeGL(int w, int h) override; void initializeGL() override; @@ -23,6 +29,12 @@ public: : QOpenGLWidget(parent), QOpenGLFunctions() { setMinimumSize(16, 16); +#ifdef WAYLAND + if (QApplication::platformName().contains("wayland")) { + wayland = true; + wl_init(); + } +#endif } ~GLESWidget() { diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index ad3a4df14..8e5b5a450 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -1,5 +1,6 @@ #include "qt_mainwindow.hpp" #include "ui_qt_mainwindow.h" +#include extern "C" { #include <86box/86box.h> @@ -42,7 +43,7 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); video_setblit(qt_blit); - ui->glesWidget->setMouseTracking(true); + //ui->glesWidget->setMouseTracking(true); connect(this, &MainWindow::showMessageForNonQtThread, this, &MainWindow::showMessage_, Qt::BlockingQueuedConnection); @@ -54,8 +55,21 @@ MainWindow::MainWindow(QWidget *parent) : connect(this, &MainWindow::setMouseCapture, this, [this](bool state) { mouse_capture = state ? 1 : 0; qt_mouse_capture(mouse_capture); - if (mouse_capture) ui->glesWidget->grabMouse(); - else ui->glesWidget->releaseMouse(); + if (mouse_capture) { + ui->glesWidget->grabMouse(); +#ifdef WAYLAND + if (QGuiApplication::platformName().contains("wayland")) { + wl_mouse_capture(this->windowHandle()); + } +#endif + } else { + ui->glesWidget->releaseMouse(); +#ifdef WAYLAND + if (QGuiApplication::platformName().contains("wayland")) { + wl_mouse_uncapture(); + } +#endif + } }); connect(this, &MainWindow::resizeContents, this, [this](int w, int h) { diff --git a/src/qt/wl_mouse.cpp b/src/qt/wl_mouse.cpp new file mode 100644 index 000000000..b399dafd7 --- /dev/null +++ b/src/qt/wl_mouse.cpp @@ -0,0 +1,89 @@ +#include "wl_mouse.hpp" +#include +#include +#include +#include +#include + +#include +#include +#include + +static zwp_relative_pointer_manager_v1* rel_manager = nullptr; +static zwp_relative_pointer_v1* rel_pointer = nullptr; +static zwp_pointer_constraints_v1* conf_pointer_interface = nullptr; +static zwp_locked_pointer_v1* conf_pointer = nullptr; + +static int rel_mouse_x = 0, rel_mouse_y = 0; + +void rel_mouse_event(void* data, zwp_relative_pointer_v1* zwp_relative_pointer_v1, uint32_t tstmp, uint32_t tstmpl, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_real, wl_fixed_t dy_real) +{ + rel_mouse_x += wl_fixed_to_int(dx_real); + rel_mouse_y += wl_fixed_to_int(dy_real); +} + +extern "C" +{ + extern int mouse_x, mouse_y; +} + +void wl_mouse_poll() +{ + mouse_x = rel_mouse_x; + mouse_y = rel_mouse_y; + rel_mouse_x = 0; + rel_mouse_y = 0; +} + +static struct zwp_relative_pointer_v1_listener rel_listener = +{ + rel_mouse_event +}; + +static void +display_handle_global(void *data, struct wl_registry *registry, uint32_t id, + const char *interface, uint32_t version) +{ + if (!strcmp(interface, "zwp_relative_pointer_manager_v1")) + { + rel_manager = (zwp_relative_pointer_manager_v1*)wl_registry_bind(registry, id, &zwp_relative_pointer_manager_v1_interface, version); + } + if (!strcmp(interface, "zwp_pointer_constraints_v1")) + { + conf_pointer_interface = (zwp_pointer_constraints_v1*)wl_registry_bind(registry, id, &zwp_pointer_constraints_v1_interface, version); + } +} + +static const struct wl_registry_listener registry_listener = { + display_handle_global, + nullptr +}; + +void wl_init() +{ + wl_display* display = (wl_display*)QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("wl_display"); + if (display) + { + auto registry = wl_display_get_registry(display); + if (registry) + { + wl_registry_add_listener(registry, ®istry_listener, nullptr); + wl_display_roundtrip(display); + } + } +} + +void wl_mouse_capture(QWindow *window) +{ + rel_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(rel_manager, (wl_pointer*)QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("wl_pointer")); + zwp_relative_pointer_v1_add_listener(rel_pointer, &rel_listener, nullptr); + conf_pointer = zwp_pointer_constraints_v1_lock_pointer(conf_pointer_interface, (wl_surface*)QGuiApplication::platformNativeInterface()->nativeResourceForWindow("surface", window), (wl_pointer*)QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("wl_pointer"), nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); +} + +void wl_mouse_uncapture() +{ + zwp_locked_pointer_v1_destroy(conf_pointer); + zwp_relative_pointer_v1_destroy(rel_pointer); + rel_pointer = nullptr; + conf_pointer = nullptr; +} \ No newline at end of file diff --git a/src/qt/wl_mouse.hpp b/src/qt/wl_mouse.hpp new file mode 100644 index 000000000..73e81c208 --- /dev/null +++ b/src/qt/wl_mouse.hpp @@ -0,0 +1,5 @@ +class QWindow; +void wl_mouse_capture(QWindow* window); +void wl_mouse_uncapture(); +void wl_mouse_poll(); +void wl_init(); \ No newline at end of file diff --git a/wl_protocols/pointer-constraints-unstable-v1.xml b/wl_protocols/pointer-constraints-unstable-v1.xml new file mode 100644 index 000000000..efd64b660 --- /dev/null +++ b/wl_protocols/pointer-constraints-unstable-v1.xml @@ -0,0 +1,339 @@ + + + + + Copyright © 2014 Jonas Ådahl + Copyright © 2015 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol specifies a set of interfaces used for adding constraints to + the motion of a pointer. Possible constraints include confining pointer + motions to a given region, or locking it to its current position. + + In order to constrain the pointer, a client must first bind the global + interface "wp_pointer_constraints" which, if a compositor supports pointer + constraints, is exposed by the registry. Using the bound global object, the + client uses the request that corresponds to the type of constraint it wants + to make. See wp_pointer_constraints for more details. + + Warning! The protocol described in this file is experimental and backward + incompatible changes may be made. Backward compatible changes may be added + together with the corresponding interface version bump. Backward + incompatible changes are done by bumping the version number in the protocol + and interface names and resetting the interface version. Once the protocol + is to be declared stable, the 'z' prefix and the version number in the + protocol and interface names are removed and the interface version number is + reset. + + + + + The global interface exposing pointer constraining functionality. It + exposes two requests: lock_pointer for locking the pointer to its + position, and confine_pointer for locking the pointer to a region. + + The lock_pointer and confine_pointer requests create the objects + wp_locked_pointer and wp_confined_pointer respectively, and the client can + use these objects to interact with the lock. + + For any surface, only one lock or confinement may be active across all + wl_pointer objects of the same seat. If a lock or confinement is requested + when another lock or confinement is active or requested on the same surface + and with any of the wl_pointer objects of the same seat, an + 'already_constrained' error will be raised. + + + + + These errors can be emitted in response to wp_pointer_constraints + requests. + + + + + + + These values represent different lifetime semantics. They are passed + as arguments to the factory requests to specify how the constraint + lifetimes should be managed. + + + + A oneshot pointer constraint will never reactivate once it has been + deactivated. See the corresponding deactivation event + (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for + details. + + + + + A persistent pointer constraint may again reactivate once it has + been deactivated. See the corresponding deactivation event + (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for + details. + + + + + + + Used by the client to notify the server that it will no longer use this + pointer constraints object. + + + + + + The lock_pointer request lets the client request to disable movements of + the virtual pointer (i.e. the cursor), effectively locking the pointer + to a position. This request may not take effect immediately; in the + future, when the compositor deems implementation-specific constraints + are satisfied, the pointer lock will be activated and the compositor + sends a locked event. + + The protocol provides no guarantee that the constraints are ever + satisfied, and does not require the compositor to send an error if the + constraints cannot ever be satisfied. It is thus possible to request a + lock that will never activate. + + There may not be another pointer constraint of any kind requested or + active on the surface for any of the wl_pointer objects of the seat of + the passed pointer when requesting a lock. If there is, an error will be + raised. See general pointer lock documentation for more details. + + The intersection of the region passed with this request and the input + region of the surface is used to determine where the pointer must be + in order for the lock to activate. It is up to the compositor whether to + warp the pointer or require some kind of user interaction for the lock + to activate. If the region is null the surface input region is used. + + A surface may receive pointer focus without the lock being activated. + + The request creates a new object wp_locked_pointer which is used to + interact with the lock as well as receive updates about its state. See + the the description of wp_locked_pointer for further information. + + Note that while a pointer is locked, the wl_pointer objects of the + corresponding seat will not emit any wl_pointer.motion events, but + relative motion events will still be emitted via wp_relative_pointer + objects of the same seat. wl_pointer.axis and wl_pointer.button events + are unaffected. + + + + + + + + + + + The confine_pointer request lets the client request to confine the + pointer cursor to a given region. This request may not take effect + immediately; in the future, when the compositor deems implementation- + specific constraints are satisfied, the pointer confinement will be + activated and the compositor sends a confined event. + + The intersection of the region passed with this request and the input + region of the surface is used to determine where the pointer must be + in order for the confinement to activate. It is up to the compositor + whether to warp the pointer or require some kind of user interaction for + the confinement to activate. If the region is null the surface input + region is used. + + The request will create a new object wp_confined_pointer which is used + to interact with the confinement as well as receive updates about its + state. See the the description of wp_confined_pointer for further + information. + + + + + + + + + + + + The wp_locked_pointer interface represents a locked pointer state. + + While the lock of this object is active, the wl_pointer objects of the + associated seat will not emit any wl_pointer.motion events. + + This object will send the event 'locked' when the lock is activated. + Whenever the lock is activated, it is guaranteed that the locked surface + will already have received pointer focus and that the pointer will be + within the region passed to the request creating this object. + + To unlock the pointer, send the destroy request. This will also destroy + the wp_locked_pointer object. + + If the compositor decides to unlock the pointer the unlocked event is + sent. See wp_locked_pointer.unlock for details. + + When unlocking, the compositor may warp the cursor position to the set + cursor position hint. If it does, it will not result in any relative + motion events emitted via wp_relative_pointer. + + If the surface the lock was requested on is destroyed and the lock is not + yet activated, the wp_locked_pointer object is now defunct and must be + destroyed. + + + + + Destroy the locked pointer object. If applicable, the compositor will + unlock the pointer. + + + + + + Set the cursor position hint relative to the top left corner of the + surface. + + If the client is drawing its own cursor, it should update the position + hint to the position of its own cursor. A compositor may use this + information to warp the pointer upon unlock in order to avoid pointer + jumps. + + The cursor position hint is double buffered. The new hint will only take + effect when the associated surface gets it pending state applied. See + wl_surface.commit for details. + + + + + + + + Set a new region used to lock the pointer. + + The new lock region is double-buffered. The new lock region will + only take effect when the associated surface gets its pending state + applied. See wl_surface.commit for details. + + For details about the lock region, see wp_locked_pointer. + + + + + + + Notification that the pointer lock of the seat's pointer is activated. + + + + + + Notification that the pointer lock of the seat's pointer is no longer + active. If this is a oneshot pointer lock (see + wp_pointer_constraints.lifetime) this object is now defunct and should + be destroyed. If this is a persistent pointer lock (see + wp_pointer_constraints.lifetime) this pointer lock may again + reactivate in the future. + + + + + + + The wp_confined_pointer interface represents a confined pointer state. + + This object will send the event 'confined' when the confinement is + activated. Whenever the confinement is activated, it is guaranteed that + the surface the pointer is confined to will already have received pointer + focus and that the pointer will be within the region passed to the request + creating this object. It is up to the compositor to decide whether this + requires some user interaction and if the pointer will warp to within the + passed region if outside. + + To unconfine the pointer, send the destroy request. This will also destroy + the wp_confined_pointer object. + + If the compositor decides to unconfine the pointer the unconfined event is + sent. The wp_confined_pointer object is at this point defunct and should + be destroyed. + + + + + Destroy the confined pointer object. If applicable, the compositor will + unconfine the pointer. + + + + + + Set a new region used to confine the pointer. + + The new confine region is double-buffered. The new confine region will + only take effect when the associated surface gets its pending state + applied. See wl_surface.commit for details. + + If the confinement is active when the new confinement region is applied + and the pointer ends up outside of newly applied region, the pointer may + warped to a position within the new confinement region. If warped, a + wl_pointer.motion event will be emitted, but no + wp_relative_pointer.relative_motion event. + + The compositor may also, instead of using the new region, unconfine the + pointer. + + For details about the confine region, see wp_confined_pointer. + + + + + + + Notification that the pointer confinement of the seat's pointer is + activated. + + + + + + Notification that the pointer confinement of the seat's pointer is no + longer active. If this is a oneshot pointer confinement (see + wp_pointer_constraints.lifetime) this object is now defunct and should + be destroyed. If this is a persistent pointer confinement (see + wp_pointer_constraints.lifetime) this pointer confinement may again + reactivate in the future. + + + + + diff --git a/wl_protocols/relative-pointer-unstable-v1.xml b/wl_protocols/relative-pointer-unstable-v1.xml new file mode 100644 index 000000000..ca6f81d12 --- /dev/null +++ b/wl_protocols/relative-pointer-unstable-v1.xml @@ -0,0 +1,136 @@ + + + + + Copyright © 2014 Jonas Ådahl + Copyright © 2015 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol specifies a set of interfaces used for making clients able to + receive relative pointer events not obstructed by barriers (such as the + monitor edge or other pointer barriers). + + To start receiving relative pointer events, a client must first bind the + global interface "wp_relative_pointer_manager" which, if a compositor + supports relative pointer motion events, is exposed by the registry. After + having created the relative pointer manager proxy object, the client uses + it to create the actual relative pointer object using the + "get_relative_pointer" request given a wl_pointer. The relative pointer + motion events will then, when applicable, be transmitted via the proxy of + the newly created relative pointer object. See the documentation of the + relative pointer interface for more details. + + Warning! The protocol described in this file is experimental and backward + incompatible changes may be made. Backward compatible changes may be added + together with the corresponding interface version bump. Backward + incompatible changes are done by bumping the version number in the protocol + and interface names and resetting the interface version. Once the protocol + is to be declared stable, the 'z' prefix and the version number in the + protocol and interface names are removed and the interface version number is + reset. + + + + + A global interface used for getting the relative pointer object for a + given pointer. + + + + + Used by the client to notify the server that it will no longer use this + relative pointer manager object. + + + + + + Create a relative pointer interface given a wl_pointer object. See the + wp_relative_pointer interface for more details. + + + + + + + + + A wp_relative_pointer object is an extension to the wl_pointer interface + used for emitting relative pointer events. It shares the same focus as + wl_pointer objects of the same seat and will only emit events when it has + focus. + + + + + + + + + Relative x/y pointer motion from the pointer of the seat associated with + this object. + + A relative motion is in the same dimension as regular wl_pointer motion + events, except they do not represent an absolute position. For example, + moving a pointer from (x, y) to (x', y') would have the equivalent + relative motion (x' - x, y' - y). If a pointer motion caused the + absolute pointer position to be clipped by for example the edge of the + monitor, the relative motion is unaffected by the clipping and will + represent the unclipped motion. + + This event also contains non-accelerated motion deltas. The + non-accelerated delta is, when applicable, the regular pointer motion + delta as it was before having applied motion acceleration and other + transformations such as normalization. + + Note that the non-accelerated delta does not represent 'raw' events as + they were read from some device. Pointer motion acceleration is device- + and configuration-specific and non-accelerated deltas and accelerated + deltas may have the same value on some devices. + + Relative motions are not coupled to wl_pointer.motion events, and can be + sent in combination with such events, but also independently. There may + also be scenarios where wl_pointer.motion is sent, but there is no + relative motion. The order of an absolute and relative motion event + originating from the same physical motion is not guaranteed. + + If the client needs button events or focus state, it can receive them + from a wl_pointer object of the same seat that the wp_relative_pointer + object is associated with. + + + + + + + + + + +