diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile index 3d4fa7e13..e9fd8b986 100644 --- a/.ci/Jenkinsfile +++ b/.ci/Jenkinsfile @@ -16,20 +16,22 @@ */ /* ['main builds', 'branch builds'] */ -def repository = ['https://github.com/86Box/86Box.git', 'https://github.com/richardg867/86Box.git'] +def repository = ['https://github.com/86Box/86Box.git', scm.userRemoteConfigs[0].url] def commitBrowser = ['https://github.com/86Box/86Box/commit/%s', null] -def branch = ['master', 'cleanup30'] +def branch = ['master', scm.branches[0].name] def buildType = ['beta', 'alpha'] def buildBranch = env.JOB_BASE_NAME.contains('-') ? 1 : 0 def osArchs = [ 'Windows': ['32', '64'], - 'Linux': ['x86', 'x86_64', 'arm32', 'arm64'] + 'Linux': ['x86', 'x86_64', 'arm32', 'arm64'], + 'macOS': ['x86_64'] ] def osFlags = [ 'Windows': '-D QT=ON', - 'Linux': '-D QT=ON' + 'Linux': '-D QT=ON', + 'macOS': '-D QT=ON' ] def archNames = [ @@ -41,6 +43,11 @@ def archNames = [ 'arm64': 'ARM (64-bit)' ] +def archNamesMac = [ + 'x86_64': 'Intel', + 'arm64': 'Apple Silicon' +] + def dynarecNames = [ 'ODR': 'Old Recompiler (recommended)', 'NDR': 'New Recompiler (beta)', @@ -53,9 +60,7 @@ def dynarecArchs = [ '64': ['ODR', 'NDR'], 'x86_64': ['ODR', 'NDR'], 'arm32': ['NDR'], - 'ARM32': ['NDR'], - 'arm64': ['NDR'], - 'ARM64': ['NDR'] + 'arm64': ['NDR'] ] def dynarecFlags = [ @@ -238,7 +243,7 @@ pipeline { osArchs.each { os, thisOsArchs -> def combinations = [:] thisOsArchs.each { arch -> - def thisArchDynarecs = dynarecArchs[arch] + def thisArchDynarecs = dynarecArchs[arch.toLowerCase()] if (!thisArchDynarecs) thisArchDynarecs = ['NoDR'] thisArchDynarecs.each { dynarec -> @@ -257,7 +262,10 @@ pipeline { /* Run build process. */ def packageName = "${env.JOB_BASE_NAME}${dynarecSlugs[dynarec]}${presetSlugs[preset]}-$os-$arch$buildSuffix" def ret = -1 - dir("${dynarecNames[dynarec]}/$os - ${archNames[arch]}") { + def archName = archNames[arch] + if (os == 'macOS') + archName = archNamesMac[arch] + dir("${dynarecNames[dynarec]}/$os - $archName") { ret = runBuild("-b \"$packageName\" \"$arch\" ${presetFlags[preset]} ${dynarecFlags[dynarec]} ${osFlags[os]} $buildFlags") } diff --git a/.ci/build.sh b/.ci/build.sh index 0a1eb9ef3..590119cc4 100644 --- a/.ci/build.sh +++ b/.ci/build.sh @@ -28,9 +28,17 @@ # - Packaging the Discord DLL requires wget (MSYS should come with it) # - For Linux builds: # - Only Debian and derivatives are supported -# - dpkg and apt-get are called through sudo to manage dependencies +# - dpkg and apt-get are called through sudo to manage dependencies; make sure those +# are configured as NOPASSWD in /etc/sudoers if you're doing unattended builds # - For macOS builds: -# - TBD +# - A standard MacPorts installation is required, with the following macports.conf settings: +# buildfromsource always +# build_arch x86_64 (or arm64) +# universal_archs (blank) +# ui_interactive no +# macosx_deployment_target 10.13 +# - port is called through sudo to manage dependencies; make sure it is configured +# as NOPASSWD in /etc/sudoers if you're doing unattended builds # # Define common functions. @@ -178,13 +186,16 @@ fi echo [-] Building [$package_name] for [$arch] with flags [$cmake_flags] # Determine CMake toolchain file for this architecture. +toolchain_prefix=flags-gcc +is_mac && toolchain_prefix=llvm-macos case $arch in - 32 | x86) toolchain="flags-gcc-i686";; - 64 | x86_64) toolchain="flags-gcc-x86_64";; - ARM32 | arm32) toolchain="flags-gcc-armv7";; - ARM64 | arm64) toolchain="flags-gcc-aarch64";; - *) toolchain="flags-gcc-$arch";; + 32 | x86) toolchain="$toolchain_prefix-i686";; + 64 | x86_64) toolchain="$toolchain_prefix-x86_64";; + ARM32 | arm32) toolchain="$toolchain_prefix-armv7";; + ARM64 | arm64) toolchain="$toolchain_prefix-aarch64";; + *) toolchain="$toolchain_prefix-$arch";; esac +[ ! -e "cmake/$toolchain.cmake" ] && toolchain=flags-gcc # Perform platform-specific setup. strip_binary=strip @@ -313,11 +324,27 @@ then fi # Point CMake to the toolchain file. - cmake_flags_extra="$cmake_flags_extra -D \"CMAKE_TOOLCHAIN_FILE=cmake/$toolchain.cmake\"" + [ -e "cmake/$toolchain.cmake" ] && cmake_flags_extra="$cmake_flags_extra -D \"CMAKE_TOOLCHAIN_FILE=cmake/$toolchain.cmake\"" elif is_mac then # macOS lacks nproc, but sysctl can do the same job. alias nproc='sysctl -n hw.logicalcpu' + + # Locate the MacPorts prefix. + macports="/opt/local" + [ -e "/opt/$arch/bin/port" ] && macports="/opt/$arch" + [ "$arch" = "x86_64" -a -e "/opt/intel/bin/port" ] && macports="/opt/intel" + + # Install dependencies. + echo [-] Installing dependencies through MacPorts + sudo $macports/bin/port selfupdate + sudo $macports/bin/port install $(cat .ci/dependencies_macports.txt) + + # Point CMake to the toolchain file. + [ -e "cmake/$toolchain.cmake" ] && cmake_flags_extra="$cmake_flags_extra -D \"CMAKE_TOOLCHAIN_FILE=cmake/$toolchain.cmake\"" + + # Use OpenAL as MacPorts doesn't package FAudio. + cmake_flags_extra="$cmake_flags_extra -D OPENAL=ON" else # Determine Debian architecture. case $arch in @@ -350,18 +377,18 @@ else [ $length -gt $longest_libpkg ] && longest_libpkg=$length done - # Determine GNU toolchain architecture. + # Determine toolchain architecture triplet. case $arch in - x86) arch_gnu="i686-linux-gnu";; - arm32) arch_gnu="arm-linux-gnueabihf";; - arm64) arch_gnu="aarch64-linux-gnu";; - *) arch_gnu="$arch-linux-gnu";; + x86) arch_triplet="i686-linux-gnu";; + arm32) arch_triplet="arm-linux-gnueabihf";; + arm64) arch_triplet="aarch64-linux-gnu";; + *) arch_triplet="$arch-linux-gnu";; esac # Determine library directory name for this architecture. case $arch in x86) libdir="i386-linux-gnu";; - *) libdir="$arch_gnu";; + *) libdir="$arch_triplet";; esac # Create CMake toolchain file. @@ -369,15 +396,15 @@ else set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR $arch) -set(CMAKE_AR $arch_gnu-ar) -set(CMAKE_ASM_COMPILER $arch_gnu-gcc) -set(CMAKE_C_COMPILER $arch_gnu-gcc) -set(CMAKE_CXX_COMPILER $arch_gnu-g++) -set(CMAKE_LINKER $arch_gnu-ld) -set(CMAKE_OBJCOPY $arch_gnu-objcopy) -set(CMAKE_RANLIB $arch_gnu-ranlib) -set(CMAKE_SIZE $arch_gnu-size) -set(CMAKE_STRIP $arch_gnu-strip) +set(CMAKE_AR $arch_triplet-ar) +set(CMAKE_ASM_COMPILER $arch_triplet-gcc) +set(CMAKE_C_COMPILER $arch_triplet-gcc) +set(CMAKE_CXX_COMPILER $arch_triplet-g++) +set(CMAKE_LINKER $arch_triplet-ld) +set(CMAKE_OBJCOPY $arch_triplet-objcopy) +set(CMAKE_RANLIB $arch_triplet-ranlib) +set(CMAKE_SIZE $arch_triplet-size) +set(CMAKE_STRIP $arch_triplet-strip) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) @@ -389,7 +416,7 @@ set(ENV{PKG_CONFIG_LIBDIR} "/usr/lib/$libdir/pkgconfig:/usr/share/$libdir/pkgcon include("$(pwd)/cmake/$toolchain.cmake") EOF cmake_flags_extra="$cmake_flags_extra -D CMAKE_TOOLCHAIN_FILE=toolchain.cmake" - strip_binary="$arch_gnu-strip" + strip_binary="$arch_triplet-strip" # Install or update dependencies. echo [-] Installing dependencies through apt @@ -524,8 +551,21 @@ then fi elif is_mac then - # TBD - : + # Archive app bundle with libraries. + cmake_flags_install= + [ $strip -ne 0 ] && cmake_flags_install="$cmake_flags_install --strip" + cmake --install build --prefix "$(pwd)/archive_tmp" $cmake_flags_install + status=$? + + if [ $status -eq 0 ] + then + # Archive Discord Game SDK library. + unzip -j discord_game_sdk.zip "lib/$arch_discord/discord_game_sdk.dylib" -d "archive_tmp/"*".app/Contents/Frameworks" + [ ! -e "archive_tmp/"*".app/Contents/Frameworks/discord_game_sdk.dylib" ] && echo [!] No Discord Game SDK for architecture [$arch_discord] + + # Sign app bundle. + codesign --force --deep -s - "archive_tmp/"*".app" + fi else cwd_root=$(pwd) @@ -643,8 +683,10 @@ then status=$? elif is_mac then - # TBD - : + # Create zip. (TODO: dmg) + cd archive_tmp + zip -r "$cwd/$package_name.zip" . + status=$? else # Determine AppImage runtime architecture. case $arch in diff --git a/.ci/dependencies_macports.txt b/.ci/dependencies_macports.txt new file mode 100644 index 000000000..4171a0a69 --- /dev/null +++ b/.ci/dependencies_macports.txt @@ -0,0 +1,10 @@ +cmake@3.22.3_0 +pkgconfig@0.29.2_0 +ninja@1.10.2_4 +freetype@2.11.1_0 +libsdl2@2.0.20_0 +libpng@1.6.37_0 +openal-soft@1.21.1_0 +rtmidi@5.0.0_0 +qt5@5.15.3_0 +wget diff --git a/src/86box.c b/src/86box.c index db0182d84..b1c969509 100644 --- a/src/86box.c +++ b/src/86box.c @@ -32,11 +32,11 @@ #ifndef _WIN32 #include +#include #endif #ifdef __APPLE__ #include #include -#include "mac/macOSXGlue.h" #ifdef __aarch64__ #include #endif @@ -125,6 +125,7 @@ uint64_t unique_id = 0; uint64_t source_hwnd = 0; #endif char rom_path[1024] = { '\0'}; /* (O) full path to ROMs */ +rom_path_t rom_paths = { "", NULL }; /* (O) full paths to ROMs */ char log_path[1024] = { '\0'}; /* (O) full path of logfile */ char vm_name[1024] = { '\0'}; /* (O) display name of the VM */ @@ -394,12 +395,12 @@ pc_log(const char *fmt, ...) int pc_init(int argc, char *argv[]) { - char path[2048], path2[2048]; + char *ppath = NULL, *rpath = NULL; char *cfg = NULL, *p; #if !defined(__APPLE__) && !defined(_WIN32) char *appimage; #endif - char temp[128]; + char temp[2048]; struct tm *info; time_t now; int c, vmrp = 0; @@ -412,6 +413,14 @@ pc_init(int argc, char *argv[]) p = plat_get_filename(exe_path); *p = '\0'; +#if !defined(_WIN32) && !defined(__APPLE__) + /* Grab the actual path if we are an AppImage. */ + appimage = getenv("APPIMAGE"); + if (appimage && (appimage[0] != '\0')) { + plat_get_dirname(exe_path, appimage); + } +#endif + /* * Get the current working directory. * @@ -423,9 +432,6 @@ pc_init(int argc, char *argv[]) plat_getcwd(usr_path, sizeof(usr_path) - 1); plat_getcwd(rom_path, sizeof(rom_path) - 1); - memset(path, 0x00, sizeof(path)); - memset(path2, 0x00, sizeof(path)); - for (c=1; cnext) { + pclog("# ROM path: %s\n", rom_path->path); + } + pclog("# Configuration file: %s\n#\n\n", cfg_path); /* * We are about to read the configuration file, which MAY diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 06cebf82b..36df54404 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,10 @@ add_executable(86Box 86box.c config.c log.c random.c timer.c io.c acpi.c apm.c dma.c ddma.c discord.c nmi.c pic.c pit.c port_6x.c port_92.c ppi.c pci.c mca.c usb.c fifo8.c device.c nvr.c nvr_at.c nvr_ps2.c) +if(CMAKE_SYSTEM_NAME MATCHES "Linux") + add_compile_definitions(_FILE_OFFSET_BITS=64 _LARGEFILE_SOURCE=1 _LARGEFILE64_SOURCE=1) +endif() + if(CPPTHREADS) target_sources(86Box PRIVATE thread.cpp) endif() diff --git a/src/device.c b/src/device.c index 136c11446..501ae6b64 100644 --- a/src/device.c +++ b/src/device.c @@ -339,6 +339,30 @@ device_available(const device_t *d) } +int +device_has_config(const device_t *d) +{ + int c = 0; + device_config_t *config; + + if (d == NULL) + return 0; + + if (d->config == NULL) + return 0; + + config = (device_config_t *) d->config; + + while (config->type != -1) { + if (config->type != CONFIG_MAC) + c++; + config++; + } + + return (c > 0) ? 1 : 0; +} + + int device_poll(const device_t *d, int x, int y, int z, int b) { diff --git a/src/disk/hdc.c b/src/disk/hdc.c index 90d46a815..7ba9a0f1e 100644 --- a/src/disk/hdc.c +++ b/src/disk/hdc.c @@ -202,7 +202,7 @@ hdc_has_config(int hdc) if (dev == NULL) return(0); - if (dev->config == NULL) return(0); + if (!device_has_config(dev)) return(0); return(1); } diff --git a/src/floppy/fdc.c b/src/floppy/fdc.c index 171a77fa7..a28328587 100644 --- a/src/floppy/fdc.c +++ b/src/floppy/fdc.c @@ -154,7 +154,7 @@ fdc_card_has_config(int card) { if (! fdc_cards[card].device) return(0); - return(fdc_cards[card].device->config ? 1 : 0); + return(device_has_config(fdc_cards[card].device) ? 1 : 0); } diff --git a/src/include/86box/device.h b/src/include/86box/device.h index 28e9485ef..afd7c3731 100644 --- a/src/include/86box/device.h +++ b/src/include/86box/device.h @@ -147,6 +147,7 @@ extern void device_register_pci_slot(const device_t *d, int device, int type, i extern void device_speed_changed(void); extern void device_force_redraw(void); extern void device_get_name(const device_t *d, int bus, char *name); +extern int device_has_config(const device_t *d); extern int device_is_valid(const device_t *, int m); diff --git a/src/include/86box/plat.h b/src/include/86box/plat.h index 71ad89927..ad7006165 100644 --- a/src/include/86box/plat.h +++ b/src/include/86box/plat.h @@ -104,6 +104,7 @@ extern int plat_getcwd(char *bufp, int max); extern int plat_chdir(char *path); extern void plat_tempfile(char *bufp, char *prefix, char *suffix); extern void plat_get_exe_name(char *s, int size); +extern void plat_init_rom_paths(); extern char *plat_get_basename(const char *path); extern void plat_get_dirname(char *dest, const char *path); extern char *plat_get_filename(char *s); diff --git a/src/include/86box/rom.h b/src/include/86box/rom.h index dfea9f0b5..30c7c0561 100644 --- a/src/include/86box/rom.h +++ b/src/include/86box/rom.h @@ -41,6 +41,15 @@ typedef struct { } rom_t; +typedef struct rom_path_t { + char path[1024]; + struct rom_path_t* next; +} rom_path_t; + +extern rom_path_t rom_paths; + +extern void rom_add_path(const char* path); + extern uint8_t rom_read(uint32_t addr, void *p); extern uint16_t rom_readw(uint32_t addr, void *p); extern uint32_t rom_readl(uint32_t addr, void *p); diff --git a/src/mac/CMakeLists.txt b/src/mac/CMakeLists.txt index ae3837f0e..aa0fdd8b5 100644 --- a/src/mac/CMakeLists.txt +++ b/src/mac/CMakeLists.txt @@ -29,7 +29,7 @@ else() endif() target_link_libraries(86Box "-framework AppKit") -target_sources(86Box PRIVATE macOSXGlue.m ${APP_ICON_MACOSX}) +target_sources(86Box PRIVATE ${APP_ICON_MACOSX}) # Make sure the icon is copied to the bundle set_source_files_properties(${APP_ICON_MACOSX} @@ -48,7 +48,6 @@ configure_file(Info.plist.in Info.plist @ONLY) set_target_properties(86Box PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist) -set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "-o linker-signed") -set(XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES") -set(XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-") -#set(XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/mac/codesign/dev/app.entitlements) +#set(XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES") +#set(XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-") +#set(XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/mac/codesign/dev/app.entitlements) \ No newline at end of file diff --git a/src/mem/rom.c b/src/mem/rom.c index 30e873a4e..2e9697801 100644 --- a/src/mem/rom.c +++ b/src/mem/rom.c @@ -57,39 +57,59 @@ rom_log(const char *fmt, ...) #define rom_log(fmt, ...) #endif +void +rom_add_path(const char* path) +{ + char cwd[1024] = { 0 }; + + rom_path_t* rom_path = &rom_paths; + + if (rom_paths.path[0] != '\0') + { + // Iterate to the end of the list. + while (rom_path->next != NULL) { + rom_path = rom_path->next; + } + + // Allocate the new entry. + rom_path = rom_path->next = calloc(1, sizeof(rom_path_t)); + } + + // Save the path, turning it into absolute if needed. + if (!plat_path_abs((char*) path)) { + plat_getcwd(cwd, sizeof(cwd)); + plat_path_slash(cwd); + snprintf(rom_path->path, sizeof(rom_path->path), "%s%s", cwd, path); + } else { + snprintf(rom_path->path, sizeof(rom_path->path), "%s", path); + } + + // Ensure the path ends with a separator. + plat_path_slash(rom_path->path); +} + FILE * rom_fopen(char *fn, char *mode) { char temp[1024]; - char *fn2; + rom_path_t *rom_path = &rom_paths; + FILE *fp; - if ((strstr(fn, "roms/") == fn) || (strstr(fn, "roms\\") == fn)) { - /* Relative path */ - fn2 = (char *) malloc(strlen(fn) + 1); - memcpy(fn2, fn, strlen(fn) + 1); + if (strstr(fn, "roms/") == fn) { + /* Relative path */ + for(rom_path_t *rom_path = &rom_paths; rom_path != NULL; rom_path = rom_path->next) { + plat_append_filename(temp, rom_path->path, fn + 5); - if (rom_path[0] != '\0') { - memset(fn2, 0x00, strlen(fn) + 1); - memcpy(fn2, &(fn[5]), strlen(fn) - 4); + if ((fp = plat_fopen(temp, mode)) != NULL) { + return fp; + } + } - plat_append_filename(temp, rom_path, fn2); - } else { - /* Make sure to make it a backslash, just in case there's malformed - code calling us that assumes Windows. */ - if (fn2[4] == '\\') - fn2[4] = '/'; - - plat_append_filename(temp, exe_path, fn2); - } - - free(fn2); - fn2 = NULL; - - return(plat_fopen(temp, mode)); + return fp; } else { - /* Absolute path */ - return(plat_fopen(fn, mode)); + /* Absolute path */ + return plat_fopen(fn, mode); } } @@ -97,17 +117,30 @@ rom_fopen(char *fn, char *mode) int rom_getfile(char *fn, char *s, int size) { - FILE *f; + char temp[1024]; + rom_path_t *rom_path = &rom_paths; - plat_append_filename(s, exe_path, fn); + if (strstr(fn, "roms/") == fn) { + /* Relative path */ + for(rom_path_t *rom_path = &rom_paths; rom_path != NULL; rom_path = rom_path->next) { + plat_append_filename(temp, rom_path->path, fn + 5); - f = plat_fopen(s, "rb"); - if (f != NULL) { - (void)fclose(f); - return(1); + if (rom_present(temp)) { + strncpy(s, temp, size); + return 1; + } + } + + return 0; + } else { + /* Absolute path */ + if (rom_present(fn)) { + strncpy(s, fn, size); + return 1; + } + + return 0; } - - return(0); } @@ -118,8 +151,8 @@ rom_present(char *fn) f = rom_fopen(fn, "rb"); if (f != NULL) { - (void)fclose(f); - return(1); + (void)fclose(f); + return(1); } return(0); diff --git a/src/network/network.c b/src/network/network.c index a02000372..cb81ea154 100644 --- a/src/network/network.c +++ b/src/network/network.c @@ -630,7 +630,7 @@ network_card_has_config(int card) { if (! net_cards[card].device) return(0); - return(net_cards[card].device->config ? 1 : 0); + return(device_has_config(net_cards[card].device) ? 1 : 0); } diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index a62032537..91891087b 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -22,6 +22,14 @@ endif() find_package(Threads REQUIRED) find_package(Qt${QT_MAJOR} COMPONENTS Core Widgets Network OpenGL REQUIRED) find_package(Qt${QT_MAJOR}LinguistTools REQUIRED) +# TODO: Is this the correct way to do this, and is it required on any +# other platforms or with Qt 5? +if(APPLE AND USE_QT6) + find_package(Qt6Gui/Qt6QCocoaIntegrationPlugin REQUIRED) + find_package(Qt6Widgets/Qt6QMacStylePlugin REQUIRED) + find_package(Qt6Gui/Qt6QICOPlugin REQUIRED) + find_package(Qt6Gui/Qt6QICNSPlugin REQUIRED) +endif() add_library(plat STATIC qt.c @@ -232,17 +240,13 @@ if (APPLE AND CMAKE_MACOSX_BUNDLE) set(INSTALL_CMAKE_DIR "${prefix}/Resources") # using the install_qt5_plugin to add Qt plugins into the macOS app bundle - if (USE_QT6) - install_qt5_plugin("Qt6::QCocoaIntegrationPlugin" QT_PLUGINS ${prefix}) - else() - install_qt5_plugin("Qt5::QCocoaIntegrationPlugin" QT_PLUGINS ${prefix}) - install_qt5_plugin("Qt5::QMacStylePlugin" QT_PLUGINS ${prefix}) - install_qt5_plugin("Qt5::QICOPlugin" QT_PLUGINS ${prefix}) - install_qt5_plugin("Qt5::QICNSPlugin" QT_PLUGINS ${prefix}) - endif() + install_qt5_plugin("Qt${QT_MAJOR}::QCocoaIntegrationPlugin" QT_PLUGINS ${prefix}) + install_qt5_plugin("Qt${QT_MAJOR}::QMacStylePlugin" QT_PLUGINS ${prefix}) + install_qt5_plugin("Qt${QT_MAJOR}::QICOPlugin" QT_PLUGINS ${prefix}) + install_qt5_plugin("Qt${QT_MAJOR}::QICNSPlugin" QT_PLUGINS ${prefix}) file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/qt.conf" - "[Paths]\nPlugins = ${_qt_plugin_dir}\n") + "[Paths]\nPlugins = PlugIns\n") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/qt.conf" DESTINATION "${INSTALL_CMAKE_DIR}") @@ -253,8 +257,8 @@ if (APPLE AND CMAKE_MACOSX_BUNDLE) endforeach() endif() - # Append Qt's lib folder which is two levels above Qt5Widgets_DIR - list(APPEND DIRS "${Qt5Widgets_DIR}/../..") + # Append Qt's lib folder which is two levels above Qt*Widgets_DIR + list(APPEND DIRS "${Qt${QT_MAJOR}Widgets_DIR}/../..") include(InstallRequiredSystemLibraries) diff --git a/src/qt/languages/pt-PT.po b/src/qt/languages/pt-PT.po index 301b52566..8497b935d 100644 --- a/src/qt/languages/pt-PT.po +++ b/src/qt/languages/pt-PT.po @@ -614,7 +614,7 @@ msgid "ZIP images" msgstr "Imagens ZIP" msgid "86Box could not find any usable ROM images.\n\nPlease download a ROM set and extract it into the \"roms\" directory." -msgstr "O 86Box não conseguiu encontrar nenhuma imagem ROM utilizável.\n\nPor favor, vá a href=\"https://github.com/86Box/roms/releases/latest\">descarregue um pacote ROM e instale-o na pasta \"roms\"." +msgstr "O 86Box não conseguiu encontrar nenhuma imagem ROM utilizável.\n\nPor favor, vá descarregue um pacote ROM e instale-o na pasta \"roms\"." msgid "(empty)" msgstr "(empty)" diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index fe249874b..ca3cf1a2e 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -82,6 +82,14 @@ extern "C" { #undef KeyRelease #endif +#ifdef Q_OS_MACOS +// The namespace is required to avoid clashing typedefs; we only use this +// header for its #defines anyway. +namespace IOKit { + #include +} +#endif + #ifdef __HAIKU__ #include #include @@ -1215,7 +1223,7 @@ uint16_t x11_keycode_to_keysym(uint32_t keycode) uint16_t finalkeycode = 0; #if defined(Q_OS_WINDOWS) finalkeycode = (keycode & 0xFFFF); -#elif defined(__APPLE__) +#elif defined(Q_OS_MACOS) finalkeycode = darwin_to_xt[keycode]; #elif defined(__HAIKU__) finalkeycode = be_to_xt[keycode]; @@ -1252,6 +1260,68 @@ uint16_t x11_keycode_to_keysym(uint32_t keycode) return finalkeycode; } +#ifdef Q_OS_MACOS +// These modifiers are listed as "device-dependent" in IOLLEvent.h, but +// that's followed up with "(really?)". It's the only way to distinguish +// left and right modifiers with Qt 6 on macOS, so let's just roll with it. +static std::unordered_map mac_modifiers_to_xt = { + {NX_DEVICELCTLKEYMASK, 0x1D}, + {NX_DEVICELSHIFTKEYMASK, 0x2A}, + {NX_DEVICERSHIFTKEYMASK, 0x36}, + {NX_DEVICELCMDKEYMASK, 0x15B}, + {NX_DEVICERCMDKEYMASK, 0x15C}, + {NX_DEVICELALTKEYMASK, 0x38}, + {NX_DEVICERALTKEYMASK, 0x138}, + {NX_DEVICE_ALPHASHIFT_STATELESS_MASK, 0x3A}, + {NX_DEVICERCTLKEYMASK, 0x11D}, +}; + +void MainWindow::processMacKeyboardInput(bool down, const QKeyEvent* event) { + // Per QTBUG-69608 (https://bugreports.qt.io/browse/QTBUG-69608), + // QKeyEvents QKeyEvents for presses/releases of modifiers on macOS give + // nativeVirtualKey() == 0 (at least in Qt 6). Handle this by manually + // processing the nativeModifiers(). We need to check whether the key() is + // a known modifier because because kVK_ANSI_A is also 0, so the + // nativeVirtualKey() == 0 condition is ambiguous... + if (event->nativeVirtualKey() == 0 + && (event->key() == Qt::Key_Shift + || event->key() == Qt::Key_Control + || event->key() == Qt::Key_Meta + || event->key() == Qt::Key_Alt + || event->key() == Qt::Key_AltGr + || event->key() == Qt::Key_CapsLock)) { + // We only process one modifier at a time since events from Qt seem to + // always be non-coalesced (NX_NONCOALESCEDMASK is always set). + uint32_t changed_modifiers = last_modifiers ^ event->nativeModifiers(); + for (auto const& pair : mac_modifiers_to_xt) { + if (changed_modifiers & pair.first) { + last_modifiers ^= pair.first; + keyboard_input(down, pair.second); + return; + } + } + + // Caps Lock seems to be delivered as a single key press event when + // enabled and a single key release event when disabled, so we can't + // detect Caps Lock being held down; just send an infinitesimally-long + // press and release as a compromise. + // + // The event also doesn't get delivered if you turn Caps Lock off after + // turning it on when the window isn't focused. Doing better than this + // probably requires bypassing Qt input processing. + // + // It's possible that other lock keys get delivered in this way, but + // standard Apple keyboards don't have them, so this is untested. + if (event->key() == Qt::Key_CapsLock) { + keyboard_input(1, 0x3A); + keyboard_input(0, 0x3A); + } + } else { + keyboard_input(down, x11_keycode_to_keysym(event->nativeVirtualKey())); + } +} +#endif + void MainWindow::on_actionFullscreen_triggered() { if (video_fullscreen > 0) { showNormal(); @@ -1370,8 +1440,8 @@ void MainWindow::keyPressEvent(QKeyEvent* event) { if (send_keyboard_input && !(kbd_req_capture && !mouse_capture && !video_fullscreen)) { -#ifdef __APPLE__ - keyboard_input(1, x11_keycode_to_keysym(event->nativeVirtualKey())); +#ifdef Q_OS_MACOS + processMacKeyboardInput(true, event); #else keyboard_input(1, x11_keycode_to_keysym(event->nativeScanCode())); #endif @@ -1397,8 +1467,8 @@ void MainWindow::keyReleaseEvent(QKeyEvent* event) if (!send_keyboard_input) return; -#ifdef __APPLE__ - keyboard_input(0, x11_keycode_to_keysym(event->nativeVirtualKey())); +#ifdef Q_OS_MACOS + processMacKeyboardInput(false, event); #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 5b155079c..84d08f8ed 100644 --- a/src/qt/qt_mainwindow.hpp +++ b/src/qt/qt_mainwindow.hpp @@ -118,6 +118,11 @@ private: std::unique_ptr status; std::shared_ptr mm; +#ifdef Q_OS_MACOS + uint32_t last_modifiers = 0; + void processMacKeyboardInput(bool down, const QKeyEvent* event); +#endif + /* If main window should send keyboard input */ bool send_keyboard_input = true; bool shownonce = false; diff --git a/src/qt/qt_mediamenu.cpp b/src/qt/qt_mediamenu.cpp index 8ffccffdd..64f547272 100644 --- a/src/qt/qt_mediamenu.cpp +++ b/src/qt/qt_mediamenu.cpp @@ -338,6 +338,9 @@ void MediaMenu::floppyExportTo86f(int i) { void MediaMenu::floppyUpdateMenu(int i) { QString name = floppyfns[i]; + if (!floppyMenus.contains(i)) + return; + auto* menu = floppyMenus[i]; auto childs = menu->children(); @@ -412,6 +415,8 @@ void MediaMenu::cdromReload(int i) { void MediaMenu::cdromUpdateMenu(int i) { QString name = cdrom[i].image_path; + if (!cdromMenus.contains(i)) + return; auto* menu = cdromMenus[i]; auto childs = menu->children(); @@ -515,6 +520,8 @@ void MediaMenu::zipReload(int i) { void MediaMenu::zipUpdateMenu(int i) { QString name = zip_drives[i].image_path; QString prev_name = zip_drives[i].prev_image_path; + if (!zipMenus.contains(i)) + return; auto* menu = zipMenus[i]; auto childs = menu->children(); @@ -612,6 +619,8 @@ void MediaMenu::moReload(int i) { void MediaMenu::moUpdateMenu(int i) { QString name = mo_drives[i].image_path; QString prev_name = mo_drives[i].prev_image_path; + if (!moMenus.contains(i)) + return; auto* menu = moMenus[i]; auto childs = menu->children(); diff --git a/src/qt/qt_platform.cpp b/src/qt/qt_platform.cpp index 508103169..366e05970 100644 --- a/src/qt/qt_platform.cpp +++ b/src/qt/qt_platform.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -88,6 +89,8 @@ extern "C" { #include <86box/timer.h> #include <86box/nvr.h> #include <86box/plat_dynld.h> +#include <86box/mem.h> +#include <86box/rom.h> #include <86box/config.h> #include <86box/ui.h> #include <86box/discord.h> @@ -375,7 +378,7 @@ extern "C++" { {0x0405, {"cs-CZ", "Czech (Czech Republic)"} }, {0x0407, {"de-DE", "German (Germany)"} }, - {0x0408, {"en-US", "English (United States)"} }, + {0x0409, {"en-US", "English (United States)"} }, {0x0809, {"en-GB", "English (United Kingdom)"} }, {0x0C0A, {"es-ES", "Spanish (Spain)"} }, {0x040B, {"fi-FI", "Finnish (Finland)"} }, @@ -517,11 +520,6 @@ size_t c16stombs(char dst[], const uint16_t src[], int len) #define LIB_NAME_FREETYPE "libfreetype" #define MOUSE_CAPTURE_KEYSEQ "CTRL-END" #endif -#ifdef Q_OS_MACOS -#define ROMDIR "~/Library/Application Support/net.86box.86box/roms" -#else -#define ROMDIR "roms" -#endif QMap ProgSettings::translatedstrings; @@ -547,7 +545,7 @@ void ProgSettings::reloadStrings() translatedstrings[IDS_2128] = QCoreApplication::translate("", "Hardware not available").toStdWString(); translatedstrings[IDS_2142] = QCoreApplication::translate("", "Monitor in sleep mode").toStdWString(); translatedstrings[IDS_2120] = QCoreApplication::translate("", "No ROMs found").toStdWString(); - translatedstrings[IDS_2056] = QCoreApplication::translate("", "86Box could not find any usable ROM images.\n\nPlease download a ROM set and extract it into the \"roms\" directory.").replace("roms", ROMDIR).toStdWString(); + translatedstrings[IDS_2056] = QCoreApplication::translate("", "86Box could not find any usable ROM images.\n\nPlease download a ROM set and extract it into the \"roms\" directory.").toStdWString(); auto flsynthstr = QCoreApplication::translate("", " is required for FluidSynth MIDI output."); if (flsynthstr.contains("libfluidsynth")) @@ -583,3 +581,26 @@ plat_chdir(char *path) { return QDir::setCurrent(QString(path)) ? 0 : -1; } + +void +plat_init_rom_paths() +{ + auto paths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); + +#ifdef _WIN32 + // HACK: The standard locations returned for GenericDataLocation include + // the EXE path and a `data` directory within it as the last two entries. + + // Remove the entries as we don't need them. + paths.removeLast(); + paths.removeLast(); +#endif + + for (auto& path : paths) { +#ifdef __APPLE__ + rom_add_path(QDir(path).filePath("net.86Box.86Box/roms").toUtf8().constData()); +#else + rom_add_path(QDir(path).filePath("86Box/roms").toUtf8().constData()); +#endif + } +} diff --git a/src/qt/qt_rendererstack.cpp b/src/qt/qt_rendererstack.cpp index c77e76474..88b2395d5 100644 --- a/src/qt/qt_rendererstack.cpp +++ b/src/qt/qt_rendererstack.cpp @@ -174,7 +174,7 @@ RendererStack::mouseMoveEvent(QMouseEvent *event) event->ignore(); return; } -#ifdef __APPLE__ +#if defined __APPLE__ || defined _WIN32 event->accept(); return; #else diff --git a/src/scsi/scsi.c b/src/scsi/scsi.c index 7edfa1844..675abff26 100644 --- a/src/scsi/scsi.c +++ b/src/scsi/scsi.c @@ -153,7 +153,7 @@ scsi_card_has_config(int card) { if (! scsi_cards[card].device) return(0); - return(scsi_cards[card].device->config ? 1 : 0); + return(device_has_config(scsi_cards[card].device) ? 1 : 0); } diff --git a/src/sound/sound.c b/src/sound/sound.c index 5a3e978f6..5110b9c59 100644 --- a/src/sound/sound.c +++ b/src/sound/sound.c @@ -186,7 +186,7 @@ sound_card_has_config(int card) { if (!sound_cards[card].device) return 0; - return sound_cards[card].device->config ? 1 : 0; + return device_has_config(sound_cards[card].device) ? 1 : 0; } char * diff --git a/src/unix/CMakeLists.txt b/src/unix/CMakeLists.txt index 1712288d2..6d1c01a34 100644 --- a/src/unix/CMakeLists.txt +++ b/src/unix/CMakeLists.txt @@ -28,3 +28,7 @@ target_link_libraries(86Box Threads::Threads) add_library(ui OBJECT unix_sdl.c unix_cdrom.c) target_compile_definitions(ui PUBLIC _FILE_OFFSET_BITS=64) target_link_libraries(ui ${CMAKE_DL_LIBS}) + +if(APPLE) + target_sources(plat PRIVATE macOSXGlue.m) +endif() diff --git a/src/mac/macOSXGlue.h b/src/unix/macOSXGlue.h similarity index 100% rename from src/mac/macOSXGlue.h rename to src/unix/macOSXGlue.h diff --git a/src/mac/macOSXGlue.m b/src/unix/macOSXGlue.m similarity index 100% rename from src/mac/macOSXGlue.m rename to src/unix/macOSXGlue.m diff --git a/src/unix/unix.c b/src/unix/unix.c index 1f60ad565..e4be19e05 100644 --- a/src/unix/unix.c +++ b/src/unix/unix.c @@ -18,9 +18,12 @@ #include #include #include +#include #include #include <86box/86box.h> +#include <86box/mem.h> +#include <86box/rom.h> #include <86box/keyboard.h> #include <86box/mouse.h> #include <86box/config.h> @@ -34,6 +37,10 @@ #include <86box/ui.h> #include <86box/gdbstub.h> +#ifdef __APPLE__ +#include "macOSXGlue.h" +#endif + static int first_use = 1; static uint64_t StartingTime; static uint64_t Frequency; @@ -744,6 +751,63 @@ plat_pause(int p) } } +void +plat_init_rom_paths() +{ +#ifndef __APPLE__ + if (getenv("XDG_DATA_HOME")) { + char xdg_rom_path[1024] = { 0 }; + strncpy(xdg_rom_path, getenv("XDG_DATA_HOME"), 1024); + plat_path_slash(xdg_rom_path); + strncat(xdg_rom_path, "86Box/", 1024); + + if (!plat_dir_check(xdg_rom_path)) + plat_dir_create(xdg_rom_path); + strcat(xdg_rom_path, "roms/"); + + if (!plat_dir_check(xdg_rom_path)) + plat_dir_create(xdg_rom_path); + rom_add_path(xdg_rom_path); + } else { + char home_rom_path[1024] = { 0 }; + snprintf(home_rom_path, 1024, "%s/.local/share/86Box/", getenv("HOME") ? getenv("HOME") : getpwuid(getuid())->pw_dir); + + if (!plat_dir_check(home_rom_path)) + plat_dir_create(home_rom_path); + strcat(home_rom_path, "roms/"); + + if (!plat_dir_check(home_rom_path)) + plat_dir_create(home_rom_path); + rom_add_path(home_rom_path); + } + if (getenv("XDG_DATA_DIRS")) { + char* xdg_rom_paths = strdup(getenv("XDG_DATA_DIRS")); + char* xdg_rom_paths_orig = xdg_rom_paths; + char* cur_xdg_rom_path = NULL; + if (xdg_rom_paths) { + while (xdg_rom_paths[strlen(xdg_rom_paths) - 1] == ':') { + xdg_rom_paths[strlen(xdg_rom_paths) - 1] = '\0'; + } + while ((cur_xdg_rom_path = local_strsep(&xdg_rom_paths, ";")) != NULL) { + char real_xdg_rom_path[1024] = { '\0' }; + strcat(real_xdg_rom_path, cur_xdg_rom_path); + plat_path_slash(real_xdg_rom_path); + strcat(real_xdg_rom_path, "86Box/roms/"); + rom_add_path(real_xdg_rom_path); + } + } + free(xdg_rom_paths_orig); + } else { + rom_add_path("/usr/local/share/86Box/roms/"); + rom_add_path("/usr/share/86Box/roms/"); + } +#else + char default_rom_path[1024] = { '\0 '}; + getDefaultROMPath(default_rom_path); + rom_path_add(default_rom_path); +#endif +} + bool process_media_commands_3(uint8_t* id, char* fn, uint8_t* wp, int cmdargc) { bool err = false; diff --git a/src/video/vid_table.c b/src/video/vid_table.c index ccd9c4071..02d5ad81c 100644 --- a/src/video/vid_table.c +++ b/src/video/vid_table.c @@ -364,7 +364,7 @@ video_card_has_config(int card) { if (video_cards[card].device == NULL) return(0); - return(video_cards[card].device->config ? 1 : 0); + return(device_has_config(video_cards[card].device) ? 1 : 0); } diff --git a/src/win/win.c b/src/win/win.c index f4b906d7d..922525cf9 100644 --- a/src/win/win.c +++ b/src/win/win.c @@ -45,6 +45,8 @@ #include <86box/timer.h> #include <86box/nvr.h> #include <86box/video.h> +#include <86box/mem.h> +#include <86box/rom.h> #define GLOBAL #include <86box/plat.h> #include <86box/ui.h> @@ -910,6 +912,30 @@ plat_mmap(size_t size, uint8_t executable) } +void +plat_init_rom_paths() +{ + wchar_t appdata_dir[1024] = { L'\0' }; + + if (_wgetenv("LOCALAPPDATA") && _wgetenv("LOCALAPPDATA")[0] != L'\0') { + char appdata_dir_a[1024] = { '\0' }; + size_t len = 0; + wcsncpy(appdata_dir, _wgetenv("LOCALAPPDATA"), 1024); + len = wcslen(appdata_dir); + if (appdata_dir[len - 1] != L'\\') { + appdata_dir[len] = L'\\'; + appdata_dir[len + 1] = L'\0'; + } + wcscat(appdata_dir, L"86box"); + CreateDirectoryW(appdata_dir, NULL); + wcscat(appdata_dir, L"\\roms"); + CreateDirectoryW(appdata_dir, NULL); + wcscat(appdata_dir, L"\\"); + c16stombs(appdata_dir_a, appdata_dir, 1024); + rom_add_path(appdata_dir_a); + } +} + void plat_munmap(void *ptr, size_t size) {