Merge branch 'master' of https://github.com/tildearrow/furnace into snes

This commit is contained in:
Natt Akuma 2022-09-18 18:33:25 +07:00
commit 0ee6d761f5
1085 changed files with 31936 additions and 7804 deletions

View File

@ -22,8 +22,10 @@ jobs:
- { name: 'Windows MSVC x86_64', os: windows-latest, compiler: msvc, arch: x86_64 }
- { name: 'Windows MinGW x86', os: ubuntu-20.04, compiler: mingw, arch: x86 }
- { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 }
- { name: 'macOS', os: macos-latest }
- { name: 'Ubuntu', os: ubuntu-18.04 }
- { name: 'macOS x86_64', os: macos-latest, arch: x86_64 }
- { name: 'macOS ARM', os: macos-latest, arch: arm64 }
- { name: 'Linux x86_64', os: ubuntu-18.04, arch: x86_64 }
- { name: 'Linux ARM', os: ubuntu-18.04, arch: armhf }
fail-fast: false
name: ${{ matrix.config.name }}
@ -76,10 +78,10 @@ jobs:
package_name="${package_name}-${{ matrix.config.arch }}"
package_ext="" # Directory, uploading will automatically zip it
elif [ '${{ runner.os }}' == 'macOS' ]; then
package_name="${package_name}-macOS"
package_name="${package_name}-macOS-${{ matrix.config.arch }}"
package_ext=".dmg"
else
package_name="${package_name}-Linux"
package_name="${package_name}-Linux-${{ matrix.config.arch }}"
package_ext=".AppImage"
fi
@ -116,8 +118,8 @@ jobs:
mingw-w64 \
mingw-w64-tools
- name: Install Dependencies [Ubuntu]
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
- name: Install Dependencies [Linux x86_64]
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }}
run: |
sudo apt update
sudo apt install \
@ -128,11 +130,34 @@ jobs:
zlib1g-dev \
libjack-jackd2-dev \
appstream
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" || wget "https://tildearrow.org/storage/furnace/ci/appimagetool-x86_64.AppImage"
chmod +x appimagetool-x86_64.AppImage
- name: Install Dependencies [Linux armhf]
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'armhf' }}
run: |
sudo sed -ri "s/^deb /deb [arch=amd64] /" /etc/apt/sources.list
echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ bionic main universe" | sudo tee -a /etc/apt/sources.list
echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main universe" | sudo tee -a /etc/apt/sources.list
sudo dpkg --add-architecture armhf
sudo apt update
sudo apt install \
crossbuild-essential-armhf \
appstream
sudo apt install \
libsdl2-dev:armhf \
libfmt-dev:armhf \
librtmidi-dev:armhf \
libsndfile1-dev:armhf \
zlib1g-dev:armhf \
libjack-jackd2-dev:armhf
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" || wget "https://tildearrow.org/storage/furnace/ci/appimagetool-x86_64.AppImage"
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-armhf" || wget "https://tildearrow.org/storage/furnace/ci/runtime-armhf"
chmod +x appimagetool-x86_64.AppImage
ls /usr/arm-linux-gnueabihf/lib
- name: Configure (System Libraries)
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }}
run: |
export USE_WAE=ON
export CMAKE_EXTRA_ARGS=()
@ -163,7 +188,7 @@ jobs:
"${CMAKE_EXTRA_ARGS[@]}"
- name: Build (System Libraries)
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }}
run: |
cmake \
--build ${PWD}/build \
@ -171,14 +196,14 @@ jobs:
--parallel ${{ steps.build-cores.outputs.amount }}
- name: Install (System Libraries)
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }}
run: |
cmake \
--install ${PWD}/build \
--config ${{ env.BUILD_TYPE }}
- name: Cleanup (System Libraries)
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' && matrix.config.arch == 'x86_64' }}
run: |
rm -rf build/ target/
@ -201,7 +226,13 @@ jobs:
elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-${{ steps.windows-identify.outputs.mingw-target }}.cmake')
elif [ '${{ runner.os }}' == 'macOS' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"')
if [ '${{ matrix.config.arch }}' == 'arm64' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="11.0"' '-DCMAKE_OSX_ARCHITECTURES=arm64')
else
CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"')
fi
elif [ '${{ runner.os }}' == 'Linux' ] && [ '${{ matrix.config.arch }}' == 'armhf' ]; then
CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-Linux-armhf.cmake')
fi
cmake \
@ -255,7 +286,7 @@ jobs:
mv Furnace-*-Darwin.dmg ../${{ steps.package-identify.outputs.filename }}
popd
- name: Package [Ubuntu]
- name: Package [Linux]
if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }}
run: |
#if [ '${{ env.BUILD_TYPE }}' == 'Release' ]; then
@ -273,7 +304,11 @@ jobs:
cp -v ../../res/AppRun ./
popd
../appimagetool-x86_64.AppImage furnace.AppDir
if [ '${{ matrix.config.arch }}' == 'armhf' ]; then
../appimagetool-x86_64.AppImage --runtime-file=../runtime-armhf furnace.AppDir
else
../appimagetool-x86_64.AppImage furnace.AppDir
fi
mv Furnace-*.AppImage ../${{ steps.package-identify.outputs.filename }}
popd

2
.gitignore vendored
View File

@ -1,5 +1,6 @@
.vscode/
build/
clangbuild/
nosdl/
release/
t/
@ -13,6 +14,7 @@ linuxbuild/
test/songs/
test/delta/
test/result/
test/assert_delta
android/.gradle/
android/app/build/
android/app/.cxx/

View File

@ -22,6 +22,9 @@ set(USE_SDL2_DEFAULT ON)
set(USE_SNDFILE_DEFAULT ON)
set(SYSTEM_SDL2_DEFAULT OFF)
include(CheckIncludeFile)
include(TestBigEndian)
if (ANDROID)
set(USE_RTMIDI_DEFAULT OFF)
set(USE_BACKWARD_DEFAULT OFF)
@ -31,7 +34,16 @@ if (ANDROID)
endif()
else()
set(USE_RTMIDI_DEFAULT ON)
set(USE_BACKWARD_DEFAULT ON)
if (WIN32 OR APPLE)
set(USE_BACKWARD_DEFAULT ON)
else()
CHECK_INCLUDE_FILE(execinfo.h EXECINFO_FOUND)
if (EXECINFO_FOUND)
set(USE_BACKWARD_DEFAULT ON)
else()
set(USE_BACKWARD_DEFAULT OFF)
endif()
endif()
endif()
find_package(PkgConfig)
@ -55,13 +67,21 @@ option(SYSTEM_RTMIDI "Use a system-installed version of RtMidi instead of the ve
option(SYSTEM_ZLIB "Use a system-installed version of zlib instead of the vendored one" OFF)
option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendored one" ${SYSTEM_SDL2_DEFAULT})
option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF)
option(WITH_DEMOS "Install demo songs" ON)
option(WITH_INSTRUMENTS "Install instruments" ON)
set(DEPENDENCIES_INCLUDE_DIRS "")
if (ANDROID AND NOT TERMUX)
set(DEPENDENCIES_DEFINES "IS_MOBILE")
set(DEPENDENCIES_DEFINES "IS_MOBILE")
else()
set(DEPENDENCIES_DEFINES "")
set(DEPENDENCIES_DEFINES "")
endif()
TEST_BIG_ENDIAN(IS_BIG_ENDIAN)
if (IS_BIG_ENDIAN)
list(APPEND DEPENDENCIES_DEFINES "TA_BIG_ENDIAN")
endif()
set(DEPENDENCIES_COMPILE_OPTIONS "")
@ -95,6 +115,7 @@ else()
if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(WITH_OUR_MALLOC ON CACHE BOOL "aaa" FORCE)
endif()
set(BUILD_TESTS OFF CACHE BOOL "come on" FORCE)
add_subdirectory(extern/fftw EXCLUDE_FROM_ALL)
list(APPEND DEPENDENCIES_INCLUDE_DIRS extern/fftw/api)
list(APPEND DEPENDENCIES_LIBRARIES fftw3)
@ -220,6 +241,11 @@ if (USE_SDL2)
set(SDL_SHARED OFF CACHE BOOL "Force no dynamically-linked SDL" FORCE)
set(SDL_STATIC ON CACHE BOOL "Force statically-linked SDL" FORCE)
endif()
# https://github.com/libsdl-org/SDL/issues/5535
# disable PipeWire support due to an unfixable bug:
# Looks like their headers have a C90 violation... I imagine they're probably on C99 so not the craziest bug in the world. Definitely file this at the PipeWire repository as well so they know this is out there.
set(SDL_PIPEWIRE OFF CACHE BOOL "Use Pipewire audio" FORCE)
# https://github.com/libsdl-org/SDL/issues/1481
# On 2014-06-22 17:15:50 +0000, Sam Lantinga wrote:
# If you link SDL statically, you also need to define HAVE_LIBC so it builds with the C runtime that your application uses.
@ -252,7 +278,7 @@ src/audio/midi.cpp
)
if (USE_SDL2)
list(APPEND AUDIO_SOURCES src/audio/sdl.cpp)
list(APPEND AUDIO_SOURCES src/audio/sdlAudio.cpp)
endif()
if (WITH_JACK)
@ -342,7 +368,26 @@ src/engine/platform/sound/c64/wave8580_PST.cc
src/engine/platform/sound/c64/wave8580_P_T.cc
src/engine/platform/sound/c64/wave8580__ST.cc
src/engine/platform/sound/tia/TIASnd.cpp
src/engine/platform/sound/c64_fp/Dac.cpp
src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp
src/engine/platform/sound/c64_fp/ExternalFilter.cpp
src/engine/platform/sound/c64_fp/Filter6581.cpp
src/engine/platform/sound/c64_fp/Filter8580.cpp
src/engine/platform/sound/c64_fp/Filter.cpp
src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp
src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp
src/engine/platform/sound/c64_fp/FilterModelConfig.cpp
src/engine/platform/sound/c64_fp/Integrator6581.cpp
src/engine/platform/sound/c64_fp/Integrator8580.cpp
src/engine/platform/sound/c64_fp/OpAmp.cpp
src/engine/platform/sound/c64_fp/SID.cpp
src/engine/platform/sound/c64_fp/Spline.cpp
src/engine/platform/sound/c64_fp/WaveformCalculator.cpp
src/engine/platform/sound/c64_fp/WaveformGenerator.cpp
src/engine/platform/sound/c64_fp/resample/SincResampler.cpp
src/engine/platform/sound/tia/AudioChannel.cpp
src/engine/platform/sound/tia/Audio.cpp
src/engine/platform/sound/ymfm/ymfm_adpcm.cpp
src/engine/platform/sound/ymfm/ymfm_opm.cpp
@ -450,15 +495,24 @@ src/engine/platform/ymz280b.cpp
src/engine/platform/namcowsg.cpp
src/engine/platform/rf5c68.cpp
src/engine/platform/snes.cpp
src/engine/platform/pcmdac.cpp
src/engine/platform/dummy.cpp
)
if (USE_SNDFILE)
list(APPEND ENGINE_SOURCES src/engine/sfWrapper.cpp)
endif()
if (WIN32)
list(APPEND ENGINE_SOURCES src/utfutils.cpp)
list(APPEND ENGINE_SOURCES src/engine/winStuff.cpp)
list(APPEND ENGINE_SOURCES res/furnace.rc)
endif()
set(CLI_SOURCES
src/cli/cli.cpp
)
set(GUI_SOURCES
extern/imgui_patched/imgui.cpp
extern/imgui_patched/imgui_draw.cpp
@ -498,6 +552,7 @@ src/gui/editing.cpp
src/gui/editControls.cpp
src/gui/effectList.cpp
src/gui/findReplace.cpp
src/gui/gradient.cpp
src/gui/insEdit.cpp
src/gui/log.cpp
src/gui/mixer.cpp
@ -505,6 +560,7 @@ src/gui/midiMap.cpp
src/gui/newSong.cpp
src/gui/orders.cpp
src/gui/osc.cpp
src/gui/patManager.cpp
src/gui/pattern.cpp
src/gui/piano.cpp
src/gui/presets.cpp
@ -513,31 +569,75 @@ src/gui/sampleEdit.cpp
src/gui/settings.cpp
src/gui/songInfo.cpp
src/gui/songNotes.cpp
src/gui/spoiler.cpp
src/gui/stats.cpp
src/gui/subSongs.cpp
src/gui/sysConf.cpp
src/gui/sysEx.cpp
src/gui/sysManager.cpp
src/gui/sysPicker.cpp
src/gui/util.cpp
src/gui/waveEdit.cpp
src/gui/volMeter.cpp
src/gui/gui.cpp
)
if (WIN32 OR APPLE)
list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_common.cpp)
endif()
if (WIN32)
list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_win.cpp)
endif()
if (APPLE)
list(APPEND GUI_SOURCES src/gui/macstuff.m)
list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_cocoa.mm)
endif()
if (NOT WIN32 AND NOT APPLE)
list(APPEND GUI_SOURCES src/gui/icon.c)
CHECK_INCLUDE_FILE(sys/io.h SYS_IO_FOUND)
CHECK_INCLUDE_FILE(linux/input.h LINUX_INPUT_FOUND)
CHECK_INCLUDE_FILE(linux/kd.h LINUX_KD_FOUND)
if (SYS_IO_FOUND)
try_compile(HAVE_INOUTB ${CMAKE_BINARY_DIR}/check SOURCES ${CMAKE_SOURCE_DIR}/src/check/check_sysIO.c)
if (HAVE_INOUTB)
list(APPEND DEPENDENCIES_DEFINES HAVE_SYS_IO)
message(STATUS "PC speaker output: outb()")
else()
message(STATUS "sys/io.h found but inb()/outb() not present")
endif()
endif()
if (LINUX_INPUT_FOUND)
list(APPEND DEPENDENCIES_DEFINES HAVE_LINUX_INPUT)
message(STATUS "PC speaker output: evdev")
endif()
if (LINUX_KD_FOUND)
list(APPEND DEPENDENCIES_DEFINES HAVE_LINUX_KD)
message(STATUS "PC speaker output: KIOCSOUND")
endif()
endif()
set(USED_SOURCES ${ENGINE_SOURCES} ${AUDIO_SOURCES} src/main.cpp)
if (NOT WIN32)
try_compile(HAVE_DIRENT_TYPE ${CMAKE_BINARY_DIR}/check SOURCES ${CMAKE_SOURCE_DIR}/src/check/check_dirent_type.c)
if (HAVE_DIRENT_TYPE)
list(APPEND DEPENDENCIES_DEFINES HAVE_DIRENT_TYPE)
endif()
endif()
set(USED_SOURCES ${ENGINE_SOURCES} ${AUDIO_SOURCES} ${CLI_SOURCES} src/main.cpp)
if (USE_BACKWARD)
list(APPEND USED_SOURCES src/backtrace.cpp)
if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
list(APPEND DEPENDENCIES_LIBRARIES dbghelp psapi)
endif()
find_library(EXECINFO_IS_LIBRARY execinfo)
if (EXECINFO_IS_LIBRARY)
list(APPEND DEPENDENCIES_LIBRARIES execinfo)
endif()
message(STATUS "Using backward-cpp")
else()
message(STATUS "Not using backward-cpp")
@ -552,6 +652,11 @@ if (BUILD_GUI)
extern/IconFontCppHeaders
extern/igfd
)
if (WIN32 OR APPLE)
list(APPEND DEPENDENCIES_INCLUDE_DIRS
extern/nfd-modified/src/include
)
endif()
list(APPEND DEPENDENCIES_DEFINES HAVE_GUI)
message(STATUS "Building GUI")
else()
@ -573,7 +678,11 @@ endif()
if (NOT MSVC)
set(WARNING_FLAGS -Wall -Wextra -Wno-unused-parameter)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
list(APPEND WARNING_FLAGS -Wno-cast-function-type)
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.0.0)
# nothing
else()
list(APPEND WARNING_FLAGS -Wno-cast-function-type)
endif()
endif()
if (WARNINGS_ARE_ERRORS)
list(APPEND WARNING_FLAGS -Werror)
@ -627,24 +736,29 @@ if (PKG_CONFIG_FOUND AND (SYSTEM_FMT OR SYSTEM_LIBSNDFILE OR SYSTEM_ZLIB OR SYST
endif()
if (NOT ANDROID OR TERMUX)
install(TARGETS furnace RUNTIME DESTINATION bin)
if (NOT WIN32 AND NOT APPLE)
include(GNUInstallDirs)
install(TARGETS furnace RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES res/furnace.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES res/furnace.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
install(DIRECTORY papers DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/furnace)
install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
install(DIRECTORY instruments DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
if (WITH_DEMOS)
install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
endif()
if (WITH_INSTRUMENTS)
install(DIRECTORY instruments DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
endif()
foreach(num 16 32 64 128 256 512)
set(res ${num}x${num})
install(FILES res/icon.iconset/icon_${res}.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}/apps)
install(FILES res/icon.iconset/icon_${res}@2x.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}@2/apps)
endforeach()
install(FILES res/logo.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/1024x1024/apps)
else()
install(TARGETS furnace RUNTIME DESTINATION bin)
endif()
set(CPACK_PACKAGE_NAME "Furnace")
set(CPACK_PACKAGE_VENDOR "tildearrow")
set(CPACK_PACKAGE_DESCRIPTION "free and open-source chiptune tracker")

View File

@ -22,7 +22,7 @@ bug fixes, improvements and several other things accepted.
the coding style is described here:
- indentation: two spaces
- indentation: two spaces. **strictly** spaces. do NOT use tabs.
- modified 1TBS style:
- no spaces in function calls
- spaces between arguments in function declarations
@ -33,6 +33,7 @@ the coding style is described here:
- indent switch cases
- preprocessor directives not intended
- if macro comprises more than one line, indent
- no new line after `template<>`
- prefer built-in types:
- `bool`
- `signed char` or `unsigned char` are 8-bit
@ -53,6 +54,11 @@ the coding style is described here:
- don't use `auto` unless needed.
- use `String` for `std::string` (this is typedef'd in ta-utils.h).
- prefer using operator for String (std::string) comparisons (a=="").
- if you have to work with C strings, only use safe C string operations:
- snprintf
- strncpy
- strncat
- any other operation which specifies a limit
some files (particularly the ones in `src/engine/platform/sound` and `extern/`) don't follow this style.
@ -76,6 +82,7 @@ just put your demo song in `demos/`! be noted there are some guidelines:
- avoid Nintendo song covers.
- avoid big label song covers.
- avoid poor quality songs.
# Finishing

203
README.md
View File

@ -1,12 +1,12 @@
# Furnace Tracker
![screenshot](papers/screenshot1.png)
![screenshot](papers/screenshot2.png)
this is a multi-system chiptune tracker.
the biggest multi-system chiptune tracker ever made!
[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [FAQ](#frequently-asked-questions)
***
---
## downloads
check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage).
@ -15,37 +15,95 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a
## features
- supports the following systems:
- Sega Genesis
- Sega Master System
- over 50 sound chips - and counting:
- Yamaha FM chips:
- YM2151 (OPM)
- YM2203 (OPN)
- YM2413 (OPLL)
- YM2414 (OPZ) used in Yamaha TX81Z
- YM2608 (OPNA) used in PC-98
- YM2610 (OPNB) used in Neo Geo
- YM2610B (OPNB2)
- YM2612 (OPN2) used in Sega Genesis and FM Towns
- YM3526 (OPL) used in C64 Sound Expander
- YM3812 (OPL2)
- YMF262 (OPL3) with full 4-op support!
- Y8950 (OPL with ADPCM)
- square wave chips:
- AY-3-8910/YM2149(F) used in several computers and game consoles
- Commodore VIC used in the VIC-20
- Microchip AY8930
- TI SN76489 used in Sega Master System and BBC Micro
- PC Speaker
- Philips SAA1099 used in SAM Coupé
- sample chips:
- Amiga
- SegaPCM - all 16 channels
- Capcom QSound
- Yamaha YMZ280B (PCMD8)
- Ricoh RF5C68 used in Sega CD and FM Towns
- OKI MSM6258 and MSM6295
- wavetable chips:
- HuC6280 used in PC Engine
- Konami Bubble System WSG
- Konami SCC/SCC+
- Namco arcade chips (WSG/C15/C30)
- WonderSwan
- Seta/Allumer X1-010
- NES (Ricoh 2A03/2A07), with additional expansion sound support:
- Konami VRC6
- Konami VRC7
- MMC5
- Famicom Disk System
- Sunsoft 5B
- Namco 163
- Family Noraebang (OPLL)
- SID (6581/8580) used in Commodore 64
- Mikey used in Atari Lynx
- ZX Spectrum beeper (SFX-like engine)
- Commodore PET
- TIA used in Atari 2600
- Game Boy
- PC Engine
- NES
- Commodore 64
- Yamaha YM2151 (plus PCM)
- Neo Geo
- AY-3-8910 (ZX Spectrum, Atari ST, etc.)
- Microchip AY8930
- Philips SAA1099
- Amiga
- TIA (Atari 2600/7800)
- multiple sound chips in a single song!
- DefleMask compatibility - loads .dmf modules, .dmp instruments and .dmw wavetables
- clean-room design (guesswork and ABX tests only, no decompilation involved)
- bug/quirk implementation for increased playback accuracy
- VGM and audio file export
- accurate emulation cores whether possible (Nuked, MAME, SameBoy, Mednafen PCE, puNES, reSID, Stella, SAASound and ymfm)
- additional features on top:
- modern/fantasy:
- Commander X16 VERA
- tildearrow Sound Unit
- mix and match sound chips!
- over 200 ready to use presets from computers, game consoles and arcade boards...
- ...or create your own - up to 32 of them or a total of 128 channels!
- DefleMask compatibility
- loads .dmf modules from all versions (beta 1 to 1.1.3)
- saves .dmf modules - both modern and legacy
- Furnace doubles as a module downgrader
- loads/saves .dmp instruments and .dmw wavetables as well
- clean-room design (guesswork and ABX tests only, no decompilation involved)
- bug/quirk implementation for increased playback accuracy through compatibility flags
- VGM export
- modular layout that you may adapt to your needs
- audio file export - entire song, per system or per channel
- quality emulation cores (Nuked, MAME, SameBoy, Mednafen PCE, NSFplay, puNES, reSID, Stella, SAASound, vgsound_emu and ymfm)
- wavetable synthesizer
- available on wavetable chips
- create complex sounds with ease - provide up to two wavetables, select and effect and let go!
- MIDI input support
- [Fractal Sound](https://gitlab.com/Natsumi/Fractal-Sound) support!
- the game-ready Sega Genesis/Mega Drive sound driver!
- compose your songs in Furnace using the Fractal Sound presets and then use them in your games with Fractal!
- additional features:
- FM macros!
- negative octaves
- arbitrary pitch samples
- sample loop points
- SSG envelopes in Neo Geo
- SSG envelopes and ADPCM-B in Neo Geo
- full duty/cutoff range in C64
- ability to change tempo mid-song with `Cxxx` effect (`xxx` between `000` and `3ff`)
- ability to change tempo mid-song
- multiple sub-songs in a module
- per-channel oscilloscope with waveform centering
- built-in sample editor
- chip mixing settings
- built-in visualizer in pattern view
- open-source under GPLv2 or later.
***
---
# quick references
- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z).
@ -59,9 +117,9 @@ some people have provided packages for Unix/Unix-like distributions. here's a li
- **Arch Linux**: [furnace-git is in the AUR.](https://aur.archlinux.org/packages/furnace-git) thank you Essem!
- **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt.
- **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608.
- **OpenSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari.
- **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari.
***
---
# developer info
[![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml)
@ -73,11 +131,17 @@ if you can't download these artifacts (because GitHub requires you to be logged
## dependencies
- CMake
- SDL2
- zlib
- JACK (optional)
- JACK (optional, macOS/Linux only)
SDL2 and zlib are included as submodules.
if building under Windows or macOS, no additional dependencies are required.
otherwise, you may also need the following:
- libpulse
- libx11
- libasound
- libGL
some Linux distributions (e.g. Ubuntu or openSUSE) will require you to install the `-dev` versions of these.
## getting the source
@ -127,14 +191,22 @@ Available options:
| Name | Default | Description |
| :--: | :-----: | ----------- |
| `BUILD_GUI` | `ON` | Build the tracker (disable to build only a headless player) |
| `USE_RTMIDI` | `ON` | Build with MIDI support using RtMidi |
| `USE_SDL2` | `ON` | Build with SDL2 (required to build with GUI) |
| `USE_SNDFILE` | `ON` | Build with libsndfile (required in order to work with audio files) |
| `USE_BACKWARD` | `ON` | Use backward-cpp to print a backtrace on crash/abort |
| `WITH_JACK` | `ON` if system-installed JACK detected, otherwise `OFF` | Whether to build with JACK support. Auto-detects if JACK is available |
| `SYSTEM_FFTW` | `OFF` | Use a system-installed version of FFTW instead of the vendored one |
| `SYSTEM_FMT` | `OFF` | Use a system-installed version of fmt instead of the vendored one |
| `SYSTEM_LIBSNDFILE` | `OFF` | Use a system-installed version of libsndfile instead of the vendored one |
| `SYSTEM_RTMIDI` | `OFF` | Use a system-installed version of RtMidi instead of the vendored one |
| `SYSTEM_ZLIB` | `OFF` | Use a system-installed version of zlib instead of the vendored one |
| `SYSTEM_SDL2` | `OFF` | Use a system-installed version of SDL2 instead of the vendored one |
| `WARNINGS_ARE_ERRORS` | `OFF` (but consider enabling this & reporting any errors that arise from it!) | Whether warnings in furnace's C++ code should be treated as errors |
| `WITH_DEMOS` | `ON` | Install demo songs on `make install` |
| `WITH_INSTRUMENTS` | `ON` | Install demo instruments on `make install` |
## usage
## console usage
```
./furnace
@ -156,36 +228,21 @@ this will play a compatible file and enable the commands view.
**note that these commands only actually work in Linux environments. on other command lines, such as Windows' Command Prompt, or MacOS Terminal, it may not work correctly.**
***
# notes
> how do I use Neo Geo SSG envelopes?
the following effects are provided:
- `22xy`: set envelope mode.
- `x` sets the envelope shape, which may be one of the following:
- `0: \___` decay
- `4: /___` attack once
- `8: \\\\` saw
- `9: \___` decay
- `A: \/\/` inverse obelisco
- `B: \¯¯¯` decay once
- `C: ////` inverse saw
- `D: /¯¯¯` attack
- `E: /\/\` obelisco
- `F: /___` attack once
- if `y` is 1 then the envelope will affect this channel.
- `23xx`: set envelope period low byte.
- `24xx`: set envelope period high byte.
- `25xx`: slide envelope period up.
- `26xx`: slide envelope period down.
a lower envelope period will make the envelope run faster.
***
---
# frequently asked questions
> woah! 50 sound chips?! I can't believe it!
yup, it's real.
> where's the manual?
see [papers/](papers/doc/README.md). it's kind of incomplete, but at least the systems (sound chips) section is there.
> it doesn't open under macOS!
this is due to Apple's application signing policy. a workaround is to right click on the Furnace app icon and select Open.
> how do I use C64 absolute filter/duty?
on Instrument Editor in the C64 tab there are two options to toggle these.
@ -195,27 +252,29 @@ also provided are two effects:
- `4xxx`: set fine cutoff. `xxx` range is 000-7ff.
additionally, you can change the cutoff and/or duty as a macro inside an instrument by clicking the `absolute cutoff macro` and/or `absolute duty macro` checkbox at the bottom of the instrument. (for the filter, you also need to click the checkbox that says `volume macro is cutoff macro`.)
> Q: how do I use PCM on a PCM-capable system?
> how do I use PCM on a PCM-capable system?
A: Two possibilities: the recommended way is via creating the "Amiga/Sample" type instrument and assigning sample to it, or via old, Deflemask-compatible method, using `17xx` effect
two possibilities:
- the recommended way is by creating the "Sample" type instrument and assigning a sample to it.
- otherwise you may employ the DefleMask-compatible method, using `17xx` effect.
> Q: my song sounds very odd at a certain point
> my .dmf song sounds very odd at a certain point
A: file a bug report. use the Issues page. it's probably another playback inaccuracy.
file a bug report. use the Issues page. it's probably another playback inaccuracy.
> Q: my song sounds correct, but it doesn't in DefleMask
> my .dmf song sounds correct, but it doesn't in DefleMask
A: file a bug report **here**. it still is a playback inaccuracy.
file a bug report **here**. it still is a playback inaccuracy.
> Q: my C64 song sounds terrible after saving as .dmf!
> my song sounds terrible after saving as .dmf!
A: that's a limitation of the DefleMask format. save in Furnace song format instead (.fur).
the DefleMask format has several limitations. save in Furnace song format instead (.fur).
> Q: how do I solo channels?
> how do I solo channels?
A: right click on the channel name.
right click on the channel name.
***
---
# footnotes
copyright (C) 2021-2022 tildearrow and contributors.

17
TODO.md
View File

@ -1,12 +1,7 @@
# to-do for 0.6pre1
# to-do for 0.6pre1.5
- rewrite the system name detection function anyway
- add another FM editor layout
- add ability to move selection by dragging
- find and replace
- implement Defle slide bug when using E1xy/E2xy and repeating origin note (requires format change)
# to-do for 0.6pre2 (as this requires new data structures)
- Game Boy envelope macro/sequence
- volume commands should work on Game Boy
- stereo separation control for AY
- "paste with instrument"
- channel appearance settings
- auto-detect system
- bug fixes

View File

@ -15,8 +15,8 @@ android {
}
minSdkVersion 21
targetSdkVersion 26
versionCode 93
versionName "0.6pre1"
versionCode 113
versionName "dev113"
externalNativeBuild {
cmake {
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static"

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.tildearrow.furnace"
android:versionCode="93"
android:versionName="0.6pre1"
android:versionCode="113"
android:versionName="dev113"
android:installLocation="auto">
<!-- OpenGL ES 2.0 -->
@ -83,13 +83,17 @@
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<!-- Drop file event -->
<!--
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:host="*"/>
<data android:mimeType="*/*"/>
<data android:pathPattern=".*\\.fur"/>
</intent-filter>
-->
</activity>
</application>

Binary file not shown.

Binary file not shown.

BIN
demos/Bullet_Hell.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/ChaosTune.fur Normal file

Binary file not shown.

BIN
demos/Checknobankh.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/E1M4OPL2.fur Normal file

Binary file not shown.

BIN
demos/Egyptian_Rule.fur Normal file

Binary file not shown.

BIN
demos/FDS TEST.fur Normal file

Binary file not shown.

BIN
demos/FEDMS.fur Normal file

Binary file not shown.

BIN
demos/Fake Gameboy.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/Fusion.fur Normal file

Binary file not shown.

BIN
demos/GEN Equinox Intro.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/Phoenix_cover.fur Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
demos/The Cheetahmen.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/Waterworld_-_Map.fur Normal file

Binary file not shown.

BIN
demos/atmosphere.fur Normal file

Binary file not shown.

BIN
demos/c64 ring test.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/game boy thing.fur Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
demos/her11_veraedit.fur Normal file

Binary file not shown.

BIN
demos/home_wfl_opl3.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/iji_tor.fur Normal file

Binary file not shown.

BIN
demos/lunacommdemo.fur Normal file

Binary file not shown.

BIN
demos/massive_x_opz.fur Normal file

Binary file not shown.

BIN
demos/rule2.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/the_king_of_crisp.fur Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
demos/thick bass test.fur Normal file

Binary file not shown.

View File

@ -1019,6 +1019,10 @@ static void OPLL_Operator(opll_t *chip) {
}
chip->ch_out = ismod1 ? routput : (output>>3);
if (!ismod1) {
chip->output_ch[(chip->cycles+1)%9] = chip->ch_out;
}
}
static void OPLL_DoRhythm(opll_t *chip) {

View File

@ -191,6 +191,8 @@ typedef struct {
int16_t output_m;
int16_t output_r;
int16_t output_ch[9];
} opll_t;
const opll_patch_t* OPLL_GetPatchROM(uint32_t chip_type);

2
extern/Nuked-OPN2 vendored

@ -1 +1 @@
Subproject commit 64704a443f8f6c1906ba26297092ea70fa1d45d7
Subproject commit b0e9de0f816943ad3820ddfefa0fff276d659250

View File

@ -130,7 +130,7 @@ static void YMPSG_ClockInternal1(ympsg_t *chip)
else if (noise_of && !chip->noise_of)
{
noise_bit1 = (chip->noise >> chip->noise_tap2) & 1;
noise_bit2 = (chip->noise >> 12) & 1;
noise_bit2 = (chip->noise >> chip->noise_tap1) & 1;
noise_bit1 ^= noise_bit2;
noise_next = ((noise_bit1 && ((chip->noise_data >> 2) & 1)) || ((chip->noise & chip->noise_size) == 0));
chip->noise <<= 1;
@ -245,6 +245,11 @@ void YMPSG_Write(ympsg_t *chip, uint8_t data)
chip->write_flag = 1;
}
void YMPSG_WriteStereo(ympsg_t *chip, uint8_t data)
{
chip->stereo = data;
}
uint16_t YMPSG_Read(ympsg_t *chip)
{
uint16_t data = 0;
@ -257,13 +262,15 @@ uint16_t YMPSG_Read(ympsg_t *chip)
return data;
}
void YMPSG_Init(ympsg_t *chip, uint8_t real_sn)
void YMPSG_Init(ympsg_t *chip, uint8_t real_sn, uint8_t noise_tap1, uint8_t noise_tap2, uint32_t noise_size)
{
uint32_t i;
memset(chip, 0, sizeof(ympsg_t));
YMPSG_SetIC(chip, 1);
chip->noise_tap2 = real_sn ? 13 : 15;
chip->noise_size = real_sn ? 16383 : 32767;
chip->noise_tap1 = noise_tap1;
chip->noise_tap2 = noise_tap2;
chip->noise_size = noise_size;
chip->stereo = 0xff;
for (i = 0; i < 17; i++)
{
chip->vol_table[i]=(real_sn?tipsg_vol[i]:ympsg_vol[i]) * 8192.0f;
@ -315,32 +322,62 @@ void YMPSG_Clock(ympsg_t *chip)
}
}
int YMPSG_GetOutput(ympsg_t *chip)
void YMPSG_GetOutput(ympsg_t *chip, int* left, int* right)
{
int sample = 0;
int sample_left = 0;
int sample_right = 0;
uint32_t i;
YMPSG_UpdateSample(chip);
if (chip->test & 1)
{
sample += chip->vol_table[chip->volume_out[chip->test >> 1]];
sample += chip->vol_table[16] * 3;
sample_left += chip->vol_table[chip->volume_out[chip->test >> 1]];
sample_left += chip->vol_table[16] * 3;
sample_right += chip->vol_table[chip->volume_out[chip->test >> 1]];
sample_right += chip->vol_table[16] * 3;
}
else if (!chip->mute)
{
sample += chip->vol_table[chip->volume_out[0]];
sample += chip->vol_table[chip->volume_out[1]];
sample += chip->vol_table[chip->volume_out[2]];
sample += chip->vol_table[chip->volume_out[3]];
if (chip->stereo&(0x10)) {
sample_left += chip->vol_table[chip->volume_out[0]];
}
if (chip->stereo&(0x01)) {
sample_right += chip->vol_table[chip->volume_out[0]];
}
if (chip->stereo&(0x20)) {
sample_left += chip->vol_table[chip->volume_out[1]];
}
if (chip->stereo&(0x02)) {
sample_right += chip->vol_table[chip->volume_out[1]];
}
if (chip->stereo&(0x40)) {
sample_left += chip->vol_table[chip->volume_out[2]];
}
if (chip->stereo&(0x04)) {
sample_right += chip->vol_table[chip->volume_out[2]];
}
if (chip->stereo&(0x80)) {
sample_left += chip->vol_table[chip->volume_out[3]];
}
if (chip->stereo&(0x08)) {
sample_right += chip->vol_table[chip->volume_out[3]];
}
}
else
{
for (i = 0; i < 4; i++)
{
if (!((chip->mute>>i) & 1))
sample += chip->vol_table[chip->volume_out[i]];
if (!((chip->mute>>i) & 1)) {
if (chip->stereo&(0x10<<i)) {
sample_left += chip->vol_table[chip->volume_out[i]];
}
if (chip->stereo&(0x01<<i)) {
sample_right += chip->vol_table[chip->volume_out[i]];
}
}
}
}
return sample;
*left=sample_left;
*right=sample_right;
}
void YMPSG_Test(ympsg_t *chip, uint16_t test)
@ -348,7 +385,7 @@ void YMPSG_Test(ympsg_t *chip, uint16_t test)
chip->test = (test >> 9) & 7;
}
/*
void YMPSG_Generate(ympsg_t *chip, int32_t *buf)
{
uint32_t i;
@ -372,7 +409,7 @@ void YMPSG_Generate(ympsg_t *chip, int32_t *buf)
}
out = YMPSG_GetOutput(chip);
*buf = (int32_t)(out * 8192.f);
}
}*/
void YMPSG_WriteBuffered(ympsg_t *chip, uint8_t data)
{

View File

@ -46,11 +46,14 @@ typedef struct {
uint8_t sign_l;
uint8_t noise_sign_l;
uint16_t noise;
uint8_t noise_tap1;
uint8_t noise_tap2;
uint16_t noise_size;
uint32_t noise_size;
uint8_t test;
uint8_t volume_out[4];
uint8_t stereo;
//
uint64_t writebuf_samplecnt;
uint32_t writebuf_cur;
@ -67,15 +70,16 @@ typedef struct {
void YMPSG_Write(ympsg_t *chip, uint8_t data);
void YMPSG_WriteStereo(ympsg_t *chip, uint8_t data);
uint16_t YMPSG_Read(ympsg_t *chip);
void YMPSG_Init(ympsg_t *chip, uint8_t real_sn);
void YMPSG_Init(ympsg_t *chip, uint8_t real_sn, uint8_t noise_tap1, uint8_t noise_tap2, uint32_t noise_size);
void YMPSG_SetIC(ympsg_t *chip, uint32_t ic);
void YMPSG_Clock(ympsg_t *chip);
int YMPSG_GetOutput(ympsg_t *chip);
void YMPSG_GetOutput(ympsg_t *chip, int* left, int* right);
void YMPSG_Test(ympsg_t *chip, uint16_t test);
void YMPSG_Generate(ympsg_t *chip, int32_t *buf);
//void YMPSG_Generate(ympsg_t *chip, int32_t *buf);
void YMPSG_WriteBuffered(ympsg_t *chip, uint8_t data);
void YMPSG_SetMute(ympsg_t *chip, uint8_t mute);

View File

@ -221,6 +221,14 @@
#include <sys/stat.h>
#include <syscall.h>
#include <unistd.h>
// https://github.com/tildearrow/furnace/issues/588
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#include <dlfcn.h>
#undef _GNU_SOURCE
#else
#include <dlfcn.h>
#endif
#if BACKWARD_HAS_BFD == 1
// NOTE: defining PACKAGE{,_VERSION} is required before including
@ -233,13 +241,6 @@
#define PACKAGE_VERSION
#endif
#include <bfd.h>
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#include <dlfcn.h>
#undef _GNU_SOURCE
#else
#include <dlfcn.h>
#endif
#endif
#if BACKWARD_HAS_DW == 1
@ -254,13 +255,6 @@
#include <libdwarf.h>
#include <libelf.h>
#include <map>
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#include <dlfcn.h>
#undef _GNU_SOURCE
#else
#include <dlfcn.h>
#endif
#endif
#if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1)

View File

@ -58,13 +58,13 @@ SOFTWARE.
#ifndef PATH_MAX
#define PATH_MAX 260
#endif // PATH_MAX
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined (__EMSCRIPTEN__)
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined (__EMSCRIPTEN__) || defined(__HAIKU__)
#define UNIX
#define stricmp strcasecmp
#include <sys/types.h>
// this option need c++17
#ifndef USE_STD_FILESYSTEM
#include <dirent.h>
#include <dirent.h>
#endif // USE_STD_FILESYSTEM
#define PATH_SEP '/'
#endif // defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__)
@ -1287,8 +1287,6 @@ namespace IGFD
std::sort(prFileList.begin(), prFileList.end(),
[](const std::shared_ptr<FileInfos>& a, const std::shared_ptr<FileInfos>& b) -> bool
{
if (a==NULL || b==NULL)
return false;
if (!a.use_count() || !b.use_count())
return false;
@ -1549,28 +1547,53 @@ namespace IGFD
for (i = 0; i < n; i++)
{
struct dirent* ent = files[i];
std::string where = path + std::string("/") + std::string(ent->d_name);
char fileType = 0;
switch (ent->d_type)
#ifdef HAVE_DIRENT_TYPE
if (ent->d_type != DT_UNKNOWN)
{
case DT_REG:
fileType = 'f'; break;
case DT_DIR:
fileType = 'd'; break;
case DT_LNK:
std::string where = path+std::string("/")+std::string(ent->d_name);
DIR* dirTest = opendir(where.c_str());
if (dirTest==NULL) {
if (errno==ENOTDIR) {
fileType = 'f';
} else {
fileType = 'l';
}
} else {
fileType = 'd';
closedir(dirTest);
}
break;
switch (ent->d_type)
{
case DT_REG:
fileType = 'f'; break;
case DT_DIR:
fileType = 'd'; break;
case DT_LNK:
DIR* dirTest = opendir(where.c_str());
if (dirTest == NULL)
{
if (errno == ENOTDIR)
{
fileType = 'f';
}
else
{
fileType = 'l';
}
}
else
{
fileType = 'd';
closedir(dirTest);
}
break;
}
}
else
#endif // HAVE_DIRENT_TYPE
{
struct stat filestat;
if (stat(where.c_str(), &filestat) == 0)
{
if (S_ISDIR(filestat.st_mode))
{
fileType = 'd';
}
else
{
fileType = 'f';
}
}
}
auto fileNameExt = ent->d_name;
@ -1760,7 +1783,7 @@ namespace IGFD
struct stat statInfos = {};
char timebuf[100];
int result = stat(fpn.c_str(), &statInfos);
if (!result)
if (result!=-1)
{
if (vInfos->fileType != 'd')
{
@ -1781,7 +1804,11 @@ namespace IGFD
{
vInfos->fileModifDate = std::string(timebuf, len);
}
}
} else {
vInfos->fileSize=0;
vInfos->formatedFileSize = prFormatFileSize(vInfos->fileSize);
vInfos->fileModifDate="???";
}
}
}

View File

@ -4545,27 +4545,52 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
// We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size
// of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used
// without any storage on user's side.
IM_ASSERT(apply_new_text_length >= 0);
if (is_resizable)
{
ImGuiInputTextCallbackData callback_data;
callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
callback_data.Flags = flags;
callback_data.Buf = buf;
callback_data.BufTextLen = apply_new_text_length;
callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
callback_data.UserData = callback_user_data;
callback(&callback_data);
buf = callback_data.Buf;
buf_size = callback_data.BufSize;
apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
IM_ASSERT(apply_new_text_length <= buf_size);
}
//IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
// If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
value_changed = true;
// don't assert because maybe the user passes an invalid UTF-8 string...
if (apply_new_text_length >= 0) {
if (is_resizable)
{
ImGuiInputTextCallbackData callback_data;
callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
callback_data.Flags = flags;
callback_data.Buf = buf;
callback_data.BufTextLen = apply_new_text_length;
callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
callback_data.UserData = callback_user_data;
callback(&callback_data);
buf = callback_data.Buf;
buf_size = callback_data.BufSize;
apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
IM_ASSERT(apply_new_text_length <= buf_size);
}
//IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
// If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
value_changed = true;
} else {
printf("invalid buffer!\n");
if (is_resizable)
{
ImGuiInputTextCallbackData callback_data;
callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
callback_data.Flags = flags;
callback_data.Buf = buf;
callback_data.BufTextLen = 0;
callback_data.BufSize = ImMax(buf_size, 1);
callback_data.UserData = callback_user_data;
callback(&callback_data);
buf = callback_data.Buf;
buf_size = callback_data.BufSize;
apply_new_text_length = 0;
IM_ASSERT(apply_new_text_length <= buf_size);
}
//IMGUI_DEBUG_LOG("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
// clear the buffer
ImStrncpy(buf, "", 1);
value_changed = true;
}
}
// Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)

181
extern/nfd-modified/.gitignore vendored Normal file
View File

@ -0,0 +1,181 @@
.sconsign.dblite
# Object files
*.o
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific folders
*.sln.ide/
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
# Roslyn cache directories
*.ide/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
#NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# If using the old MSBuild-Integrated Package Restore, uncomment this:
#!**/packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/

16
extern/nfd-modified/LICENSE vendored Normal file
View File

@ -0,0 +1,16 @@
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

7
extern/nfd-modified/MODIFIED.md vendored Normal file
View File

@ -0,0 +1,7 @@
# MODIFIED
this is a modified, altered, edited and revised version of the Native File Dialog library used to display native-feeling, original and operating-system specific file archive dialog picker selectors, which is done by the Native File Dialog library.
it should not and shall NOT be mistaken for the original, authentic or actual version and revision of the library.
this is a version tailored for Furnace.

182
extern/nfd-modified/README.md vendored Normal file
View File

@ -0,0 +1,182 @@
# Native File Dialog Modified Version!!! #
A tiny, neat C library that portably invokes native file open, folder select and save dialogs. Write dialog code once and have it pop up native dialogs on all supported platforms. Avoid linking large dependencies like wxWidgets and qt.
This is a modified version of Native File Dialog, tailored for Furnace.
Features:
- Lean C API, static library -- no ObjC, no C++, no STL.
- Zlib licensed.
- Consistent UTF-8 support on all platforms.
- Simple universal file filter syntax.
- Paid support available.
- Multiple file selection support.
- 64-bit and 32-bit friendly.
- GCC, Clang, Xcode, Mingw and Visual Studio supported.
- No third party dependencies for building or linking.
- Support for Vista's modern `IFileDialog` on Windows.
- Support for non-deprecated Cocoa APIs on OS X.
- GTK3 dialog on Linux.
- Optional Zenity support on Linux to avoid linking GTK.
- Tested, works alongside [http://www.libsdl.org](SDL2) on all platforms, for the game developers out there.
# Example Usage #
```C
#include <nfd.h>
#include <stdio.h>
#include <stdlib.h>
int main( void )
{
nfdchar_t *outPath = NULL;
nfdresult_t result = NFD_OpenDialog( NULL, NULL, &outPath );
if ( result == NFD_OKAY ) {
puts("Success!");
puts(outPath);
free(outPath);
}
else if ( result == NFD_CANCEL ) {
puts("User pressed cancel.");
}
else {
printf("Error: %s\n", NFD_GetError() );
}
return 0;
}
```
See self-documenting API [NFD.h](src/include/nfd.h) for more options.
# Screenshots #
![Windows rendering a dialog](screens/open_win.png?raw=true)
![GTK3 on Linux rendering a dialog](screens/open_gtk3.png?raw=true)
![Cocoa on MacOS rendering a dialog](screens/open_cocoa.png?raw=true)
## Changelog ##
- **Major** version increments denote API or ABI departure.
- **Minor** version increments denote build or trivial departures.
- **Micro** version increments just recompile and drop-in.
release | what's new | date
--------|-----------------------------|---------
1.0.0 | initial | oct 2014
1.1.0 | premake5; scons deprecated | aug 2016
1.1.1 | mingw support, build fixes | aug 2016
1.1.2 | test_pickfolder() added | aug 2016
1.1.3 | zenity linux backend added | nov 2017
<i></i> | fix char type in decls | nov 2017
1.1.4 | fix win32 memleaks | dec 2018
<i></i> | improve win32 errorhandling | dec 2018
<i></i> | macos fix focus bug | dec 2018
1.1.5 | win32 fix com reinitialize | aug 2019
1.1.6 | fix osx filter bug | aug 2019
<i></i> | remove deprecated scons | aug 2019
<i></i> | fix mingw compilation | aug 2019
<i></i> | -Wextra warning cleanup | aug 2019
## Building ##
NFD uses [Premake5](https://premake.github.io/download.html) generated Makefiles and IDE project files. The generated project files are checked in under `build/` so you don't have to download and use Premake in most cases.
If you need to run Premake5 directly, further [build documentation](docs/build.md) is available.
Previously, NFD used SCons to build. As of 1.1.6, SCons support has been removed entirely.
`nfd.a` will be built for release builds, and `nfd_d.a` will be built for debug builds.
### Makefiles ###
The makefile offers up to four options, with `release_x64` as the default.
make config=release_x86
make config=release_x64
make config=debug_x86
make config=debug_x64
### Compiling Your Programs ###
1. Add `src/include` to your include search path.
2. Add `nfd.lib` or `nfd_d.lib` to the list of list of static libraries to link against (for release or debug, respectively).
3. Add `build/<debug|release>/<arch>` to the library search path.
#### Linux GTK ####
`apt-get libgtk-3-dev` installs the gtk dependency for library compilation.
On Linux, you have the option of compiling and linking against GTK. If you use it, the recommended way to compile is to include the arguments of `pkg-config --cflags --libs gtk+-3.0`.
#### Linux Zenity ####
Alternatively, you can use the Zenity backend by running the Makefile in `build/gmake_linux_zenity`. Zenity runs the dialog in its own address space, but requires the user to have Zenity correctly installed and configured on their system.
#### MacOS ####
On Mac OS, add `AppKit` to the list of frameworks.
#### Windows ####
On Windows, ensure you are linking against `comctl32.lib`.
## Usage ##
See `NFD.h` for API calls. See `tests/*.c` for example code.
After compiling, `build/bin` contains compiled test programs. The appropriate subdirectory under `build/lib` contains the built library.
## File Filter Syntax ##
There is a form of file filtering in every file dialog API, but no consistent means of supporting it. NFD provides support for filtering files by groups of extensions, providing its own descriptions (where applicable) for the extensions.
A wildcard filter is always added to every dialog.
### Separators ###
- `;` Begin a new filter.
- `,` Add a separate type to the filter.
#### Examples ####
`txt` The default filter is for text files. There is a wildcard option in a dropdown.
`png,jpg;psd` The default filter is for png and jpg files. A second filter is available for psd files. There is a wildcard option in a dropdown.
`NULL` Wildcard only.
## Iterating Over PathSets ##
See [test_opendialogmultiple.c](test/test_opendialogmultiple.c).
# Known Limitations #
I accept quality code patches, or will resolve these and other matters through support. See [contributing](docs/contributing.md) for details.
- No support for Windows XP's legacy dialogs such as `GetOpenFileName`.
- No support for file filter names -- ex: "Image Files" (*.png, *.jpg). Nameless filters are supported, however.
- GTK Zenity implementation's process exec error handling does not gracefully handle numerous error cases, choosing to abort rather than cleanup and return.
- GTK 3 spams one warning per dialog created.
# Copyright and Credit #
Copyright &copy; 2014-2019 [Frogtoss Games](http://www.frogtoss.com), Inc.
File [LICENSE](LICENSE) covers all files in this repo.
Native File Dialog by Michael Labbe
<mike@frogtoss.com>
Tomasz Konojacki for [microutf8](http://puszcza.gnu.org.ua/software/microutf8/)
[Denis Kolodin](https://github.com/DenisKolodin) for mingw support.
[Tom Mason](https://github.com/wheybags) for Zenity support.
## Support ##
Directed support for this work is available from the original author under a paid agreement.
[Contact Frogtoss Games](http://www.frogtoss.com/pages/contact.html).

39
extern/nfd-modified/docs/build.md vendored Normal file
View File

@ -0,0 +1,39 @@
# Building NFD #
Most of the building instructions are included in [README.md](/README.md). This file just contains apocrypha.
## Running Premake5 Directly ##
*You shouldn't have to run Premake5 directly to build Native File Dialog. This is for package maintainers or people with exotic demands only!*
1. [Clone premake-core](https://github.com/premake/premake-core)
2. [Follow instructions on how to build premake](https://github.com/premake/premake-core/wiki/Building-Premake)
3. `cd` to `build`
4. Type `premake5 <type>`, where <type> is the build you want to create.
### Package Maintainer Only ###
I support a custom Premake action: `premake5 dist`, which generates all of the checked in project types in subdirectories. It is useful to run this command if you are submitting a pull request to test all of the supported premake configurations. Do not check in the built projects; I will do so while accepting your pull request.
## SCons build (deprecated) ##
As of 1.1.6, the deprecated and unmaintained SCons support is removed.
## Compiling with Mingw ##
Use the Makefile in `build/gmake_windows` to build Native File Dialog with mingw. Mingw has many distributions and not all of them are reliable. Here is what worked for me, the primary author of Native File Dialog:
1. Use mingw64, not mingw32. Downloaded from [sourceforge.net](https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/installer/mingw-w64-install.exe/download).
2. When prompted in the intsaller, install the basic compiler and g++.
3. Add the installed bin dir to command prompt path.
4. Run `set CC=g++` to enforce `g++` instead of the default, `cc` for compiling and linking.
5. In `build/gmake_windows`, run `mingw32-make config=release_x64 clean`. Running clean ensures no Visual Studio build products conflict which can cause link errors.
6. Now run `mingw32-make config=release_x64`.
The author has not attempted to build or even install an x86 toolchain for mingw.
If you report an issue, be sure to run make with `verbose=1` so commands are visible.
## Adding NFD source directly to your project ##
Lots of developers add NFD source directly to their projects instead of using the included build scripts. As of 1.1.6, this is an acknowledged approach to building. Of course, everyone has a slightly different toolchain with various warnings and linters enabled. If you run a linter or catch a warning, please consider submitting a pull request to help NFD build cleanly for everyone.

View File

@ -0,0 +1,25 @@
# Pull Requests #
I have had to turn away a number of pull requests due to avoidable circumstances. Please read this file before submitting a pull request. Also look at existing, rejected pull requests which state the reason for rejection.
Here are the rules:
- **Submit pull requests to the devel branch**. The library must be tested on every compiler and OS, so there is no way I am going to just put your change in the master before it has been sync'd and tested on a number of machines. Master branch is depended upon by hundreds of projects.
- **Test your changes on all platforms and compilers that you can.** Also, state which platforms you have tested your code on. 32-bit or 64-bit. Clang or GCC. Visual Studio or Mingw. I have to test all these to accept pull requests, so I prioritize changes that respect my time.
- **Submit Premake build changes only**. As of 1.1, SCons is deprecated. Also, do not submit altered generated projects. I will re-run Premake to re-generate them to ensure that I can still generate the project prior to admitting your pull request.
- **Do not alter existing behavior to support your desired behavior**. For instance, rewriting file open dialogs to behave differently, while trading off functionality for compatibility, will get you rejected. Consider creating an additional code path. Instead of altering `nfd_win.cpp` to support Windows XP, create `nfd_win_legacy.cpp`, which exists alongside the newer file dialog.
- **Do not submit anything I can't verify or maintain**. If you add support for a compiler, include from-scratch install instructions that you have tested yourself. Accepting a pull request means I am now the maintainer of your code, so I must understand what it does and how to test it.
- **Do not change the externally facing API**. NFD needs to maintain ABI compatibility.
## Submitting Cloud Autobuild systems ##
I have received a few pull requests for Travis and AppVeyor-based autobuilding which I have not accepted. NativeFileDialog is officially covered by my private BuildBot network which supports all three target OSes, both CPU architectures and four compilers. I take the view that having a redundant, lesser autobuild system does not improve coverage: it gives a false positive when partial building succeeds. Please do not invest time into cloud-based building with the hope of a pull request being accepted.
## Contact Me ##
Despite all of the "do nots" above, I am happy to recieve new pull requests! If you have any questions about style, or what I would need to accept your specific request, please contact me ahead of submitting the pull request by opening an issue on Github with your question. I will do my best to answer you.

21
extern/nfd-modified/src/common.h vendored Normal file
View File

@ -0,0 +1,21 @@
/*
Native File Dialog
Internal, common across platforms
http://www.frogtoss.com/labs
*/
#ifndef _NFD_COMMON_H
#define _NFD_COMMON_H
#define NFD_MAX_STRLEN 256
#define _NFD_UNUSED(x) ((void)x)
void *NFDi_Malloc( size_t bytes );
void NFDi_Free( void *ptr );
void NFDi_SetError( const char *msg );
void NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy );
#endif

73
extern/nfd-modified/src/include/nfd.h vendored Normal file
View File

@ -0,0 +1,73 @@
/*
Native File Dialog
User API
http://www.frogtoss.com/labs
*/
#ifndef _NFD_H
#define _NFD_H
#include <stddef.h>
#include <functional>
#include <string>
#include <vector>
/* denotes UTF-8 char */
typedef char nfdchar_t;
typedef std::function<void(const char*)> nfdselcallback_t;
/* opaque data structure -- see NFD_PathSet_* */
typedef struct {
nfdchar_t *buf;
size_t *indices; /* byte offsets into buf */
size_t count; /* number of indices into buf */
}nfdpathset_t;
typedef enum {
NFD_ERROR, /* programmatic error */
NFD_OKAY, /* user pressed okay, or successful return */
NFD_CANCEL /* user pressed cancel */
}nfdresult_t;
/* nfd_<targetplatform>.c */
/* single file open dialog */
nfdresult_t NFD_OpenDialog( const std::vector<std::string>& filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath,
nfdselcallback_t selCallback = NULL );
/* multiple file open dialog */
nfdresult_t NFD_OpenDialogMultiple( const std::vector<std::string>& filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths,
nfdselcallback_t selCallback = NULL );
/* save dialog */
nfdresult_t NFD_SaveDialog( const std::vector<std::string>& filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath,
nfdselcallback_t selCallback = NULL );
/* select folder dialog */
nfdresult_t NFD_PickFolder( const nfdchar_t *defaultPath,
nfdchar_t **outPath);
/* nfd_common.c */
/* get last error -- set when nfdresult_t returns NFD_ERROR */
const char *NFD_GetError( void );
/* get the number of entries stored in pathSet */
size_t NFD_PathSet_GetCount( const nfdpathset_t *pathSet );
/* Get the UTF-8 path at offset index */
nfdchar_t *NFD_PathSet_GetPath( const nfdpathset_t *pathSet, size_t index );
/* Free the pathSet */
void NFD_PathSet_Free( nfdpathset_t *pathSet );
#endif

301
extern/nfd-modified/src/nfd_cocoa.mm vendored Normal file
View File

@ -0,0 +1,301 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
#include <AppKit/AppKit.h>
#include "nfd.h"
#include "nfd_common.h"
// this language is a mess!
// who thought it was a good idea to combine Objective-C and C++ together
// when you could just have used C++ and call it a day!!!
//
// might as well make Objective-Ruswift++...
static NSArray *BuildAllowedFileTypes( const std::vector<std::string>& filterList )
{
// Commas and semicolons are the same thing on this platform
// like what about THIS INSTEAD!
// NSMutableArray *buildFilterList = NSMutableArray::alloc()->init();
NSMutableArray *buildFilterList = [[NSMutableArray alloc] init];
std::string typebuf;
int index=-1;
for (const std::string& i: filterList) {
index++;
if (!(index&1)) {
continue;
}
typebuf="";
for (const char& j: i) {
if (j==' ' || j==',' || j ==';') {
// or this: NSString::stringWithUTF8String(typebuf);
// buildFilterList->addObject(thisType);
// really? did you have to make this mess?!
const char* typebufC=typebuf.c_str();
NSString *thisType = [NSString stringWithUTF8String:typebufC];
[buildFilterList addObject:thisType];
typebuf="";
} else if (j!='.' && j!='*') {
typebuf+=j;
}
}
if (!typebuf.empty()) {
// I don't think this will work, but come on...
const char* typebufC=typebuf.c_str();
NSString *thisType = [NSString stringWithUTF8String:typebufC];
[buildFilterList addObject:thisType];
}
}
NSArray *returnArray = [NSArray arrayWithArray:buildFilterList];
[buildFilterList release];
return returnArray;
}
static void AddFilterListToDialog( NSSavePanel *dialog, const std::vector<std::string>& filterList )
{
if ( filterList.size()&1 )
return;
NSArray *allowedFileTypes = BuildAllowedFileTypes( filterList );
if ( [allowedFileTypes count] != 0 )
{
[dialog setAllowedFileTypes:allowedFileTypes];
}
}
static void SetDefaultPath( NSSavePanel *dialog, const nfdchar_t *defaultPath )
{
if ( !defaultPath || strlen(defaultPath) == 0 )
return;
NSString *defaultPathString = [NSString stringWithUTF8String: defaultPath];
NSURL *url = [NSURL fileURLWithPath:defaultPathString isDirectory:YES];
[dialog setDirectoryURL:url];
}
/* fixme: pathset should be pathSet */
static nfdresult_t AllocPathSet( NSArray *urls, nfdpathset_t *pathset )
{
assert(pathset);
assert([urls count]);
pathset->count = (size_t)[urls count];
pathset->indices = (size_t*)NFDi_Malloc( sizeof(size_t)*pathset->count );
if ( !pathset->indices )
{
return NFD_ERROR;
}
// count the total space needed for buf
size_t bufsize = 0;
for ( NSURL *url in urls )
{
NSString *path = [url path];
bufsize += [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
}
pathset->buf = (nfdchar_t*)NFDi_Malloc( sizeof(nfdchar_t) * bufsize );
if ( !pathset->buf )
{
return NFD_ERROR;
}
// fill buf
nfdchar_t *p_buf = pathset->buf;
size_t count = 0;
for ( NSURL *url in urls )
{
NSString *path = [url path];
const nfdchar_t *utf8Path = [path UTF8String];
size_t byteLen = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
memcpy( p_buf, utf8Path, byteLen );
ptrdiff_t index = p_buf - pathset->buf;
assert( index >= 0 );
pathset->indices[count] = (size_t)index;
p_buf += byteLen;
++count;
}
return NFD_OKAY;
}
/* public */
nfdresult_t NFD_OpenDialog( const std::vector<std::string>& filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath,
nfdselcallback_t selCallback )
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
NSOpenPanel *dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:NO];
// Build the filter list
AddFilterListToDialog(dialog, filterList);
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
nfdresult_t nfdResult = NFD_CANCEL;
if ( [dialog runModal] == NSModalResponseOK )
{
NSURL *url = [dialog URL];
const char *utf8Path = [[url path] UTF8String];
// byte count, not char count
size_t len = strlen(utf8Path);//NFDi_UTF8_Strlen(utf8Path);
*outPath = (nfdchar_t*)NFDi_Malloc( len+1 );
if ( !*outPath )
{
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return NFD_ERROR;
}
memcpy( *outPath, utf8Path, len+1 ); /* copy null term */
nfdResult = NFD_OKAY;
}
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return nfdResult;
}
nfdresult_t NFD_OpenDialogMultiple( const std::vector<std::string>& filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths,
nfdselcallback_t selCallback )
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
NSOpenPanel *dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:YES];
// Build the fiter list.
AddFilterListToDialog(dialog, filterList);
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
nfdresult_t nfdResult = NFD_CANCEL;
if ( [dialog runModal] == NSModalResponseOK )
{
NSArray *urls = [dialog URLs];
if ( [urls count] == 0 )
{
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return NFD_CANCEL;
}
if ( AllocPathSet( urls, outPaths ) == NFD_ERROR )
{
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return NFD_ERROR;
}
nfdResult = NFD_OKAY;
}
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return nfdResult;
}
nfdresult_t NFD_SaveDialog( const std::vector<std::string>& filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath,
nfdselcallback_t selCallback )
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
NSSavePanel *dialog = [NSSavePanel savePanel];
[dialog setExtensionHidden:NO];
// Build the filter list.
AddFilterListToDialog(dialog, filterList);
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
nfdresult_t nfdResult = NFD_CANCEL;
if ( [dialog runModal] == NSModalResponseOK )
{
NSURL *url = [dialog URL];
const char *utf8Path = [[url path] UTF8String];
size_t byteLen = [url.path lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
*outPath = (nfdchar_t*)NFDi_Malloc( byteLen );
if ( !*outPath )
{
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return NFD_ERROR;
}
memcpy( *outPath, utf8Path, byteLen );
nfdResult = NFD_OKAY;
}
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return nfdResult;
}
nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
nfdchar_t **outPath)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
NSOpenPanel *dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:NO];
[dialog setCanChooseDirectories:YES];
[dialog setCanCreateDirectories:YES];
[dialog setCanChooseFiles:NO];
// Set the starting directory
SetDefaultPath(dialog, defaultPath);
nfdresult_t nfdResult = NFD_CANCEL;
if ( [dialog runModal] == NSModalResponseOK )
{
NSURL *url = [dialog URL];
const char *utf8Path = [[url path] UTF8String];
// byte count, not char count
size_t len = strlen(utf8Path);//NFDi_UTF8_Strlen(utf8Path);
*outPath = (nfdchar_t*)NFDi_Malloc( len+1 );
if ( !*outPath )
{
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return NFD_ERROR;
}
memcpy( *outPath, utf8Path, len+1 ); /* copy null term */
nfdResult = NFD_OKAY;
}
[pool release];
[keyWindow makeKeyAndOrderFront:nil];
return nfdResult;
}

142
extern/nfd-modified/src/nfd_common.cpp vendored Normal file
View File

@ -0,0 +1,142 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "nfd_common.h"
static char g_errorstr[NFD_MAX_STRLEN] = {0};
/* public routines */
const char *NFD_GetError( void )
{
return g_errorstr;
}
size_t NFD_PathSet_GetCount( const nfdpathset_t *pathset )
{
assert(pathset);
return pathset->count;
}
nfdchar_t *NFD_PathSet_GetPath( const nfdpathset_t *pathset, size_t num )
{
assert(pathset);
assert(num < pathset->count);
return pathset->buf + pathset->indices[num];
}
void NFD_PathSet_Free( nfdpathset_t *pathset )
{
assert(pathset);
NFDi_Free( pathset->indices );
NFDi_Free( pathset->buf );
}
/* internal routines */
void *NFDi_Malloc( size_t bytes )
{
void *ptr = malloc(bytes);
if ( !ptr )
NFDi_SetError("NFDi_Malloc failed.");
return ptr;
}
void NFDi_Free( void *ptr )
{
assert(ptr);
free(ptr);
}
void NFDi_SetError( const char *msg )
{
int bTruncate = NFDi_SafeStrncpy( g_errorstr, msg, NFD_MAX_STRLEN );
assert( !bTruncate ); _NFD_UNUSED(bTruncate);
}
int NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy )
{
size_t n = maxCopy;
char *d = dst;
assert( src );
assert( dst );
while ( n > 0 && *src != '\0' )
{
*d++ = *src++;
--n;
}
/* Truncation case -
terminate string and return true */
if ( n == 0 )
{
dst[maxCopy-1] = '\0';
return 1;
}
/* No truncation. Append a single NULL and return. */
*d = '\0';
return 0;
}
/* adapted from microutf8 */
int32_t NFDi_UTF8_Strlen( const nfdchar_t *str )
{
/* This function doesn't properly check validity of UTF-8 character
sequence, it is supposed to use only with valid UTF-8 strings. */
int32_t character_count = 0;
int32_t i = 0; /* Counter used to iterate over string. */
nfdchar_t maybe_bom[4];
/* If there is UTF-8 BOM ignore it. */
if (strlen(str) > 2)
{
strncpy(maybe_bom, str, 3);
maybe_bom[3] = 0;
if (strcmp(maybe_bom, (nfdchar_t*)NFD_UTF8_BOM) == 0)
i += 3;
}
while(str[i])
{
if (str[i] >> 7 == 0)
{
/* If bit pattern begins with 0 we have ascii character. */
++character_count;
}
else if (str[i] >> 6 == 3)
{
/* If bit pattern begins with 11 it is beginning of UTF-8 byte sequence. */
++character_count;
}
else if (str[i] >> 6 == 2)
; /* If bit pattern begins with 10 it is middle of utf-8 byte sequence. */
else
{
/* In any other case this is not valid UTF-8. */
return -1;
}
++i;
}
return character_count;
}
int NFDi_IsFilterSegmentChar( char ch )
{
return (ch==','||ch==';'||ch=='\0');
}

31
extern/nfd-modified/src/nfd_common.h vendored Normal file
View File

@ -0,0 +1,31 @@
/*
Native File Dialog
Internal, common across platforms
http://www.frogtoss.com/labs
*/
#ifndef _NFD_COMMON_H
#define _NFD_COMMON_H
#include "nfd.h"
#include <stdint.h>
#define NFD_MAX_STRLEN 256
#define _NFD_UNUSED(x) ((void)x)
#define NFD_UTF8_BOM "\xEF\xBB\xBF"
void *NFDi_Malloc( size_t bytes );
void NFDi_Free( void *ptr );
void NFDi_SetError( const char *msg );
int NFDi_SafeStrncpy( char *dst, const char *src, size_t maxCopy );
int32_t NFDi_UTF8_Strlen( const nfdchar_t *str );
int NFDi_IsFilterSegmentChar( char ch );
#endif

379
extern/nfd-modified/src/nfd_gtk.cpp vendored Normal file
View File

@ -0,0 +1,379 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <gtk/gtk.h>
#include "nfd.h"
#include "nfd_common.h"
const char INIT_FAIL_MSG[] = "gtk_init_check failed to initilaize GTK+";
static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize )
{
const char SEP[] = ", ";
size_t len = strlen(filterName);
if ( len != 0 )
{
strncat( filterName, SEP, bufsize - len - 1 );
len += strlen(SEP);
}
strncat( filterName, typebuf, bufsize - len - 1 );
}
static void AddFiltersToDialog( GtkWidget *dialog, const char *filterList )
{
GtkFileFilter *filter;
char typebuf[NFD_MAX_STRLEN] = {0};
const char *p_filterList = filterList;
char *p_typebuf = typebuf;
char filterName[NFD_MAX_STRLEN] = {0};
if ( !filterList || strlen(filterList) == 0 )
return;
filter = gtk_file_filter_new();
while ( 1 )
{
if ( NFDi_IsFilterSegmentChar(*p_filterList) )
{
char typebufWildcard[NFD_MAX_STRLEN];
/* add another type to the filter */
assert( strlen(typebuf) > 0 );
assert( strlen(typebuf) < NFD_MAX_STRLEN-1 );
snprintf( typebufWildcard, NFD_MAX_STRLEN, "*.%s", typebuf );
AddTypeToFilterName( typebuf, filterName, NFD_MAX_STRLEN );
gtk_file_filter_add_pattern( filter, typebufWildcard );
p_typebuf = typebuf;
memset( typebuf, 0, sizeof(char) * NFD_MAX_STRLEN );
}
if ( *p_filterList == ';' || *p_filterList == '\0' )
{
/* end of filter -- add it to the dialog */
gtk_file_filter_set_name( filter, filterName );
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
filterName[0] = '\0';
if ( *p_filterList == '\0' )
break;
filter = gtk_file_filter_new();
}
if ( !NFDi_IsFilterSegmentChar( *p_filterList ) )
{
*p_typebuf = *p_filterList;
p_typebuf++;
}
p_filterList++;
}
/* always append a wildcard option to the end*/
filter = gtk_file_filter_new();
gtk_file_filter_set_name( filter, "*.*" );
gtk_file_filter_add_pattern( filter, "*" );
gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(dialog), filter );
}
static void SetDefaultPath( GtkWidget *dialog, const char *defaultPath )
{
if ( !defaultPath || strlen(defaultPath) == 0 )
return;
/* GTK+ manual recommends not specifically setting the default path.
We do it anyway in order to be consistent across platforms.
If consistency with the native OS is preferred, this is the line
to comment out. -ml */
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), defaultPath );
}
static nfdresult_t AllocPathSet( GSList *fileList, nfdpathset_t *pathSet )
{
size_t bufSize = 0;
GSList *node;
nfdchar_t *p_buf;
size_t count = 0;
assert(fileList);
assert(pathSet);
pathSet->count = (size_t)g_slist_length( fileList );
assert( pathSet->count > 0 );
pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count );
if ( !pathSet->indices )
{
return NFD_ERROR;
}
/* count the total space needed for buf */
for ( node = fileList; node; node = node->next )
{
assert(node->data);
bufSize += strlen( (const gchar*)node->data ) + 1;
}
pathSet->buf = NFDi_Malloc( sizeof(nfdchar_t) * bufSize );
/* fill buf */
p_buf = pathSet->buf;
for ( node = fileList; node; node = node->next )
{
nfdchar_t *path = (nfdchar_t*)(node->data);
size_t byteLen = strlen(path)+1;
ptrdiff_t index;
memcpy( p_buf, path, byteLen );
g_free(node->data);
index = p_buf - pathSet->buf;
assert( index >= 0 );
pathSet->indices[count] = (size_t)index;
p_buf += byteLen;
++count;
}
g_slist_free( fileList );
return NFD_OKAY;
}
static void WaitForCleanup(void)
{
while (gtk_events_pending())
gtk_main_iteration();
}
/* public */
nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath )
{
GtkWidget *dialog;
nfdresult_t result;
if ( !gtk_init_check( NULL, NULL ) )
{
NFDi_SetError(INIT_FAIL_MSG);
return NFD_ERROR;
}
dialog = gtk_file_chooser_dialog_new( "Open File",
NULL,
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT,
NULL );
/* Build the filter list */
AddFiltersToDialog(dialog, filterList);
/* Set the default path */
SetDefaultPath(dialog, defaultPath);
result = NFD_CANCEL;
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
{
char *filename;
filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
{
size_t len = strlen(filename);
*outPath = NFDi_Malloc( len + 1 );
memcpy( *outPath, filename, len + 1 );
if ( !*outPath )
{
g_free( filename );
gtk_widget_destroy(dialog);
return NFD_ERROR;
}
}
g_free( filename );
result = NFD_OKAY;
}
WaitForCleanup();
gtk_widget_destroy(dialog);
WaitForCleanup();
return result;
}
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths )
{
GtkWidget *dialog;
nfdresult_t result;
if ( !gtk_init_check( NULL, NULL ) )
{
NFDi_SetError(INIT_FAIL_MSG);
return NFD_ERROR;
}
dialog = gtk_file_chooser_dialog_new( "Open Files",
NULL,
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT,
NULL );
gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), TRUE );
/* Build the filter list */
AddFiltersToDialog(dialog, filterList);
/* Set the default path */
SetDefaultPath(dialog, defaultPath);
result = NFD_CANCEL;
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
{
GSList *fileList = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) );
if ( AllocPathSet( fileList, outPaths ) == NFD_ERROR )
{
gtk_widget_destroy(dialog);
return NFD_ERROR;
}
result = NFD_OKAY;
}
WaitForCleanup();
gtk_widget_destroy(dialog);
WaitForCleanup();
return result;
}
nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath )
{
GtkWidget *dialog;
nfdresult_t result;
if ( !gtk_init_check( NULL, NULL ) )
{
NFDi_SetError(INIT_FAIL_MSG);
return NFD_ERROR;
}
dialog = gtk_file_chooser_dialog_new( "Save File",
NULL,
GTK_FILE_CHOOSER_ACTION_SAVE,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Save", GTK_RESPONSE_ACCEPT,
NULL );
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
/* Build the filter list */
AddFiltersToDialog(dialog, filterList);
/* Set the default path */
SetDefaultPath(dialog, defaultPath);
result = NFD_CANCEL;
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
{
char *filename;
filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
{
size_t len = strlen(filename);
*outPath = NFDi_Malloc( len + 1 );
memcpy( *outPath, filename, len + 1 );
if ( !*outPath )
{
g_free( filename );
gtk_widget_destroy(dialog);
return NFD_ERROR;
}
}
g_free(filename);
result = NFD_OKAY;
}
WaitForCleanup();
gtk_widget_destroy(dialog);
WaitForCleanup();
return result;
}
nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
nfdchar_t **outPath)
{
GtkWidget *dialog;
nfdresult_t result;
if (!gtk_init_check(NULL, NULL))
{
NFDi_SetError(INIT_FAIL_MSG);
return NFD_ERROR;
}
dialog = gtk_file_chooser_dialog_new( "Select folder",
NULL,
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Select", GTK_RESPONSE_ACCEPT,
NULL );
gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(dialog), TRUE );
/* Set the default path */
SetDefaultPath(dialog, defaultPath);
result = NFD_CANCEL;
if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
{
char *filename;
filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(dialog) );
{
size_t len = strlen(filename);
*outPath = NFDi_Malloc( len + 1 );
memcpy( *outPath, filename, len + 1 );
if ( !*outPath )
{
g_free( filename );
gtk_widget_destroy(dialog);
return NFD_ERROR;
}
}
g_free(filename);
result = NFD_OKAY;
}
WaitForCleanup();
gtk_widget_destroy(dialog);
WaitForCleanup();
return result;
}

801
extern/nfd-modified/src/nfd_win.cpp vendored Normal file
View File

@ -0,0 +1,801 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
#ifdef __MINGW32__
// Explicitly setting NTDDI version, this is necessary for the MinGW compiler
#define NTDDI_VERSION NTDDI_VISTA
#define _WIN32_WINNT _WIN32_WINNT_VISTA
#endif
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
/* only locally define UNICODE in this compilation unit */
#ifndef UNICODE
#define UNICODE
#endif
#include <wchar.h>
#include <stdio.h>
#include <assert.h>
#include <windows.h>
#include <shobjidl.h>
#include "nfd_common.h"
// hack I know
#include "../../../src/utfutils.h"
class NFDWinEvents: public IFileDialogEvents {
nfdselcallback_t selCallback;
size_t refCount;
virtual ~NFDWinEvents() {
}
public:
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv) {
printf("QueryInterface called DAMN IT\n");
*ppv=NULL;
return E_NOTIMPL;
}
IFACEMETHODIMP_(ULONG) AddRef() {
printf("AddRef() called\n");
return InterlockedIncrement(&refCount);
}
IFACEMETHODIMP_(ULONG) Release() {
printf("Release() called\n");
LONG ret=InterlockedDecrement(&refCount);
if (ret==0) {
printf("Destroying the final object.\n");
delete this;
}
return ret;
}
IFACEMETHODIMP OnFileOk(IFileDialog*) { return E_NOTIMPL; }
IFACEMETHODIMP OnFolderChange(IFileDialog*) { return E_NOTIMPL; }
IFACEMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) { return E_NOTIMPL; }
IFACEMETHODIMP OnOverwrite(IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) { return E_NOTIMPL; }
IFACEMETHODIMP OnShareViolation(IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) { return E_NOTIMPL; }
IFACEMETHODIMP OnTypeChange(IFileDialog*) { return E_NOTIMPL; }
IFACEMETHODIMP OnSelectionChange(IFileDialog* dialog) {
// Get the file name
::IShellItem *shellItem(NULL);
HRESULT result = dialog->GetCurrentSelection(&shellItem);
if ( !SUCCEEDED(result) )
{
printf("failure!\n");
return S_OK;
}
wchar_t *filePath(NULL);
result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
if ( !SUCCEEDED(result) )
{
printf("GDN failure!\n");
shellItem->Release();
return S_OK;
}
std::string utf8FilePath=utf16To8(filePath);
if (selCallback!=NULL) selCallback(utf8FilePath.c_str());
printf("I got you for a value of %s\n",utf8FilePath.c_str());
shellItem->Release();
return S_OK;
}
NFDWinEvents(nfdselcallback_t callback):
selCallback(callback),
refCount(1) {
}
};
#define COM_INITFLAGS ::COINIT_APARTMENTTHREADED | ::COINIT_DISABLE_OLE1DDE
static BOOL COMIsInitialized(HRESULT coResult)
{
if (coResult == RPC_E_CHANGED_MODE)
{
// If COM was previously initialized with different init flags,
// NFD still needs to operate. Eat this warning.
return TRUE;
}
return SUCCEEDED(coResult);
}
static HRESULT COMInit(void)
{
return ::CoInitializeEx(NULL, COM_INITFLAGS);
}
static void COMUninit(HRESULT coResult)
{
// do not uninitialize if RPC_E_CHANGED_MODE occurred -- this
// case does not refcount COM.
if (SUCCEEDED(coResult))
::CoUninitialize();
}
// allocs the space in outPath -- call free()
static void CopyWCharToNFDChar( const wchar_t *inStr, nfdchar_t **outStr )
{
int inStrCharacterCount = static_cast<int>(wcslen(inStr));
int bytesNeeded = WideCharToMultiByte( CP_UTF8, 0,
inStr, inStrCharacterCount,
NULL, 0, NULL, NULL );
assert( bytesNeeded );
bytesNeeded += 1;
*outStr = (nfdchar_t*)NFDi_Malloc( bytesNeeded );
if ( !*outStr )
return;
int bytesWritten = WideCharToMultiByte( CP_UTF8, 0,
inStr, -1,
*outStr, bytesNeeded,
NULL, NULL );
assert( bytesWritten ); _NFD_UNUSED( bytesWritten );
}
/* includes NULL terminator byte in return */
static size_t GetUTF8ByteCountForWChar( const wchar_t *str )
{
size_t bytesNeeded = WideCharToMultiByte( CP_UTF8, 0,
str, -1,
NULL, 0, NULL, NULL );
assert( bytesNeeded );
return bytesNeeded+1;
}
// write to outPtr -- no free() necessary.
static int CopyWCharToExistingNFDCharBuffer( const wchar_t *inStr, nfdchar_t *outPtr )
{
int bytesNeeded = static_cast<int>(GetUTF8ByteCountForWChar( inStr ));
/* invocation copies null term */
int bytesWritten = WideCharToMultiByte( CP_UTF8, 0,
inStr, -1,
outPtr, bytesNeeded,
NULL, 0 );
assert( bytesWritten );
return bytesWritten;
}
// allocs the space in outStr -- call free()
static void CopyNFDCharToWChar( const nfdchar_t *inStr, wchar_t **outStr )
{
int inStrByteCount = static_cast<int>(strlen(inStr));
int charsNeeded = MultiByteToWideChar(CP_UTF8, 0,
inStr, inStrByteCount,
NULL, 0 );
assert( charsNeeded );
assert( !*outStr );
charsNeeded += 1; // terminator
*outStr = (wchar_t*)NFDi_Malloc( charsNeeded * sizeof(wchar_t) );
if ( !*outStr )
return;
int ret = MultiByteToWideChar(CP_UTF8, 0,
inStr, inStrByteCount,
*outStr, charsNeeded);
(*outStr)[charsNeeded-1] = '\0';
#ifdef _DEBUG
int inStrCharacterCount = static_cast<int>(NFDi_UTF8_Strlen(inStr));
assert( ret == inStrCharacterCount );
#else
_NFD_UNUSED(ret);
#endif
}
static nfdresult_t AddFiltersToDialog( ::IFileDialog *fileOpenDialog, const std::vector<std::string>& filterList )
{
const wchar_t WILDCARD[] = L"*.*";
if (filterList.empty())
return NFD_OKAY;
// list size has to be an even number (name/filter)
if (filterList.size()&1)
return NFD_ERROR;
// Count rows to alloc
UINT filterCount = filterList.size()>>1; /* guaranteed to have one filter on a correct, non-empty parse */
if (filterCount==0) filterCount=1;
if ( !filterCount )
{
NFDi_SetError("Error parsing filters.");
return NFD_ERROR;
}
/* filterCount plus 1 because we hardcode the *.* wildcard after the while loop */
COMDLG_FILTERSPEC *specList = (COMDLG_FILTERSPEC*)NFDi_Malloc( sizeof(COMDLG_FILTERSPEC) * ((size_t)filterCount) );
if ( !specList )
{
return NFD_ERROR;
}
for (UINT i = 0; i < filterCount; ++i )
{
specList[i].pszName = NULL;
specList[i].pszSpec = NULL;
}
size_t specIdx = 0;
for (size_t i=0; i<filterList.size(); i+=2) {
String name=filterList[i];
String spec=filterList[i+1];
for (char& i: spec) {
if (i==' ') i=';';
}
if (spec==".*") spec="*.*";
CopyNFDCharToWChar( name.c_str(), (wchar_t**)&specList[specIdx].pszName );
CopyNFDCharToWChar( spec.c_str(), (wchar_t**)&specList[specIdx].pszSpec );
++specIdx;
}
/* Add wildcard if specIdx is 0 */
if (specIdx==0) {
specList[specIdx].pszSpec = WILDCARD;
specList[specIdx].pszName = WILDCARD;
}
fileOpenDialog->SetFileTypes( filterCount, specList );
/* free speclist */
for ( size_t i = 0; i < filterCount; ++i )
{
NFDi_Free( (void*)specList[i].pszSpec );
}
NFDi_Free( specList );
return NFD_OKAY;
}
static nfdresult_t AllocPathSet( IShellItemArray *shellItems, nfdpathset_t *pathSet )
{
const char ERRORMSG[] = "Error allocating pathset.";
assert(shellItems);
assert(pathSet);
// How many items in shellItems?
DWORD numShellItems;
HRESULT result = shellItems->GetCount(&numShellItems);
if ( !SUCCEEDED(result) )
{
NFDi_SetError(ERRORMSG);
return NFD_ERROR;
}
pathSet->count = static_cast<size_t>(numShellItems);
assert( pathSet->count > 0 );
pathSet->indices = (size_t*)NFDi_Malloc( sizeof(size_t)*pathSet->count );
if ( !pathSet->indices )
{
return NFD_ERROR;
}
/* count the total bytes needed for buf */
size_t bufSize = 0;
for ( DWORD i = 0; i < numShellItems; ++i )
{
::IShellItem *shellItem;
result = shellItems->GetItemAt(i, &shellItem);
if ( !SUCCEEDED(result) )
{
NFDi_SetError(ERRORMSG);
return NFD_ERROR;
}
// Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it.
SFGAOF attribs;
result = shellItem->GetAttributes( SFGAO_FILESYSTEM, &attribs );
if ( !SUCCEEDED(result) )
{
NFDi_SetError(ERRORMSG);
return NFD_ERROR;
}
if ( !(attribs & SFGAO_FILESYSTEM) )
continue;
LPWSTR name;
shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name);
// Calculate length of name with UTF-8 encoding
bufSize += GetUTF8ByteCountForWChar( name );
CoTaskMemFree(name);
}
assert(bufSize);
pathSet->buf = (nfdchar_t*)NFDi_Malloc( sizeof(nfdchar_t) * bufSize );
memset( pathSet->buf, 0, sizeof(nfdchar_t) * bufSize );
/* fill buf */
nfdchar_t *p_buf = pathSet->buf;
for (DWORD i = 0; i < numShellItems; ++i )
{
::IShellItem *shellItem;
result = shellItems->GetItemAt(i, &shellItem);
if ( !SUCCEEDED(result) )
{
NFDi_SetError(ERRORMSG);
return NFD_ERROR;
}
// Confirm SFGAO_FILESYSTEM is true for this shellitem, or ignore it.
SFGAOF attribs;
result = shellItem->GetAttributes( SFGAO_FILESYSTEM, &attribs );
if ( !SUCCEEDED(result) )
{
NFDi_SetError(ERRORMSG);
return NFD_ERROR;
}
if ( !(attribs & SFGAO_FILESYSTEM) )
continue;
LPWSTR name;
shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name);
int bytesWritten = CopyWCharToExistingNFDCharBuffer(name, p_buf);
CoTaskMemFree(name);
ptrdiff_t index = p_buf - pathSet->buf;
assert( index >= 0 );
pathSet->indices[i] = static_cast<size_t>(index);
p_buf += bytesWritten;
}
return NFD_OKAY;
}
static nfdresult_t SetDefaultPath( IFileDialog *dialog, const char *defaultPath )
{
if ( !defaultPath || strlen(defaultPath) == 0 || strcmp(defaultPath,"\\")==0 )
return NFD_OKAY;
wchar_t *defaultPathW = {0};
CopyNFDCharToWChar( defaultPath, &defaultPathW );
IShellItem *folder;
HRESULT result = SHCreateItemFromParsingName( defaultPathW, NULL, IID_PPV_ARGS(&folder) );
// Valid non results.
if ( result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || result == HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE) )
{
NFDi_Free( defaultPathW );
return NFD_OKAY;
}
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Error creating ShellItem");
NFDi_Free( defaultPathW );
return NFD_ERROR;
}
// Could also call SetDefaultFolder(), but this guarantees defaultPath -- more consistency across API.
dialog->SetFolder( folder );
NFDi_Free( defaultPathW );
folder->Release();
return NFD_OKAY;
}
/* public */
nfdresult_t NFD_OpenDialog( const std::vector<std::string>& filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath,
nfdselcallback_t selCallback )
{
nfdresult_t nfdResult = NFD_ERROR;
NFDWinEvents* winEvents;
bool hasEvents=true;
DWORD eventID=0;
HRESULT coResult = COMInit();
if (!COMIsInitialized(coResult))
{
NFDi_SetError("Could not initialize COM.");
return nfdResult;
}
// Create dialog
::IFileOpenDialog *fileOpenDialog(NULL);
HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL,
CLSCTX_ALL, ::IID_IFileOpenDialog,
reinterpret_cast<void**>(&fileOpenDialog) );
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not create dialog.");
goto end;
}
// Build the filter list
if ( !AddFiltersToDialog( fileOpenDialog, filterList ) )
{
goto end;
}
// Set the default path
if ( !SetDefaultPath( fileOpenDialog, defaultPath ) )
{
goto end;
}
// Pass the callback
winEvents=new NFDWinEvents(selCallback);
if ( !SUCCEEDED(fileOpenDialog->Advise(winEvents,&eventID)) ) {
// error... ignore
hasEvents=false;
winEvents->Release();
} else {
winEvents->Release();
}
// Show the dialog.
// TODO: pass the Furnace window here
result = fileOpenDialog->Show(NULL);
if ( SUCCEEDED(result) )
{
// Get the file name
::IShellItem *shellItem(NULL);
result = fileOpenDialog->GetResult(&shellItem);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get shell item from dialog.");
goto end;
}
wchar_t *filePath(NULL);
result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get file path for selected.");
shellItem->Release();
goto end;
}
CopyWCharToNFDChar( filePath, outPath );
CoTaskMemFree(filePath);
if ( !*outPath )
{
/* error is malloc-based, error message would be redundant */
shellItem->Release();
goto end;
}
nfdResult = NFD_OKAY;
shellItem->Release();
}
else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
{
nfdResult = NFD_CANCEL;
}
else
{
NFDi_SetError("File dialog box show failed.");
nfdResult = NFD_ERROR;
}
end:
if (fileOpenDialog) {
if (hasEvents) {
fileOpenDialog->Unadvise(eventID);
}
fileOpenDialog->Release();
}
COMUninit(coResult);
return nfdResult;
}
nfdresult_t NFD_OpenDialogMultiple( const std::vector<std::string>& filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths,
nfdselcallback_t selCallback )
{
nfdresult_t nfdResult = NFD_ERROR;
HRESULT coResult = COMInit();
if (!COMIsInitialized(coResult))
{
NFDi_SetError("Could not initialize COM.");
return nfdResult;
}
// Create dialog
::IFileOpenDialog *fileOpenDialog(NULL);
HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL,
CLSCTX_ALL, ::IID_IFileOpenDialog,
reinterpret_cast<void**>(&fileOpenDialog) );
if ( !SUCCEEDED(result) )
{
fileOpenDialog = NULL;
NFDi_SetError("Could not create dialog.");
goto end;
}
// Build the filter list
if ( !AddFiltersToDialog( fileOpenDialog, filterList ) )
{
goto end;
}
// Set the default path
if ( !SetDefaultPath( fileOpenDialog, defaultPath ) )
{
goto end;
}
// Set a flag for multiple options
DWORD dwFlags;
result = fileOpenDialog->GetOptions(&dwFlags);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get options.");
goto end;
}
result = fileOpenDialog->SetOptions(dwFlags | FOS_ALLOWMULTISELECT);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not set options.");
goto end;
}
// Show the dialog.
result = fileOpenDialog->Show(NULL);
if ( SUCCEEDED(result) )
{
IShellItemArray *shellItems;
result = fileOpenDialog->GetResults( &shellItems );
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get shell items.");
goto end;
}
if ( AllocPathSet( shellItems, outPaths ) == NFD_ERROR )
{
shellItems->Release();
goto end;
}
shellItems->Release();
nfdResult = NFD_OKAY;
}
else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
{
nfdResult = NFD_CANCEL;
}
else
{
NFDi_SetError("File dialog box show failed.");
nfdResult = NFD_ERROR;
}
end:
if ( fileOpenDialog )
fileOpenDialog->Release();
COMUninit(coResult);
return nfdResult;
}
nfdresult_t NFD_SaveDialog( const std::vector<std::string>& filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath,
nfdselcallback_t selCallback )
{
nfdresult_t nfdResult = NFD_ERROR;
HRESULT coResult = COMInit();
if (!COMIsInitialized(coResult))
{
NFDi_SetError("Could not initialize COM.");
return nfdResult;
}
// Create dialog
::IFileSaveDialog *fileSaveDialog(NULL);
HRESULT result = ::CoCreateInstance(::CLSID_FileSaveDialog, NULL,
CLSCTX_ALL, ::IID_IFileSaveDialog,
reinterpret_cast<void**>(&fileSaveDialog) );
if ( !SUCCEEDED(result) )
{
fileSaveDialog = NULL;
NFDi_SetError("Could not create dialog.");
goto end;
}
// Build the filter list
if ( !AddFiltersToDialog( fileSaveDialog, filterList ) )
{
goto end;
}
// Set the default path
if ( !SetDefaultPath( fileSaveDialog, defaultPath ) )
{
goto end;
}
// Show the dialog.
result = fileSaveDialog->Show(NULL);
if ( SUCCEEDED(result) )
{
// Get the file name
::IShellItem *shellItem;
result = fileSaveDialog->GetResult(&shellItem);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get shell item from dialog.");
goto end;
}
wchar_t *filePath(NULL);
result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
if ( !SUCCEEDED(result) )
{
shellItem->Release();
NFDi_SetError("Could not get file path for selected.");
goto end;
}
CopyWCharToNFDChar( filePath, outPath );
CoTaskMemFree(filePath);
if ( !*outPath )
{
/* error is malloc-based, error message would be redundant */
shellItem->Release();
goto end;
}
nfdResult = NFD_OKAY;
shellItem->Release();
}
else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
{
nfdResult = NFD_CANCEL;
}
else
{
NFDi_SetError("File dialog box show failed.");
nfdResult = NFD_ERROR;
}
end:
if ( fileSaveDialog )
fileSaveDialog->Release();
COMUninit(coResult);
return nfdResult;
}
nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
nfdchar_t **outPath)
{
nfdresult_t nfdResult = NFD_ERROR;
DWORD dwOptions = 0;
HRESULT coResult = COMInit();
if (!COMIsInitialized(coResult))
{
NFDi_SetError("CoInitializeEx failed.");
return nfdResult;
}
// Create dialog
::IFileOpenDialog *fileDialog(NULL);
HRESULT result = CoCreateInstance(CLSID_FileOpenDialog,
NULL,
CLSCTX_ALL,
IID_PPV_ARGS(&fileDialog));
if ( !SUCCEEDED(result) )
{
NFDi_SetError("CoCreateInstance for CLSID_FileOpenDialog failed.");
goto end;
}
// Set the default path
if (SetDefaultPath(fileDialog, defaultPath) != NFD_OKAY)
{
NFDi_SetError("SetDefaultPath failed.");
goto end;
}
// Get the dialogs options
if (!SUCCEEDED(fileDialog->GetOptions(&dwOptions)))
{
NFDi_SetError("GetOptions for IFileDialog failed.");
goto end;
}
// Add in FOS_PICKFOLDERS which hides files and only allows selection of folders
if (!SUCCEEDED(fileDialog->SetOptions(dwOptions | FOS_PICKFOLDERS)))
{
NFDi_SetError("SetOptions for IFileDialog failed.");
goto end;
}
// Show the dialog to the user
result = fileDialog->Show(NULL);
if ( SUCCEEDED(result) )
{
// Get the folder name
::IShellItem *shellItem(NULL);
result = fileDialog->GetResult(&shellItem);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("Could not get file path for selected.");
shellItem->Release();
goto end;
}
wchar_t *path = NULL;
result = shellItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &path);
if ( !SUCCEEDED(result) )
{
NFDi_SetError("GetDisplayName for IShellItem failed.");
shellItem->Release();
goto end;
}
CopyWCharToNFDChar(path, outPath);
CoTaskMemFree(path);
if ( !*outPath )
{
shellItem->Release();
goto end;
}
nfdResult = NFD_OKAY;
shellItem->Release();
}
else if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
{
nfdResult = NFD_CANCEL;
}
else
{
NFDi_SetError("Show for IFileDialog failed.");
nfdResult = NFD_ERROR;
}
end:
if (fileDialog)
fileDialog->Release();
COMUninit(coResult);
return nfdResult;
}

307
extern/nfd-modified/src/nfd_zenity.cpp vendored Normal file
View File

@ -0,0 +1,307 @@
/*
Native File Dialog
http://www.frogtoss.com/labs
*/
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include "nfd.h"
#include "nfd_common.h"
#define SIMPLE_EXEC_IMPLEMENTATION
#include "simple_exec.h"
const char NO_ZENITY_MSG[] = "zenity not installed";
static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize )
{
size_t len = strlen(filterName);
if( len > 0 )
strncat( filterName, " *.", bufsize - len - 1 );
else
strncat( filterName, "--file-filter=*.", bufsize - len - 1 );
len = strlen(filterName);
strncat( filterName, typebuf, bufsize - len - 1 );
}
static void AddFiltersToCommandArgs(char** commandArgs, int commandArgsLen, const char *filterList )
{
char typebuf[NFD_MAX_STRLEN] = {0};
const char *p_filterList = filterList;
char *p_typebuf = typebuf;
char filterName[NFD_MAX_STRLEN] = {0};
int i;
if ( !filterList || strlen(filterList) == 0 )
return;
while ( 1 )
{
if ( NFDi_IsFilterSegmentChar(*p_filterList) )
{
char typebufWildcard[NFD_MAX_STRLEN];
/* add another type to the filter */
assert( strlen(typebuf) > 0 );
assert( strlen(typebuf) < NFD_MAX_STRLEN-1 );
snprintf( typebufWildcard, NFD_MAX_STRLEN, "*.%s", typebuf );
AddTypeToFilterName( typebuf, filterName, NFD_MAX_STRLEN );
p_typebuf = typebuf;
memset( typebuf, 0, sizeof(char) * NFD_MAX_STRLEN );
}
if ( *p_filterList == ';' || *p_filterList == '\0' )
{
/* end of filter -- add it to the dialog */
for(i = 0; commandArgs[i] != NULL && i < commandArgsLen; i++);
commandArgs[i] = strdup(filterName);
filterName[0] = '\0';
if ( *p_filterList == '\0' )
break;
}
if ( !NFDi_IsFilterSegmentChar( *p_filterList ) )
{
*p_typebuf = *p_filterList;
p_typebuf++;
}
p_filterList++;
}
/* always append a wildcard option to the end*/
for(i = 0; commandArgs[i] != NULL && i < commandArgsLen; i++);
commandArgs[i] = strdup("--file-filter=*.*");
}
static nfdresult_t ZenityCommon(char** command, int commandLen, const char* defaultPath, const char* filterList, char** stdOut)
{
if(defaultPath != NULL)
{
char* prefix = "--filename=";
int len = strlen(prefix) + strlen(defaultPath) + 1;
char* tmp = (char*) calloc(len, 1);
strcat(tmp, prefix);
strcat(tmp, defaultPath);
int i;
for(i = 0; command[i] != NULL && i < commandLen; i++);
command[i] = tmp;
}
AddFiltersToCommandArgs(command, commandLen, filterList);
int byteCount = 0;
int exitCode = 0;
int processInvokeError = runCommandArray(stdOut, &byteCount, &exitCode, 0, command);
for(int i = 0; command[i] != NULL && i < commandLen; i++)
free(command[i]);
nfdresult_t result = NFD_OKAY;
if(processInvokeError == COMMAND_NOT_FOUND)
{
NFDi_SetError(NO_ZENITY_MSG);
result = NFD_ERROR;
}
else
{
if(exitCode == 1)
result = NFD_CANCEL;
}
return result;
}
static nfdresult_t AllocPathSet(char* zenityList, nfdpathset_t *pathSet )
{
assert(zenityList);
assert(pathSet);
size_t len = strlen(zenityList) + 1;
pathSet->buf = NFDi_Malloc(len);
int numEntries = 1;
for(size_t i = 0; i < len; i++)
{
char ch = zenityList[i];
if(ch == '|')
{
numEntries++;
ch = '\0';
}
pathSet->buf[i] = ch;
}
pathSet->count = numEntries;
assert( pathSet->count > 0 );
pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count );
int entry = 0;
pathSet->indices[0] = 0;
for(size_t i = 0; i < len; i++)
{
char ch = zenityList[i];
if(ch == '|')
{
entry++;
pathSet->indices[entry] = i + 1;
}
}
return NFD_OKAY;
}
/* public */
nfdresult_t NFD_OpenDialog( const char *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath )
{
int commandLen = 100;
char* command[commandLen];
memset(command, 0, commandLen * sizeof(char*));
command[0] = strdup("zenity");
command[1] = strdup("--file-selection");
command[2] = strdup("--title=Open File");
char* stdOut = NULL;
nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut);
if(stdOut != NULL)
{
size_t len = strlen(stdOut);
*outPath = NFDi_Malloc(len);
memcpy(*outPath, stdOut, len);
(*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator
free(stdOut);
}
else
{
*outPath = NULL;
}
return result;
}
nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdpathset_t *outPaths )
{
int commandLen = 100;
char* command[commandLen];
memset(command, 0, commandLen * sizeof(char*));
command[0] = strdup("zenity");
command[1] = strdup("--file-selection");
command[2] = strdup("--title=Open Files");
command[3] = strdup("--multiple");
char* stdOut = NULL;
nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut);
if(stdOut != NULL)
{
size_t len = strlen(stdOut);
stdOut[len-1] = '\0'; // remove trailing newline
if ( AllocPathSet( stdOut, outPaths ) == NFD_ERROR )
result = NFD_ERROR;
free(stdOut);
}
else
{
result = NFD_ERROR;
}
return result;
}
nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
const nfdchar_t *defaultPath,
nfdchar_t **outPath )
{
int commandLen = 100;
char* command[commandLen];
memset(command, 0, commandLen * sizeof(char*));
command[0] = strdup("zenity");
command[1] = strdup("--file-selection");
command[2] = strdup("--title=Save File");
command[3] = strdup("--save");
char* stdOut = NULL;
nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut);
if(stdOut != NULL)
{
size_t len = strlen(stdOut);
*outPath = NFDi_Malloc(len);
memcpy(*outPath, stdOut, len);
(*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator
free(stdOut);
}
else
{
*outPath = NULL;
}
return result;
}
nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
nfdchar_t **outPath)
{
int commandLen = 100;
char* command[commandLen];
memset(command, 0, commandLen * sizeof(char*));
command[0] = strdup("zenity");
command[1] = strdup("--file-selection");
command[2] = strdup("--directory");
command[3] = strdup("--title=Select folder");
char* stdOut = NULL;
nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, "", &stdOut);
if(stdOut != NULL)
{
size_t len = strlen(stdOut);
*outPath = NFDi_Malloc(len);
memcpy(*outPath, stdOut, len);
(*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator
free(stdOut);
}
else
{
*outPath = NULL;
}
return result;
}

218
extern/nfd-modified/src/simple_exec.h vendored Normal file
View File

@ -0,0 +1,218 @@
// copied from: https://github.com/wheybags/simple_exec/blob/5a74c507c4ce1b2bb166177ead4cca7cfa23cb35/simple_exec.h
// simple_exec.h, single header library to run external programs + retrieve their status code and output (unix only for now)
//
// do this:
// #define SIMPLE_EXEC_IMPLEMENTATION
// before you include this file in *one* C or C++ file to create the implementation.
// i.e. it should look like this:
// #define SIMPLE_EXEC_IMPLEMENTATION
// #include "simple_exec.h"
#ifndef SIMPLE_EXEC_H
#define SIMPLE_EXEC_H
int runCommand(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* command, ...);
int runCommandArray(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* const* allArgs);
#endif // SIMPLE_EXEC_H
#ifdef SIMPLE_EXEC_IMPLEMENTATION
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/wait.h>
#include <stdarg.h>
#include <fcntl.h>
#define release_assert(exp) { if (!(exp)) { abort(); } }
enum PIPE_FILE_DESCRIPTORS
{
READ_FD = 0,
WRITE_FD = 1
};
enum RUN_COMMAND_ERROR
{
COMMAND_RAN_OK = 0,
COMMAND_NOT_FOUND = 1
};
int runCommandArray(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* const* allArgs)
{
// adapted from: https://stackoverflow.com/a/479103
int bufferSize = 256;
char buffer[bufferSize + 1];
int dataReadFromChildDefaultSize = bufferSize * 5;
int dataReadFromChildSize = dataReadFromChildDefaultSize;
int dataReadFromChildUsed = 0;
char* dataReadFromChild = (char*)malloc(dataReadFromChildSize);
int parentToChild[2];
release_assert(pipe(parentToChild) == 0);
int childToParent[2];
release_assert(pipe(childToParent) == 0);
int errPipe[2];
release_assert(pipe(errPipe) == 0);
pid_t pid;
switch( pid = fork() )
{
case -1:
{
release_assert(0 && "Fork failed");
break;
}
case 0: // child
{
release_assert(dup2(parentToChild[READ_FD ], STDIN_FILENO ) != -1);
release_assert(dup2(childToParent[WRITE_FD], STDOUT_FILENO) != -1);
if(includeStdErr)
{
release_assert(dup2(childToParent[WRITE_FD], STDERR_FILENO) != -1);
}
else
{
int devNull = open("/dev/null", O_WRONLY);
release_assert(dup2(devNull, STDERR_FILENO) != -1);
}
// unused
release_assert(close(parentToChild[WRITE_FD]) == 0);
release_assert(close(childToParent[READ_FD ]) == 0);
release_assert(close(errPipe[READ_FD]) == 0);
const char* command = allArgs[0];
execvp(command, allArgs);
char err = 1;
ssize_t result = write(errPipe[WRITE_FD], &err, 1);
release_assert(result != -1);
close(errPipe[WRITE_FD]);
close(parentToChild[READ_FD]);
close(childToParent[WRITE_FD]);
exit(0);
}
default: // parent
{
// unused
release_assert(close(parentToChild[READ_FD]) == 0);
release_assert(close(childToParent[WRITE_FD]) == 0);
release_assert(close(errPipe[WRITE_FD]) == 0);
while(1)
{
ssize_t bytesRead = 0;
switch(bytesRead = read(childToParent[READ_FD], buffer, bufferSize))
{
case 0: // End-of-File, or non-blocking read.
{
int status = 0;
release_assert(waitpid(pid, &status, 0) == pid);
// done with these now
release_assert(close(parentToChild[WRITE_FD]) == 0);
release_assert(close(childToParent[READ_FD]) == 0);
char errChar = 0;
ssize_t result = read(errPipe[READ_FD], &errChar, 1);
release_assert(result != -1);
close(errPipe[READ_FD]);
if(errChar)
{
free(dataReadFromChild);
return COMMAND_NOT_FOUND;
}
// free any un-needed memory with realloc + add a null terminator for convenience
dataReadFromChild = (char*)realloc(dataReadFromChild, dataReadFromChildUsed + 1);
dataReadFromChild[dataReadFromChildUsed] = '\0';
if(stdOut != NULL)
*stdOut = dataReadFromChild;
else
free(dataReadFromChild);
if(stdOutByteCount != NULL)
*stdOutByteCount = dataReadFromChildUsed;
if(returnCode != NULL)
*returnCode = WEXITSTATUS(status);
return COMMAND_RAN_OK;
}
case -1:
{
release_assert(0 && "read() failed");
break;
}
default:
{
if(dataReadFromChildUsed + bytesRead + 1 >= dataReadFromChildSize)
{
dataReadFromChildSize += dataReadFromChildDefaultSize;
dataReadFromChild = (char*)realloc(dataReadFromChild, dataReadFromChildSize);
}
memcpy(dataReadFromChild + dataReadFromChildUsed, buffer, bytesRead);
dataReadFromChildUsed += bytesRead;
break;
}
}
}
}
}
}
int runCommand(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* command, ...)
{
va_list vl;
va_start(vl, command);
char* currArg = NULL;
int allArgsInitialSize = 16;
int allArgsSize = allArgsInitialSize;
char** allArgs = (char**)malloc(sizeof(char*) * allArgsSize);
allArgs[0] = command;
int i = 1;
do
{
currArg = va_arg(vl, char*);
allArgs[i] = currArg;
i++;
if(i >= allArgsSize)
{
allArgsSize += allArgsInitialSize;
allArgs = (char**)realloc(allArgs, sizeof(char*) * allArgsSize);
}
} while(currArg != NULL);
va_end(vl);
int retval = runCommandArray(stdOut, stdOutByteCount, returnCode, includeStdErr, allArgs);
free(allArgs);
return retval;
}
#endif //SIMPLE_EXEC_IMPLEMENTATION

View File

@ -0,0 +1,29 @@
#include "nfd.h"
#include <stdio.h>
#include <stdlib.h>
/* this test should compile on all supported platforms */
int main( void )
{
nfdchar_t *outPath = NULL;
nfdresult_t result = NFD_OpenDialog( "png,jpg;pdf", NULL, &outPath );
if ( result == NFD_OKAY )
{
puts("Success!");
puts(outPath);
free(outPath);
}
else if ( result == NFD_CANCEL )
{
puts("User pressed cancel.");
}
else
{
printf("Error: %s\n", NFD_GetError() );
}
return 0;
}

View File

@ -0,0 +1,32 @@
#include "nfd.h"
#include <stdio.h>
#include <stdlib.h>
/* this test should compile on all supported platforms */
int main( void )
{
nfdpathset_t pathSet;
nfdresult_t result = NFD_OpenDialogMultiple( "png,jpg;pdf", NULL, &pathSet );
if ( result == NFD_OKAY )
{
size_t i;
for ( i = 0; i < NFD_PathSet_GetCount(&pathSet); ++i )
{
nfdchar_t *path = NFD_PathSet_GetPath(&pathSet, i);
printf("Path %i: %s\n", (int)i, path );
}
NFD_PathSet_Free(&pathSet);
}
else if ( result == NFD_CANCEL )
{
puts("User pressed cancel.");
}
else
{
printf("Error: %s\n", NFD_GetError() );
}
return 0;
}

View File

@ -0,0 +1,29 @@
#include "nfd.h"
#include <stdio.h>
#include <stdlib.h>
/* this test should compile on all supported platforms */
int main( void )
{
nfdchar_t *outPath = NULL;
nfdresult_t result = NFD_PickFolder( NULL, &outPath );
if ( result == NFD_OKAY )
{
puts("Success!");
puts(outPath);
free(outPath);
}
else if ( result == NFD_CANCEL )
{
puts("User pressed cancel.");
}
else
{
printf("Error: %s\n", NFD_GetError() );
}
return 0;
}

View File

@ -0,0 +1,28 @@
#include "nfd.h"
#include <stdio.h>
#include <stdlib.h>
/* this test should compile on all supported platforms */
int main( void )
{
nfdchar_t *savePath = NULL;
nfdresult_t result = NFD_SaveDialog( "png,jpg;pdf", NULL, &savePath );
if ( result == NFD_OKAY )
{
puts("Success!");
puts(savePath);
free(savePath);
}
else if ( result == NFD_CANCEL )
{
puts("User pressed cancel.");
}
else
{
printf("Error: %s\n", NFD_GetError() );
}
return 0;
}

6
extern/opm/opm.c vendored
View File

@ -1,5 +1,5 @@
/* Nuked OPM
* Copyright (C) 2020 Nuke.YKT
* Copyright (C) 2022 Nuke.YKT
*
* This file is part of Nuked OPM.
*
@ -21,7 +21,7 @@
* siliconpr0n.org(digshadow, John McMaster):
* YM2151 and other FM chip decaps and die shots.
*
* version: 0.9.2 beta
* version: 0.9.3 beta
*/
#include <string.h>
#include <stdint.h>
@ -651,7 +651,7 @@ static inline void OPM_EnvelopePhase4(opm_t *chip)
chip->eg_instantattack = chip->eg_ratemax[1] && (kon || !chip->eg_ratemax[1]);
eg_off = (chip->eg_level[slot] & 0x3f0) == 0x3f0;
slreach = (chip->eg_level[slot] >> 5) == chip->eg_sl[1];
slreach = (chip->eg_level[slot] >> 4) == (chip->eg_sl[1] << 1);
eg_zero = chip->eg_level[slot] == 0;
chip->eg_mute = eg_off && chip->eg_state[slot] != eg_num_attack && !kon;

View File

@ -40,6 +40,7 @@
#include "RtMidi.h"
#include <sstream>
#ifdef TARGET_OS_IPHONE
#if (TARGET_OS_IPHONE == 1)
#define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime
@ -66,6 +67,7 @@
#define EndianS32_BtoN(n) n
#endif
#endif
// Default for Windows is to add an identifier to the port names; this
// flag can be defined (e.g. in your project file) to disable this behaviour.
@ -814,7 +816,7 @@ MidiOutApi :: ~MidiOutApi( void )
// time values.
// These are not available on iOS.
#if (TARGET_OS_IPHONE == 0)
#ifdef TARGET_OS_IPHONE
#include <CoreAudio/HostTime.h>
#include <CoreServices/CoreServices.h>
#endif

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More