Ψ-W1-T4 remediation: gate silent arrange drops, pin surface totality, hedge unverified SDK claims, close comment/exception-safety minors
This commit is contained in:
@@ -39,7 +39,7 @@ is owned by other directories and only skinned here.
|
||||
- `shell/actions` (`action_registry` / `design_view_actions` / `bank_actions` / `prune_action`) — the bindable action families, all routed via the `command_id`/`gaccel`/`hookcommand` contract. `action_registry` owns the shared registration plumbing (interned channel-qualified id strings; register and mirror-unregister present the identical pointer) **and the Q-W6 registration TABLE**: `main.cpp`'s own family (capture scopes, panel toggle, insert, batch, realtime, recapture, version) is one `ActionTableRow` array — suffix, phrase, flat function-pointer handler — that registration, hookcommand dispatch, and the unload mirror-unregister all iterate, so adding an action touches the table only (OCP). Bank mutations flow through the promptless `shell/bank_ops` verbs (`bankOp*` + `persistBankOp`, taking `ReaSamplerSession&`), which the panel menus and `bank_actions` consume as thin UX skins. **Every bank index verb wraps its mutation in a batched REAPER undo point (`Undo_BeginBlock2`/`EndBlock2`, `UNDO_STATE_MISCCFG`) so one bank operation is one Ctrl-Z.** The prune action (`prune_action`, `BANK_PRUNE_FOLDER`) is **the ONLY file-deletion action in the system**; it opens no undo point (file deletion is not REAPER-undoable). `BANK_PRUNE_FOLDER` halts on `blockedByTracking` and prints each blocker that fired, with recovery instructions.
|
||||
- `drag_out_win` — OS drag-out shell: Windows OLE `DoDragDrop`/`CF_HDROP`, copy-only (`DROPEFFECT_MOVE` not offered); macOS/Linux via `SWELL_InitiateDragDropOfFileList`.
|
||||
- `instrument_drop_win` — instrument-drop shell: `probeDropTarget` resolves a screen point to a track + a `ReaperSurface` (via the pure `classifyReaperSurface`, which owns every token rule), and the drop half adds a ReaSampler 9000 instance and applies the dragged capture's state via a transient `.vstpreset` + `TrackFX_SetPreset` (the former `TrackFX_SetNamedConfigParm` "vst_chunk" write was silently unappliable for VST3). Exposes `loadInstrumentOntoTrack` (inner half, no own undo block) and `performInstrumentDrop` (wraps in its own undo block). **Never captures, never writes the bank, never inserts a timeline item.**
|
||||
- `arrange_drop_win` — the drag-out gesture's arrange outcome: `arrangeTimeAtScreenX` (pointer column → time via `GetSet_ArrangeView2` over a one-pixel span) and `performArrangeDrop` (snap the drop time, then one `InsertMedia` per capture on the pointer's track, laid out sequentially, in ONE undo block, with the caller's track selection and edit cursor restored). This is the one shell in this directory that DOES place timeline items — and it is legitimate because the user dragged them there, the same class of act as `RunInsertSelected`. It never captures and never writes the bank.
|
||||
- `arrange_drop_win` — the drag-out gesture's arrange outcome: `arrangeTimeAtScreenX` (pointer column → time via `GetSet_ArrangeView2`'s one-pixel-span reading — inferred, not SDK-documented) and `performArrangeDrop` (snap the drop time, then one `InsertMedia` per capture on the pointer's track — assumed, not confirmed, to land end-to-end via REAPER's own cursor advance — in ONE undo block, counting only InsertMedia's reported successes, with the caller's track selection and edit cursor restored). This is the one shell in this directory that DOES place timeline items — and it is legitimate because the user dragged them there, the same class of act as `RunInsertSelected`. It never captures and never writes the bank.
|
||||
- `ingest` — ingest-through-the-bank shell on the EXTENSION side: three surfaces — (1) arrange capture→bank→assign (bindable action), (2) Media-Explorer import→bank→instrument on the selected track, (3) file drop onto the bank panel→bank only. Only surface (1) writes the `assignment_request` ext-state wire. **ingest NEVER inserts a timeline item.**
|
||||
|
||||
## Gotchas
|
||||
|
||||
@@ -7,13 +7,24 @@
|
||||
// B. InsertMedia advances the edit cursor past the media it added. That advance IS the
|
||||
// multi-file layout: the cursor is deliberately not reset between files, so N captures
|
||||
// land end to end. If REAPER does not advance it, they stack at one position instead —
|
||||
// visible and one Ctrl-Z away, never silent.
|
||||
// visible and one Ctrl-Z away, never silent. insert.cpp does NOT share this assumption: it
|
||||
// resets the cursor before every track's insert specifically to stay independent of
|
||||
// cursor-advance behavior (insert.cpp:18-20, "this doesn't matter either way"). This call
|
||||
// site is the first in the tree to depend on it.
|
||||
// C. SnapToGrid honors the project's snap-enabled toggle. The header documents no
|
||||
// snap-enabled query for the arrange, so a drop taken with snapping OFF is the test that
|
||||
// settles it.
|
||||
// D. GetSet_ArrangeView2's one-pixel span [screenX, screenX+1) reads the time at that column.
|
||||
// The header documents only the all-zero span (screen_x_start==screen_x_end==0) as the
|
||||
// "whole view" special case; the per-column reading for any other span is inferred, not
|
||||
// documented.
|
||||
// E. InsertMedia's int return isn't SDK-documented; treated conservatively as 0=failure,
|
||||
// nonzero=success — performArrangeDrop counts only the latter.
|
||||
|
||||
#include "shell/actions/arrange_drop_win.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -43,8 +54,13 @@ using capture::InsertOptions;
|
||||
namespace {
|
||||
|
||||
// Selection snapshot/restore, so a drop leaves the user's track selection exactly as it found
|
||||
// it. Mirrors insert.cpp's pair; that TU belongs to the capture pillar and this one to the drag
|
||||
// chain, so they are kept separate rather than sharing a helper across the seam.
|
||||
// it. Mirrors insert.cpp's pair; kept separate here because insert.cpp sits in the capture
|
||||
// pillar, outside this track's surface fence — not a ruling that the two should never share a
|
||||
// helper, just not this track's call to make.
|
||||
//
|
||||
// CountSelectedTracks/GetSelectedTrack both skip the master track (SDK header), so a user with
|
||||
// the master selected loses that selection across the drop — pre-existing behavior inherited
|
||||
// from insert.cpp's identical pair, not fixed here.
|
||||
std::vector<MediaTrack*> snapshotSelectedTracks() {
|
||||
const int n = CountSelectedTracks(nullptr); // nullptr = active project
|
||||
std::vector<MediaTrack*> tracks;
|
||||
@@ -63,9 +79,11 @@ void restoreSelectedTracks(const std::vector<MediaTrack*>& tracks) {
|
||||
|
||||
double arrangeTimeAtScreenX(int screenX) {
|
||||
double start = 0.0, end = 0.0;
|
||||
// isSet=false reads the arrange times spanned by [screen_x_start, screen_x_end); a
|
||||
// one-pixel span therefore reads the time at that column. (The all-zero span is the SDK's
|
||||
// documented "whole view" form, which is why the span here must not be empty.)
|
||||
// isSet=false with a one-pixel span [screenX, screenX+1) is assumed to read the time at
|
||||
// that column — inferred, not documented (assumption D in the file header). The SDK's ONLY
|
||||
// documented special form is screen_x_start==screen_x_end==0 (both zero) for "the whole
|
||||
// arrange view's start/end time"; a zero-width span at a nonzero column (e.g. screenX,
|
||||
// screenX) is NOT that special case, so the +1 here is precautionary rather than required.
|
||||
GetSet_ArrangeView2(nullptr, false, screenX, screenX + 1, &start, &end);
|
||||
return start < 0.0 ? 0.0 : start;
|
||||
}
|
||||
@@ -87,18 +105,24 @@ int performArrangeDrop(MediaTrack* track, double time,
|
||||
|
||||
int inserted = 0;
|
||||
for (const std::string& path : absolutePaths) {
|
||||
// No cursor reset between files — see assumption B in the file header.
|
||||
InsertMedia(path.c_str(), mode);
|
||||
++inserted;
|
||||
// No cursor reset between files — see assumption B in the file header. InsertMedia's
|
||||
// return isn't SDK-documented (assumption E); treated conservatively as 0=failure, so a
|
||||
// REAPER-side refusal is reflected in the count and in the undo label, not silent.
|
||||
if (InsertMedia(path.c_str(), mode) != 0) ++inserted;
|
||||
}
|
||||
|
||||
// A fixed stack buffer, not std::string concatenation: the prior shape built the label with
|
||||
// std::to_string + `+` between the restore below and Undo_EndBlock2, so a bad_alloc there
|
||||
// would leave an unbalanced undo block open. snprintf here removes the allocation outright.
|
||||
char label[64];
|
||||
std::snprintf(label, sizeof(label), "ReaSampler: drop %d %s onto arrange", inserted,
|
||||
inserted == 1 ? "capture" : "captures");
|
||||
|
||||
restoreSelectedTracks(priorSelection);
|
||||
SetEditCurPos(priorCursor, /*moveview=*/false, /*seekplay=*/false);
|
||||
|
||||
// extraflags -1 = UNDO_STATE_ALL, matching the insert action's own block.
|
||||
const std::string label = "ReaSampler: drop " + std::to_string(inserted) +
|
||||
(inserted == 1 ? " capture" : " captures") + " onto arrange";
|
||||
Undo_EndBlock2(nullptr, label.c_str(), -1);
|
||||
Undo_EndBlock2(nullptr, label, -1);
|
||||
return inserted;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,14 +16,18 @@ class MediaTrack;
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
// The arrange time under a screen X, via REAPER's own pixel-column -> time mapping. Times left
|
||||
// of project start clamp to 0.
|
||||
// The arrange time under a screen X, via GetSet_ArrangeView2's one-pixel-column reading —
|
||||
// inferred behavior, not SDK-documented (see the .cpp's assumption D). Times left of project
|
||||
// start clamp to 0.
|
||||
double arrangeTimeAtScreenX(int screenX);
|
||||
|
||||
// Places every path in `absolutePaths` on `track`, the first at `time` and each subsequent one
|
||||
// after the previous (REAPER's own multi-file drop shape), inside ONE undo block. The drop time
|
||||
// honors the project's snap setting. The caller's track selection and edit-cursor position are
|
||||
// restored before returning. Returns the number of files handed to REAPER.
|
||||
// Places every path in `absolutePaths` on `track`, inside ONE undo block. The first lands at
|
||||
// `time`; whether REAPER's own cursor advance lands the rest end-to-end, or stacks them at one
|
||||
// position instead, is assumption B in the .cpp (visible, one Ctrl-Z away, either way). The
|
||||
// drop time is assumed to honor the project's snap setting (assumption C). The caller's track
|
||||
// selection and edit-cursor position are restored before returning. Returns the number of files
|
||||
// InsertMedia reported inserting successfully — its return isn't SDK-documented; treated as
|
||||
// 0=failure (assumption E in the .cpp).
|
||||
int performArrangeDrop(MediaTrack* track, double time,
|
||||
const std::vector<std::string>& absolutePaths);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user