Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
win_plugin_drop_target.hpp
Go to the documentation of this file.
1#pragma once
2
3// win_plugin_drop_target.hpp — the OLE inbound-drop half of the Windows
4// plug-in editor host.
5//
6// Extracted verbatim from plugin_view_host_win.cpp (WAH-6), which had grown to
7// ~1,430 lines carrying HWND lifecycle, OLE drop handling, pointer/capture,
8// keyboard/text/focus, DPI/viewport conversion, rendering/readback, and WndProc
9// dispatch in one class. OLE is the most self-contained of those: a
10// COM-refcounted object implementing a Microsoft interface, with lifetime rules
11// that have nothing to do with the rest of the host.
12//
13// HEADER-ONLY, and the definitions are unchanged from where they lived before.
14// That is deliberate: this code only compiles under `_WIN32`, so on a macOS
15// development machine there is no compiler to catch a transcription slip. A
16// textual move cannot introduce one; splitting the members into out-of-line
17// definitions could, and the first place that would surface is the Windows CI
18// gate — or a user's DAW.
19//
20// The host keeps ownership of the HWND and of registration/revocation. This
21// module owns IDataObject decoding and the IDropTarget protocol, routing
22// results into the SHARED cross-platform dispatch core (drag_drop.cpp) — the
23// same core the SDL and macOS producers use, so a drop behaves identically
24// everywhere.
25
29#include <pulp/view/view.hpp>
30#include <pulp/runtime/log.hpp>
31
32#include <functional>
33#include <string>
34
35// Everything below is Windows-only BY CONSTRUCTION — it implements a Microsoft
36// COM interface over the Win32 SDK, so unlike its WAH-6 siblings
37// (win_plugin_input_router.hpp, win_pointer_input.hpp, win_surface_lifecycle.hpp,
38// which are deliberately free of <windows.h>) it cannot be made portable.
39//
40// The guard is what lets it satisfy the self-contained-header gate, which
41// compiles every changed public header alone under Linux Clang, where <ole2.h>
42// does not exist. Off Windows this header contributes nothing, which is
43// accurate: its only consumer, platform/win/plugin_view_host_win.cpp, is added
44// to the build solely in the Windows branch of core/view/CMakeLists.txt.
45#ifdef _WIN32
46
47// win32_sane.hpp, never raw <windows.h>: it pre-sets WIN32_LEAN_AND_MEAN and
48// NOMINMAX so the min/max macros cannot collide with std::min/std::max or with
49// Skia's headers (issue #384). A header that pulls windows.h directly drags
50// that collision into whatever translation unit includes it FIRST, which is a
51// failure that only shows up on the Windows lane.
53
54#include <ole2.h>
55#include <shellapi.h>
56
57namespace pulp::view::win_drop {
58
59// UTF-16 → UTF-8 for dropped text / paths.
60std::string wide_to_utf8(const wchar_t* w, int wlen) {
61 if (!w || wlen <= 0) return {};
62 int n = WideCharToMultiByte(CP_UTF8, 0, w, wlen, nullptr, 0, nullptr, nullptr);
63 if (n <= 0) return {};
64 std::string out(static_cast<size_t>(n), '\0');
65 WideCharToMultiByte(CP_UTF8, 0, w, wlen, out.data(), n, nullptr, nullptr);
66 return out;
67}
68
69// Pull a DropData out of an OLE IDataObject (files take priority over text,
70// matching the SDL + macOS producers).
71DropData extract_idata_drop(IDataObject* data) {
72 DropData out;
73 if (!data) return out;
74
75 // CF_HDROP — a list of file paths.
76 FORMATETC fmt_files{CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
77 STGMEDIUM stg{};
78 if (data->GetData(&fmt_files, &stg) == S_OK) {
79 if (auto hdrop = static_cast<HDROP>(GlobalLock(stg.hGlobal))) {
80 const UINT count = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0);
81 out.type = DropData::Type::files;
82 for (UINT i = 0; i < count; ++i) {
83 const UINT len = DragQueryFileW(hdrop, i, nullptr, 0);
84 std::wstring buf(len + 1, L'\0');
85 const UINT got = DragQueryFileW(hdrop, i, buf.data(),
86 static_cast<UINT>(buf.size()));
87 if (got > 0) out.file_paths.push_back(wide_to_utf8(buf.data(),
88 static_cast<int>(got)));
89 }
90 GlobalUnlock(stg.hGlobal);
91 }
92 ReleaseStgMedium(&stg);
93 if (!out.file_paths.empty()) return out;
94 }
95
96 // CF_UNICODETEXT — a single text payload.
97 FORMATETC fmt_text{CF_UNICODETEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
98 STGMEDIUM stg_text{};
99 if (data->GetData(&fmt_text, &stg_text) == S_OK) {
100 if (auto* w = static_cast<const wchar_t*>(GlobalLock(stg_text.hGlobal))) {
101 out.type = DropData::Type::text;
102 out.text = wide_to_utf8(w, static_cast<int>(wcslen(w)));
103 GlobalUnlock(stg_text.hGlobal);
104 }
105 ReleaseStgMedium(&stg_text);
106 }
107 return out;
108}
109
110// Minimal IDropTarget that routes OLE drops on the child HWND into the shared
111// cross-platform view-tree dispatch core (drag_drop.cpp) — the same core the SDL
112// and macOS producers use. Owned by WinPluginViewHost; registered via
113// RegisterDragDrop on the child HWND.
114class PulpWinDropTarget : public IDropTarget {
115public:
116 PulpWinDropTarget(View& root, HWND hwnd, std::function<Point(Point)> to_root)
117 : root_(root), hwnd_(hwnd), to_root_(std::move(to_root)) {}
118
119 // Screen POINTL → client → root coordinates (mirrors the mouse path).
120 Point to_root_point(POINTL pt) const {
121 POINT p{pt.x, pt.y};
122 ScreenToClient(hwnd_, &p);
123 Point client{static_cast<float>(p.x), static_cast<float>(p.y)};
124 return to_root_ ? to_root_(client) : client;
125 }
126
127 // ── IUnknown ──────────────────────────────────────────────────────────
128 HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) override {
129 if (riid == IID_IUnknown || riid == IID_IDropTarget) {
130 *ppv = static_cast<IDropTarget*>(this);
131 AddRef();
132 return S_OK;
133 }
134 *ppv = nullptr;
135 return E_NOINTERFACE;
136 }
137 ULONG STDMETHODCALLTYPE AddRef() override {
138 return static_cast<ULONG>(++ref_);
139 }
140 ULONG STDMETHODCALLTYPE Release() override {
141 const long r = --ref_;
142 if (r == 0) delete this;
143 return static_cast<ULONG>(r);
144 }
145
146 // ── IDropTarget ───────────────────────────────────────────────────────
147 HRESULT STDMETHODCALLTYPE DragEnter(IDataObject* data, DWORD, POINTL pt,
148 DWORD* effect) override {
149 pending_ = extract_idata_drop(data);
150 const bool ok = dispatch_drag_enter(root_, session_, pending_,
151 to_root_point(pt));
152 *effect = ok ? DROPEFFECT_COPY : DROPEFFECT_NONE;
153 return S_OK;
154 }
155 HRESULT STDMETHODCALLTYPE DragOver(DWORD, POINTL pt, DWORD* effect) override {
156 const bool ok = dispatch_drag_enter(root_, session_, pending_,
157 to_root_point(pt));
158 *effect = ok ? DROPEFFECT_COPY : DROPEFFECT_NONE;
159 return S_OK;
160 }
161 HRESULT STDMETHODCALLTYPE DragLeave() override {
162 dispatch_drag_exit(root_, session_);
163 pending_ = {};
164 return S_OK;
165 }
166 HRESULT STDMETHODCALLTYPE Drop(IDataObject* data, DWORD, POINTL pt,
167 DWORD* effect) override {
168 DropData d = extract_idata_drop(data);
169 const bool ok = dispatch_drop(root_, session_, d, to_root_point(pt));
170 *effect = ok ? DROPEFFECT_COPY : DROPEFFECT_NONE;
171 pending_ = {};
172 return S_OK;
173 }
174
175private:
176 View& root_;
177 HWND hwnd_;
178 std::function<Point(Point)> to_root_;
179 DragSession session_;
180 DropData pending_; // payload cached between DragEnter and Drop
181 long ref_ = 1;
182};
183
200class DropTargetRegistration {
201public:
202 DropTargetRegistration() = default;
203 DropTargetRegistration(const DropTargetRegistration&) = delete;
204 DropTargetRegistration& operator=(const DropTargetRegistration&) = delete;
205 ~DropTargetRegistration() { reset(); }
206
210 bool register_for(View& root, HWND hwnd, std::function<Point(Point)> to_root) {
211 reset();
212 if (!hwnd) return false;
213 const HRESULT hr = OleInitialize(nullptr);
214 if (hr != S_OK && hr != S_FALSE) {
215 runtime::log_warn("DropTargetRegistration: OleInitialize failed "
216 "(0x{:08x}); drag-drop disabled",
217 static_cast<unsigned>(hr));
218 return false;
219 }
220 ole_initialized_ = true;
221 hwnd_ = hwnd;
222 target_ = new PulpWinDropTarget(root, hwnd, std::move(to_root));
223 if (RegisterDragDrop(hwnd, target_) != S_OK) {
224 runtime::log_warn("DropTargetRegistration: RegisterDragDrop failed");
225 target_->Release();
226 target_ = nullptr;
227 return false;
228 }
229 return true;
230 }
231
234 void reset() {
235 if (target_) {
236 if (hwnd_) RevokeDragDrop(hwnd_);
237 target_->Release();
238 target_ = nullptr;
239 }
240 hwnd_ = nullptr;
241 if (ole_initialized_) {
242 OleUninitialize();
243 ole_initialized_ = false;
244 }
245 }
246
247 bool registered() const noexcept { return target_ != nullptr; }
248
249private:
250 bool ole_initialized_ = false;
251 HWND hwnd_ = nullptr;
252 PulpWinDropTarget* target_ = nullptr; // ref-counted by OLE; we hold one
253};
254
255} // namespace pulp::view::win_drop
256
257#endif // _WIN32
@ Release
Definition instrument_envelope.hpp:14
@ ok
Definition analyzer_provider.hpp:58
@ count
sentinel: number of real capabilities (keep LAST)
Definition canvas_capability.hpp:38
@ Drop
Definition capability_tables.hpp:23
@ d
Definition pitch.hpp:22
@ effect
Definition lfo.hpp:68
@ i
Definition formant_filter_bank.hpp:20
@ move
Definition web_event_translate.hpp:110
bool dispatch_drop(View &root, DragSession &session, const DropData &data, Point root_pos)
bool dispatch_drag_enter(View &root, DragSession &session, const DropData &data, Point root_pos)
void dispatch_drag_exit(View &root, DragSession &session)
@ r
Definition input_events.hpp:41
@ n
Definition input_events.hpp:40
@ p
Definition input_events.hpp:40