Fix the package fs seam: UTF-8 paths, GetUserFileName pickers, exclusive-create landing, rollback arm/disarm
Both pickers now ride GetUserFileName (mode 0/1); the "no save picker" premise was false. Landing uses O_EXCL so the create is the existence check, not a TOCTOU pair.
This commit is contained in:
@@ -1,93 +1,45 @@
|
||||
// package_pickers.cpp — see package_pickers.h. Export is native rather than REAPER
|
||||
// API: the SDK's newer GetUserFileName(mode=0) could save, but it resolves to null
|
||||
// on older REAPER builds this extension still loads in, while GetSaveFileNameW /
|
||||
// BrowseForSaveFile are always present. main.cpp owns the REAPER API pointers; this
|
||||
// TU gets them extern.
|
||||
// package_pickers.cpp — see package_pickers.h. GetUserFileName cannot be null here:
|
||||
// main.cpp defines REAPERAPI_IMPLEMENT without REAPERAPI_MINIMAL, so the generated
|
||||
// resolver walks the FULL table, and it aborts the extension load if any single name
|
||||
// fails to resolve. A fallback picker would be unreachable code.
|
||||
|
||||
#include "shell/package/package_pickers.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <commdlg.h>
|
||||
#else
|
||||
#include "swell/swell.h"
|
||||
#endif
|
||||
|
||||
#define REAPERAPI_MINIMAL
|
||||
#define REAPERAPI_WANT_GetUserFileNameForRead
|
||||
#define REAPERAPI_WANT_GetMainHwnd
|
||||
#define REAPERAPI_WANT_GetUserFileName
|
||||
#include "reaper_plugin_functions.h"
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
bool pickPackageForImport(std::string& outAbsPath) {
|
||||
namespace {
|
||||
|
||||
// GetUserFileName's documented pair format: label|pattern|label|pattern.
|
||||
const char kExtList[] =
|
||||
"ReaSampler bank package (*.rsbank)|*.rsbank|All files (*.*)|*.*";
|
||||
|
||||
bool runPicker(int mode, const char* caption, const char* initial,
|
||||
std::string& outAbsPath) {
|
||||
outAbsPath.clear();
|
||||
if (!GetUserFileNameForRead) return false;
|
||||
char buf[4096];
|
||||
buf[0] = '\0';
|
||||
// defext spelled without the dot, matching Win32 lpstrDefExt. [verify — DAW]
|
||||
// whether REAPER's picker applies it to the shown filter.
|
||||
if (!GetUserFileNameForRead(buf, "Import bank package", "rsbank")) return false;
|
||||
if (!GetUserFileName(mode, caption, initial, kExtList, buf,
|
||||
static_cast<int>(sizeof(buf)))) {
|
||||
return false;
|
||||
}
|
||||
outAbsPath = buf;
|
||||
return !outAbsPath.empty();
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
} // namespace
|
||||
|
||||
bool pickPackageSavePath(const std::string& suggestedFileName, std::string& outAbsPath) {
|
||||
outAbsPath.clear();
|
||||
|
||||
wchar_t file[4096];
|
||||
file[0] = L'\0';
|
||||
if (!suggestedFileName.empty()) {
|
||||
const int wrote = MultiByteToWideChar(CP_UTF8, 0, suggestedFileName.c_str(),
|
||||
-1, file, 4096);
|
||||
if (wrote <= 0) file[0] = L'\0'; // unconvertible suggestion -> empty box
|
||||
}
|
||||
|
||||
OPENFILENAMEW ofn{};
|
||||
ofn.lStructSize = sizeof(ofn);
|
||||
ofn.hwndOwner = GetMainHwnd ? GetMainHwnd() : nullptr;
|
||||
ofn.lpstrFilter =
|
||||
L"ReaSampler bank package (*.rsbank)\0*.rsbank\0All files (*.*)\0*.*\0";
|
||||
ofn.lpstrFile = file;
|
||||
ofn.nMaxFile = 4096;
|
||||
ofn.lpstrTitle = L"Export bank package";
|
||||
ofn.lpstrDefExt = L"rsbank";
|
||||
// NOCHANGEDIR: REAPER's process-wide working directory is not ours to move.
|
||||
ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR |
|
||||
OFN_HIDEREADONLY;
|
||||
if (!GetSaveFileNameW(&ofn)) return false;
|
||||
|
||||
const int need =
|
||||
WideCharToMultiByte(CP_UTF8, 0, file, -1, nullptr, 0, nullptr, nullptr);
|
||||
if (need <= 0) return false;
|
||||
std::string utf8(static_cast<std::size_t>(need), '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, file, -1, &utf8[0], need, nullptr, nullptr);
|
||||
if (!utf8.empty() && utf8.back() == '\0') utf8.pop_back();
|
||||
outAbsPath = std::move(utf8);
|
||||
return !outAbsPath.empty();
|
||||
bool pickPackageForImport(std::string& outAbsPath) {
|
||||
return runPicker(1, "Import bank package", "", outAbsPath);
|
||||
}
|
||||
|
||||
#else // SWELL (macOS / Linux)
|
||||
|
||||
bool pickPackageSavePath(const std::string& suggestedFileName, std::string& outAbsPath) {
|
||||
outAbsPath.clear();
|
||||
// GetOpenFileName-style pair list; the literal's implicit terminator supplies the
|
||||
// closing double-NUL. [verify — DAW] the exact filter strings SWELL accepts.
|
||||
static const char kExtList[] = "ReaSampler bank package (*.rsbank)\0*.rsbank\0";
|
||||
char fn[4096];
|
||||
fn[0] = '\0';
|
||||
if (!BrowseForSaveFile("Export bank package", nullptr,
|
||||
suggestedFileName.empty() ? nullptr
|
||||
: suggestedFileName.c_str(),
|
||||
kExtList, fn, static_cast<int>(sizeof(fn)))) {
|
||||
return false;
|
||||
}
|
||||
outAbsPath = fn;
|
||||
return !outAbsPath.empty();
|
||||
bool pickPackageSavePath(const std::string& suggestedPath, std::string& outAbsPath) {
|
||||
// [verify — DAW] GetUserFileName takes no owner window, so the dialog's parenting
|
||||
// is REAPER's to do; the previous Win32 path passed GetMainHwnd() explicitly.
|
||||
return runPicker(0, "Export bank package", suggestedPath.c_str(), outAbsPath);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace reasampler
|
||||
|
||||
Reference in New Issue
Block a user